diff --git a/.github/netpoll b/.github/netpoll new file mode 160000 index 0000000..bb9c3f7 --- /dev/null +++ b/.github/netpoll @@ -0,0 +1 @@ +Subproject commit bb9c3f74620c19b139d13c20793f9ff016d302c0 diff --git a/utils/chrono/adjuster.go b/utils/chrono/adjuster.go new file mode 100644 index 0000000..8f26ed6 --- /dev/null +++ b/utils/chrono/adjuster.go @@ -0,0 +1,38 @@ +package chrono + +import "time" + +// NewAdjuster 创建一个时间调节器 +func NewAdjuster(adjust time.Duration) *Adjuster { + return &Adjuster{adjust: adjust} +} + +// Adjuster 时间调节器是一个用于获取偏移时间的工具 +type Adjuster struct { + adjust time.Duration +} + +// Adjust 获取偏移调整的时间量 +func (a *Adjuster) Adjust() time.Duration { + return a.adjust +} + +// SetAdjust 设置偏移调整的时间量 +func (a *Adjuster) SetAdjust(adjust time.Duration) { + a.adjust = adjust +} + +// AddAdjust 增加偏移调整的时间量 +func (a *Adjuster) AddAdjust(adjust time.Duration) { + a.adjust += adjust +} + +// Now 获取经过偏移调整的当前时间 +func (a *Adjuster) Now() time.Time { + return time.Now().Add(a.adjust) +} + +// Since 获取经过偏移调整的时间间隔 +func (a *Adjuster) Since(t time.Time) time.Duration { + return a.Now().Sub(t) +} diff --git a/utils/chrono/adjuster_built_in.go b/utils/chrono/adjuster_built_in.go new file mode 100644 index 0000000..30b0eb9 --- /dev/null +++ b/utils/chrono/adjuster_built_in.go @@ -0,0 +1,26 @@ +package chrono + +import "time" + +var ( + builtInAdjuster *Adjuster +) + +func init() { + builtInAdjuster = NewAdjuster(0) +} + +// BuiltInAdjuster 获取内置的由 NewAdjuster(0) 函数创建的时间调节器 +func BuiltInAdjuster() *Adjuster { + return builtInAdjuster +} + +// Now 调用内置时间调节器 BuiltInAdjuster 的 Adjuster.Now 函数 +func Now() time.Time { + return BuiltInAdjuster().Now() +} + +// Since 调用内置时间调节器 BuiltInAdjuster 的 Adjuster.Since 函数 +func Since(t time.Time) time.Duration { + return BuiltInAdjuster().Since(t) +} diff --git a/utils/chrono/constants.go b/utils/chrono/constants.go new file mode 100644 index 0000000..12df9ec --- /dev/null +++ b/utils/chrono/constants.go @@ -0,0 +1,14 @@ +package chrono + +import "time" + +const ( + Nanosecond = time.Nanosecond + Microsecond = time.Microsecond + Millisecond = time.Millisecond + Second = time.Second + Minute = time.Minute + Hour = time.Hour + Day = Hour * 24 + Week = Day * 7 +) diff --git a/utils/chrono/moment.go b/utils/chrono/moment.go new file mode 100644 index 0000000..c7ddff1 --- /dev/null +++ b/utils/chrono/moment.go @@ -0,0 +1,350 @@ +package chrono + +import ( + "github.com/kercylan98/minotaur/utils/generic" + "math" + "time" +) + +var zero = time.Time{} + +// IsMomentReached 检查指定时刻是否已到达且未发生过 +// - now: 当前时间 +// - last: 上次发生的时间 +// - hour: 要检查的时刻的小时数 +// - min: 要检查的时刻的分钟数 +// - sec: 要检查的时刻的秒数 +func IsMomentReached(now time.Time, last time.Time, hour, min, sec int) bool { + moment := time.Date(last.Year(), last.Month(), last.Day(), hour, min, sec, 0, time.Local) + if !moment.Before(now) { + return false + } else if moment.After(last) { + return true + } + + // 如果要检查的时刻在上次发生的时间和当前时间之间,并且已经过了一天,说明已经发生 + nextDayMoment := moment.AddDate(0, 0, 1) + return !nextDayMoment.After(now) +} + +// GetNextMoment 获取下一个指定时刻发生的时间。 +func GetNextMoment(now time.Time, hour, min, sec int) time.Time { + moment := time.Date(now.Year(), now.Month(), now.Day(), hour, min, sec, 0, time.Local) + // 如果要检查的时刻已经过了,则返回明天的这个时刻 + if moment.Before(now) { + moment = moment.AddDate(0, 0, 1) + } + return moment +} + +// IsMomentInDays 检查指定时刻是否在给定的天数内发生。 +// - now: 当前时间 +// - hour: 要检查的时刻的小时数 +// - min: 要检查的时刻的分钟数 +// - sec: 要检查的时刻的秒数 +// - days: 表示要偏移的天数。正数表示未来,负数表示过去,0 即今天 +func IsMomentInDays(now time.Time, hour, min, sec, days int) bool { + offsetTime := now.AddDate(0, 0, days) + moment := time.Date(offsetTime.Year(), offsetTime.Month(), offsetTime.Day(), hour, min, sec, 0, time.Local) + return now.Before(moment.AddDate(0, 0, 1)) && now.After(moment) +} + +// IsMomentYesterday 检查指定时刻是否在昨天发生 +func IsMomentYesterday(now time.Time, hour, min, sec int) bool { + return IsMomentInDays(now, hour, min, sec, -1) +} + +// IsMomentToday 检查指定时刻是否在今天发生 +func IsMomentToday(now time.Time, hour, min, sec int) bool { + return IsMomentInDays(now, hour, min, sec, 0) +} + +// IsMomentTomorrow 检查指定时刻是否在明天发生 +func IsMomentTomorrow(now time.Time, hour, min, sec int) bool { + return IsMomentInDays(now, hour, min, sec, 1) +} + +// IsMomentPassed 检查指定时刻是否已经过去 +func IsMomentPassed(now time.Time, hour, min, sec int) bool { + // 构建要检查的时刻 + moment := time.Date(now.Year(), now.Month(), now.Day(), hour, min, sec, 0, time.Local) + return now.After(moment) +} + +// IsMomentFuture 检查指定时刻是否在未来 +func IsMomentFuture(now time.Time, hour, min, sec int) bool { + // 构建要检查的时刻 + moment := time.Date(now.Year(), now.Month(), now.Day(), hour, min, sec, 0, time.Local) + return now.Before(moment) +} + +// GetStartOfDay 获取指定时间的当天第一刻,即 00:00:00 +func GetStartOfDay(t time.Time) time.Time { + return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) +} + +// GetEndOfDay 获取指定时间的当天最后一刻,即 23:59:59 +func GetEndOfDay(t time.Time) time.Time { + return time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 0, t.Location()) +} + +// GetRelativeStartOfDay 获取相对于指定时间减去或加上指定天数后的当天开始时间 +// - offsetDays: 要偏移的天数,负数表示过去的某一天,正数表示未来的某一天 +func GetRelativeStartOfDay(t time.Time, offsetDays int) time.Time { + return GetStartOfDay(GetStartOfDay(t.AddDate(0, 0, offsetDays))) +} + +// GetRelativeEndOfDay 获取相对于指定时间减去或加上指定天数后的当天结束时间 +// - offsetDays: 要偏移的天数,负数表示过去的某一天,正数表示未来的某一天 +func GetRelativeEndOfDay(t time.Time, offsetDays int) time.Time { + return GetEndOfDay(GetEndOfDay(t.AddDate(0, 0, offsetDays))) +} + +// GetStartOfWeek 获取指定时间所在周的特定周的开始时刻,即 00:00:00 +func GetStartOfWeek(t time.Time, weekday time.Weekday) time.Time { + t = GetStartOfDay(t) + tw := t.Weekday() + if tw == 0 { + tw = 7 + } + d := 1 - int(tw) + switch weekday { + case time.Sunday: + d += 6 + default: + d += int(weekday) - 1 + } + return t.AddDate(0, 0, d) +} + +// GetEndOfWeek 获取指定时间所在周的特定周的最后时刻,即 23:59:59 +func GetEndOfWeek(t time.Time, weekday time.Weekday) time.Time { + return GetEndOfDay(GetStartOfWeek(t, weekday)) +} + +// GetRelativeStartOfWeek 获取相对于当前时间的本周开始时间,以指定的星期作为一周的开始,并根据需要进行周数的偏移 +// - now:当前时间 +// - week:以哪一天作为一周的开始 +// - offsetWeeks:要偏移的周数,正数表示向未来偏移,负数表示向过去偏移 +// +// 该函数返回以指定星期作为一周的开始时间,然后根据偏移量进行周数偏移,得到相对于当前时间的周的开始时间 +// +// 假设 week 为 time.Saturday 且 offsetWeeks 为 -1,则表示获取上周六的开始时间,一下情况中第一个时间为 now,第二个时间为函数返回值 +// - 2024-03-01 00:00:00 --相对时间-> 2024-02-24 00:00:00 --偏移时间--> 2024-02-17 00:00:00 +// - 2024-03-02 00:00:00 --相对时间-> 2024-02-24 00:00:00 --偏移时间--> 2024-02-17 00:00:00 +// - 2024-03-03 00:00:00 --相对时间-> 2024-03-02 00:00:00 --偏移时间--> 2024-02-24 00:00:00 +func GetRelativeStartOfWeek(now time.Time, week time.Weekday, offsetWeeks int) time.Time { + nowWeekday, weekday := int(now.Weekday()), int(week) + if nowWeekday == 0 { + nowWeekday = 7 + } + if weekday == 0 { + weekday = 7 + } + if nowWeekday < weekday { + now = now.Add(-Week) + } + moment := GetStartOfWeek(now, week) + return moment.Add(Week * time.Duration(offsetWeeks)) +} + +// GetRelativeEndOfWeek 获取相对于当前时间的本周结束时间,以指定的星期作为一周的开始,并根据需要进行周数的偏移 +// - 该函数详细解释参考 GetRelativeEndOfWeek 函数,其中不同的是,该函数返回的是这一天最后一刻的时间,即 23:59:59 +func GetRelativeEndOfWeek(now time.Time, week time.Weekday, offsetWeeks int) time.Time { + return GetEndOfDay(GetRelativeStartOfWeek(now, week, offsetWeeks)) +} + +// GetRelativeTimeOfWeek 获取相对于当前时间的本周指定星期的指定时刻,以指定的星期作为一周的开始,并根据需要进行周数的偏移 +// - 该函数详细解释参考 GetRelativeStartOfWeek 函数,其中不同的是,该函数返回的是这一天对应 now 的时间 +func GetRelativeTimeOfWeek(now time.Time, week time.Weekday, offsetWeeks int) time.Time { + moment := GetRelativeStartOfWeek(now, week, offsetWeeks) + return time.Date(moment.Year(), moment.Month(), moment.Day(), now.Hour(), now.Minute(), now.Second(), now.Nanosecond(), now.Location()) +} + +// Zero 获取一个零值的时间 +func Zero() time.Time { + return zero +} + +// IsZero 检查一个时间是否为零值 +func IsZero(t time.Time) bool { + return t.IsZero() +} + +// Max 获取两个时间中的最大值 +func Max(t1, t2 time.Time) time.Time { + if t1.After(t2) { + return t1 + } + return t2 +} + +// Min 获取两个时间中的最小值 +func Min(t1, t2 time.Time) time.Time { + if t1.Before(t2) { + return t1 + } + return t2 +} + +// SmallerFirst 将两个时间按照从小到大的顺序排列 +func SmallerFirst(t1, t2 time.Time) (time.Time, time.Time) { + if t1.Before(t2) { + return t1, t2 + } + return t2, t1 +} + +// SmallerLast 将两个时间按照从大到小的顺序排列 +func SmallerLast(t1, t2 time.Time) (time.Time, time.Time) { + if t1.Before(t2) { + return t2, t1 + } + return t1, t2 +} + +// Delta 获取两个时间之间的时间差 +func Delta(t1, t2 time.Time) time.Duration { + if t1.Before(t2) { + return t2.Sub(t1) + } + return t1.Sub(t2) +} + +// FloorDeltaDays 计算两个时间之间的天数差异,并向下取整 +func FloorDeltaDays(t1, t2 time.Time) int { + t1, t2 = SmallerFirst(t1, t2) + return int(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Day) +} + +// CeilDeltaDays 计算两个时间之间的天数差异,并向上取整 +func CeilDeltaDays(t1, t2 time.Time) int { + t1, t2 = SmallerFirst(t1, t2) + return int(math.Ceil(float64(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Day))) +} + +// RoundDeltaDays 计算两个时间之间的天数差异,并四舍五入 +func RoundDeltaDays(t1, t2 time.Time) int { + t1, t2 = SmallerFirst(t1, t2) + return int(math.Round(float64(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Day))) +} + +// FloorDeltaHours 计算两个时间之间的小时数差异,并向下取整 +func FloorDeltaHours(t1, t2 time.Time) int { + t1, t2 = SmallerFirst(t1, t2) + return int(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Hour) +} + +// CeilDeltaHours 计算两个时间之间的小时数差异,并向上取整 +func CeilDeltaHours(t1, t2 time.Time) int { + t1, t2 = SmallerFirst(t1, t2) + return int(math.Ceil(float64(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Hour))) +} + +// RoundDeltaHours 计算两个时间之间的小时数差异,并四舍五入 +func RoundDeltaHours(t1, t2 time.Time) int { + t1, t2 = SmallerFirst(t1, t2) + return int(math.Round(float64(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Hour))) +} + +// FloorDeltaMinutes 计算两个时间之间的分钟数差异,并向下取整 +func FloorDeltaMinutes(t1, t2 time.Time) int { + t1, t2 = SmallerFirst(t1, t2) + return int(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Minute) +} + +// CeilDeltaMinutes 计算两个时间之间的分钟数差异,并向上取整 +func CeilDeltaMinutes(t1, t2 time.Time) int { + t1, t2 = SmallerFirst(t1, t2) + return int(math.Ceil(float64(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Minute))) +} + +// RoundDeltaMinutes 计算两个时间之间的分钟数差异,并四舍五入 +func RoundDeltaMinutes(t1, t2 time.Time) int { + t1, t2 = SmallerFirst(t1, t2) + return int(math.Round(float64(GetStartOfDay(t2).Sub(GetStartOfDay(t1)) / Minute))) +} + +// IsSameSecond 检查两个时间是否在同一秒 +func IsSameSecond(t1, t2 time.Time) bool { + return t1.Unix() == t2.Unix() +} + +// IsSameMinute 检查两个时间是否在同一分钟 +func IsSameMinute(t1, t2 time.Time) bool { + return t1.Minute() == t2.Minute() && IsSameHour(t1, t2) +} + +// IsSameHour 检查两个时间是否在同一小时 +func IsSameHour(t1, t2 time.Time) bool { + return t1.Hour() == t2.Hour() && IsSameDay(t1, t2) +} + +// IsSameDay 检查两个时间是否在同一天 +func IsSameDay(t1, t2 time.Time) bool { + return GetStartOfDay(t1).Equal(GetStartOfDay(t2)) +} + +// IsSameWeek 检查两个时间是否在同一周 +func IsSameWeek(t1, t2 time.Time) bool { + return GetStartOfWeek(t1, time.Monday).Equal(GetStartOfWeek(t2, time.Monday)) +} + +// IsSameMonth 检查两个时间是否在同一月 +func IsSameMonth(t1, t2 time.Time) bool { + return t1.Month() == t2.Month() && t1.Year() == t2.Year() +} + +// IsSameYear 检查两个时间是否在同一年 +func IsSameYear(t1, t2 time.Time) bool { + return t1.Year() == t2.Year() +} + +// GetMonthDays 获取指定时间所在月的天数 +func GetMonthDays(t time.Time) int { + year, month, _ := t.Date() + if month != 2 { + if month == 4 || month == 6 || month == 9 || month == 11 { + return 30 + } + return 31 + } + if ((year%4 == 0) && (year%100 != 0)) || year%400 == 0 { + return 29 + } + return 28 +} + +// ToDuration 将一个数值转换为 time.Duration 类型,当 unit 为空时,默认为纳秒单位 +func ToDuration[V generic.Number](v V, unit ...time.Duration) time.Duration { + var u = Nanosecond + if len(unit) > 0 { + u = unit[0] + } + return time.Duration(v) * u +} + +// ToDurationSecond 将一个数值转换为秒的 time.Duration 类型 +func ToDurationSecond[V generic.Number](v V) time.Duration { + return ToDuration(v, Second) +} + +// ToDurationMinute 将一个数值转换为分钟的 time.Duration 类型 +func ToDurationMinute[V generic.Number](v V) time.Duration { + return ToDuration(v, Minute) +} + +// ToDurationHour 将一个数值转换为小时的 time.Duration 类型 +func ToDurationHour[V generic.Number](v V) time.Duration { + return ToDuration(v, Hour) +} + +// ToDurationDay 将一个数值转换为天的 time.Duration 类型 +func ToDurationDay[V generic.Number](v V) time.Duration { + return ToDuration(v, Day) +} + +// ToDurationWeek 将一个数值转换为周的 time.Duration 类型 +func ToDurationWeek[V generic.Number](v V) time.Duration { + return ToDuration(v, Week) +} diff --git a/utils/chrono/period.go b/utils/chrono/period.go new file mode 100644 index 0000000..288fe67 --- /dev/null +++ b/utils/chrono/period.go @@ -0,0 +1,172 @@ +package chrono + +import ( + "time" +) + +// NewPeriod 创建一个时间段 +// - 如果 start 比 end 晚,则会自动交换两个时间 +func NewPeriod(start, end time.Time) Period { + if start.After(end) { + start, end = end, start + } + return Period{start, end} +} + +// NewPeriodWindow 创建一个特定长度的时间窗口 +func NewPeriodWindow(t time.Time, size time.Duration) Period { + start := t.Truncate(size) + end := start.Add(size) + return Period{start, end} +} + +// NewPeriodWindowWeek 创建一周长度的时间窗口,从周一零点开始至周日 23:59:59 结束 +func NewPeriodWindowWeek(t time.Time) Period { + var start = GetStartOfWeek(t, time.Monday) + end := start.Add(Week) + return Period{start, end} +} + +// NewPeriodWithTimeArray 创建一个时间段 +func NewPeriodWithTimeArray(times [2]time.Time) Period { + return NewPeriod(times[0], times[1]) +} + +// NewPeriodWithDayZero 创建一个时间段,从 t 开始,持续到 day 天后的 0 点 +func NewPeriodWithDayZero(t time.Time, day int) Period { + return NewPeriod(t, GetStartOfDay(t.AddDate(0, 0, day))) +} + +// NewPeriodWithDay 创建一个时间段,从 t 开始,持续 day 天 +func NewPeriodWithDay(t time.Time, day int) Period { + return NewPeriod(t, t.AddDate(0, 0, day)) +} + +// NewPeriodWithHour 创建一个时间段,从 t 开始,持续 hour 小时 +func NewPeriodWithHour(t time.Time, hour int) Period { + return NewPeriod(t, t.Add(time.Duration(hour)*time.Hour)) +} + +// NewPeriodWithMinute 创建一个时间段,从 t 开始,持续 minute 分钟 +func NewPeriodWithMinute(t time.Time, minute int) Period { + return NewPeriod(t, t.Add(time.Duration(minute)*time.Minute)) +} + +// NewPeriodWithSecond 创建一个时间段,从 t 开始,持续 second 秒 +func NewPeriodWithSecond(t time.Time, second int) Period { + return NewPeriod(t, t.Add(time.Duration(second)*time.Second)) +} + +// NewPeriodWithMillisecond 创建一个时间段,从 t 开始,持续 millisecond 毫秒 +func NewPeriodWithMillisecond(t time.Time, millisecond int) Period { + return NewPeriod(t, t.Add(time.Duration(millisecond)*time.Millisecond)) +} + +// NewPeriodWithMicrosecond 创建一个时间段,从 t 开始,持续 microsecond 微秒 +func NewPeriodWithMicrosecond(t time.Time, microsecond int) Period { + return NewPeriod(t, t.Add(time.Duration(microsecond)*time.Microsecond)) +} + +// NewPeriodWithNanosecond 创建一个时间段,从 t 开始,持续 nanosecond 纳秒 +func NewPeriodWithNanosecond(t time.Time, nanosecond int) Period { + return NewPeriod(t, t.Add(time.Duration(nanosecond)*time.Nanosecond)) +} + +// Period 表示一个时间段 +type Period [2]time.Time + +// Start 返回时间段的开始时间 +func (p Period) Start() time.Time { + return p[0] +} + +// End 返回时间段的结束时间 +func (p Period) End() time.Time { + return p[1] +} + +// Duration 返回时间段的持续时间 +func (p Period) Duration() time.Duration { + return p[1].Sub(p[0]) +} + +// Days 返回时间段的持续天数 +func (p Period) Days() int { + return int(p.Duration().Hours() / 24) +} + +// Hours 返回时间段的持续小时数 +func (p Period) Hours() int { + return int(p.Duration().Hours()) +} + +// Minutes 返回时间段的持续分钟数 +func (p Period) Minutes() int { + return int(p.Duration().Minutes()) +} + +// Seconds 返回时间段的持续秒数 +func (p Period) Seconds() int { + return int(p.Duration().Seconds()) +} + +// Milliseconds 返回时间段的持续毫秒数 +func (p Period) Milliseconds() int { + return int(p.Duration().Milliseconds()) +} + +// Microseconds 返回时间段的持续微秒数 +func (p Period) Microseconds() int { + return int(p.Duration().Microseconds()) +} + +// Nanoseconds 返回时间段的持续纳秒数 +func (p Period) Nanoseconds() int { + return int(p.Duration().Nanoseconds()) +} + +// IsZero 判断时间段是否为零值 +func (p Period) IsZero() bool { + return p[0].IsZero() && p[1].IsZero() +} + +// IsInvalid 判断时间段是否无效 +func (p Period) IsInvalid() bool { + return p[0].IsZero() || p[1].IsZero() +} + +// IsBefore 判断时间段是否在指定时间之前 +func (p Period) IsBefore(t time.Time) bool { + return p[1].Before(t) +} + +// IsAfter 判断时间段是否在指定时间之后 +func (p Period) IsAfter(t time.Time) bool { + return p[0].After(t) +} + +// IsBetween 判断指定时间是否在时间段之间 +func (p Period) IsBetween(t time.Time) bool { + return p[0].Before(t) && p[1].After(t) +} + +// IsOngoing 判断指定时间是否正在进行时 +// - 如果时间段的开始时间在指定时间之前或者等于指定时间,且时间段的结束时间在指定时间之后,则返回 true +func (p Period) IsOngoing(t time.Time) bool { + return (p[0].Before(t) || p[0].Equal(t)) && p[1].After(t) +} + +// IsBetweenOrEqual 判断指定时间是否在时间段之间或者等于时间段的开始或结束时间 +func (p Period) IsBetweenOrEqual(t time.Time) bool { + return p.IsBetween(t) || p[0].Equal(t) || p[1].Equal(t) +} + +// IsBetweenOrEqualPeriod 判断指定时间是否在时间段之间或者等于时间段的开始或结束时间 +func (p Period) IsBetweenOrEqualPeriod(t Period) bool { + return p.IsBetween(t[0]) || p.IsBetween(t[1]) || p[0].Equal(t[0]) || p[1].Equal(t[1]) +} + +// IsOverlap 判断时间段是否与指定时间段重叠 +func (p Period) IsOverlap(t Period) bool { + return p.IsBetweenOrEqualPeriod(t) || t.IsBetweenOrEqualPeriod(p) +} diff --git a/utils/chrono/period_test.go b/utils/chrono/period_test.go new file mode 100644 index 0000000..8e8aaaa --- /dev/null +++ b/utils/chrono/period_test.go @@ -0,0 +1,15 @@ +package chrono_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/chrono" + "testing" + "time" +) + +func TestNewPeriodWindow(t *testing.T) { + cur := time.Now() + fmt.Println(cur) + window := chrono.NewPeriodWindow(cur, chrono.Day) + fmt.Println(window) +} diff --git a/utils/chrono/scheduler.go b/utils/chrono/scheduler.go new file mode 100644 index 0000000..d7ed3a1 --- /dev/null +++ b/utils/chrono/scheduler.go @@ -0,0 +1,218 @@ +package chrono + +import ( + "github.com/RussellLuo/timingwheel" + "github.com/gorhill/cronexpr" + "github.com/kercylan98/minotaur/utils/collection" + "reflect" + "sync" + "time" +) + +const ( + DefaultSchedulerTick = SchedulerPoolDefaultTick + DefaultSchedulerWheelSize = SchedulerPoolDefaultWheelSize +) + +const ( + SchedulerForever = -1 // 无限循环 + SchedulerOnce = 1 // 一次 + SchedulerInstantly = 0 // 立刻 +) + +// NewDefaultScheduler 创建一个默认的时间调度器 +// - tick: DefaultSchedulerTick +// - wheelSize: DefaultSchedulerWheelSize +func NewDefaultScheduler() *Scheduler { + return NewScheduler(DefaultSchedulerTick, DefaultSchedulerWheelSize) +} + +// NewScheduler 创建一个并发安全的时间调度器 +// - tick: 时间轮的刻度间隔。 +// - wheelSize: 时间轮的大小。 +func NewScheduler(tick time.Duration, wheelSize int64) *Scheduler { + return newScheduler(nil, tick, wheelSize) +} + +func newScheduler(pool *SchedulerPool, tick time.Duration, wheelSize int64) *Scheduler { + scheduler := &Scheduler{ + pool: pool, + wheel: timingwheel.NewTimingWheel(tick, wheelSize), + tasks: make(map[string]*schedulerTask), + } + if pool != nil { + scheduler.generation = pool.getGeneration() + } + scheduler.wheel.Start() + return scheduler +} + +// Scheduler 并发安全的时间调度器 +type Scheduler struct { + pool *SchedulerPool // 时间调度器所属的池,当该值为 nil 时,该时间调度器不属于任何池 + wheel *timingwheel.TimingWheel // 时间轮 + tasks map[string]*schedulerTask // 所有任务 + lock sync.RWMutex // 用于确保并发安全的锁 + generation int64 // 时间调度器的代数 + tick time.Duration // 时间周期 + + executor func(name string, caller func()) // 任务执行器 +} + +// SetExecutor 设置任务执行器 +// - 如果该任务执行器来自于时间调度器对象池,那么默认情况下将会使用时间调度器对象池的任务执行器,主动设置将会覆盖默认的任务执行器 +func (s *Scheduler) SetExecutor(executor func(name string, caller func())) { + s.lock.Lock() + defer s.lock.Unlock() + s.executor = executor +} + +// Release 释放时间调度器,时间调度器被释放后将不再可用,如果时间调度器属于某个池且池未满,则会重新加入到池中 +// - 释放后所有已注册的任务将会被取消 +func (s *Scheduler) Release() { + s.lock.Lock() + defer s.lock.Unlock() + for name, task := range s.tasks { + task.close() + delete(s.tasks, name) + } + + if s.pool == nil || s.pool.getGeneration() != s.generation { + s.wheel.Stop() + return + } + + s.pool.lock.Lock() + if len(s.pool.schedulers) < s.pool.size { + s.pool.schedulers = append(s.pool.schedulers, s) + s.pool.lock.Unlock() + return + } + s.pool.lock.Unlock() + s.wheel.Stop() +} + +// UnregisterTask 取消特定任务的执行计划的注册 +// - 如果任务不存在,则不执行任何操作 +func (s *Scheduler) UnregisterTask(name string) { + s.lock.Lock() + defer s.lock.Unlock() + if task, exist := s.tasks[name]; exist { + task.close() + delete(s.tasks, name) + } +} + +// GetRegisteredTasks 获取所有未执行完成的任务名称 +func (s *Scheduler) GetRegisteredTasks() []string { + s.lock.RLock() + defer s.lock.RUnlock() + return collection.ConvertMapKeysToSlice(s.tasks) +} + +// RegisterCronTask 通过 cron 表达式注册一个任务。 +// - 当 cron 表达式错误时,将会返回错误信息 +func (s *Scheduler) RegisterCronTask(name, expression string, function interface{}, args ...interface{}) error { + expr, err := cronexpr.Parse(expression) + if err != nil { + return err + } + s.task(name, 0, 0, expr, 0, function, args...) + return nil +} + +// RegisterImmediateCronTask 与 RegisterCronE 相同,但是会立即执行一次 +func (s *Scheduler) RegisterImmediateCronTask(name, expression string, function interface{}, args ...interface{}) error { + if err := s.RegisterCronTask(name, expression, function, args...); err != nil { + return err + } + s.call(name, function, args...) + return nil +} + +// RegisterAfterTask 注册一个在特定时间后执行一次的任务 +func (s *Scheduler) RegisterAfterTask(name string, after time.Duration, function interface{}, args ...interface{}) { + s.task(name, after, s.pool.tick, nil, 1, function, args...) +} + +// RegisterRepeatedTask 注册一个在特定时间后反复执行的任务 +func (s *Scheduler) RegisterRepeatedTask(name string, after, interval time.Duration, times int, function interface{}, args ...interface{}) { + s.task(name, after, interval, nil, times, function, args...) +} + +// RegisterDayMomentTask 注册一个在每天特定时刻执行的任务 +// - 其中 lastExecuted 为上次执行时间,adjust 为时间偏移量,hour、min、sec 为时、分、秒 +// - 当上次执行时间被错过时,将会立即执行一次 +func (s *Scheduler) RegisterDayMomentTask(name string, lastExecuted time.Time, offset time.Duration, hour, min, sec int, function interface{}, args ...interface{}) { + now := time.Now().Add(offset) + if IsMomentReached(now, lastExecuted, hour, min, sec) { + s.call(name, function, args...) + } + + moment := GetNextMoment(now, hour, min, sec) + s.RegisterRepeatedTask(name, moment.Sub(now), time.Hour*24, SchedulerForever, function, args...) +} + +func (s *Scheduler) task(name string, after, interval time.Duration, expr *cronexpr.Expression, times int, function interface{}, args ...interface{}) { + s.UnregisterTask(name) + + if expr == nil { + if after < s.tick { + after = s.tick + } + if interval < s.tick { + interval = s.tick + } + } + + var values = make([]reflect.Value, len(args)) + for i, v := range args { + values[i] = reflect.ValueOf(v) + } + + task := &schedulerTask{ + name: name, + after: after, + interval: interval, + total: times, + function: reflect.ValueOf(function), + args: values, + scheduler: s, + expr: expr, + } + var executor func(name string, caller func()) + if s.pool != nil { + executor = s.pool.getExecutor() + } + s.lock.Lock() + if s.executor != nil { + executor = s.pool.getExecutor() + } + + s.tasks[name] = task + if executor != nil { + task.timer = s.wheel.ScheduleFunc(task, func() { + executor(task.Name(), task.caller) + }) + } else { + task.timer = s.wheel.ScheduleFunc(task, task.caller) + } + s.lock.Unlock() +} + +func (s *Scheduler) call(name string, function any, args ...any) { + var values = make([]reflect.Value, len(args)) + for i, v := range args { + values[i] = reflect.ValueOf(v) + } + f := reflect.ValueOf(function) + s.lock.RLock() + defer s.lock.RUnlock() + if s.executor != nil { + s.executor(name, func() { + f.Call(values) + }) + } else { + f.Call(values) + } +} diff --git a/utils/chrono/scheduler_built_in.go b/utils/chrono/scheduler_built_in.go new file mode 100644 index 0000000..c494ae7 --- /dev/null +++ b/utils/chrono/scheduler_built_in.go @@ -0,0 +1,57 @@ +package chrono + +import "time" + +const ( + BuiltInSchedulerWheelSize = 50 +) + +var ( + buildInSchedulerPool *SchedulerPool + builtInScheduler *Scheduler +) + +func init() { + buildInSchedulerPool = NewDefaultSchedulerPool() + builtInScheduler = NewScheduler(DefaultSchedulerTick, BuiltInSchedulerWheelSize) +} + +// BuiltInSchedulerPool 获取内置的由 NewDefaultSchedulerPool 函数创建的时间调度器对象池 +func BuiltInSchedulerPool() *SchedulerPool { + return buildInSchedulerPool +} + +// BuiltInScheduler 获取内置的由 NewScheduler(DefaultSchedulerTick, BuiltInSchedulerWheelSize) 创建的时间调度器 +func BuiltInScheduler() *Scheduler { + return builtInScheduler +} + +// UnregisterTask 调用内置时间调度器 BuiltInScheduler 的 Scheduler.UnregisterTask 函数 +func UnregisterTask(name string) { + BuiltInScheduler().UnregisterTask(name) +} + +// RegisterCronTask 调用内置时间调度器 BuiltInScheduler 的 Scheduler.RegisterCronTask 函数 +func RegisterCronTask(name, expression string, function interface{}, args ...interface{}) error { + return BuiltInScheduler().RegisterCronTask(name, expression, function, args...) +} + +// RegisterImmediateCronTask 调用内置时间调度器 BuiltInScheduler 的 Scheduler.RegisterImmediateCronTask 函数 +func RegisterImmediateCronTask(name, expression string, function interface{}, args ...interface{}) error { + return BuiltInScheduler().RegisterImmediateCronTask(name, expression, function, args...) +} + +// RegisterAfterTask 调用内置时间调度器 BuiltInScheduler 的 Scheduler.RegisterAfterTask 函数 +func RegisterAfterTask(name string, after time.Duration, function interface{}, args ...interface{}) { + BuiltInScheduler().RegisterAfterTask(name, after, function, args...) +} + +// RegisterRepeatedTask 调用内置时间调度器 BuiltInScheduler 的 Scheduler.RegisterRepeatedTask 函数 +func RegisterRepeatedTask(name string, after, interval time.Duration, times int, function interface{}, args ...interface{}) { + BuiltInScheduler().RegisterRepeatedTask(name, after, interval, times, function, args...) +} + +// RegisterDayMomentTask 调用内置时间调度器 BuiltInScheduler 的 Scheduler.RegisterDayMomentTask 函数 +func RegisterDayMomentTask(name string, lastExecuted time.Time, offset time.Duration, hour, min, sec int, function interface{}, args ...interface{}) { + BuiltInScheduler().RegisterDayMomentTask(name, lastExecuted, offset, hour, min, sec, function, args...) +} diff --git a/utils/chrono/scheduler_pool.go b/utils/chrono/scheduler_pool.go new file mode 100644 index 0000000..ede53d4 --- /dev/null +++ b/utils/chrono/scheduler_pool.go @@ -0,0 +1,110 @@ +package chrono + +import ( + "fmt" + "sync" + "time" +) + +const ( + SchedulerPoolDefaultSize = 96 + SchedulerPoolDefaultTick = time.Millisecond * 10 + SchedulerPoolDefaultWheelSize = 10 +) + +// NewDefaultSchedulerPool 创建一个默认参数的并发安全的时间调度器对象池 +// - size: SchedulerPoolDefaultSize +// - tick: SchedulerPoolDefaultTick +// - wheelSize: SchedulerPoolDefaultWheelSize +func NewDefaultSchedulerPool() *SchedulerPool { + scheduler, err := NewSchedulerPool(SchedulerPoolDefaultSize, SchedulerPoolDefaultTick, SchedulerPoolDefaultWheelSize) + if err != nil { + panic(err) // 该错误不应该发生,用于在参数或实现变更后的提示 + } + return scheduler +} + +// NewSchedulerPool 创建一个并发安全的时间调度器对象池 +func NewSchedulerPool(size int, tick time.Duration, wheelSize int64) (*SchedulerPool, error) { + if size <= 0 { + return nil, fmt.Errorf("scheduler pool size must greater than 0, got: %d", size) + } + if wheelSize <= 0 { + return nil, fmt.Errorf("scheduler pool wheelSize must greater than 0, got: %d", size) + } + return &SchedulerPool{ + size: size, + tick: tick, + wheelSize: wheelSize, + generation: 1, + }, nil +} + +// SchedulerPool 并发安全的时间调度器对象池 +type SchedulerPool struct { + schedulers []*Scheduler // 池中维护的时间调度器 + lock sync.RWMutex // 用于并发安全的锁 + tick time.Duration // 时间周期 + wheelSize int64 // 时间轮尺寸 + size int // 池大小,控制了池中时间调度器的数量 + generation int64 // 池的代数 + executor func(name string, caller func()) // 任务执行器 +} + +// SetExecutor 设置该事件调度器对象池中整体的任务执行器 +func (p *SchedulerPool) SetExecutor(executor func(name string, caller func())) { + p.lock.Lock() + defer p.lock.Unlock() + p.executor = executor +} + +// SetSize 改变时间调度器对象池的大小,当传入的大小小于或等于 0 时,将会返回错误,并且不会发生任何改变 +// - 设置时间调度器对象池的大小可以在运行时动态调整,但是需要注意的是,调整后的大小不会影响已经产生的 Scheduler +// - 已经产生的 Scheduler 在被释放后将不会回到 SchedulerPool 中 +func (p *SchedulerPool) SetSize(size int) error { + if size <= 0 { + return fmt.Errorf("scheduler pool size must greater than 0, got: %d", size) + } + p.lock.Lock() + defer p.lock.Unlock() + p.size = size + return nil +} + +// Get 获取一个特定时间周期及时间轮尺寸的时间调度器,当池中存在可用的时间调度器时,将会直接返回,否则将会创建一个新的时间调度器 +func (p *SchedulerPool) Get() *Scheduler { + p.lock.Lock() + defer p.lock.Unlock() + var scheduler *Scheduler + if len(p.schedulers) > 0 { + scheduler = p.schedulers[0] + p.schedulers = p.schedulers[1:] + return scheduler + } + return newScheduler(p, p.tick, p.wheelSize) +} + +// Recycle 释放定时器池的资源并将其重置为全新的状态 +// - 执行该函数后,已有的时间调度器将会被停止,且不会重新加入到池中,一切都是新的开始 +func (p *SchedulerPool) Recycle() { + p.lock.Lock() + defer p.lock.Unlock() + for _, scheduler := range p.schedulers { + scheduler.wheel.Stop() + } + p.schedulers = nil + p.generation++ + return +} + +func (p *SchedulerPool) getGeneration() int64 { + p.lock.RLock() + defer p.lock.RUnlock() + return p.generation +} + +func (p *SchedulerPool) getExecutor() func(name string, caller func()) { + p.lock.RLock() + defer p.lock.RUnlock() + return p.executor +} diff --git a/utils/chrono/scheduler_task.go b/utils/chrono/scheduler_task.go new file mode 100644 index 0000000..d17fdd4 --- /dev/null +++ b/utils/chrono/scheduler_task.go @@ -0,0 +1,81 @@ +package chrono + +import ( + "github.com/RussellLuo/timingwheel" + "github.com/gorhill/cronexpr" + "reflect" + "sync" + "time" +) + +// schedulerTask 调度器 +type schedulerTask struct { + lock sync.RWMutex + scheduler *Scheduler // 任务所属的调度器 + timer *timingwheel.Timer // 任务执行定时器 + name string // 任务名称 + after time.Duration // 任务首次执行延迟 + interval time.Duration // 任务执行间隔 + function reflect.Value // 任务执行函数 + args []reflect.Value // 任务执行参数 + expr *cronexpr.Expression // 任务执行时间表达式 + + total int // 任务执行次数 + trigger int // 任务已执行次数 + kill bool // 任务是否已关闭 +} + +// Name 获取任务名称 +func (t *schedulerTask) Name() string { + return t.name +} + +// Next 获取任务下一次执行的时间 +func (t *schedulerTask) Next(prev time.Time) time.Time { + t.lock.RLock() + defer t.lock.RUnlock() + + if t.kill || (t.expr != nil && t.total > 0 && t.trigger > t.total) { + return time.Time{} + } + if t.expr != nil { + next := t.expr.Next(prev) + return next + } + if t.trigger == 0 { + t.trigger++ + return prev.Add(t.after) + } + t.trigger++ + return prev.Add(t.interval) +} + +func (t *schedulerTask) caller() { + t.lock.Lock() + + if t.kill { + t.lock.Unlock() + return + } + + if t.total > 0 && t.trigger > t.total { + t.lock.Unlock() + t.scheduler.UnregisterTask(t.name) + } else { + t.lock.Unlock() + } + t.function.Call(t.args) +} + +func (t *schedulerTask) close() { + t.lock.Lock() + defer t.lock.Unlock() + + if t.kill { + return + } + t.kill = true + if t.total <= 0 || t.trigger < t.total { + t.timer.Stop() + } +} diff --git a/utils/chrono/shceduler_test.go b/utils/chrono/shceduler_test.go new file mode 100644 index 0000000..9ecba82 --- /dev/null +++ b/utils/chrono/shceduler_test.go @@ -0,0 +1,14 @@ +package chrono_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/chrono" + "testing" + "time" +) + +func TestRegisterCronTask(t *testing.T) { + chrono.RegisterDayMomentTask("newday", time.Now().Add(time.Minute*-2), 0, 0, 0, 0, func() { + fmt.Println("newday") + }) +} diff --git a/utils/chrono/state_line.go b/utils/chrono/state_line.go new file mode 100644 index 0000000..e0377a4 --- /dev/null +++ b/utils/chrono/state_line.go @@ -0,0 +1,261 @@ +package chrono + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/collection" + "github.com/kercylan98/minotaur/utils/generic" + "strings" + "time" +) + +// NewStateLine 创建一个从左向右由早到晚的状态时间线 +func NewStateLine[State generic.Basic](zero State) *StateLine[State] { + return &StateLine[State]{ + states: []State{zero}, + points: []time.Time{{}}, + trigger: [][]func(){{}}, + } +} + +// StateLine 表示一个状态时间线,它记录了一系列时间点及其对应的状态和触发器。 +// 在时间线中,每个时间点都与一个状态和一组触发器相关联,可以通过时间点查找状态,并触发与之相关联的触发器。 +type StateLine[State generic.Basic] struct { + states []State // 每个时间点对应的状态 + points []time.Time // 每个时间点 + trigger [][]func() // 每个时间点对应的触发器 +} + +// Check 根据状态顺序检查时间线是否合法 +// - missingAllowed: 是否允许状态缺失,如果为 true,则状态可以不连续,如果为 false,则状态必须连续 +// +// 状态不连续表示时间线中存在状态缺失,例如: +// - 状态为 [1, 2, 3, 4, 5] 的时间线,如果 missingAllowed 为 true,则状态为 [1, 3, 5] 也是合法的 +// - 状态为 [1, 2, 3, 4, 5] 的时间线,如果 missingAllowed 为 false,则状态为 [1, 3, 5] 是不合法的 +func (s *StateLine[State]) Check(missingAllowed bool, states ...State) bool { + var indexStored int + var indexInput int + + for indexStored < len(s.states) && indexInput < len(states) { + if s.states[indexStored] == states[indexInput] { + indexStored++ + indexInput++ + } else if missingAllowed { + indexInput++ + } else { + return false + } + } + + //如果存储序列还有剩余, 而输入序列已经遍历完 + if indexStored != len(s.states) && indexInput == len(states) { + return false + } + + // 如果输入序列还有剩余, 而存储序列已经遍历完 + if indexStored == len(s.states) && indexInput != len(states) && !missingAllowed { + return false + } + + return true +} + +// GetMissingStates 获取缺失的状态 +func (s *StateLine[State]) GetMissingStates(states ...State) []State { + var missing = make([]State, 0, len(states)) + for _, state := range states { + if !collection.InComparableSlice(s.states, state) { + missing = append(missing, state) + } + } + return missing +} + +// HasState 检查时间线中是否包含指定状态 +func (s *StateLine[State]) HasState(state State) bool { + return collection.InComparableSlice(s.states, state) +} + +// String 获取时间线的字符串表示 +func (s *StateLine[State]) String() string { + var parts []string + for i := 0; i < len(s.states); i++ { + parts = append(parts, fmt.Sprintf("[%v] %v", s.states[i], s.points[i])) + } + return strings.Join(parts, " > ") +} + +// AddState 添加一个状态到时间线中,状态不能与任一时间点重合,否则将被忽略 +// - onTrigger: 该状态绑定的触发器,该触发器不会被主动执行,需要主动获取触发器执行 +func (s *StateLine[State]) AddState(state State, t time.Time, onTrigger ...func()) *StateLine[State] { + if collection.InComparableSlice(s.states, state) { + return s + } + // 将 t 按照从左到右由早到晚的顺序插入到 points 中 + for i := 0; i < len(s.points); i++ { + if s.points[i].After(t) { + s.points = append(s.points[:i], append([]time.Time{t}, s.points[i:]...)...) + s.states = append(s.states[:i], append([]State{state}, s.states[i:]...)...) + s.trigger = append(s.trigger[:i], append([][]func(){onTrigger}, s.trigger[i:]...)...) + return s + } + } + s.points = append(s.points, t) + s.states = append(s.states, state) + s.trigger = append(s.trigger, onTrigger) + return s +} + +// GetTimeByState 获取指定状态的时间点 +func (s *StateLine[State]) GetTimeByState(state State) time.Time { + for i := 0; i < len(s.states); i++ { + if s.states[i] == state { + return s.points[i] + } + } + return time.Time{} +} + +// GetNextTimeByState 获取指定状态的下一个时间点 +func (s *StateLine[State]) GetNextTimeByState(state State) time.Time { + for i := 0; i < len(s.states); i++ { + if s.states[i] == state && i+1 < len(s.points) { + return s.points[i+1] + } + } + return s.points[0] +} + +// GetLastState 获取最后一个状态 +func (s *StateLine[State]) GetLastState() State { + return s.states[len(s.states)-1] +} + +// GetPrevTimeByState 获取指定状态的上一个时间点 +func (s *StateLine[State]) GetPrevTimeByState(state State) time.Time { + for i := len(s.states) - 1; i >= 0; i-- { + if s.states[i] == state && i > 0 { + return s.points[i-1] + } + } + return time.Time{} +} + +// GetIndexByState 获取指定状态的索引 +func (s *StateLine[State]) GetIndexByState(state State) int { + for i := 0; i < len(s.states); i++ { + if s.states[i] == state { + return i + } + } + return -1 +} + +// GetStateByTime 获取指定时间点的状态 +func (s *StateLine[State]) GetStateByTime(t time.Time) State { + for i := len(s.points) - 1; i >= 0; i-- { + point := s.points[i] + if point.Before(t) || point.Equal(t) { + return s.states[i] + } + } + return s.states[len(s.points)-1] +} + +// GetTimeByIndex 获取指定索引的时间点 +func (s *StateLine[State]) GetTimeByIndex(index int) time.Time { + return s.points[index] +} + +// Move 时间线整体移动 +func (s *StateLine[State]) Move(d time.Duration) *StateLine[State] { + for i := 0; i < len(s.points); i++ { + s.points[i] = s.points[i].Add(d) + } + return s +} + +// GetNextStateTimeByIndex 获取指定索引的下一个时间点 +func (s *StateLine[State]) GetNextStateTimeByIndex(index int) time.Time { + return s.points[index+1] +} + +// GetPrevStateTimeByIndex 获取指定索引的上一个时间点 +func (s *StateLine[State]) GetPrevStateTimeByIndex(index int) time.Time { + return s.points[index-1] +} + +// GetStateIndexByTime 获取指定时间点的索引 +func (s *StateLine[State]) GetStateIndexByTime(t time.Time) int { + for i := len(s.points) - 1; i >= 0; i-- { + var point = s.points[i] + if point.Before(t) || point.Equal(t) { + return i + } + } + return -1 +} + +// GetStateCount 获取状态数量 +func (s *StateLine[State]) GetStateCount() int { + return len(s.states) +} + +// GetStateByIndex 获取指定索引的状态 +func (s *StateLine[State]) GetStateByIndex(index int) State { + return s.states[index] +} + +// GetTriggerByTime 获取指定时间点的触发器 +func (s *StateLine[State]) GetTriggerByTime(t time.Time) []func() { + for i := len(s.points) - 1; i >= 0; i-- { + var point = s.points[i] + if point.Before(t) || point.Equal(t) { + return s.trigger[i] + } + } + return nil +} + +// GetTriggerByIndex 获取指定索引的触发器 +func (s *StateLine[State]) GetTriggerByIndex(index int) []func() { + return s.trigger[index] +} + +// GetTriggerByState 获取指定状态的触发器 +func (s *StateLine[State]) GetTriggerByState(state State) []func() { + for i := 0; i < len(s.states); i++ { + if s.states[i] == state { + return s.trigger[i] + } + } + return nil +} + +// AddTriggerToState 给指定状态添加触发器 +func (s *StateLine[State]) AddTriggerToState(state State, onTrigger ...func()) *StateLine[State] { + for i := 0; i < len(s.states); i++ { + if s.states[i] == state { + s.trigger[i] = append(s.trigger[i], onTrigger...) + return s + } + } + return s +} + +// Iterate 按照时间顺序遍历时间线 +func (s *StateLine[State]) Iterate(handler func(index int, state State, t time.Time) bool) { + for i := 0; i < len(s.points); i++ { + if !handler(i, s.states[i], s.points[i]) { + return + } + } +} + +// IterateReverse 按照时间逆序遍历时间线 +func (s *StateLine[State]) IterateReverse(handler func(index int, state State, t time.Time) bool) { + for i := len(s.points) - 1; i >= 0; i-- { + if !handler(i, s.states[i], s.points[i]) { + return + } + } +} diff --git a/utils/chrono/state_line_test.go b/utils/chrono/state_line_test.go new file mode 100644 index 0000000..df22895 --- /dev/null +++ b/utils/chrono/state_line_test.go @@ -0,0 +1,20 @@ +package chrono_test + +import ( + "github.com/kercylan98/minotaur/utils/chrono" + "testing" + "time" +) + +func TestNewStateLine(t *testing.T) { + sl := chrono.NewStateLine(0) + sl.AddState(1, time.Now()) + sl.AddState(2, time.Now().Add(-chrono.Hour)) + + sl.Iterate(func(index int, state int, ts time.Time) bool { + t.Log(index, state, ts) + return true + }) + + t.Log(sl.GetStateByTime(time.Now())) +}