package bot import ( "testing" ) func TestGregorianToJDN_RoundTrip(t *testing.T) { cases := []struct{ y, m, d int }{ {2024, 1, 1}, {2024, 12, 31}, {1970, 1, 1}, {1, 1, 1}, {2024, 2, 29}, {1900, 2, 28}, {2000, 2, 29}, } for _, c := range cases { jdn := gregorianToJDN(c.y, c.m, c.d) y, m, d := jdnToGregorian(jdn) if y != c.y || m != c.m || d != c.d { t.Errorf("round-trip %04d-%02d-%02d -> JDN %d -> %04d-%02d-%02d", c.y, c.m, c.d, jdn, y, m, d) } } } func TestGregorianToJalali_RoundTrip(t *testing.T) { cases := []struct{ gy, gm, gd int }{ {2024, 3, 20}, {2024, 3, 21}, {2024, 9, 22}, {2025, 3, 20}, {1979, 2, 11}, {622, 3, 22}, {2024, 12, 31}, {2024, 6, 21}, {2024, 1, 1}, } for _, c := range cases { jy, jm, jd := gregorianToJalali(c.gy, c.gm, c.gd) if jy == 0 { t.Errorf("gregorianToJalali(%d, %d, %d) returned 0", c.gy, c.gm, c.gd) continue } gy2, gm2, gd2 := jalaliToGregorian(jy, jm, jd) if gy2 != c.gy || gm2 != c.gm || gd2 != c.gd { t.Errorf("round-trip Gregorian %04d-%02d-%02d -> Jalali %04d-%02d-%02d -> Gregorian %04d-%02d-%02d", c.gy, c.gm, c.gd, jy, jm, jd, gy2, gm2, gd2) } } } func TestGregorianToHijri_RoundTrip(t *testing.T) { cases := []struct{ gy, gm, gd int }{ {2024, 3, 21}, {2024, 1, 1}, {2024, 12, 31}, {1979, 2, 11}, {622, 7, 16}, {2024, 7, 7}, {2024, 6, 21}, {2000, 1, 1}, } for _, c := range cases { hy, hm, hd := gregorianToHijri(c.gy, c.gm, c.gd) if hy == 0 { t.Errorf("gregorianToHijri(%d, %d, %d) returned 0", c.gy, c.gm, c.gd) continue } gy2, gm2, gd2 := hijriToGregorian(hy, hm, hd) diff := (gy2-c.gy)*365 + (gm2-c.gm)*30 + (gd2 - c.gd) if diff < 0 { diff = -diff } if diff > 1 { t.Errorf("round-trip off by %d days: Gregorian %04d-%02d-%02d -> Hijri %04d-%02d-%02d -> Gregorian %04d-%02d-%02d", diff, c.gy, c.gm, c.gd, hy, hm, hd, gy2, gm2, gd2) } } } func TestIsLeapYear(t *testing.T) { gregLeaps := map[int]bool{ 2000: true, 2020: true, 2024: true, 1900: false, 2100: false, 2023: false, } for y, want := range gregLeaps { if got := isGregorianLeap(y); got != want { t.Errorf("isGregorianLeap(%d) = %v, want %v", y, got, want) } } jalaliLeaps := map[int]bool{ 1: true, 5: true, 9: true, 13: true, 17: true, 22: true, 26: true, 30: true, 2: false, 33: false, 1403: true, } for y, want := range jalaliLeaps { if got := isJalaliLeap(y); got != want { t.Errorf("isJalaliLeap(%d) = %v, want %v", y, got, want) } } hijriLeaps := map[int]bool{ 2: true, 5: true, 7: true, 10: true, 13: true, 16: true, 18: true, 21: true, 24: true, 27: true, 29: true, 1: false, 3: false, 30: false, } for y, want := range hijriLeaps { if got := isHijriLeap(y); got != want { t.Errorf("isHijriLeap(%d) = %v, want %v", y, got, want) } } } func TestMonthLengths(t *testing.T) { if got := monthLenGreg(2024, 2); got != 29 { t.Errorf("monthLenGreg(2024, 2) = %d, want 29", got) } if got := monthLenGreg(2023, 2); got != 28 { t.Errorf("monthLenGreg(2023, 2) = %d, want 28", got) } if got := monthLenGreg(2024, 1); got != 31 { t.Errorf("monthLenGreg(2024, 1) = %d, want 31", got) } if got := monthLenJalali(1, 12); got != 30 { t.Errorf("monthLenJalali(1, 12) = %d, want 30 (leap)", got) } if got := monthLenJalali(2, 12); got != 29 { t.Errorf("monthLenJalali(2, 12) = %d, want 29 (non-leap)", got) } if got := monthLenJalali(1403, 1); got != 31 { t.Errorf("monthLenJalali(1403, 1) = %d, want 31", got) } if got := monthLenHijri(1, 1); got != 30 { t.Errorf("monthLenHijri(1, 1) = %d, want 30 (odd month)", got) } if got := monthLenHijri(1, 2); got != 29 { t.Errorf("monthLenHijri(1, 2) = %d, want 29 (even non-leap)", got) } if got := monthLenHijri(2, 12); got != 30 { t.Errorf("monthLenHijri(2, 12) = %d, want 30 (leap year, month 12)", got) } } func TestParseGregorianDateKey(t *testing.T) { y, m, d := parseGregorianDateKey("2024-03-21") if y != 2024 || m != 3 || d != 21 { t.Errorf("parseGregorianDateKey got %d-%d-%d, want 2024-3-21", y, m, d) } } func TestFormatDateForCalendar(t *testing.T) { if got := formatDateForCalendar("2024-03-21", "gregorian"); got != "2024-03-21" { t.Errorf("formatDateForCalendar gregorian: got %s", got) } if got := formatDateForCalendar("2024-03-21", "jalali"); got != "1403-01-02" { t.Errorf("formatDateForCalendar jalali: got %s, want 1403-01-02", got) } if got := formatDateForCalendar("2024-03-21", "hijri"); got != "1445-09-11" { t.Errorf("formatDateForCalendar hijri: got %s, want 1445-09-11", got) } } func TestUserDateToGregorian(t *testing.T) { if got := userDateToGregorian("1403-01-02", "jalali"); got != "2024-03-21" { t.Errorf("userDateToGregorian jalali: got %s, want 2024-03-21", got) } if got := userDateToGregorian("1445-09-11", "hijri"); got != "2024-03-21" { t.Errorf("userDateToGregorian hijri: got %s, want 2024-03-21", got) } } func TestMonthGregorianRange(t *testing.T) { start, end := monthGregorianRange("gregorian", 2024, 1) if start != "2024-01-01" || end != "2024-01-31" { t.Errorf("gregorian range: got %s - %s", start, end) } start, end = monthGregorianRange("jalali", 1403, 1) if start != "2024-03-20" || end != "2024-04-19" { t.Errorf("jalali range: got %s - %s", start, end) } start, end = monthGregorianRange("hijri", 1445, 9) if start != "2024-03-11" || end != "2024-04-09" { t.Errorf("hijri range: got %s - %s", start, end) } } func TestBuildCalendarMonth(t *testing.T) { cm := buildCalendarMonth("gregorian", 2024, 1) if cm.title != "January 2024" { t.Errorf("title: got %q", cm.title) } if len(cm.weekDays) != 7 { t.Errorf("weekDays count: %d", len(cm.weekDays)) } if len(cm.days) < 28 { t.Errorf("too few days: %d", len(cm.days)) } cm2 := buildCalendarMonth("jalali", 1403, 1) if cm2.title != "Farvardin 1403" { t.Errorf("jalali title: got %q", cm2.title) } if len(cm2.days) < 31 { t.Errorf("too few jalali days: %d", len(cm2.days)) } cm3 := buildCalendarMonth("hijri", 1445, 9) if cm3.title != "Ramadan 1445" { t.Errorf("hijri title: got %q", cm3.title) } } func TestLoadLocation(t *testing.T) { loc := loadLocation("Asia/Tehran") if loc == nil { t.Fatal("loadLocation returned nil") } if loc.String() != "Asia/Tehran" { t.Errorf("unexpected location: %s", loc) } loc = loadLocation("Invalid/Timezone") if loc.String() != "UTC" { t.Errorf("expected UTC fallback, got %s", loc) } } func TestFormatDuration(t *testing.T) { if got := formatDuration(3600); got != "1h 0m" { t.Errorf("formatDuration(3600) = %q, want %q", got, "1h 0m") } if got := formatDuration(3660); got != "1h 1m" { t.Errorf("formatDuration(3660) = %q, want %q", got, "1h 1m") } if got := formatDuration(59); got != "0m" { t.Errorf("formatDuration(59) = %q, want %q", got, "0m") } if got := formatDuration(1800); got != "30m" { t.Errorf("formatDuration(1800) = %q, want %q", got, "30m") } if got := formatDuration(0); got != "0m" { t.Errorf("formatDuration(0) = %q, want %q", got, "0m") } }