feat: 新增 chrono 包,用于替代原本的 timer 及 times 包

This commit is contained in:
kercylan98 2024-03-19 18:05:03 +08:00
parent 92d6c5680d
commit e608e9257e
14 changed files with 1377 additions and 0 deletions

1
.github/netpoll vendored Submodule

@ -0,0 +1 @@
Subproject commit bb9c3f74620c19b139d13c20793f9ff016d302c0

38
utils/chrono/adjuster.go Normal file
View File

@ -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)
}

View File

@ -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)
}

14
utils/chrono/constants.go Normal file
View File

@ -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
)

350
utils/chrono/moment.go Normal file
View File

@ -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)
}

172
utils/chrono/period.go Normal file
View File

@ -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)
}

View File

@ -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)
}

218
utils/chrono/scheduler.go Normal file
View File

@ -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)
}
}

View File

@ -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...)
}

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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")
})
}

261
utils/chrono/state_line.go Normal file
View File

@ -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
}
}
}

View File

@ -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()))
}