const MONTH_NAMES = [ 'فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند', ] as const; function div(a: number, b: number) { return Math.trunc(a / b); } function mod(a: number, b: number) { return a - Math.trunc(a / b) * b; } export function toJalali(gy: number, gm: number, gd: number) { const gdm = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; const gy2 = gm > 2 ? gy + 1 : gy; let days = 355666 + 365 * gy + div(gy2 + 3, 4) - div(gy2 + 99, 100) + div(gy2 + 399, 400) + gd + gdm[gm - 1]; let jy = -1595 + 33 * div(days, 12053); days %= 12053; jy += 4 * div(days, 1461); days %= 1461; if (days > 365) { jy += div(days - 1, 365); days = (days - 1) % 365; } const jm = days < 186 ? 1 + div(days, 31) : 7 + div(days - 186, 30); const jd = 1 + (days < 186 ? days % 31 : (days - 186) % 30); return { jy, jm, jd }; } export function toGregorian(jy: number, jm: number, jd: number) { const jy2 = jy + 1595; let days = -355668 + 365 * jy2 + div(jy2, 33) * 8 + div(mod(jy2, 33) + 3, 4) + jd + (jm < 7 ? (jm - 1) * 31 : (jm - 7) * 30 + 186); let gy = 400 * div(days, 146097); days %= 146097; if (days > 36524) { gy += 100 * div(--days, 36524); days %= 36524; if (days >= 365) days += 1; } gy += 4 * div(days, 1461); days %= 1461; if (days > 365) { gy += div(days - 1, 365); days = (days - 1) % 365; } const gd = days + 1; const salA = [ 0, 31, (gy % 4 === 0 && gy % 100 !== 0) || gy % 400 === 0 ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, ]; let gm = 0; let dayAcc = gd; for (gm = 1; gm <= 12 && dayAcc > salA[gm]; gm += 1) { dayAcc -= salA[gm]; } return { gy, gm, gd: dayAcc }; } export function dateToJalali(d: Date) { return toJalali(d.getFullYear(), d.getMonth() + 1, d.getDate()); } export function jalaliToDate(jy: number, jm: number, jd: number): Date { const { gy, gm, gd } = toGregorian(jy, jm, jd); return new Date(gy, gm - 1, gd); } export function addJalaliMonths(jy: number, jm: number, delta: number) { const idx = jy * 12 + (jm - 1) + delta; return { jy: Math.floor(idx / 12), jm: (idx % 12) + 1 }; } export function jalaliMonthName(jm: number): string { return MONTH_NAMES[jm - 1] ?? ''; } export function jalaliMonthKey(d: Date): string { const { jy, jm } = dateToJalali(d); return `${jy}-${String(jm).padStart(2, '0')}`; } export function startOfJalaliMonth(jy: number, jm: number): Date { return jalaliToDate(jy, jm, 1); } export function endOfJalaliMonth(jy: number, jm: number): Date { const next = addJalaliMonths(jy, jm, 1); const startNext = jalaliToDate(next.jy, next.jm, 1); return new Date(startNext.getTime() - 1); }