Last active
March 26, 2025 02:18
Revisions
-
reloadlife revised this gist
Mar 26, 2025 . 1 changed file with 152 additions and 79 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,102 @@ /** * Converts a Gregorian date to a Jalali (Persian) date * @param gy Gregorian year * @param gm Gregorian month (0-indexed, like JavaScript Date) * @param gd Gregorian day of month * @returns [jalaliYear, jalaliMonth, jalaliDay] (month is 0-indexed) */ const gregorianToJalali = ( gy: number, gm: number, gd: number, ): [number, number, number] => { // Convert from 0-based month to 1-based month for calculation const g_month = gm + 1; // Days accumulated in each month in Gregorian calendar const g_days_in_month = [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, ]; // Calculate Jalali year let jy = gy - 621; // Adjust calculation based on where date falls in Gregorian calendar const leap = (gy % 4 === 0 && gy % 100 !== 0) || gy % 400 === 0; const march_day = g_month > 2 || (g_month === 2 && gd >= (leap ? 20 : 19)) ? 1 : 0; jy -= g_month < 3 ? 1 : 0; // Calculate day of year in Gregorian calendar let g_day_of_year = g_days_in_month[g_month - 1]! + gd; if (leap && g_month > 2) { g_day_of_year++; } // Calculate day of year in Jalali calendar let j_day_of_year = g_day_of_year - (march_day === 1 ? 79 : leap ? 11 : 10); if (j_day_of_year < 1) { // Date is in previous Jalali year const j_leap = isJalaliLeapYear(jy - 1); j_day_of_year += j_leap ? 366 : 365; } // Determine Jalali month and day let jm = 0; let jd = 0; if (j_day_of_year <= 186) { // First 6 months are 31 days each jm = Math.ceil(j_day_of_year / 31); jd = j_day_of_year - (jm - 1) * 31; } else { // Remaining months are 30 days each (except the last one which can be 29 or 30) jm = 6 + Math.ceil((j_day_of_year - 186) / 30); jd = j_day_of_year - 186 - (jm - 7) * 30; } // Return Jalali date with 0-based month to match JavaScript conventions return [jy, jm - 1, jd]; }; /** * Determines if a Jalali year is a leap year * @param jy Jalali year * @returns true if leap year, false otherwise */ const isJalaliLeapYear = (jy: number): boolean => { // Algorithm to determine Jalali leap years const breaks = [ -61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210, 1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, ]; const bl = breaks.length; let jp = breaks[0]!; if (jy < jp || jy >= breaks[bl - 1]!) { throw new Error("Invalid Jalali year"); } let jump = 0; for (let i = 1; i < bl; i++) { const curr = breaks[i]!; jump = curr - jp; if (jy < curr) { break; } jp = curr; } jy -= jp; // Check leap year based on 33-year cycle with 8 leap years return jump - (jy % 33) > 4 || jump - (jy % 33) === 4 ? (jy % 33) % 4 === 0 : (jy % 33) % 4 === 3; }; /** * Formats a date according to the specified format string in either Gregorian or Jalali calendar. * Supports: @@ -131,78 +229,21 @@ export function FormatDate( .replace(/\d/g, (digit) => persianDigits[parseInt(digit)] ?? ""); }; let result: string = format; // Get Jalali date components if needed const [jYear, jMonth, jDay] = gregorianToJalali(year, month, day); // Handle Jalali format tokens if (format.includes("j")) { // Jalali year formats result = result.replace( /jYYYY/g, usePersianDigits ? toPersianNumeral(jYear) : jYear.toString(), ); result = result.replace( /jYY/g, usePersianDigits ? toPersianNumeral(jYear % 100) : (jYear % 100).toString().padStart(2, "0"), ); @@ -212,39 +253,71 @@ export function FormatDate( result = result.replace(/jMMM/g, jalaliMonthShortNames[jMonth] ?? ""); result = result.replace( /jMM/g, usePersianDigits ? toPersianNumeral(jMonth + 1) : (jMonth + 1).toString().padStart(2, "0"), ); result = result.replace( /jM(?!M)/g, usePersianDigits ? toPersianNumeral(jMonth + 1) : (jMonth + 1).toString(), ); // Jalali day formats result = result.replace( /jDo/g, usePersianDigits ? getPersianOrdinalSuffix(jDay) : getOrdinalSuffix(jDay), ); result = result.replace( /jDD/g, usePersianDigits ? toPersianNumeral(jDay) : jDay.toString().padStart(2, "0"), ); result = result.replace( /jD(?!o)/g, usePersianDigits ? toPersianNumeral(jDay) : jDay.toString(), ); } // Handle Gregorian format tokens (only if not in full Jalali mode or when explicit Gregorian tokens exist) if (!useJalali || !format.startsWith("j")) { result = result.replace(/MMMM/g, monthNames[month] ?? ""); result = result.replace(/MMM/g, monthShortNames[month] ?? ""); result = result.replace( /MM/g, usePersianDigits ? toPersianNumeral(month + 1) : (month + 1).toString().padStart(2, "0"), ); result = result.replace( /\bM(?![M\w])/g, usePersianDigits ? toPersianNumeral(month + 1) : (month + 1).toString(), ); result = result.replace( /Do/g, usePersianDigits ? getPersianOrdinalSuffix(day) : getOrdinalSuffix(day), ); result = result.replace( /DD/g, usePersianDigits ? toPersianNumeral(day) : day.toString().padStart(2, "0"), ); result = result.replace( /D(?!o)/g, usePersianDigits ? toPersianNumeral(day) : day.toString(), ); result = result.replace( /YYYY/g, usePersianDigits ? toPersianNumeral(year) : year.toString(), ); result = result.replace( /YY(?!YY)/g, usePersianDigits ? toPersianNumeral(parseInt(year.toString().slice(-2))) : year.toString().slice(-2), ); } return result; } -
reloadlife revised this gist
Mar 25, 2025 . No changes.There are no files selected for viewing
-
reloadlife renamed this gist
Mar 25, 2025 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
reloadlife created this gist
Mar 25, 2025 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,250 @@ /** * 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; }