From e4edd2361a2e59c37cb8ef30f30bafe3f87d5836 Mon Sep 17 00:00:00 2001 From: penglj Date: Mon, 9 May 2022 10:23:38 +0800 Subject: [PATCH] origin modules not active,thanks https://github.com/robfig/cron/pull/317 --- constantdelay.go | 4 ++++ cron.go | 13 ++++++++++++- cron_test.go | 4 ++++ doc.go | 1 + parser.go | 31 ++++++++++++++++++++++++++++++- parser_test.go | 8 ++++---- spec.go | 13 +++++++++++-- 7 files changed, 66 insertions(+), 8 deletions(-) diff --git a/constantdelay.go b/constantdelay.go index 9e4db072..ad4ed83c 100644 --- a/constantdelay.go +++ b/constantdelay.go @@ -26,6 +26,10 @@ func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time { return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond) } +func (schedule ConstantDelaySchedule) IsOnce() bool { + return false +} + // Prev returns the previous time this should be run. // This rounds so that the previous activation time will be on the second. func (schedule ConstantDelaySchedule) Prev(t time.Time) time.Time { diff --git a/cron.go b/cron.go index 4e2dcc9a..67036439 100644 --- a/cron.go +++ b/cron.go @@ -54,6 +54,7 @@ type Schedule interface { // Prev returns the previous activation time, earlier than the given time. Prev(time.Time) time.Time + IsOnce() bool } // EntryID identifies an entry within a Cron instance @@ -319,16 +320,26 @@ func (c *Cron) run() { now = now.In(c.location) c.logger.Info("wake", "now", now) + del := 0 + // Run every entry whose next time was less than now - for _, e := range c.entries { + for k, e := range c.entries { if e.Next.After(now) || e.Next.IsZero() { break } + c.startJob(e.WrappedJob) e.Prev = e.Next e.Next = e.Schedule.Next(now) c.logger.Info("run", "now", now, "entry", e.ID, "next", e.Next) + // only run once + if e.Schedule.IsOnce() { + c.entries[k] = c.entries[del] + del++ + } + } + c.entries = c.entries[del:] case newEntry := <-c.add: timer.Stop() diff --git a/cron_test.go b/cron_test.go index d8f0f279..c690531c 100644 --- a/cron_test.go +++ b/cron_test.go @@ -545,6 +545,10 @@ func (*ZeroSchedule) Prev(time.Time) time.Time { return time.Time{} } +func (*ZeroSchedule) IsOnce() bool { + return false +} + // Tests that job without time does not run func TestJobWithZeroTimeDoesNotRun(t *testing.T) { cron := newWithSeconds() diff --git a/doc.go b/doc.go index 6957ae7f..4f0e4ae0 100644 --- a/doc.go +++ b/doc.go @@ -24,6 +24,7 @@ them in their own goroutines. c.AddFunc("CRON_TZ=Asia/Tokyo 30 04 * * *", func() { fmt.Println("Runs at 04:30 Tokyo time every day") }) c.AddFunc("@hourly", func() { fmt.Println("Every hour, starting an hour from now") }) c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty, starting an hour thirty from now") }) + c.AddFunc("@once 2020-06-02 17:04:31", func() { fmt.Println("Hello! Now is 2020-06-02 17:04:31") }) c.Start() .. // Funcs are invoked in their own goroutine, asynchronously. diff --git a/parser.go b/parser.go index 5dec287a..bf9240c7 100644 --- a/parser.go +++ b/parser.go @@ -358,7 +358,6 @@ func mustParseInt(expr string) (uint, error) { // getBits sets all bits in the range [min, max], modulo the given step size. func getBits(min, max, step uint) uint64 { var bits uint64 - // If step is 1, use shifts. if step == 1 { return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min) @@ -445,5 +444,35 @@ func parseDescriptor(descriptor string, loc *time.Location) (Schedule, error) { return Every(duration), nil } + const once = "@once " + if strings.HasPrefix(descriptor, once) { + runDate, err := time.Parse("2006-01-02 15:04:05", strings.Replace(descriptor, once, "", 1)) + if err != nil { + return nil, fmt.Errorf("failed to parse duration %s: %s", descriptor, err) + } + + var ( + second = uint(runDate.Second()) + minute = uint(runDate.Minute()) + hour = uint(runDate.Hour()) + dom = uint(runDate.Day()) + month = uint(runDate.Month()) + year = uint(runDate.Year()) + ) + + ret := &SpecSchedule{ + Second: uint64(second), + Minute: uint64(minute), + Hour: uint64(hour), + Dom: uint64(dom), + Month: uint64(month), + Year: uint64(year), + Dow: all(dow), + Once: true, + Location: loc, + } + return ret, nil + } + return nil, fmt.Errorf("unrecognized descriptor: %s", descriptor) } diff --git a/parser_test.go b/parser_test.go index 41c8c520..c1fbbc4d 100644 --- a/parser_test.go +++ b/parser_test.go @@ -320,7 +320,7 @@ func TestStandardSpecSchedule(t *testing.T) { }{ { expr: "5 * * * *", - expected: &SpecSchedule{1 << seconds.min, 1 << 5, all(hours), all(dom), all(months), all(dow), time.Local}, + expected: &SpecSchedule{1 << seconds.min, 1 << 5, all(hours), all(dom), all(months), all(dow), 0, false, time.Local}, }, { expr: "@every 5m", @@ -359,15 +359,15 @@ func TestNoDescriptorParser(t *testing.T) { } func every5min(loc *time.Location) *SpecSchedule { - return &SpecSchedule{1 << 0, 1 << 5, all(hours), all(dom), all(months), all(dow), loc} + return &SpecSchedule{1 << 0, 1 << 5, all(hours), all(dom), all(months), all(dow), 0, false, loc} } func every5min5s(loc *time.Location) *SpecSchedule { - return &SpecSchedule{1 << 5, 1 << 5, all(hours), all(dom), all(months), all(dow), loc} + return &SpecSchedule{1 << 5, 1 << 5, all(hours), all(dom), all(months), all(dow), 0, false, loc} } func midnight(loc *time.Location) *SpecSchedule { - return &SpecSchedule{1, 1, 1, all(dom), all(months), all(dow), loc} + return &SpecSchedule{1, 1, 1, all(dom), all(months), all(dow), 0, false, loc} } func annual(loc *time.Location) *SpecSchedule { diff --git a/spec.go b/spec.go index dfa7d684..79ce7628 100644 --- a/spec.go +++ b/spec.go @@ -7,8 +7,8 @@ import ( // SpecSchedule specifies a duty cycle (to the second granularity), based on a // traditional crontab specification. It is computed initially and stored as bit sets. type SpecSchedule struct { - Second, Minute, Hour, Dom, Month, Dow uint64 - + Second, Minute, Hour, Dom, Month, Dow, Year uint64 + Once bool // Override location for this schedule. Location *time.Location } @@ -78,9 +78,18 @@ const ( starBit = 1 << 63 ) +func (s *SpecSchedule) IsOnce() bool { + return s.Once +} + // Next returns the next time this schedule is activated, greater than the given // time. If no time can be found to satisfy the schedule, return the zero time. func (s *SpecSchedule) Next(t time.Time) time.Time { + // Once Task is definite datetime + if s.Once { + return time.Date(int(s.Year), time.Month(s.Month), int(s.Dom), int(s.Hour), int(s.Minute), int(s.Second), 0, s.Location) + } + // General approach // // For Month, Day, Hour, Minute, Second: