152 lines
4.3 KiB
JavaScript
152 lines
4.3 KiB
JavaScript
'use strict';
|
||
|
||
const toEnglishDigits = (value) =>
|
||
String(value ?? '')
|
||
.replace(/[۰-۹]/g, (d) => '۰۱۲۳۴۵۶۷۸۹'.indexOf(d))
|
||
.replace(/[٠-٩]/g, (d) => '٠١٢٣٤٥٦٧٨٩'.indexOf(d));
|
||
|
||
const jalaliToGregorian = (jy, jm, jd) => {
|
||
const gy = jy <= 979 ? 621 : 1600;
|
||
jy -= jy <= 979 ? 0 : 979;
|
||
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);
|
||
let gyOut = gy + 400 * Math.floor(days / 146097);
|
||
days %= 146097;
|
||
if (days > 36524) {
|
||
gyOut += 100 * Math.floor(--days / 36524);
|
||
days %= 36524;
|
||
if (days >= 365) days += 1;
|
||
}
|
||
gyOut += 4 * Math.floor(days / 1461);
|
||
days %= 1461;
|
||
if (days > 365) {
|
||
gyOut += Math.floor((days - 1) / 365);
|
||
days = (days - 1) % 365;
|
||
}
|
||
let gd = days + 1;
|
||
const sal_a = [
|
||
0,
|
||
31,
|
||
(gyOut % 4 === 0 && gyOut % 100 !== 0) || gyOut % 400 === 0 ? 29 : 28,
|
||
31,
|
||
30,
|
||
31,
|
||
30,
|
||
31,
|
||
31,
|
||
30,
|
||
31,
|
||
30,
|
||
31
|
||
];
|
||
let gm = 0;
|
||
for (gm = 1; gm <= 12 && gd > sal_a[gm]; gm += 1) gd -= sal_a[gm];
|
||
return `${gyOut}-${String(gm).padStart(2, '0')}-${String(gd).padStart(2, '0')}`;
|
||
};
|
||
|
||
const parseJalaliDate = (raw) => {
|
||
if (!raw) return null;
|
||
const text = toEnglishDigits(raw).replace(/[./\-]/g, '/').trim();
|
||
const match = text.match(/^(\d{3,4})\/(\d{1,2})\/(\d{1,2})$/);
|
||
if (!match) return null;
|
||
const jy = Number(match[1]);
|
||
const jm = Number(match[2]);
|
||
const jd = Number(match[3]);
|
||
if (!jy || !jm || !jd || jm > 12 || jd > 31 || jy < 1200 || jy > 1599) return null;
|
||
try {
|
||
return jalaliToGregorian(jy, jm, jd);
|
||
} catch {
|
||
return null;
|
||
}
|
||
};
|
||
|
||
const parseImportDate = (raw) => {
|
||
if (!raw) return null;
|
||
if (raw instanceof Date) {
|
||
return Number.isNaN(raw.getTime()) ? null : raw;
|
||
}
|
||
const text = toEnglishDigits(raw).trim();
|
||
if (!text) return null;
|
||
|
||
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(text)) {
|
||
const d = new Date(text);
|
||
return Number.isNaN(d.getTime()) ? null : d;
|
||
}
|
||
|
||
const dateMatch = text.match(/^(\d{3,4})[./\-](\d{1,2})[./\-](\d{1,2})/);
|
||
if (dateMatch) {
|
||
const year = Number(dateMatch[1]);
|
||
const month = Number(dateMatch[2]);
|
||
const day = Number(dateMatch[3]);
|
||
if (year >= 1200 && year <= 1599) {
|
||
const gDate = jalaliToGregorian(year, month, day);
|
||
if (gDate) return new Date(`${gDate}T00:00:00.000Z`);
|
||
} else if (year >= 1900 && year <= 2200) {
|
||
const mStr = String(month).padStart(2, '0');
|
||
const dStr = String(day).padStart(2, '0');
|
||
return new Date(`${year}-${mStr}-${dStr}T00:00:00.000Z`);
|
||
}
|
||
}
|
||
|
||
const jalali = parseJalaliDate(text);
|
||
if (jalali) return new Date(`${jalali}T00:00:00.000Z`);
|
||
|
||
const date = new Date(text);
|
||
return Number.isNaN(date.getTime()) ? null : date;
|
||
};
|
||
|
||
const gregorianToJalali = (gy, gm, gd) => {
|
||
const g_d_m = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
|
||
let jy;
|
||
if (gy > 1600) {
|
||
jy = 979;
|
||
gy -= 1600;
|
||
} else {
|
||
jy = 0;
|
||
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}/${String(jm).padStart(2, '0')}/${String(jd).padStart(2, '0')}`;
|
||
};
|
||
|
||
const formatJalaliDate = (rawDate) => {
|
||
if (!rawDate) return '';
|
||
const date = rawDate instanceof Date ? rawDate : new Date(rawDate);
|
||
if (Number.isNaN(date.getTime())) return '';
|
||
return gregorianToJalali(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate());
|
||
};
|
||
|
||
const utcDayRange = (value) => {
|
||
const date = parseImportDate(value);
|
||
if (!date) return null;
|
||
const start = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
|
||
const end = new Date(start.getTime() + 24 * 60 * 60 * 1000);
|
||
return { start, end };
|
||
};
|
||
|
||
module.exports = {
|
||
toEnglishDigits,
|
||
jalaliToGregorian,
|
||
gregorianToJalali,
|
||
formatJalaliDate,
|
||
parseJalaliDate,
|
||
parseImportDate,
|
||
utcDayRange
|
||
};
|