Files
worktimeBot/internal/bot/dateutil.go
db123 69c5e20d8e display date in user's selected calendar on main menu and reports
buildStatusText and buildDailyReport now use formatDateForCalendar
to convert the stored Gregorian date to the user's calendar type
(jalali/hijri/gregorian).
2026-06-24 10:06:39 +03:30

293 lines
6.4 KiB
Go

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
}
// JDN for March 21, 622 CE (Jalali epoch: 1 Farvardin 1)
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 {
// 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)
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
}
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
}
}