package engine import ( "testing" "time" ) func TestParseMonthName(t *testing.T) { // Base: Jan 5, 2026 base := time.Date(2026, 1, 5, 12, 0, 0, 0, time.UTC) parser := NewDateParser(base, time.Monday) tests := []struct { name string input string expected time.Time }{ {"jan (passed)", "jan", time.Date(2027, 1, 1, 0, 0, 0, 0, time.UTC)}, {"january (passed)", "january", time.Date(2027, 1, 1, 0, 0, 0, 0, time.UTC)}, {"feb (future)", "feb", time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)}, {"february (future)", "february", time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)}, {"mar", "mar", time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC)}, {"march", "march", time.Date(2026, 3, 1, 0, 0, 0, 0, time.UTC)}, {"dec", "dec", time.Date(2026, 12, 1, 0, 0, 0, 0, time.UTC)}, {"december", "december", time.Date(2026, 12, 1, 0, 0, 0, 0, time.UTC)}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, ok := parser.parseMonthName(tt.input) if !ok { t.Fatalf("Failed to parse month name: %s", tt.input) } if !result.Equal(tt.expected) { t.Errorf("Expected %v, got %v", tt.expected, result) } }) } } func TestParseDayMonth(t *testing.T) { // Base: Jan 5, 2026 base := time.Date(2026, 1, 5, 12, 0, 0, 0, time.UTC) parser := NewDateParser(base, time.Monday) tests := []struct { name string input string expected time.Time }{ {"21jan (future this year)", "21jan", time.Date(2026, 1, 21, 0, 0, 0, 0, time.UTC)}, {"21Jan", "21Jan", time.Date(2026, 1, 21, 0, 0, 0, 0, time.UTC)}, {"Jan21", "Jan21", time.Date(2026, 1, 21, 0, 0, 0, 0, time.UTC)}, {"jan21", "jan21", time.Date(2026, 1, 21, 0, 0, 0, 0, time.UTC)}, {"1jan (passed)", "1jan", time.Date(2027, 1, 1, 0, 0, 0, 0, time.UTC)}, {"30dec", "30dec", time.Date(2026, 12, 30, 0, 0, 0, 0, time.UTC)}, {"15feb", "15feb", time.Date(2026, 2, 15, 0, 0, 0, 0, time.UTC)}, {"15February", "15February", time.Date(2026, 2, 15, 0, 0, 0, 0, time.UTC)}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, ok := parser.parseDayMonth(tt.input) if !ok { t.Fatalf("Failed to parse day+month: %s", tt.input) } if !result.Equal(tt.expected) { t.Errorf("Expected %v, got %v", tt.expected, result) } }) } } func TestParsePeriodBoundary(t *testing.T) { // Base: Monday, Jan 5, 2026, 14:30:00 base := time.Date(2026, 1, 5, 14, 30, 0, 0, time.UTC) parser := NewDateParser(base, time.Monday) tests := []struct { name string input string expected time.Time }{ {"sod", "sod", time.Date(2026, 1, 5, 0, 0, 0, 0, time.UTC)}, {"eod", "eod", time.Date(2026, 1, 5, 23, 59, 59, 0, time.UTC)}, {"sow", "sow", time.Date(2026, 1, 12, 0, 0, 0, 0, time.UTC)}, // Next Monday {"eow", "eow", time.Date(2026, 1, 11, 23, 59, 59, 0, time.UTC)}, // Next Sunday {"som", "som", time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)}, // Next month (Jan 1 passed) {"eom", "eom", time.Date(2026, 1, 31, 23, 59, 59, 0, time.UTC)}, // End of Jan {"soy", "soy", time.Date(2027, 1, 1, 0, 0, 0, 0, time.UTC)}, // Next year (Jan 1 passed) {"eoy", "eoy", time.Date(2026, 12, 31, 23, 59, 59, 0, time.UTC)}, // End of 2026 } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, ok := parser.parsePeriodBoundary(tt.input) if !ok { t.Fatalf("Failed to parse period boundary: %s", tt.input) } if !result.Equal(tt.expected) { t.Errorf("Expected %v, got %v", tt.expected, result) } }) } } func TestParseSpecialKeyword(t *testing.T) { base := time.Date(2026, 1, 5, 12, 0, 0, 0, time.UTC) parser := NewDateParser(base, time.Monday) tests := []struct { name string input string expected time.Time }{ {"later", "later", time.Date(2150, 1, 1, 0, 0, 0, 0, time.UTC)}, {"someday", "someday", time.Date(2150, 1, 1, 0, 0, 0, 0, time.UTC)}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, ok := parser.parseSpecialKeyword(tt.input) if !ok { t.Fatalf("Failed to parse special keyword: %s", tt.input) } if !result.Equal(tt.expected) { t.Errorf("Expected %v, got %v", tt.expected, result) } }) } } func TestParseDurationAsDate(t *testing.T) { base := time.Date(2026, 1, 5, 12, 0, 0, 0, time.UTC) parser := NewDateParser(base, time.Monday) tests := []struct { name string input string expected time.Time }{ {"2d", "2d", base.AddDate(0, 0, 2)}, {"3w", "3w", base.AddDate(0, 0, 21)}, {"1m", "1m", base.AddDate(0, 0, 30)}, {"1y", "1y", base.AddDate(0, 0, 365)}, {"daily", "daily", base.AddDate(0, 0, 1)}, {"weekly", "weekly", base.AddDate(0, 0, 7)}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, ok := parser.parseDurationAsDate(tt.input) if !ok { t.Fatalf("Failed to parse duration as date: %s", tt.input) } if !result.Equal(tt.expected) { t.Errorf("Expected %v, got %v", tt.expected, result) } }) } } func TestParseTimeOfDay(t *testing.T) { base := time.Date(2026, 1, 5, 12, 0, 0, 0, time.UTC) parser := NewDateParser(base, time.Monday) tests := []struct { name string date time.Time timeStr string expected time.Time wantErr bool }{ {"HH:MM format", base, "15:35", time.Date(2026, 1, 5, 15, 35, 0, 0, time.UTC), false}, {"HHMM format", base, "0800", time.Date(2026, 1, 5, 8, 0, 0, 0, time.UTC), false}, {"midnight", base, "00:00", time.Date(2026, 1, 5, 0, 0, 0, 0, time.UTC), false}, {"end of day", base, "23:59", time.Date(2026, 1, 5, 23, 59, 0, 0, time.UTC), false}, {"invalid hour", base, "25:00", time.Time{}, true}, {"invalid minute", base, "12:65", time.Time{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := parser.parseTimeOfDay(tt.date, tt.timeStr) if tt.wantErr { if err == nil { t.Error("Expected error but got none") } return } if err != nil { t.Fatalf("Unexpected error: %v", err) } if !result.Equal(tt.expected) { t.Errorf("Expected %v, got %v", tt.expected, result) } }) } } func TestParseDateWithTime(t *testing.T) { base := time.Date(2026, 1, 5, 12, 0, 0, 0, time.UTC) // Monday parser := NewDateParser(base, time.Monday) tests := []struct { name string input string expected time.Time }{ {"mon:15:35", "mon:15:35", time.Date(2026, 1, 12, 15, 35, 0, 0, time.UTC)}, // Next Monday at 15:35 {"tomorrow:0800", "tomorrow:0800", time.Date(2026, 1, 6, 8, 0, 0, 0, time.UTC)}, {"21jan:1430", "21jan:1430", time.Date(2026, 1, 21, 14, 30, 0, 0, time.UTC)}, {"just time 15:35", "15:35", time.Date(2026, 1, 5, 15, 35, 0, 0, time.UTC)}, // Today at 15:35 } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := parser.ParseDate(tt.input) if err != nil { t.Fatalf("Failed to parse date with time: %v", err) } if !result.Equal(tt.expected) { t.Errorf("Expected %v, got %v", tt.expected, result) } }) } } func TestExpandedDurationFormats(t *testing.T) { tests := []struct { name string input string expected time.Duration }{ {"5sec", "5sec", 5 * time.Second}, {"5seconds", "5seconds", 5 * time.Second}, {"10min", "10min", 10 * time.Minute}, {"10minutes", "10minutes", 10 * time.Minute}, {"2hrs", "2hrs", 2 * time.Hour}, {"2hours", "2hours", 2 * time.Hour}, {"3days", "3days", 3 * 24 * time.Hour}, {"2weeks", "2weeks", 2 * 7 * 24 * time.Hour}, {"1month", "1month", 30 * 24 * time.Hour}, {"1year", "1year", 365 * 24 * time.Hour}, {"hour (no number)", "hour", time.Hour}, {"day (no number)", "day", 24 * time.Hour}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := ParseRecurrencePattern(tt.input) if err != nil { t.Fatalf("Failed to parse duration: %v", err) } if result != tt.expected { t.Errorf("Expected %v, got %v", tt.expected, result) } }) } }