1package scheduler
2
3import (
4 "testing"
5 "time"
6)
7
8func TestParseCronNext(t *testing.T) {
9 loc := time.FixedZone("UTC+2", 2*60*60)
10 schedule, err := ParseCron("*/15 9-10 * * 1-5", loc)
11 if err != nil {
12 t.Fatalf("ParseCron() error = %v", err)
13 }
14
15 after := time.Date(2026, time.August, 14, 9, 14, 45, 0, loc) // Friday
16 want := time.Date(2026, time.August, 14, 9, 15, 0, 0, loc)
17 if got := schedule.Next(after); !got.Equal(want) {
18 t.Fatalf("Next() = %v, want %v", got, want)
19 }
20}
21
22func TestParseCronDayOfMonthOrDayOfWeek(t *testing.T) {
23 loc := time.UTC
24 schedule, err := ParseCron("0 9 15 * 1", loc)
25 if err != nil {
26 t.Fatalf("ParseCron() error = %v", err)
27 }
28
29 // Vixie cron semantics: when both fields are restricted, either match is
30 // sufficient. Monday 2026-06-08 is not the fifteenth.
31 after := time.Date(2026, time.June, 7, 9, 0, 0, 0, loc)
32 want := time.Date(2026, time.June, 8, 9, 0, 0, 0, loc)
33 if got := schedule.Next(after); !got.Equal(want) {
34 t.Fatalf("Next() = %v, want %v", got, want)
35 }
36}
37
38func TestParseCronDSTTransitions(t *testing.T) {
39 loc, err := time.LoadLocation("America/New_York")
40 if err != nil {
41 t.Fatalf("LoadLocation() error = %v", err)
42 }
43 fallBack, err := ParseCron("30 1 * * *", loc)
44 if err != nil {
45 t.Fatalf("ParseCron() error = %v", err)
46 }
47 first := fallBack.Next(time.Date(2026, time.November, 1, 0, 59, 0, 0, loc))
48 second := fallBack.Next(first)
49 if first.Hour() != 1 || first.Minute() != 30 || second.Hour() != 1 || second.Minute() != 30 || first.Equal(second) || second.Sub(first) != time.Hour {
50 t.Fatalf("fall-back occurrences = %v, %v; want 01:30 exactly one elapsed hour apart", first, second)
51 }
52
53 springForward, err := ParseCron("30 2 * * *", loc)
54 if err != nil {
55 t.Fatalf("ParseCron() error = %v", err)
56 }
57 got := springForward.Next(time.Date(2026, time.March, 8, 1, 59, 0, 0, loc))
58 want := time.Date(2026, time.March, 9, 2, 30, 0, 0, loc)
59 if !got.Equal(want) {
60 t.Fatalf("spring-forward Next() = %v, want %v", got, want)
61 }
62}
63
64func TestParseCronRejectsInvalidField(t *testing.T) {
65 if _, err := ParseCron("61 * * * *", time.UTC); err == nil {
66 t.Fatal("ParseCron() error = nil, want invalid minute error")
67 }
68}
69
70func TestParseCronSupportsMacro(t *testing.T) {
71 schedule, err := ParseCron("@daily", time.UTC)
72 if err != nil {
73 t.Fatalf("ParseCron() error = %v", err)
74 }
75 got := schedule.Next(time.Date(2026, time.August, 14, 23, 59, 0, 0, time.UTC))
76 want := time.Date(2026, time.August, 15, 0, 0, 0, 0, time.UTC)
77 if !got.Equal(want) {
78 t.Fatalf("Next() = %v, want %v", got, want)
79 }
80}