From 5a894989c74a0a4885a2a4f39064592d9f1331d0 Mon Sep 17 00:00:00 2001 From: John Roesler Date: Sun, 21 Jan 2024 16:04:16 -0600 Subject: [PATCH] fix monthly jobs when counting days from the end (#662) --- job.go | 31 ++++++++++++++++++------------- job_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/job.go b/job.go index d25cab48..093b7f8c 100644 --- a/job.go +++ b/job.go @@ -756,32 +756,37 @@ type monthlyJob struct { func (m monthlyJob) next(lastRun time.Time) time.Time { daysList := make([]int, len(m.days)) copy(daysList, m.days) - firstDayNextMonth := time.Date(lastRun.Year(), lastRun.Month()+1, 1, 0, 0, 0, 0, lastRun.Location()) - for _, daySub := range m.daysFromEnd { - // getting a combined list of all the daysList and the negative daysList - // which count backwards from the first day of the next month - // -1 == the last day of the month - day := firstDayNextMonth.AddDate(0, 0, daySub).Day() - daysList = append(daysList, day) - } - slices.Sort(daysList) - firstPass := true - next := m.nextMonthDayAtTime(lastRun, daysList, firstPass) + daysFromEnd := m.handleNegativeDays(lastRun, daysList, m.daysFromEnd) + next := m.nextMonthDayAtTime(lastRun, daysFromEnd, true) if !next.IsZero() { return next } - firstPass = false from := time.Date(lastRun.Year(), lastRun.Month()+time.Month(m.interval), 1, 0, 0, 0, 0, lastRun.Location()) for next.IsZero() { - next = m.nextMonthDayAtTime(from, daysList, firstPass) + daysFromEnd = m.handleNegativeDays(from, daysList, m.daysFromEnd) + next = m.nextMonthDayAtTime(from, daysFromEnd, false) from = from.AddDate(0, int(m.interval), 0) } return next } +func (m monthlyJob) handleNegativeDays(from time.Time, days, negativeDays []int) []int { + var out []int + // getting a list of the days from the end of the following month + // -1 == the last day of the month + firstDayNextMonth := time.Date(from.Year(), from.Month()+1, 1, 0, 0, 0, 0, from.Location()) + for _, daySub := range negativeDays { + day := firstDayNextMonth.AddDate(0, 0, daySub).Day() + out = append(out, day) + } + out = append(out, days...) + slices.Sort(out) + return out +} + func (m monthlyJob) nextMonthDayAtTime(lastRun time.Time, days []int, firstPass bool) time.Time { // find the next day in the month that should run and then check for an at time for _, day := range days { diff --git a/job_test.go b/job_test.go index fae68db2..84c428ab 100644 --- a/job_test.go +++ b/job_test.go @@ -257,6 +257,42 @@ func TestMonthlyJob_next(t *testing.T) { time.Date(2000, 8, 31, 5, 30, 0, 0, time.UTC), 244 * 24 * time.Hour, }, + { + "handle -1 with differing month's day count", + 1, + nil, + []int{-1}, + []time.Time{ + time.Date(0, 0, 0, 5, 30, 0, 0, time.UTC), + }, + time.Date(2024, 1, 31, 5, 30, 0, 0, time.UTC), + time.Date(2024, 2, 29, 5, 30, 0, 0, time.UTC), + 29 * 24 * time.Hour, + }, + { + "handle -1 with another differing month's day count", + 1, + nil, + []int{-1}, + []time.Time{ + time.Date(0, 0, 0, 5, 30, 0, 0, time.UTC), + }, + time.Date(2024, 2, 29, 5, 30, 0, 0, time.UTC), + time.Date(2024, 3, 31, 5, 30, 0, 0, time.UTC), + 31 * 24 * time.Hour, + }, + { + "handle -1 every 3 months next run in February", + 3, + nil, + []int{-1}, + []time.Time{ + time.Date(0, 0, 0, 5, 30, 0, 0, time.UTC), + }, + time.Date(2023, 11, 30, 5, 30, 0, 0, time.UTC), + time.Date(2024, 2, 29, 5, 30, 0, 0, time.UTC), + 91 * 24 * time.Hour, + }, } for _, tt := range tests {