diff --git a/constantdelay.go b/constantdelay.go index cd6e7b1b..3c69be42 100644 --- a/constantdelay.go +++ b/constantdelay.go @@ -1,11 +1,14 @@ package cron -import "time" +import ( + "time" +) // ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes". // It does not support jobs more frequent than once a second. type ConstantDelaySchedule struct { - Delay time.Duration + Delay time.Duration + startAt time.Time } // Every returns a crontab Schedule that activates once every duration. @@ -20,8 +23,24 @@ func Every(duration time.Duration) ConstantDelaySchedule { } } +// StartingAt sets the start time of the simple recurring duty cycle. This is useful +// when we want the cycle to start at a specific date which makes it independent +// from the Cron start time. +func (schedule ConstantDelaySchedule) StartingAt(start time.Time) ConstantDelaySchedule { + schedule.startAt = start + return schedule +} + // Next returns the next time this should be run. // This rounds so that the next activation time will be on the second. func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time { + if !schedule.startAt.IsZero() { + if schedule.startAt.After(t) { + return time.Time{} + } + + t = schedule.startAt.Add((t.Sub(schedule.startAt) / schedule.Delay) * (schedule.Delay)) + } + return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond) } diff --git a/constantdelay_test.go b/constantdelay_test.go index f43a58ad..51c9f4ca 100644 --- a/constantdelay_test.go +++ b/constantdelay_test.go @@ -52,3 +52,49 @@ func TestConstantDelayNext(t *testing.T) { } } } + +func TestConstantDelayWithStartNext(t *testing.T) { + tests := []struct { + time string + delay time.Duration + startAt string + expected string + }{ + // The starting date is the same as time + {"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 14:45 2012", "Mon Jul 9 15:00 2012"}, + {"Mon Jul 9 14:45 2012", 15 * time.Minute, "Mon Jul 9 14:45 2012", "Mon Jul 9 15:00 2012"}, + {"Mon Jul 9 14:59:59 2012", 15 * time.Minute, "Mon Jul 9 14:59:59 2012", "Mon Jul 9 15:14:59 2012"}, + + // The starting date is in the past + {"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 8 14:45 2012", "Mon Jul 9 15:00 2012"}, + {"Mon Jul 9 14:45 2012", 15 * time.Minute, "Mon Jul 8 14:45 2012", "Mon Jul 9 15:00 2012"}, + {"Mon Jul 9 14:59:59 2012", 15 * time.Minute, "Mon Jul 8 14:59:59 2012", "Mon Jul 9 15:14:59 2012"}, + + // The starting date is way in the past + {"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Thu Jul 9 14:45 1992", "Mon Jul 9 15:00 2012"}, + {"Mon Jul 9 14:45 2012", 15 * time.Minute, "Thu Jul 9 14:45 1992", "Mon Jul 9 15:00 2012"}, + {"Mon Jul 9 14:59:59 2012", 15 * time.Minute, "Thu Jul 9 14:59:59 1992", "Mon Jul 9 15:14:59 2012"}, + + // The starting date is truncated + {"Mon Jul 9 14:45:12 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 9 00:00 2012", "Mon Jul 9 15:00 2012"}, + {"Mon Jul 9 14:45:12 2012", 15 * time.Minute, "Mon Jul 9 00:00 2012", "Mon Jul 9 15:00 2012"}, + {"Mon Jul 9 14:59:59 2012", 15 * time.Minute, "Mon Jul 9 00:00:00 2012", "Mon Jul 9 15:00:00 2012"}, + + // The starting date is in the future - the job should not run + {"Mon Jul 9 14:45 2012", 15*time.Minute + 50*time.Nanosecond, "Mon Jul 10 14:45 2012", ""}, + {"Mon Jul 9 14:45 2012", 15 * time.Minute, "Mon Jul 10 14:45 2012", ""}, + {"Mon Jul 9 14:59:59 2012", 15 * time.Minute, "Mon Jul 10 14:59:59 2012", ""}, + + // Wrap around months + {"Mon Jul 9 23:35 2012", 720 * time.Hour, "Mon Jul 9 00:00 2012", "Thu Aug 8 00:00 2012"}, + {"Thu Aug 8 23:35 2012", 720 * time.Hour, "Mon Jul 9 00:00 2012", "Fri Sep 7 00:00 2012"}, + } + + for _, c := range tests { + actual := Every(c.delay).StartingAt(getTime(c.startAt)).Next(getTime(c.time)) + expected := getTime(c.expected) + if actual != expected { + t.Errorf("%s, \"%s\", %s: (expected) %v != %v (actual)", c.time, c.delay, c.startAt, expected, actual) + } + } +} diff --git a/parser_test.go b/parser_test.go index 41c8c520..c983ae7b 100644 --- a/parser_test.go +++ b/parser_test.go @@ -149,7 +149,7 @@ func TestParseSchedule(t *testing.T) { {secondParser, "CRON_TZ=UTC 0 5 * * * *", every5min(time.UTC)}, {standardParser, "CRON_TZ=UTC 5 * * * *", every5min(time.UTC)}, {secondParser, "CRON_TZ=Asia/Tokyo 0 5 * * * *", every5min(tokyo)}, - {secondParser, "@every 5m", ConstantDelaySchedule{5 * time.Minute}}, + {secondParser, "@every 5m", ConstantDelaySchedule{Delay: 5 * time.Minute}}, {secondParser, "@midnight", midnight(time.Local)}, {secondParser, "TZ=UTC @midnight", midnight(time.UTC)}, {secondParser, "TZ=Asia/Tokyo @midnight", midnight(tokyo)}, @@ -324,7 +324,7 @@ func TestStandardSpecSchedule(t *testing.T) { }, { expr: "@every 5m", - expected: ConstantDelaySchedule{time.Duration(5) * time.Minute}, + expected: ConstantDelaySchedule{Delay: time.Duration(5) * time.Minute}, }, { expr: "5 j * * *",