package domain 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 } const jalaliEpochJDN = 1948320 func GregorianToJDN(gy, gm, gd int) int { a := (14 - gm) / 12 y := gy + 4800 - a m := gm + 12*a - 3 return gd + (153*m+2)/5 + 365*y + y/4 - y/100 + y/400 - 32045 } func JDNToGregorian(jdn int) (int, int, int) { 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 GregorianToJalali(gy, gm, gd int) (int, int, int) { jdn := GregorianToJDN(gy, gm, gd) days := jdn - jalaliEpochJDN if days < 0 { return 0, 0, 0 } jy := 1 for { yearDays := 365 if IsJalaliLeap(jy) { yearDays = 366 } if days < yearDays { break } days -= yearDays jy++ } jm := 1 for mm := 0; mm < 12; mm++ { md := MonthLenJalali(jy, mm+1) if days < md { break } days -= md jm++ } jd := days + 1 return jy, jm, jd } func JalaliToGregorian(jy, jm, jd int) (int, int, int) { days := 0 for y := 1; y < jy; y++ { if IsJalaliLeap(y) { days += 366 } else { days += 365 } } for m := 1; m < jm; m++ { days += MonthLenJalali(jy, m) } days += jd - 1 jdn := jalaliEpochJDN + days return JDNToGregorian(jdn) } 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) { jdn := GregorianToJDN(gy, gm, gd) 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 { 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 } 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) startOffset := dow 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 = 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 + 5) % 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) startOffset := (dow + 5) % 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 ParseGregorianDateKey(key string) (int, int, int) { var y, m, d int fmt.Sscanf(key, "%04d-%02d-%02d", &y, &m, &d) return y, m, d } func FormatDateForCalendar(date, calType string) string { y, m, d := ParseGregorianDateKey(date) switch calType { case "jalali": jy, jm, jd := GregorianToJalali(y, m, d) return fmt.Sprintf("%04d-%02d-%02d", jy, jm, jd) case "hijri": hy, hm, hd := GregorianToHijri(y, m, d) return fmt.Sprintf("%04d-%02d-%02d", hy, hm, hd) default: return date } } func UserDateToGregorian(dateStr, cal string) string { y, m, d := ParseGregorianDateKey(dateStr) switch cal { case "jalali": y, m, d = JalaliToGregorian(y, m, d) case "hijri": y, m, d = HijriToGregorian(y, m, d) } return fmt.Sprintf("%04d-%02d-%02d", y, m, d) } func MonthGregorianRange(cal string, year, month int) (start, end string) { switch cal { case "jalali": gy, gm, gd := JalaliToGregorian(year, month, 1) start = fmt.Sprintf("%04d-%02d-%02d", gy, gm, gd) lastDay := MonthLenJalali(year, month) gy, gm, gd = JalaliToGregorian(year, month, lastDay) end = fmt.Sprintf("%04d-%02d-%02d", gy, gm, gd) case "hijri": gy, gm, gd := HijriToGregorian(year, month, 1) start = fmt.Sprintf("%04d-%02d-%02d", gy, gm, gd) lastDay := MonthLenHijri(year, month) gy, gm, gd = HijriToGregorian(year, month, lastDay) end = fmt.Sprintf("%04d-%02d-%02d", gy, gm, gd) default: start = fmt.Sprintf("%04d-%02d-%02d", year, month, 1) lastDay := MonthLenGreg(year, month) end = fmt.Sprintf("%04d-%02d-%02d", year, month, lastDay) } return } func FormatMonthTitle(cal string, year, month int) string { switch cal { case "jalali": return fmt.Sprintf("%s %d", JalaliMonthNames[month-1], year) case "hijri": return fmt.Sprintf("%s %d", HijriMonthNames[month-1], year) default: return fmt.Sprintf("%s %d", GregMonthNames[month-1], year) } } func NavMonth(year, month int) (prevY, prevM, nextY, nextM int) { prevY, prevM = year, month-1 if prevM < 1 { prevM = 12 prevY-- } nextY, nextM = year, month+1 if nextM > 12 { nextM = 1 nextY++ } return }