/** * Jalali (Solar Hijri / Shamsi) calendar conversion utilities (Backend) */ const persianMonths = [ { value: 1, id: 1, label: 'فروردین', name: 'فروردین' }, { value: 2, id: 2, label: 'اردیبهشت', name: 'اردیبهشت' }, { value: 3, id: 3, label: 'خرداد', name: 'خرداد' }, { value: 4, id: 4, label: 'تیر', name: 'تیر' }, { value: 5, id: 5, label: 'مرداد', name: 'مرداد' }, { value: 6, id: 6, label: 'شهریور', name: 'شهریور' }, { value: 7, id: 7, label: 'مهر', name: 'مهر' }, { value: 8, id: 8, label: 'آبان', name: 'آبان' }, { value: 9, id: 9, label: 'آذر', name: 'آذر' }, { value: 10, id: 10, label: 'دی', name: 'دی' }, { value: 11, id: 11, label: 'بهمن', name: 'بهمن' }, { value: 12, id: 12, label: 'اسفند', name: 'اسفند' } ] function toEnglishDigits(str) { if (str === null || str === undefined) return '' return String(str) .replace(/[۰-۹]/g, (d) => '۰۱۲۳۴۵۶۷۸۹'.indexOf(d)) .replace(/[٠-٩]/g, (d) => '٠١٢٣٤٥٦٧٨٩'.indexOf(d)) } function jalaliToGregorian(jy, jm, jd) { jy = Number(jy) jm = Number(jm) jd = Number(jd) let gy = jy > 979 ? 1600 : 621 jy = jy > 979 ? jy - 979 : jy let days = 365 * jy + Math.floor(jy / 33) * 8 + Math.floor(((jy % 33) + 3) / 4) + 78 + jd + (jm < 7 ? (jm - 1) * 31 : (jm - 7) * 30 + 186) gy += 400 * Math.floor(days / 146097) days %= 146097 if (days > 36524) { gy += 100 * Math.floor(--days / 36524) days %= 36524 if (days >= 365) days++ } gy += 4 * Math.floor(days / 1461) days %= 1461 if (days > 365) { gy += Math.floor((days - 1) / 365) days = (days - 1) % 365 } let gd = days + 1 const isLeap = (gy % 4 === 0 && gy % 100 !== 0) || gy % 400 === 0 const sal_a = [0, 31, isLeap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] let gm for (gm = 0; gm < 13; gm++) { const v = sal_a[gm] if (gd <= v) break gd -= v } return { gy, gm, gd } } function gregorianToJalali(gy, gm, gd) { gy = Number(gy) gm = Number(gm) gd = Number(gd) const g_d_m = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334] let jy = gy > 1600 ? 979 : 0 gy = gy > 1600 ? gy - 1600 : gy - 621 const gy2 = gm > 2 ? gy + 1 : gy let days = 365 * gy + Math.floor((gy2 + 3) / 4) - Math.floor((gy2 + 99) / 100) + Math.floor((gy2 + 399) / 400) - 80 + gd + g_d_m[gm - 1] jy += 33 * Math.floor(days / 12053) days %= 12053 jy += 4 * Math.floor(days / 1461) days %= 1461 if (days > 365) { jy += Math.floor((days - 1) / 365) days = (days - 1) % 365 } const jm = days < 186 ? 1 + Math.floor(days / 31) : 7 + Math.floor((days - 186) / 30) const jd = 1 + (days < 186 ? days % 31 : (days - 186) % 30) return { jy, jm, jd } } function jalaliToDate(jy, jm, jd) { const y = parseInt(toEnglishDigits(jy), 10) const m = parseInt(toEnglishDigits(jm), 10) const d = parseInt(toEnglishDigits(jd), 10) if (isNaN(y) || isNaN(m) || isNaN(d) || y <= 0 || m < 1 || m > 12 || d < 1 || d > 31) { return null } const { gy, gm, gd } = jalaliToGregorian(y, m, d) return new Date(Date.UTC(gy, gm - 1, gd, 12, 0, 0)) } function parseDateToJalali(dateVal) { if (!dateVal) return null if (dateVal instanceof Date || (typeof dateVal === 'string' && (dateVal.includes('T') || dateVal.includes('-')))) { const d = new Date(dateVal) if (!isNaN(d.getTime())) { return gregorianToJalali(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate()) } } if (typeof dateVal === 'string' && dateVal.includes('/')) { const clean = toEnglishDigits(dateVal.trim()) const parts = clean.split('/') if (parts.length === 3) { const jy = parseInt(parts[0], 10) const jm = parseInt(parts[1], 10) const jd = parseInt(parts[2], 10) if (!isNaN(jy) && !isNaN(jm) && !isNaN(jd)) { return { jy, jm, jd } } } } return null } function normalizeToDate(input) { if (!input) return null if (input instanceof Date && !isNaN(input.getTime())) return input if (typeof input === 'string') { const trimmed = input.trim() if (trimmed.includes('/')) { const parts = toEnglishDigits(trimmed).split('/') if (parts.length === 3) { return jalaliToDate(parts[0], parts[1], parts[2]) } } const d = new Date(trimmed) if (!isNaN(d.getTime())) return d } return null } module.exports = { persianMonths, toEnglishDigits, jalaliToGregorian, gregorianToJalali, jalaliToDate, parseDateToJalali, normalizeToDate, }