package day import ( "reflect" "testing" "time" ) func at(h, m int) time.Time { return time.Date(2026, 7, 29, h, m, 0, 0, time.UTC) } func TestParseTarget(t *testing.T) { cases := []struct { name string arg string blockSize int now time.Time want []int wantErr bool }{ {"empty is current slot", "", 30, at(14, 47), []int{29}, false}, {"single time", "14:30", 30, at(0, 0), []int{29}, false}, {"snap down", "14:47", 30, at(0, 0), []int{29}, false}, {"single digit hour", "9:00", 30, at(0, 0), []int{18}, false}, {"leading zero", "09:00", 30, at(0, 0), []int{18}, false}, {"range end exclusive", "13:00-15:00", 30, at(0, 0), []int{26, 27, 28, 29}, false}, {"range at 15min blocks", "13:00-14:00", 15, at(0, 0), []int{52, 53, 54, 55}, false}, {"range at 60min blocks", "13:30-15:00", 60, at(0, 0), []int{13, 14}, false}, {"midnight slot", "0:00", 30, at(0, 0), []int{0}, false}, {"last slot", "23:30", 30, at(0, 0), []int{47}, false}, {"end before start", "15:00-13:00", 30, at(0, 0), nil, true}, {"end equals start", "13:00-13:00", 30, at(0, 0), nil, true}, {"hour out of range", "24:00", 30, at(0, 0), nil, true}, {"minute out of range", "12:60", 30, at(0, 0), nil, true}, {"garbage", "archer", 30, at(0, 0), nil, true}, {"single digit minute", "1:5", 30, at(0, 0), nil, true}, {"three digit hour", "007:00", 30, at(0, 0), nil, true}, {"leading space", " 9:00", 30, at(0, 0), nil, true}, {"incomplete range end", "9:00-", 30, at(0, 0), nil, true}, {"hyphen prefix", "-9:00", 30, at(0, 0), nil, true}, {"double hyphen in range", "9:00--15:00", 30, at(0, 0), nil, true}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { got, err := ParseTarget(c.arg, c.blockSize, c.now) if c.wantErr != (err != nil) { t.Fatalf("err = %v, wantErr %v", err, c.wantErr) } if !c.wantErr && !reflect.DeepEqual(got, c.want) { t.Fatalf("got %v, want %v", got, c.want) } }) } } func TestIsTimeSyntax(t *testing.T) { yes := []string{"14:30", "9:00", "09:00", "13:00-15:00"} no := []string{"archer", "", "14", "14:3", "a-b", "14:30ish", "1:5", " 9:00", "9:00-"} for _, s := range yes { if !IsTimeSyntax(s) { t.Errorf("IsTimeSyntax(%q) = false, want true", s) } } for _, s := range no { if IsTimeSyntax(s) { t.Errorf("IsTimeSyntax(%q) = true, want false", s) } } } func TestSlotLabel(t *testing.T) { if got := SlotLabel(29, 30); got != "14:30" { t.Fatalf("got %q", got) } if got := SlotLabel(0, 30); got != "0:00" { t.Fatalf("got %q", got) } if got := SlotLabel(55, 15); got != "13:45" { t.Fatalf("got %q", got) } }