Skip to content

Commit c2d87d6

Browse files
authored
fix(cron): next time calculation for expressions with L or W modifiers (#168)
1 parent e6d6b95 commit c2d87d6

File tree

2 files changed

+66
-8
lines changed

2 files changed

+66
-8
lines changed

internal/csm/day_node.go

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,50 @@ func (n *DayNode) findForward() result {
9494
}
9595

9696
func (n *DayNode) isValid() bool {
97-
withinLimits := n.isValidDay()
97+
if !n.isValidDay() {
98+
return false
99+
}
100+
98101
if n.isWeekday() {
99-
withinLimits = withinLimits && n.isValidWeekday()
102+
return n.isValidWeekday()
100103
}
101-
return withinLimits
104+
105+
return n.isValidDayOfMonth()
102106
}
103107

104108
func (n *DayNode) isValidWeekday() bool {
105-
return contains(n.weekdayValues, n.getWeekday())
109+
if n.n == 0 {
110+
return contains(n.weekdayValues, n.getWeekday())
111+
}
112+
113+
daysOfWeek := n.daysOfWeekInMonth()
114+
if n.n > len(daysOfWeek) {
115+
return false
116+
}
117+
118+
if n.n > 0 {
119+
return n.Value() == daysOfWeek[n.n-1]
120+
}
121+
122+
return n.Value() == daysOfWeek[len(daysOfWeek)-1]
123+
}
124+
125+
func (n *DayNode) isValidDayOfMonth() bool {
126+
switch {
127+
case n.n == 0:
128+
case n.n < 0:
129+
return n.Value() == n.max()+n.n
130+
case n.n == NLastDayOfMonth:
131+
return n.Value() == n.max()
132+
case n.n&NWeekday != 0:
133+
dayDate := n.dayDate()
134+
if n.n&NLastDayOfMonth != 0 {
135+
return n.Value() == n.max() && isWeekday(dayDate)
136+
}
137+
return isWeekday(dayDate)
138+
}
139+
140+
return true
106141
}
107142

108143
func (n *DayNode) isValidDay() bool {
@@ -114,18 +149,20 @@ func (n *DayNode) isWeekday() bool {
114149
}
115150

116151
func (n *DayNode) getWeekday() int {
117-
date := makeDateTime(n.year.Value(), n.month.Value(), n.c.value)
118-
return int(date.Weekday())
152+
return int(n.dayDate().Weekday())
119153
}
120154

121155
func (n *DayNode) addDays(offset int) (overflowed bool) {
122156
overflowed = n.Value()+offset > n.max()
123-
today := makeDateTime(n.year.Value(), n.month.Value(), n.c.value)
124-
newDate := today.AddDate(0, 0, offset)
157+
newDate := n.dayDate().AddDate(0, 0, offset)
125158
n.c.value = newDate.Day()
126159
return
127160
}
128161

162+
func (n *DayNode) dayDate() time.Time {
163+
return makeDateTime(n.year.Value(), n.month.Value(), n.c.value)
164+
}
165+
129166
func (n *DayNode) max() int {
130167
month := time.Month(n.month.Value())
131168
year := n.year.Value()

quartz/cron_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,18 @@ func TestCronExpressionDayOfMonth(t *testing.T) {
285285
expression: "0 15 10 L * ?",
286286
expected: "Mon Mar 31 10:15:00 2025",
287287
},
288+
{
289+
expression: "0 15 12 L * ?",
290+
expected: "Mon Mar 31 12:15:00 2025",
291+
},
288292
{
289293
expression: "0 15 10 L-5 * ?",
290294
expected: "Wed Mar 26 10:15:00 2025",
291295
},
296+
{
297+
expression: "0 15 12 L-25 * ?",
298+
expected: "Thu Mar 6 12:15:00 2025",
299+
},
292300
{
293301
expression: "0 15 10 15W * ?",
294302
expected: "Fri Mar 14 10:15:00 2025",
@@ -305,6 +313,11 @@ func TestCronExpressionDayOfMonth(t *testing.T) {
305313
expression: "0 15 10 LW * ?",
306314
expected: "Mon Mar 31 10:15:00 2025",
307315
},
316+
// Verify no trigger in January, and in months with fewer than 31 days.
317+
{
318+
expression: "0 0 12 L-30 * ?",
319+
expected: "Sun Mar 1 12:00:00 2026",
320+
},
308321
}
309322

310323
prev := time.Date(2024, 1, 1, 12, 00, 00, 00, time.UTC).UnixNano()
@@ -330,6 +343,10 @@ func TestCronExpressionDayOfWeek(t *testing.T) {
330343
expression: "0 15 10 ? * L",
331344
expected: "Sat Oct 26 10:15:00 2024",
332345
},
346+
{
347+
expression: "0 15 12 ? * L",
348+
expected: "Sat Oct 26 12:15:00 2024",
349+
},
333350
{
334351
expression: "0 15 10 ? * 5L",
335352
expected: "Thu Oct 31 10:15:00 2024",
@@ -342,6 +359,10 @@ func TestCronExpressionDayOfWeek(t *testing.T) {
342359
expression: "0 15 10 ? * 2#1",
343360
expected: "Mon Nov 4 10:15:00 2024",
344361
},
362+
{
363+
expression: "0 15 12 ? * 2#1",
364+
expected: "Mon Oct 7 12:15:00 2024",
365+
},
345366
{
346367
expression: "0 15 10 ? * MON#1",
347368
expected: "Mon Nov 4 10:15:00 2024",

0 commit comments

Comments
 (0)