diff --git a/internal/csm/day_node.go b/internal/csm/day_node.go index 8ba682c..3a2e3c9 100644 --- a/internal/csm/day_node.go +++ b/internal/csm/day_node.go @@ -140,8 +140,8 @@ func (n *DayNode) max() int { } func (n *DayNode) nextDayN() (overflowed bool) { - switch n.n { - case NWeekday: + switch { + case n.n > 0 && n.n&NWeekday != 0: n.nextWeekdayOfMonth() default: n.nextLastDayOfMonth() @@ -155,7 +155,7 @@ func (n *DayNode) nextWeekdayOfMonth() { monthLastDate := lastDayOfMonth(year, month) date := n.c.values[0] - if date > monthLastDate { + if date > monthLastDate || n.n&NLastDayOfMonth != 0 { date = monthLastDate } diff --git a/quartz/cron.go b/quartz/cron.go index 8a216ea..5bec075 100644 --- a/quartz/cron.go +++ b/quartz/cron.go @@ -288,16 +288,22 @@ func parseDayOfMonthField(field string, min, max int, translate ...[]string) (*c return newCronFieldN([]int{}, -n), nil } - if strings.ContainsRune(field, weekdayRune) && cronWeekdayRegex.MatchString(field) { - day := strings.TrimSuffix(field, string(weekdayRune)) - if day == "" { - return nil, newInvalidCronFieldError("weekday", field) + if strings.ContainsRune(field, weekdayRune) { + if field == fmt.Sprintf("%c%c", lastRune, weekdayRune) { + return newCronFieldN([]int{0}, cronLastDayOfMonthN|cronWeekdayN), nil } - dayOfMonth, err := strconv.Atoi(day) - if err != nil || !inScope(dayOfMonth, min, max) { - return nil, newInvalidCronFieldError("weekday", field) + + if cronWeekdayRegex.MatchString(field) { + day := strings.TrimSuffix(field, string(weekdayRune)) + if day == "" { + return nil, newInvalidCronFieldError("weekday", field) + } + dayOfMonth, err := strconv.Atoi(day) + if err != nil || !inScope(dayOfMonth, min, max) { + return nil, newInvalidCronFieldError("weekday", field) + } + return newCronFieldN([]int{dayOfMonth}, cronWeekdayN), nil } - return newCronFieldN([]int{dayOfMonth}, cronWeekdayN), nil } return parseField(field, min, max, translate...) diff --git a/quartz/cron_test.go b/quartz/cron_test.go index a29f8b4..b1da8c5 100644 --- a/quartz/cron_test.go +++ b/quartz/cron_test.go @@ -264,6 +264,10 @@ func TestCronExpressionDayOfMonth(t *testing.T) { expression: "0 15 10 31W * ?", expected: "Mon Mar 31 10:15:00 2025", }, + { + expression: "0 15 10 LW * ?", + expected: "Mon Mar 31 10:15:00 2025", + }, } prev := time.Date(2024, 1, 1, 12, 00, 00, 00, time.UTC).UnixNano() @@ -419,6 +423,7 @@ func TestCronExpressionParseError(t *testing.T) { "0 15 10 L- * ?", "0 15 10 L-a * ?", "0 15 10 L-32 * ?", + "0 15 10 WL * ?", } for _, tt := range tests {