const JALALI_BREAKS = [ -61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210, 1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178, ]; export function jalaliDateToGregorianDate(jalaliDate: string): string { const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(jalaliDate); if (!match) { throw new Error('birthDate must be a Jalali date in YYYY-MM-DD format'); } const jy = Number(match[1]); const jm = Number(match[2]); const jd = Number(match[3]); if (!isValidJalaliDate(jy, jm, jd)) { throw new Error('birthDate is not a valid Jalali date'); } const { gy, gm, gd } = jalaliToGregorian(jy, jm, jd); return `${gy}-${pad2(gm)}-${pad2(gd)}`; } function isValidJalaliDate(jy: number, jm: number, jd: number): boolean { if (jm < 1 || jm > 12 || jd < 1) return false; if (jm <= 6) return jd <= 31; if (jm <= 11) return jd <= 30; return jd <= (isJalaliLeapYear(jy) ? 30 : 29); } function isJalaliLeapYear(jy: number): boolean { return jalCal(jy).leap === 0; } function jalaliToGregorian( jy: number, jm: number, jd: number, ): { gy: number; gm: number; gd: number } { const gy = jalCal(jy).gy; const march = jalCal(jy).march; const daysInGregorianMonth = [ 31, isGregorianLeapYear(gy) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, ]; let dayOfYear = jd - 1; if (jm <= 6) { dayOfYear += (jm - 1) * 31; } else { dayOfYear += 186 + (jm - 7) * 30; } let gd = march + dayOfYear; let gm = 3; let targetGy = gy; while (gd > daysInGregorianMonth[gm - 1]) { gd -= daysInGregorianMonth[gm - 1]; gm += 1; if (gm > 12) { gm = 1; targetGy += 1; daysInGregorianMonth[1] = isGregorianLeapYear(targetGy) ? 29 : 28; } } return { gy: targetGy, gm, gd }; } function jalCal(jy: number): { leap: number; gy: number; march: number } { const breaksLength = JALALI_BREAKS.length; const gy = jy + 621; let leapJ = -14; let jp = JALALI_BREAKS[0]; let jump = 0; if (jy < jp || jy >= JALALI_BREAKS[breaksLength - 1]) { throw new Error('Jalali year is out of supported range'); } for (let i = 1; i < breaksLength; i += 1) { const jm = JALALI_BREAKS[i]; jump = jm - jp; if (jy < jm) break; leapJ += div(jump, 33) * 8 + div(mod(jump, 33), 4); jp = jm; } let n = jy - jp; leapJ += div(n, 33) * 8 + div(mod(n, 33) + 3, 4); if (mod(jump, 33) === 4 && jump - n === 4) leapJ += 1; const leapG = div(gy, 4) - div((div(gy, 100) + 1) * 3, 4) - 150; const march = 20 + leapJ - leapG; if (jump - n < 6) { n = n - jump + div(jump + 4, 33) * 33; } let leap = mod(mod(n + 1, 33) - 1, 4); if (leap === -1) leap = 4; return { leap, gy, march }; } function isGregorianLeapYear(year: number): boolean { return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); } function div(a: number, b: number): number { return Math.floor(a / b); } function mod(a: number, b: number): number { return a - Math.floor(a / b) * b; } function pad2(value: number): string { return String(value).padStart(2, '0'); }