package bot import "fmt" var gregMonthDays = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} var jalaliMonthDays = []int{31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29} var gregMonthNames = []string{ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", } var jalaliMonthNames = []string{ "Farvardin", "Ordibehesht", "Khordad", "Tir", "Mordad", "Shahrivar", "Mehr", "Aban", "Azar", "Dey", "Bahman", "Esfand", } var hijriMonthNames = []string{ "Muharram", "Safar", "Rabi' I", "Rabi' II", "Jumada I", "Jumada II", "Rajab", "Sha'ban", "Ramadan", "Shawwal", "Dhu al-Qa'dah", "Dhu al-Hijjah", } func isGregorianLeap(year int) bool { return year%4 == 0 && (year%100 != 0 || year%400 == 0) } func isJalaliLeap(year int) bool { r := year % 33 return r == 1 || r == 5 || r == 9 || r == 13 || r == 17 || r == 22 || r == 26 || r == 30 } func monthLenGreg(year, month int) int { if month == 2 && isGregorianLeap(year) { return 29 } return gregMonthDays[month-1] } func monthLenJalali(year, month int) int { if month == 12 && isJalaliLeap(year) { return 30 } return jalaliMonthDays[month-1] } func isHijriLeap(year int) bool { switch year % 30 { case 2, 5, 7, 10, 13, 16, 18, 21, 24, 27, 29: return true } return false } func monthLenHijri(year, month int) int { if month == 12 && isHijriLeap(year) { return 30 } if month%2 == 1 { return 30 } return 29 } func gregorianToJalali(gy, gm, gd int) (int, int, int) { gdm := []int{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334} gy2 := gy - 1 gd2 := gdm[gm-1] + gd if gm > 2 && isGregorianLeap(gy) { gd2++ } gd2 += gy2*365 + gy2/4 - gy2/100 + gy2/400 gd2 -= 226899 jy := 979 for { jyDays := 365 if isJalaliLeap(jy) { jyDays = 366 } if gd2 < jyDays { break } gd2 -= jyDays jy++ } jm := 1 for _, d := range jalaliMonthDays { if gd2 < d { break } gd2 -= d jm++ } return jy, jm, gd2 + 1 } func jalaliToGregorian(jy, jm, jd int) (int, int, int) { jy2 := jy - 979 days := jy2 * 365 for y := 979; y < jy; y++ { if isJalaliLeap(y) { days++ } } jdm := []int{0, 31, 62, 93, 124, 155, 186, 216, 246, 276, 306, 336} days += jdm[jm-1] + jd - 1 days += 226899 gy := 1 for { gyDays := 365 if isGregorianLeap(gy) { gyDays = 366 } if days < gyDays { break } days -= gyDays gy++ } gm := 1 for i, d := range gregMonthDays { if gm == 2 && isGregorianLeap(gy) { d = 29 } if days < d { break } days -= d gm = i + 2 } return gy, gm, days + 1 } func hijriToGregorian(hy, hm, hd int) (int, int, int) { days := (hy-1)*354 + (hy-1)*11/30 for m := 1; m < hm; m++ { days += monthLenHijri(hy, m) } days += hd - 1 jdn := 1948440 + days f := jdn + 1401 + ((4*jdn+274277)/146097)*3/4 - 38 e := 4*f + 3 g := (e % 1461) / 4 h := 5*g + 2 day := (h%153)/5 + 1 month := ((h/153 + 2) % 12) + 1 year := e/1461 - 4716 + (14-month)/12 return year, month, day } func gregorianToHijri(gy, gm, gd int) (int, int, int) { a := (14 - gm) / 12 y := gy + 4800 - a m := gm + 12*a - 3 jdn := gd + (153*m+2)/5 + 365*y + y/4 - y/100 + y/400 - 32045 days := jdn - 1948440 hy := 1 for { yearDays := 354 if isHijriLeap(hy) { yearDays = 355 } if days < yearDays { break } days -= yearDays hy++ } hm := 1 for mm := 1; mm <= 12; mm++ { md := monthLenHijri(hy, mm) if days < md { break } days -= md hm++ } hd := days + 1 return hy, hm, hd } func dayOfWeekGregorian(gy, gm, gd int) int { // Zeller-like: returns 0=Sun,1=Mon,...,6=Sat if gm < 3 { gm += 12 gy-- } return (gd + (13*(gm+1))/5 + gy + gy/4 - gy/100 + gy/400) % 7 } type calDay struct { dayNum int date string // YYYY-MM-DD gregorian key } type calMonth struct { title string weekDays []string days []calDay } func buildCalendarMonth(cal string, year, month int) calMonth { var title string var weekDays []string var totalDays int if cal == "jalali" { title = fmt.Sprintf("%s %d", jalaliMonthNames[month-1], year) weekDays = []string{"Sh", "Ye", "Do", "Se", "Ch", "Pa", "Jo"} totalDays = monthLenJalali(year, month) gy, gm, gd := jalaliToGregorian(year, month, 1) dow := dayOfWeekGregorian(gy, gm, gd) _ = dow var days []calDay for d := 1; d <= totalDays; d++ { gy, gm, gd = jalaliToGregorian(year, month, d) days = append(days, calDay{ dayNum: d, date: fmt.Sprintf("%04d-%02d-%02d", gy, gm, gd), }) } return calMonth{title: title, weekDays: weekDays, days: days} } if cal == "hijri" { title = fmt.Sprintf("%s %d", hijriMonthNames[month-1], year) weekDays = []string{"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"} totalDays = monthLenHijri(year, month) gy, gm, gd := hijriToGregorian(year, month, 1) dow := dayOfWeekGregorian(gy, gm, gd) startOffset := (dow + 6) % 7 var days []calDay for i := 0; i < startOffset; i++ { days = append(days, calDay{dayNum: 0, date: ""}) } for d := 1; d <= totalDays; d++ { gy, gm, gd = hijriToGregorian(year, month, d) days = append(days, calDay{ dayNum: d, date: fmt.Sprintf("%04d-%02d-%02d", gy, gm, gd), }) } return calMonth{title: title, weekDays: weekDays, days: days} } title = fmt.Sprintf("%s %d", gregMonthNames[month-1], year) weekDays = []string{"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"} totalDays = monthLenGreg(year, month) dow := dayOfWeekGregorian(year, month, 1) // Convert Sunday=0 to Monday=0 alignment startOffset := (dow + 6) % 7 var days []calDay for i := 0; i < startOffset; i++ { days = append(days, calDay{dayNum: 0, date: ""}) } for d := 1; d <= totalDays; d++ { days = append(days, calDay{ dayNum: d, date: fmt.Sprintf("%04d-%02d-%02d", year, month, d), }) } return calMonth{title: title, weekDays: weekDays, days: days} } func gregorianDateKey(year, month, day int) string { return fmt.Sprintf("%04d-%02d-%02d", year, month, day) } func parseGregorianDateKey(key string) (int, int, int) { var y, m, d int fmt.Sscanf(key, "%04d-%02d-%02d", &y, &m, &d) return y, m, d }