Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions pkg/parser/schedule_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,31 @@ func (p *ScheduleParser) parseInterval() (string, error) {
}
}

// Handle "every day [at HH:MM]" as an alias for a daily schedule.
// Examples: "every day" (2 tokens), "every day on weekdays" (4 tokens),
// or "every day at 9am" (4+ tokens with "at" at index 2).
if p.tokens[1] == "day" || p.tokens[1] == "days" {
// len == 2: "every day"; len == 4 with weekdays suffix: "every day on weekdays"
if len(p.tokens) == 2 || (len(p.tokens) == 4 && hasWeekdaysSuffix) {
// "every day" or "every day on weekdays" — fuzzy daily schedule
if hasWeekdaysSuffix {
return "FUZZY:DAILY_WEEKDAYS * * *", nil
}
return "FUZZY:DAILY * * *", nil
}
// tokens[2] == "at": "every day at HH:MM" — token layout is [every, day, at, time...]
if len(p.tokens) > 2 && p.tokens[2] == "at" {
// extractTime handles the "at" keyword at index 2 and reads the time token(s) after it
timeStr, err := p.extractTime(2)
if err != nil {
return "", err
}
min, hr := parseTime(timeStr)
return fmt.Sprintf("%s %s * * *", min, hr), nil
}
Comment on lines +203 to +212
return "", errors.New("invalid 'every day' format, use 'every day' or 'every day at HH:MM'")
}

// Fall back to original parsing for "every N minutes" format
minTokens := 3
if hasWeekdaysSuffix {
Expand Down
6 changes: 6 additions & 0 deletions pkg/parser/schedule_parser_fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ func FuzzScheduleParser(f *testing.F) {
f.Add("hourly on weekdays")
f.Add("every 2h on weekdays")
f.Add("every 2 hours on weekdays")
f.Add("every day")
f.Add("every day on weekdays")
f.Add("every day at 9am")
f.Add("every day at 14:30")
f.Add("every day at midnight")
f.Add("every day at noon")

// UTC offset schedules
f.Add("daily at 02:00 utc+9")
Expand Down
62 changes: 62 additions & 0 deletions pkg/parser/schedule_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,68 @@ func TestParseSchedule(t *testing.T) {
expectedCron: "0 0 * * *",
expectedOrig: "every 1 day",
},

// "every day [at TIME]" — natural-language daily alias
{
name: "every day (fuzzy)",
input: "every day",
expectedCron: "FUZZY:DAILY * * *",
expectedOrig: "every day",
},
{
name: "every days (plural, fuzzy)",
input: "every days",
expectedCron: "FUZZY:DAILY * * *",
expectedOrig: "every days",
},
{
name: "every day on weekdays",
input: "every day on weekdays",
expectedCron: "FUZZY:DAILY_WEEKDAYS * * *",
expectedOrig: "every day on weekdays",
},
{
name: "every day at 9am",
input: "every day at 9am",
expectedCron: "0 9 * * *",
expectedOrig: "every day at 9am",
},
{
name: "every day at 09:00",
input: "every day at 09:00",
expectedCron: "0 9 * * *",
expectedOrig: "every day at 09:00",
},
{
name: "every day at 14:30",
input: "every day at 14:30",
expectedCron: "30 14 * * *",
expectedOrig: "every day at 14:30",
},
{
name: "every day at midnight",
input: "every day at midnight",
expectedCron: "0 0 * * *",
expectedOrig: "every day at midnight",
},
{
name: "every day at noon",
input: "every day at noon",
expectedCron: "0 12 * * *",
expectedOrig: "every day at noon",
},
{
name: "every day at 6pm",
input: "every day at 6pm",
expectedCron: "0 18 * * *",
expectedOrig: "every day at 6pm",
},
{
name: "every day with unrecognised extra token",
input: "every day around 9am",
shouldError: true,
errorSubstring: "invalid 'every day' format",
},
{
name: "weekly without on",
input: "weekly monday",
Expand Down
2 changes: 1 addition & 1 deletion pkg/parser/spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ func TestSpec_PublicAPI_NewFormattedParserError(t *testing.T) {
t.Run("empty message is preserved", func(t *testing.T) {
err := NewFormattedParserError("")
require.NotNil(t, err, "NewFormattedParserError should return non-nil for empty message")
assert.Equal(t, "", err.Error(),
assert.Empty(t, err.Error(),
"NewFormattedParserError should preserve empty message")
})
}
Expand Down
Loading