From b86d02b285cd8a183a27e347ed8ec9f85f83557a Mon Sep 17 00:00:00 2001 From: John Roesler Date: Mon, 28 Aug 2023 21:53:41 -0500 Subject: [PATCH] every X days should run on the same day if the At() time has yet to come (#553) * every x days should run on the same day if the At() time has yet to come * lint --- job.go | 23 ++++++++++------------- scheduler.go | 20 ++++++++------------ scheduler_test.go | 25 +++++++++++++++++-------- 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/job.go b/job.go index ad125f67..57fb81f7 100644 --- a/job.go +++ b/job.go @@ -231,24 +231,21 @@ func (j *Job) getFirstAtTime() time.Duration { } func (j *Job) getAtTime(lastRun time.Time) time.Duration { - var r time.Duration if len(j.atTimes) == 0 { - return r + return 0 } - if len(j.atTimes) == 1 { - return j.atTimes[0] + r := j.atTimes[0] + + if len(j.atTimes) == 1 || lastRun.IsZero() { + return r } - if lastRun.IsZero() { - r = j.atTimes[0] - } else { - for _, d := range j.atTimes { - nt := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day(), 0, 0, 0, 0, lastRun.Location()).Add(d) - if nt.After(lastRun) { - r = d - break - } + for _, d := range j.atTimes { + nt := time.Date(lastRun.Year(), lastRun.Month(), lastRun.Day(), 0, 0, 0, 0, lastRun.Location()).Add(d) + if nt.After(lastRun) { + r = d + break } } diff --git a/scheduler.go b/scheduler.go index 2e7aa48b..258424d9 100644 --- a/scheduler.go +++ b/scheduler.go @@ -418,15 +418,10 @@ func (s *Scheduler) calculateTotalDaysDifference(lastRun time.Time, daysToWeekda } func (s *Scheduler) calculateDays(job *Job, lastRun time.Time) nextRun { - if job.getInterval() == 1 { - lastRunDayPlusJobAtTime := s.roundToMidnightAndAddDSTAware(lastRun, job.getAtTime(lastRun)) - - if shouldRunToday(lastRun, lastRunDayPlusJobAtTime) { - return nextRun{duration: until(lastRun, lastRunDayPlusJobAtTime), dateTime: lastRunDayPlusJobAtTime} - } + nextRunAtTime := s.roundToMidnightAndAddDSTAware(lastRun, job.getAtTime(lastRun)).In(s.Location()) + if s.now().After(nextRunAtTime) || s.now() == nextRunAtTime { + nextRunAtTime = nextRunAtTime.AddDate(0, 0, job.getInterval()) } - - nextRunAtTime := s.roundToMidnightAndAddDSTAware(lastRun, job.getFirstAtTime()).AddDate(0, 0, job.getInterval()).In(s.Location()) return nextRun{duration: until(lastRun, nextRunAtTime), dateTime: nextRunAtTime} } @@ -434,10 +429,6 @@ func until(from time.Time, until time.Time) time.Duration { return until.Sub(from) } -func shouldRunToday(lastRun time.Time, atTime time.Time) bool { - return lastRun.Before(atTime) -} - func in(scheduleWeekdays []time.Weekday, weekday time.Weekday) bool { in := false @@ -1026,6 +1017,11 @@ func (s *Scheduler) DoWithJobDetails(jobFun interface{}, params ...interface{}) // At schedules the Job at a specific time of day in the form "HH:MM:SS" or "HH:MM" // or time.Time (note that only the hours, minutes, seconds and nanos are used). +// When the At time(s) occur on the same day on which the scheduler is started +// the Job will be run at the first available At time. +// For example: a schedule for every 2 days at 9am and 11am +// - currently 7am -> Job runs at 9am and 11am on the day the scheduler was started +// - currently 12 noon -> Job runs at 9am and 11am two days after the scheduler started func (s *Scheduler) At(i interface{}) *Scheduler { job := s.getCurrentJob() diff --git a/scheduler_test.go b/scheduler_test.go index 356f977d..0e04bc18 100644 --- a/scheduler_test.go +++ b/scheduler_test.go @@ -1128,10 +1128,6 @@ func calculateNextRunHelper( } func TestScheduler_CalculateNextRun(t *testing.T) { - ft := fakeTime{onNow: func(l *time.Location) time.Time { - return time.Date(2020, 1, 1, 12, 0, 0, 0, l) - }} - day := time.Hour * 24 januaryFirst2020At := func(hour, minute, second int) time.Time { return time.Date(2020, time.January, 1, hour, minute, second, 0, time.UTC) @@ -1158,15 +1154,15 @@ func TestScheduler_CalculateNextRun(t *testing.T) { {name: "every hour test", job: calculateNextRunHelper(1, hours, januaryFirst2020At(0, 0, 0), nil, nil, nil), wantTimeUntilNextRun: _getHours(1)}, {name: "every 25 hours test", job: calculateNextRunHelper(25, hours, januaryFirst2020At(0, 0, 0), nil, nil, nil), wantTimeUntilNextRun: _getHours(25)}, // DAYS - {name: "every day at midnight", job: calculateNextRunHelper(1, days, januaryFirst2020At(0, 0, 0), nil, nil, nil), wantTimeUntilNextRun: 1 * day}, + {name: "every day at midnight", job: calculateNextRunHelper(1, days, januaryFirst2020At(0, 0, 1), nil, nil, nil), wantTimeUntilNextRun: 1*day - time.Second}, {name: "every day at 09:30AM with scheduler starting before 09:30AM should run at same day at time", job: calculateNextRunHelper(1, days, januaryFirst2020At(0, 0, 0), []time.Duration{_getHours(9) + _getMinutes(30)}, nil, nil), wantTimeUntilNextRun: _getHours(9) + _getMinutes(30)}, {name: "every day at 09:30AM which just ran should run tomorrow at 09:30AM", job: calculateNextRunHelper(1, days, januaryFirst2020At(9, 30, 0), []time.Duration{_getHours(9) + _getMinutes(30)}, nil, nil), wantTimeUntilNextRun: 1 * day}, {name: "every 31 days at midnight should run 31 days later", job: calculateNextRunHelper(31, days, januaryFirst2020At(0, 0, 0), nil, nil, nil), wantTimeUntilNextRun: 31 * day}, {name: "daily job just ran at 8:30AM and should be scheduled for next day's 8:30AM", job: calculateNextRunHelper(1, days, januaryFirst2020At(8, 30, 0), []time.Duration{8*time.Hour + 30*time.Minute}, nil, nil), wantTimeUntilNextRun: 24 * time.Hour}, {name: "daily job just ran at 5:30AM and should be scheduled for today at 8:30AM", job: calculateNextRunHelper(1, days, januaryFirst2020At(5, 30, 0), []time.Duration{8*time.Hour + 30*time.Minute}, nil, nil), wantTimeUntilNextRun: 3 * time.Hour}, - {name: "job runs every 2 days, just ran at 5:30AM and should be scheduled for 2 days at 8:30AM", job: calculateNextRunHelper(2, days, januaryFirst2020At(5, 30, 0), []time.Duration{8*time.Hour + 30*time.Minute}, nil, nil), wantTimeUntilNextRun: (2 * day) + 3*time.Hour}, + {name: "job runs every 2 days, just ran at 5:30AM and should be scheduled for same day 8:30AM", job: calculateNextRunHelper(2, days, januaryFirst2020At(5, 30, 0), []time.Duration{5*time.Hour + 30*time.Minute, 8*time.Hour + 30*time.Minute}, nil, nil), wantTimeUntilNextRun: 3 * time.Hour}, {name: "job runs every 2 days, just ran at 8:30AM and should be scheduled for 2 days at 8:30AM", job: calculateNextRunHelper(2, days, januaryFirst2020At(8, 30, 0), []time.Duration{8*time.Hour + 30*time.Minute}, nil, nil), wantTimeUntilNextRun: 2 * day}, - {name: "daily, last run was 1 second ago", job: calculateNextRunHelper(1, days, ft.Now(time.UTC).Add(-time.Second), []time.Duration{12 * time.Hour}, nil, nil), wantTimeUntilNextRun: time.Second}, + {name: "daily, last run was 1 second ago", job: calculateNextRunHelper(1, days, januaryFirst2020At(11, 59, 59), []time.Duration{12 * time.Hour}, nil, nil), wantTimeUntilNextRun: time.Second}, //// WEEKS {name: "every week should run in 7 days", job: calculateNextRunHelper(1, weeks, januaryFirst2020At(0, 0, 0), nil, nil, nil), wantTimeUntilNextRun: 7 * day}, {name: "every week with .At time rule should run respect .At time rule", job: calculateNextRunHelper(1, weeks, januaryFirst2020At(9, 31, 0), []time.Duration{_getHours(9) + _getMinutes(30)}, nil, nil), wantTimeUntilNextRun: 7*day - time.Minute}, @@ -1210,6 +1206,10 @@ func TestScheduler_CalculateNextRun(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + ft := fakeTime{onNow: func(l *time.Location) time.Time { + return tc.job.lastRun + }} + s := NewScheduler(time.UTC) s.time = ft tc.job.runStartCount = atomic.NewInt64(0) @@ -2471,8 +2471,11 @@ func TestScheduler_MultipleAtTime(t *testing.T) { {description: "day test3", job: calculateNextRunHelper(1, days, getTime(5, 27, 10), atTimes, nil, nil), wantTimeUntilNextRun: _getMinutes(2) + _getSeconds(50)}, {description: "day test4", job: calculateNextRunHelper(1, days, getTime(5, 30, 0), atTimes, nil, nil), wantTimeUntilNextRun: _getHours(1) + _getMinutes(30)}, {description: "day test5", job: calculateNextRunHelper(1, days, getTime(15, 0, 0), atTimes, nil, nil), wantTimeUntilNextRun: _getHours(12) + _getMinutes(20)}, - {description: "week test1", job: calculateNextRunHelper(1, weeks, getTime(5, 30, 0), atTimes, nil, nil), wantTimeUntilNextRun: _getDays(7) - _getHours(2) - _getMinutes(10)}, + {description: "day test6", job: calculateNextRunHelper(2, days, getTime(15, 0, 0), atTimes, nil, nil), wantTimeUntilNextRun: _getHours(36) + _getMinutes(20)}, + {description: "day test7", job: calculateNextRunHelper(2, days, getTime(1, 0, 0), atTimes, nil, nil), wantTimeUntilNextRun: _getHours(2) + _getMinutes(20)}, + {description: "week test1", job: calculateNextRunHelper(1, weeks, getTime(5, 30, 0), atTimes, nil, nil), wantTimeUntilNextRun: _getHours(1) + _getMinutes(30)}, {description: "week test2", job: calculateNextRunHelper(1, weeks, getTime(15, 0, 0), atTimes, nil, nil), wantTimeUntilNextRun: _getDays(7) - _getHours(15) + _getHours(3) + _getMinutes(20)}, + {description: "week test2", job: calculateNextRunHelper(2, weeks, getTime(1, 0, 0), atTimes, nil, nil), wantTimeUntilNextRun: _getHours(2) + _getMinutes(20)}, {description: "weekday before test1", job: calculateNextRunHelper(1, weeks, getTime(5, 30, 0), atTimes, []time.Weekday{time.Tuesday}, nil), wantTimeUntilNextRun: _getDays(6) - _getHours(2) - _getMinutes(10)}, {description: "weekday before test2", job: calculateNextRunHelper(1, weeks, getTime(15, 0, 0), atTimes, []time.Weekday{time.Tuesday}, nil), wantTimeUntilNextRun: _getDays(6) - _getHours(15) + _getHours(3) + _getMinutes(20)}, {description: "weekday equals test1", job: calculateNextRunHelper(1, weeks, getTime(5, 30, 0), atTimes, []time.Weekday{time.Wednesday}, nil), wantTimeUntilNextRun: _getHours(1) + _getMinutes(30)}, @@ -2487,7 +2490,13 @@ func TestScheduler_MultipleAtTime(t *testing.T) { for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { + ft := fakeTime{onNow: func(l *time.Location) time.Time { + return tc.job.LastRun() + }} + s := NewScheduler(time.UTC) + s.time = ft + got := s.durationToNextRun(tc.job.LastRun(), tc.job).duration assert.Equalf(t, tc.wantTimeUntilNextRun, got, fmt.Sprintf("expected %s / got %s", tc.wantTimeUntilNextRun.String(), got.String())) })