X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/a5c83e92d7feea2e104b5515043a76c8c4c56c60..f5c601f1801aaa276f8c335fcb51c373edc0b50c:/schedule_cron.go diff --git a/schedule_cron.go b/schedule_cron.go index 91fe8ce..3255f99 100644 --- a/schedule_cron.go +++ b/schedule_cron.go @@ -53,61 +53,6 @@ var scheduleCronFields = []struct { }, } -// scheduleParseCron parses a cron expression of five fields: minute, hour, day -// of month, month, and day of week, where 0 and 7 are Sunday. A field is a -// comma list of items, and an item is *, N, or N-M, where * and N-M can have a -// step /S. Names, macros, the characters ? L W #, and N/S are errors. -// -// It refuses an expression whose runs can be less than scheduleCronGap minutes -// apart. The check takes the wall times of one day from the hour and the minute -// fields, and the gap across midnight as if the next day matches too. It also -// refuses an expression that matches no day in scheduleCronYears years, such as -// one on February 30. -func scheduleParseCron(expression string) (ScheduleCron, error) { - fields := strings.Fields(expression) - if len(fields) != len(scheduleCronFields) { - return ScheduleCron{}, fmt.Errorf("the cron expression %q has %d fields, not 5: minute, hour, day of month, month, day of week", expression, len(fields)) - } - - var sets [5]uint64 - - for index, field := range fields { - set, err := scheduleCronField(field, scheduleCronFields[index].low, scheduleCronFields[index].high) - if err != nil { - return ScheduleCron{}, fmt.Errorf("the %s field of %q: %w", scheduleCronFields[index].name, expression, err) - } - - sets[index] = set - } - - c := ScheduleCron{ - minutes: sets[0], - hours: sets[1], - days: sets[2], - months: sets[3], - weekdays: sets[4], - anyDay: strings.HasPrefix(fields[2], "*"), - anyWeekday: strings.HasPrefix(fields[4], "*"), - description: strings.Join(fields, " "), - } - - if c.weekdays&(1<<7) != 0 { - c.weekdays |= 1 - } - - if c.gap() < scheduleCronGap { - return ScheduleCron{}, fmt.Errorf("the runs of %q can be less than %d minutes apart: the runs of a schedule must be at least %d minutes apart", expression, scheduleCronGap, scheduleCronGap) - } - - start := time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC) - _, found := c.Next(start) - if !found { - return ScheduleCron{}, fmt.Errorf("the cron expression %q matches no day in %d years", expression, scheduleCronYears) - } - - return c, nil -} - // ScheduleCron is a parsed cron expression. Each set holds a bit for each value // of its field. anyDay and anyWeekday tell that the day field or the weekday // field starts with *. When neither does, a day matches when either field @@ -209,6 +154,61 @@ func (c ScheduleCron) gap() int { return gap } +// scheduleParseCron parses a cron expression of five fields: minute, hour, day +// of month, month, and day of week, where 0 and 7 are Sunday. A field is a +// comma list of items, and an item is *, N, or N-M, where * and N-M can have a +// step /S. Names, macros, the characters ? L W #, and N/S are errors. +// +// It refuses an expression whose runs can be less than scheduleCronGap minutes +// apart. The check takes the wall times of one day from the hour and the minute +// fields, and the gap across midnight as if the next day matches too. It also +// refuses an expression that matches no day in scheduleCronYears years, such as +// one on February 30. +func scheduleParseCron(expression string) (ScheduleCron, error) { + fields := strings.Fields(expression) + if len(fields) != len(scheduleCronFields) { + return ScheduleCron{}, fmt.Errorf("the cron expression %q has %d fields, not 5: minute, hour, day of month, month, day of week", expression, len(fields)) + } + + var sets [5]uint64 + + for index, field := range fields { + set, err := scheduleCronField(field, scheduleCronFields[index].low, scheduleCronFields[index].high) + if err != nil { + return ScheduleCron{}, fmt.Errorf("the %s field of %q: %w", scheduleCronFields[index].name, expression, err) + } + + sets[index] = set + } + + c := ScheduleCron{ + minutes: sets[0], + hours: sets[1], + days: sets[2], + months: sets[3], + weekdays: sets[4], + anyDay: strings.HasPrefix(fields[2], "*"), + anyWeekday: strings.HasPrefix(fields[4], "*"), + description: strings.Join(fields, " "), + } + + if c.weekdays&(1<<7) != 0 { + c.weekdays |= 1 + } + + if c.gap() < scheduleCronGap { + return ScheduleCron{}, fmt.Errorf("the runs of %q can be less than %d minutes apart: the runs of a schedule must be at least %d minutes apart", expression, scheduleCronGap, scheduleCronGap) + } + + start := time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC) + _, found := c.Next(start) + if !found { + return ScheduleCron{}, fmt.Errorf("the cron expression %q matches no day in %d years", expression, scheduleCronYears) + } + + return c, nil +} + // scheduleWallTime returns the time of a wall time in location. A wall time // that the clock skips, at the start of daylight saving time, is later by the // length of the skip: 02:30 becomes 03:30 when the clock goes from 02:00 to