Skip to content

Instantly share code, notes, and snippets.

@reloadlife
Last active March 26, 2025 02:18
Show Gist options
  • Select an option

  • Save reloadlife/235a43deef6cea713c87e27358b2fd5b to your computer and use it in GitHub Desktop.

Select an option

Save reloadlife/235a43deef6cea713c87e27358b2fd5b to your computer and use it in GitHub Desktop.
DateFormatter that supports both Jalali and Gregorian
/**
* Formats a date according to the specified format string in either Gregorian or Jalali calendar.
* Supports:
* - MMMM: Full month name (e.g., "March" or "فروردین")
* - MMM: Short month name (e.g., "Mar" or "فرو")
* - MM: Zero-padded month (e.g., "03")
* - M: Month number (e.g., "3")
* - Do: Day with ordinal suffix (e.g., "26th" or "۲۶ام")
* - DD: Zero-padded day (e.g., "26")
* - D: Day number (e.g., "26")
* - YYYY: Full year (e.g., "2025" or "۱۴۰۴")
* - YY: Two-digit year (e.g., "25" or "۰۴")
* - jMMMM, jMMM, jMM, jM: Jalali month formats
* - jDo, jDD, jD: Jalali day formats
* - jYYYY, jYY: Jalali year formats
*
* @param date - The date to format
* @param format - The format string
* @param useJalali - Whether to use Jalali calendar (default: false)
* @param usePersianDigits - Whether to use Persian digits (default: false)
* @returns The formatted date string
*/
export function FormatDate(
date: Date,
format: string,
useJalali = false,
usePersianDigits = false,
): string {
const day: number = date.getDate();
const month: number = date.getMonth();
const year: number = date.getFullYear();
const monthNames: string[] = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
const monthShortNames: string[] = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
const jalaliMonthNames: string[] = [
"فروردین",
"اردیبهشت",
"خرداد",
"تیر",
"مرداد",
"شهریور",
"مهر",
"آبان",
"آذر",
"دی",
"بهمن",
"اسفند",
];
const jalaliMonthShortNames: string[] = [
"فرو",
"ارد",
"خرد",
"تیر",
"مرد",
"شهر",
"مهر",
"آبا",
"آذر",
"دی",
"بهم",
"اسف",
];
const getOrdinalSuffix = (num: number): string => {
const j: number = num % 10;
const k: number = num % 100;
if (j === 1 && k !== 11) {
return num + "st";
}
if (j === 2 && k !== 12) {
return num + "nd";
}
if (j === 3 && k !== 13) {
return num + "rd";
}
return num + "th";
};
const getPersianOrdinalSuffix = (num: number): string => {
const persianNum = toPersianNumeral(num);
return persianNum + "ام";
};
const toPersianNumeral = (num: number): string => {
if (!usePersianDigits) return num.toString();
const persianDigits = [
"۰",
"۱",
"۲",
"۳",
"۴",
"۵",
"۶",
"۷",
"۸",
"۹",
] as const;
return num
.toString()
.replace(/\d/g, (digit) => persianDigits[parseInt(digit)] ?? "");
};
const gregorianToJalali = (
gy: number,
gm: number,
gd: number,
): [number, number, number] => {
const gdm: readonly number[] = [
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,
];
const jd =
1721425.5 +
365 * (gy - 1) +
Math.floor((gy - 1) / 4) -
Math.floor((gy - 1) / 100) +
Math.floor((gy - 1) / 400) +
Math.floor(
gdm[gm]! +
gd +
(gm > 1 && ((gy % 4 === 0 && gy % 100 !== 0) || gy % 400 === 0)
? 1
: 0),
);
const depoch = jd - 1948320.5;
const cycle = Math.floor(depoch / 1029983);
const cyear = depoch % 1029983;
let ycycle: number;
if (cyear === 1029982) {
ycycle = 2820;
} else {
const aux1 = Math.floor(cyear / 366);
const aux2 = cyear % 366;
ycycle =
Math.floor((2134 * aux1 + 2816 * aux2 + 2815) / 1028522) + aux1 + 1;
}
let jy = ycycle + 2820 * cycle + 474;
if (jy <= 0) {
jy--;
}
const jdn = Math.floor(jd) + 0.5;
const depoch2 = jdn - 2121445.5;
const dayInYear =
depoch2 - 365 * (jy - 474) - Math.floor((jy - 474) / 4) + 1;
let jm: number;
let jd2: number;
if (dayInYear <= 186) {
jm = Math.ceil(dayInYear / 31);
jd2 = ((dayInYear - 1) % 31) + 1;
} else {
jm = Math.ceil((dayInYear - 6) / 30);
jd2 = ((dayInYear - 6 - 1) % 30) + 1;
}
return [jy, jm - 1, jd2];
};
let result: string = format;
if (useJalali || format.includes("j")) {
const [jYear, jMonth, jDay] = gregorianToJalali(year, month, day);
result = result.replace(
/jYYYY/g,
useJalali ? toPersianNumeral(jYear) : jYear.toString(),
);
result = result.replace(
/jYY/g,
useJalali
? toPersianNumeral(jYear % 100)
: (jYear % 100).toString().padStart(2, "0"),
);
// Jalali month formats
result = result.replace(/jMMMM/g, jalaliMonthNames[jMonth] ?? "");
result = result.replace(/jMMM/g, jalaliMonthShortNames[jMonth] ?? "");
result = result.replace(
/jMM/g,
useJalali
? toPersianNumeral(jMonth + 1)
: (jMonth + 1).toString().padStart(2, "0"),
);
result = result.replace(
/jM(?!M)/g,
useJalali ? toPersianNumeral(jMonth + 1) : (jMonth + 1).toString(),
);
// Jalali day formats
result = result.replace(
/jDo/g,
useJalali ? getPersianOrdinalSuffix(jDay) : getOrdinalSuffix(jDay),
);
result = result.replace(
/jDD/g,
useJalali ? toPersianNumeral(jDay) : jDay.toString().padStart(2, "0"),
);
result = result.replace(
/jD(?!o)/g,
useJalali ? toPersianNumeral(jDay) : jDay.toString(),
);
}
result = result.replace(/MMMM/g, monthNames[month] ?? "");
result = result.replace(/MMM/g, monthShortNames[month] ?? "");
result = result.replace(/MM/g, (month + 1).toString().padStart(2, "0"));
result = result.replace(/M(?!M)/g, (month + 1).toString());
result = result.replace(/Do/g, getOrdinalSuffix(day));
result = result.replace(/DD/g, day.toString().padStart(2, "0"));
result = result.replace(/D(?!o)/g, day.toString());
result = result.replace(/YYYY/g, year.toString());
result = result.replace(/YY(?!YY)/g, year.toString().slice(-2));
return result;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment