From 22e3fddf8db7f7236ff5883547047a90c580a75a Mon Sep 17 00:00:00 2001 From: kingeasternsun Date: Mon, 23 Aug 2021 23:12:16 +0800 Subject: [PATCH 1/6] feat(fix bug , add nexttime feature): 1. fix bug: "Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon/2". wo should get 1,3,5,7 2. add function to get the next run time --- os/gcron/gcron_schedule.go | 122 +++++++++++++++++++++++++++++++- os/gcron/gcron_schedule_test.go | 92 ++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 os/gcron/gcron_schedule_test.go diff --git a/os/gcron/gcron_schedule.go b/os/gcron/gcron_schedule.go index 0fdd83206..6d725f277 100644 --- a/os/gcron/gcron_schedule.go +++ b/os/gcron/gcron_schedule.go @@ -7,12 +7,13 @@ package gcron import ( - "github.com/gogf/gf/errors/gerror" - "github.com/gogf/gf/os/gtime" "strconv" "strings" "time" + "github.com/gogf/gf/errors/gerror" + "github.com/gogf/gf/os/gtime" + "github.com/gogf/gf/text/gregex" ) @@ -181,7 +182,9 @@ func parseItem(item string, min int, max int, allowQuestionMark bool) (map[int]s return nil, gerror.NewCodef(gerror.CodeInvalidParameter, `invalid pattern item: "%s"`, item) } else { rangeMin = i - rangeMax = i + if len(intervalArray) == 1 { + rangeMax = i + } } } if len(rangeArray) == 2 { @@ -189,6 +192,7 @@ func parseItem(item string, min int, max int, allowQuestionMark bool) (map[int]s return nil, gerror.NewCodef(gerror.CodeInvalidParameter, `invalid pattern item: "%s"`, item) } else { rangeMax = i + } } for i := rangeMin; i <= rangeMax; i += interval { @@ -255,3 +259,115 @@ func (s *cronSchedule) meet(t time.Time) bool { return true } } + +// 参考 robfig/cron的方法计算 github.com/robfi/cron/v3@v3.0.0/spec.go +// https://github.com/robfig/cron/blob/master/spec_test.go +func (s *cronSchedule) Next(t time.Time) time.Time { + + if s.every != 0 { + + diff := t.Unix() - s.create + cnt := diff/s.every + 1 + return t.Add(time.Duration(cnt*s.every) * time.Second) + } + + // Start at the earliest possible time (the upcoming second). + t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond) + + loc := t.Location() + added := false + yearLimit := t.Year() + 5 + // fmt.Printf("%+v \n", s) + +WRAP: + if t.Year() > yearLimit { + return t // who will care the job that run in five years later + } + + for !match(s.month, int(t.Month())) { + if !added { + added = true + t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, loc) + } + t = t.AddDate(0, 1, 0) + // need recheck + if t.Month() == time.January { + goto WRAP + } + } + + for !s.dayMatcher(t) { + if !added { + added = true + t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, loc) + } + t = t.AddDate(0, 0, 1) + + // Notice if the hour is no longer midnight due to DST. + // Add an hour if it's 23, subtract an hour if it's 1. + if t.Hour() != 0 { + if t.Hour() > 12 { + t = t.Add(time.Duration(24-t.Hour()) * time.Hour) + } else { + t = t.Add(time.Duration(-t.Hour()) * time.Hour) + } + } + + // fmt.Printf("%v %v\n", t, t.Weekday()) + if t.Day() == 1 { + goto WRAP + } + } + + for !match(s.hour, t.Hour()) { + if !added { + added = true + t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, loc) + } + t = t.Add(time.Hour) + // need recheck + if t.Hour() == 0 { + goto WRAP + } + } + + for !match(s.minute, t.Minute()) { + if !added { + added = true + t = t.Truncate(time.Minute) + } + t = t.Add(1 * time.Minute) + + if t.Minute() == 0 { + goto WRAP + } + } + + for !match(s.second, t.Second()) { + if !added { + added = true + t = t.Truncate(time.Second) + } + t = t.Add(1 * time.Second) + + // fmt.Printf("%v\n", t) + if t.Second() == 0 { + goto WRAP + } + } + return t.In(loc) + +} + +func (s *cronSchedule) dayMatcher(t time.Time) bool { + + _, ok1 := s.day[t.Day()] + _, ok2 := s.week[int(t.Weekday())] + + return ok1 && ok2 +} + +func match(m map[int]struct{}, key int) bool { + _, ok := m[key] + return ok +} diff --git a/os/gcron/gcron_schedule_test.go b/os/gcron/gcron_schedule_test.go new file mode 100644 index 000000000..1404ba74d --- /dev/null +++ b/os/gcron/gcron_schedule_test.go @@ -0,0 +1,92 @@ +package gcron + +import ( + "strings" + "testing" + "time" +) + +func TestNext(t *testing.T) { + runs := []struct { + time, spec string + expected string + }{ + // Simple cases + {"Mon Jul 9 14:45 2012", "0 0/15 * * * *", "Mon Jul 9 15:00 2012"}, + {"Mon Jul 9 14:59 2012", "0 0/15 * * * *", "Mon Jul 9 15:00 2012"}, + {"Mon Jul 9 14:59:59 2012", "0 0/15 * * * *", "Mon Jul 9 15:00 2012"}, + + // Wrap around hours + {"Mon Jul 9 15:45 2012", "0 20-35/15 * * * *", "Mon Jul 9 16:20 2012"}, + + // Wrap around days + {"Mon Jul 9 23:46 2012", "0 */15 * * * *", "Tue Jul 10 00:00 2012"}, + {"Mon Jul 9 23:45 2012", "0 20-35/15 * * * *", "Tue Jul 10 00:20 2012"}, + {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * * * *", "Tue Jul 10 00:20:15 2012"}, + {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 * * *", "Tue Jul 10 01:20:15 2012"}, + {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 10-12 * * *", "Tue Jul 10 10:20:15 2012"}, + + {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 1/2 */2 * *", "Thu Jul 11 01:20:15 2012"}, + {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 * *", "Wed Jul 10 00:20:15 2012"}, + {"Mon Jul 9 23:35:51 2012", "15/35 20-35/15 * 9-20 Jul *", "Wed Jul 10 00:20:15 2012"}, + + // Wrap around months + {"Mon Jul 9 23:35 2012", "0 0 0 9 Apr-Oct ?", "Thu Aug 9 00:00 2012"}, + {"Mon Jul 9 23:35 2012", "0 0 0 */5 Apr,Aug,Oct Mon", "Mon Aug 6 00:00 2012"}, + {"Mon Jul 9 23:35 2012", "0 0 0 */5 Oct Mon", "Mon Oct 1 00:00 2012"}, + + // Wrap around years + {"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon", "Mon Feb 4 00:00 2013"}, + {"Mon Jul 9 23:35 2012", "0 0 0 * Feb Mon/2", "Fri Feb 1 00:00 2013"}, + + // Wrap around minute, hour, day, month, and year + {"Mon Dec 31 23:59:45 2012", "0 * * * * *", "Tue Jan 1 00:00:00 2013"}, + + // Leap year + {"Mon Jul 9 23:35 2012", "0 0 0 29 Feb ?", "Mon Feb 29 00:00 2016"}, + } + + for _, c := range runs { + sched, err := newSchedule(c.spec) + if err != nil { + t.Error(err) + continue + } + // fmt.Printf("%+v", sched) + actual := sched.Next(getTime(c.time)) + expected := getTime(c.expected) + if !(actual.Unix() == expected.Unix()) { + t.Errorf("%s, \"%s\": (expected) %v != %v (actual)", c.time, c.spec, expected, actual) + } + } +} +func getTime(value string) time.Time { + if value == "" { + return time.Time{} + } + + var location = time.Local + if strings.HasPrefix(value, "TZ=") { + parts := strings.Fields(value) + loc, err := time.LoadLocation(parts[0][len("TZ="):]) + if err != nil { + panic("could not parse location:" + err.Error()) + } + location = loc + value = parts[1] + } + + var layouts = []string{ + "Mon Jan 2 15:04 2006", + "Mon Jan 2 15:04:05 2006", + } + for _, layout := range layouts { + if t, err := time.ParseInLocation(layout, value, location); err == nil { + return t + } + } + if t, err := time.ParseInLocation("2006-01-02T15:04:05-0700", value, location); err == nil { + return t + } + panic("could not parse time value " + value) +} From b9a07b104a5f19331741525039e6324fe347cab4 Mon Sep 17 00:00:00 2001 From: kingeasternsun Date: Thu, 26 Aug 2021 10:58:54 +0800 Subject: [PATCH 2/6] Update gcron_schedule_test.go add test to slash template parse --- os/gcron/gcron_schedule_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/os/gcron/gcron_schedule_test.go b/os/gcron/gcron_schedule_test.go index 1404ba74d..cfb4c5ffd 100644 --- a/os/gcron/gcron_schedule_test.go +++ b/os/gcron/gcron_schedule_test.go @@ -4,8 +4,33 @@ import ( "strings" "testing" "time" + + "github.com/gogf/gf/test/gtest" ) +func TestSlash(t *testing.T) { + + runs := []struct { + spec string + expected map[int]struct{} + }{ + {"0 0 0 * Feb Mon/2", map[int]struct{}{1: struct{}{}, 3: struct{}{}, 5: struct{}{}}}, + {"0 0 0 * Feb *", map[int]struct{}{1: struct{}{}, 2: struct{}{}, 3: struct{}{}, 4: struct{}{}, 5: struct{}{}, 6: struct{}{}, 0: struct{}{}}}, + } + + gtest.C(t, func(t *gtest.T) { + for _, c := range runs { + sched, err := newSchedule(c.spec) + if err != nil { + t.Fatal(err) + + } + t.AssertEQ(sched.week, c.expected) + } + }) + +} + func TestNext(t *testing.T) { runs := []struct { time, spec string From a6c9a7a944248b34f032c4cd51fb7a66c62ef334 Mon Sep 17 00:00:00 2001 From: kingeasternsun Date: Fri, 3 Sep 2021 19:35:19 +0800 Subject: [PATCH 3/6] fix test --- os/gcron/gcron_schedule.go | 3 +-- os/gcron/gcron_schedule_test.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/os/gcron/gcron_schedule.go b/os/gcron/gcron_schedule.go index e1b6bee63..c46ccdbf8 100644 --- a/os/gcron/gcron_schedule.go +++ b/os/gcron/gcron_schedule.go @@ -7,14 +7,13 @@ package gcron import ( - "github.com/gogf/gf/errors/gcode" "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/os/gtime" + "github.com/gogf/gf/text/gregex" "strconv" "strings" "time" - "github.com/gogf/gf/text/gregex" ) // cronSchedule is the schedule for cron job. diff --git a/os/gcron/gcron_schedule_test.go b/os/gcron/gcron_schedule_test.go index cfb4c5ffd..100ee54a6 100644 --- a/os/gcron/gcron_schedule_test.go +++ b/os/gcron/gcron_schedule_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" "time" - + "github.com/gogf/gf/test/gtest" ) From cfc1f8fbe4a148504a714b9d5908719e372ee038 Mon Sep 17 00:00:00 2001 From: kingeasternsun Date: Fri, 17 Sep 2021 09:27:31 +0800 Subject: [PATCH 4/6] use english doc --- os/gcron/gcron_schedule.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/os/gcron/gcron_schedule.go b/os/gcron/gcron_schedule.go index b1c749d33..40de1953b 100644 --- a/os/gcron/gcron_schedule.go +++ b/os/gcron/gcron_schedule.go @@ -7,13 +7,14 @@ package gcron import ( + "strconv" + "strings" + "time" + "github.com/gogf/gf/errors/gcode" "github.com/gogf/gf/errors/gerror" "github.com/gogf/gf/os/gtime" "github.com/gogf/gf/text/gregex" - "strconv" - "strings" - "time" ) // cronSchedule is the schedule for cron job. @@ -265,7 +266,7 @@ func (s *cronSchedule) meet(t time.Time) bool { } } -// 参考 robfig/cron的方法计算 github.com/robfi/cron/v3@v3.0.0/spec.go +// inspired by robfig/cron github.com/robfi/cron/v3@v3.0.0/spec.go // https://github.com/robfig/cron/blob/master/spec_test.go func (s *cronSchedule) Next(t time.Time) time.Time { From e21bd9b4cb6c860c87515344b435f51430501791 Mon Sep 17 00:00:00 2001 From: kingeasternsun Date: Wed, 27 Oct 2021 10:44:28 +0800 Subject: [PATCH 5/6] Update gcron_schedule.go --- os/gcron/gcron_schedule.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/os/gcron/gcron_schedule.go b/os/gcron/gcron_schedule.go index 58782016c..e25ce25f7 100644 --- a/os/gcron/gcron_schedule.go +++ b/os/gcron/gcron_schedule.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" "time" - + "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/os/gtime" From 14b7c591d0517a32db3d74570f59329299cc218a Mon Sep 17 00:00:00 2001 From: kingeasternsun Date: Tue, 9 Nov 2021 16:25:44 +0800 Subject: [PATCH 6/6] fix "github.com/gogf/gf/v2/test/gtest" --- os/gcron/gcron_schedule_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/os/gcron/gcron_schedule_test.go b/os/gcron/gcron_schedule_test.go index 100ee54a6..06c07d72c 100644 --- a/os/gcron/gcron_schedule_test.go +++ b/os/gcron/gcron_schedule_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/gogf/gf/test/gtest" + "github.com/gogf/gf/v2/test/gtest" ) func TestSlash(t *testing.T) {