From 71fd0ef390a1cc68ebad5738642a1c1c4afd5170 Mon Sep 17 00:00:00 2001 From: reugn Date: Mon, 3 Feb 2025 09:48:30 +0200 Subject: [PATCH] feat(cron): allow LW characters combination in the day-of-month field --- internal/csm/day_node.go | 6 +++--- quartz/cron.go | 22 ++++++++++++++-------- quartz/cron_test.go | 5 +++++ 3 files changed, 22 insertions(+), 11 deletions(-) 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 {