Last active
March 26, 2025 02:18
-
-
Save reloadlife/235a43deef6cea713c87e27358b2fd5b to your computer and use it in GitHub Desktop.
DateFormatter that supports both Jalali and Gregorian
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * 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