From d41b1134c9dac08354208b378b23d80ccd037a3a Mon Sep 17 00:00:00 2001 From: 兔子 Date: Tue, 18 Apr 2023 13:38:58 +0800 Subject: [PATCH] support cron --- cron.go | 180 +++++++++++++++++++++++++++++++++++++++++++++++++-- time_test.go | 44 +++++++++++++ timer.go | 28 ++++++-- typed.go | 2 +- 4 files changed, 242 insertions(+), 12 deletions(-) diff --git a/cron.go b/cron.go index 9027c25..7f074e3 100644 --- a/cron.go +++ b/cron.go @@ -2,10 +2,80 @@ package startimer import ( "errors" + "strconv" "strings" + "sync" + "time" ) -func parseCron(cron string) (StarTimer, error) { +var rpmonths = map[string]int{ + "JAN": 1, + "FEB": 2, + "MAR": 3, + "APR": 4, + "MAY": 5, + "JUN": 6, + "JUL": 7, + "AUG": 8, + "SEP": 9, + "OCT": 10, + "NOV": 11, + "DEC": 12, +} + +var rpweekdays = map[string]int{ + "SUN": 0, + "MON": 1, + "TUE": 2, + "WED": 3, + "THU": 4, + "FRI": 5, + "SAT": 6, +} + +func NewTimerWithCron(cron ...string) (StarTimer, error) { + tmr := StarTimer{ + base: time.Now(), + nextDate: time.Time{}, + mu: new(sync.RWMutex), + } + for _, c := range cron { + c = "0 " + c + rpt, err := parseCron(c) + if err != nil { + return tmr, err + } + tmr.repeat = append(tmr.repeat, rpt) + } + return tmr, nil +} + +func NewTimerWithSecCron(cron ...string) (StarTimer, error) { + tmr := StarTimer{ + base: time.Now(), + nextDate: time.Time{}, + mu: new(sync.RWMutex), + } + for _, c := range cron { + rpt, err := parseCron(c) + if err != nil { + return tmr, err + } + tmr.repeat = append(tmr.repeat, rpt) + } + return tmr, nil +} + +func parseCron(cron string) (*Repeats, error) { + { + cron = strings.ToUpper(cron) + for k, v := range rpmonths { + cron = strings.ReplaceAll(cron, k, strconv.Itoa(v)) + } + for k, v := range rpweekdays { + cron = strings.ReplaceAll(cron, k, strconv.Itoa(v)) + } + } for { oldLen := len(cron) cron = strings.ReplaceAll(strings.TrimSpace(cron), " ", " ") @@ -15,8 +85,110 @@ func parseCron(cron string) (StarTimer, error) { } ct := strings.Split(cron, " ") if len(ct) != 6 { - return StarTimer{}, errors.New("Invalid cron,argument not enough") + return nil, errors.New("Invalid cron,argument not enough") + } + + foundFirstAll := false + var myMap = make(map[Unit]map[uint32]Repeat) + for idx, c := range ct { + if _, ok := myMap[Unit(idx)]; !ok { + myMap[Unit(idx)] = make(map[uint32]Repeat) + } + var minVal, maxVal int + switch Unit(idx) { + case STAR_MINUTE, STAR_SECOND: + minVal = 0 + maxVal = 60 + case STAR_HOUR: + minVal = 0 + maxVal = 24 + case STAR_DAY: + minVal = 1 + maxVal = 32 + case STAR_MONTH: + minVal = 1 + maxVal = 13 + case STAR_WEEK: + minVal = 0 + maxVal = 7 + } + + cdt := strings.Split(c, ",") + for _, dtl := range cdt { + dtl = strings.TrimSpace(dtl) + if dtl == "*" { + if !foundFirstAll { + foundFirstAll = true + if maxVal > 0 { + for i := minVal; i < maxVal; i++ { + val := uint32(i) + myMap[Unit(idx)][val] = Repeat{ + Unit: Unit(idx), + Value: val, + } + } + } + } + continue + } + if strings.Contains(dtl, "*/") { + num, err := strconv.Atoi(strings.TrimPrefix(dtl, "*/")) + if err != nil { + return nil, err + } + for i := minVal; i < maxVal; i++ { + val := i + if Unit(idx) == STAR_DAY || Unit(idx) == STAR_MONTH { + val-- + } + if val%num == 0 { + myMap[Unit(idx)][uint32(val)] = Repeat{ + Unit: Unit(idx), + Value: uint32(i), + } + } + } + continue + } + if strings.Contains(dtl, "-") { + numbers := strings.Split(dtl, "-") + if len(numbers) != 2 { + return nil, errors.New("Invalid Cron") + } + startNum, err := strconv.Atoi(numbers[0]) + if err != nil { + return nil, err + } + endNum, err := strconv.Atoi(numbers[1]) + if err != nil { + return nil, err + } + if startNum < minVal && endNum >= maxVal { + return nil, errors.New("Invalid Cron") + } + for i := startNum; i <= endNum; i++ { + myMap[Unit(idx)][uint32(i)] = Repeat{ + Unit: Unit(idx), + Value: uint32(i), + } + } + continue + } + number, err := strconv.Atoi(dtl) + if err != nil { + return nil, err + } + myMap[Unit(idx)][uint32(number)] = Repeat{ + Unit: Unit(idx), + Value: uint32(number), + } + } + } + var repeat Repeats + for _, v := range myMap { + for _, rpt := range v { + repeat.Repeat = append(repeat.Repeat, rpt) + } } - - return StarTimer{}, nil + return &repeat, nil } diff --git a/time_test.go b/time_test.go index d563f1c..544bb0c 100644 --- a/time_test.go +++ b/time_test.go @@ -151,3 +151,47 @@ func TestPrepareCronSimple(t *testing.T) { fmt.Println("") } } + +func TestInvalidCron(t *testing.T) { + tk := StarTimer{ + base: time.Now(), + repeat: []*Repeats{ + { + Every: false, + Repeat: []Repeat{ + {Unit: STAR_MINUTE, Value: 0}, + {Unit: STAR_HOUR, Value: 2}, + {Unit: STAR_DAY, Value: 29}, {Unit: STAR_DAY, Value: 30}, {Unit: STAR_DAY, Value: 31}, + {Unit: STAR_MONTH, Value: 4}, + }, + }, + }, + } + base := tk.base + for i := 0; i < 10; i++ { + base = tk.parseNextDate(base, true) + fmt.Println(base) + fmt.Println("") + } +} + +func TestParseCronMax(t *testing.T) { + tk, err := NewTimerWithSecCron("*/5 40-59 * * * *") + if err != nil { + t.Fatal(err) + } + tk.AddTask(func() { + fmt.Println("现在是", time.Now()) + }) + for i := 0; i < 20; i++ { + fmt.Println() + } + /* + err = tk.Run() + if err != nil { + t.Fatal(err) + } + time.Sleep(time.Second * 300) + + */ +} diff --git a/timer.go b/timer.go index 8ebba0b..c859e17 100644 --- a/timer.go +++ b/timer.go @@ -134,6 +134,9 @@ func (t *StarTimer) ImportRepeats(r string) error { func (t *StarTimer) BaseDate() time.Time { return t.base } +func (t *StarTimer) SetBaseDate(date time.Time) { + t.base = date +} func (t *StarTimer) ResetWithRepeat(base time.Time, repeat []*Repeats) error { t.Stop() @@ -142,6 +145,10 @@ func (t *StarTimer) ResetWithRepeat(base time.Time, repeat []*Repeats) error { return t.Run() } +func (t *StarTimer) Repeats() []*Repeats { + return t.repeat +} + func (t *StarTimer) Run() error { t.mu.Lock() defer t.mu.Unlock() @@ -192,7 +199,7 @@ func (t *StarTimer) parseNextDate(base time.Time, isMock bool) time.Time { if d.Every { dates = append(dates, t.parseEveryNextDate(base, d, isMock)...) } else { - dates = append(dates, t.parseStaticNextDate(base, d)) + dates = append(dates, t.parseStaticNextDate(base, d, false)) } } sort.SliceStable(dates, func(i, j int) bool { @@ -215,7 +222,8 @@ func (t *StarTimer) parseNextDate(base time.Time, isMock bool) time.Time { return time.Time{} } -func (t *StarTimer) parseStaticNextDate(base time.Time, r *Repeats) time.Time { +func (t *StarTimer) parseStaticNextDate(base time.Time, r *Repeats, recall bool) time.Time { + base = time.Date(base.Year(), base.Month(), base.Day(), base.Hour(), base.Minute(), base.Second(), 0, base.Location()) var targets []time.Time var uniqueRepeat [][]Repeat selectMap := make(map[Unit][]Repeat) @@ -249,7 +257,8 @@ func (t *StarTimer) parseStaticNextDate(base time.Time, r *Repeats) time.Time { return task[i].Unit < task[j].Unit }) target := base - if !r.Every { //固定日期 + veryDay, veryMonth := 0, 0 //验证日期 + if !r.Every { //固定日期 for _, d := range task { switch d.Unit { case STAR_SECOND: @@ -271,6 +280,7 @@ func (t *StarTimer) parseStaticNextDate(base time.Time, r *Repeats) time.Time { } target = target.Add(time.Hour * time.Duration(sub)) case STAR_DAY: + veryDay = int(d.Value) sub := int(d.Value) - target.Day() if sub >= 0 { target = target.Add(time.Hour * 24 * time.Duration(sub)) @@ -278,6 +288,7 @@ func (t *StarTimer) parseStaticNextDate(base time.Time, r *Repeats) time.Time { } target = time.Date(target.Year(), target.Month()+1, int(d.Value), target.Hour(), target.Minute(), target.Second(), 0, target.Location()) case STAR_MONTH: + veryMonth = int(d.Value) sub := int(d.Value) - int(target.Month()) if sub < 0 { sub += 12 @@ -292,8 +303,10 @@ func (t *StarTimer) parseStaticNextDate(base time.Time, r *Repeats) time.Time { } } } - - if target == base { + if (veryDay != 0 && target.Day() != veryDay) || (veryMonth != 0 && int(target.Month()) != veryMonth) { + continue + } + if target == base && !recall { continue } targets = append(targets, target) @@ -303,7 +316,7 @@ func (t *StarTimer) parseStaticNextDate(base time.Time, r *Repeats) time.Time { return targets[i].UnixNano() < targets[j].UnixNano() }) for k, v := range targets { - if v.After(base) { + if v.UnixNano() > base.UnixNano() || (recall && v.UnixNano() == base.UnixNano()) { targets = targets[k:] break } @@ -315,7 +328,8 @@ func (t *StarTimer) parseStaticNextDate(base time.Time, r *Repeats) time.Time { return targets[0] } } - return t.parseStaticNextDate(targets[0].Add(time.Hour*24), r) + nextBase := time.Date(targets[0].Year(), targets[0].Month(), targets[0].Day(), 0, 0, 0, 0, targets[0].Location()) + return t.parseStaticNextDate(nextBase.Add(time.Hour*24), r, true) } } if len(targets) > 0 { diff --git a/typed.go b/typed.go index 3370b09..ac605dc 100644 --- a/typed.go +++ b/typed.go @@ -14,8 +14,8 @@ const ( STAR_HOUR STAR_DAY STAR_MONTH - STAR_YEAR STAR_WEEK + STAR_YEAR ) type Repeats struct {