@@ -29,6 +29,8 @@ import (
2929// "0 15 10 15 * ?" Fire at 10:15am on the 15th day of every month
3030// "0 15 10 ? * 6L" Fire at 10:15am on the last Friday of every month
3131// "0 15 10 ? * 6#3" Fire at 10:15am on the third Friday of every month
32+ // "0 15 10 L * ?" Fire at 10:15am on the last day of every month
33+ // "0 15 10 L-2 * ?" Fire at 10:15am on the 2nd-to-last last day of every month
3234type CronTrigger struct {
3335 expression string
3436 fields []* cronField
@@ -94,14 +96,8 @@ func (ct *CronTrigger) Description() string {
9496type cronField struct {
9597 // stores the parsed and sorted numeric values for the field
9698 values []int
97- // n specifies the occurrence of the day of week within a
98- // month when '#' is used in the day-of-week field.
99- // When 'L' (last) is used, it will be set to -1.
100- //
101- // Examples:
102- //
103- // - For "5#3" (third Thursday of the month), n will be 3.
104- // - For "2L" (last Sunday of the month), n will be -1.
99+ // n is used to store special values for the day-of-month
100+ // and day-of-week fields
105101 n int
106102}
107103
@@ -200,7 +196,7 @@ func buildCronField(tokens []string) ([]*cronField, error) {
200196 return nil , err
201197 }
202198 // day-of-month field
203- fields [3 ], err = parseField (tokens [3 ], 1 , 31 )
199+ fields [3 ], err = parseDayOfMonthField (tokens [3 ], 1 , 31 )
204200 if err != nil {
205201 return nil , err
206202 }
@@ -269,12 +265,46 @@ func parseField(field string, min, max int, translate ...[]string) (*cronField,
269265}
270266
271267var (
272- cronLastCharacterRegex = regexp .MustCompile (`^[0-9]*L$` )
273- cronHashCharacterRegex = regexp .MustCompile (`^[0-9]+#[0-9]+$` )
268+ cronLastMonthDayRegex = regexp .MustCompile (`^L(-[0-9]+)?$` )
269+ cronWeekdayRegex = regexp .MustCompile (`^[0-9]+W$` )
270+
271+ cronLastWeekdayRegex = regexp .MustCompile (`^[0-9]*L$` )
272+ cronHashRegex = regexp .MustCompile (`^[0-9]+#[0-9]+$` )
274273)
275274
275+ func parseDayOfMonthField (field string , min , max int , translate ... []string ) (* cronField , error ) {
276+ if strings .ContainsRune (field , lastRune ) && cronLastMonthDayRegex .MatchString (field ) {
277+ if field == string (lastRune ) {
278+ return newCronFieldN ([]int {}, cronLastDayOfMonthN ), nil
279+ }
280+ values := strings .Split (field , string (rangeRune ))
281+ if len (values ) != 2 {
282+ return nil , newInvalidCronFieldError ("last" , field )
283+ }
284+ n , err := strconv .Atoi (values [1 ])
285+ if err != nil || ! inScope (n , 1 , 30 ) {
286+ return nil , newInvalidCronFieldError ("last" , field )
287+ }
288+ return newCronFieldN ([]int {}, - n ), nil
289+ }
290+
291+ if strings .ContainsRune (field , weekdayRune ) && cronWeekdayRegex .MatchString (field ) {
292+ day := strings .TrimSuffix (field , string (weekdayRune ))
293+ if day == "" {
294+ return nil , newInvalidCronFieldError ("weekday" , field )
295+ }
296+ dayOfMonth , err := strconv .Atoi (day )
297+ if err != nil || ! inScope (dayOfMonth , min , max ) {
298+ return nil , newInvalidCronFieldError ("weekday" , field )
299+ }
300+ return newCronFieldN ([]int {dayOfMonth }, cronWeekdayN ), nil
301+ }
302+
303+ return parseField (field , min , max , translate ... )
304+ }
305+
276306func parseDayOfWeekField (field string , min , max int , translate ... []string ) (* cronField , error ) {
277- if strings .ContainsRune (field , lastRune ) && cronLastCharacterRegex .MatchString (field ) {
307+ if strings .ContainsRune (field , lastRune ) && cronLastWeekdayRegex .MatchString (field ) {
278308 day := strings .TrimSuffix (field , string (lastRune ))
279309 if day == "" { // Saturday
280310 return newCronFieldN ([]int {7 }, - 1 ), nil
@@ -286,7 +316,7 @@ func parseDayOfWeekField(field string, min, max int, translate ...[]string) (*cr
286316 return newCronFieldN ([]int {dayOfWeek }, - 1 ), nil
287317 }
288318
289- if strings .ContainsRune (field , hashRune ) && cronHashCharacterRegex .MatchString (field ) {
319+ if strings .ContainsRune (field , hashRune ) && cronHashRegex .MatchString (field ) {
290320 values := strings .Split (field , string (hashRune ))
291321 if len (values ) != 2 {
292322 return nil , newInvalidCronFieldError ("hash" , field )
0 commit comments