mirror of
https://github.com/acamarata/luxon-hijri.git
synced 2026-06-30 18:54:28 +00:00
84 lines
3.2 KiB
TypeScript
84 lines
3.2 KiB
TypeScript
// formatHijriDate.ts
|
|
import { DateTime } from 'luxon';
|
|
import { hmLong, hmMedium } from './hMonths';
|
|
import { hwLong, hwShort, hwNumeric } from './hWeekdays';
|
|
import { toGregorian } from './toGregorian';
|
|
import type { HijriDate } from './types';
|
|
|
|
// Token regex: longest tokens first to prevent partial matches.
|
|
const TOKEN_RE =
|
|
/iYYYY|iYY|iMMMM|iMMM|iMM|iM|iDD|iD|iEEEE|iEEE|iE|ioooo|iooo|HH|H|hh|h|mm|m|ss|s|a|z{1,3}|ZZ|Z/g;
|
|
|
|
/**
|
|
* Format a Hijri date using a token-based format string.
|
|
*
|
|
* Hijri-specific tokens use the `i` prefix (iYYYY, iMM, iDD, iEEEE, etc.).
|
|
* Time and timezone tokens (HH, mm, ss, a, Z, z) are delegated to Luxon via the
|
|
* corresponding Gregorian date.
|
|
*
|
|
* @param hijriDate - the Hijri date to format
|
|
* @param format - a format string containing Hijri and/or Luxon tokens
|
|
* @returns the formatted date string
|
|
* @throws {RangeError} if the Hijri month is outside the 1-12 range
|
|
*/
|
|
export function formatHijriDate(hijriDate: HijriDate, format: string): string {
|
|
if (hijriDate.hm < 1 || hijriDate.hm > 12) {
|
|
throw new RangeError(`Hijri month must be 1-12, got ${hijriDate.hm}`);
|
|
}
|
|
|
|
// Lazy Gregorian DateTime, computed at most once per format call,
|
|
// only when a token that needs it is encountered.
|
|
let _gregDt: DateTime | undefined;
|
|
|
|
function getGregDt(): DateTime {
|
|
if (!_gregDt) {
|
|
const greg = toGregorian(hijriDate.hy, hijriDate.hm, hijriDate.hd);
|
|
_gregDt = DateTime.fromJSDate(greg, { zone: 'UTC' });
|
|
}
|
|
return _gregDt;
|
|
}
|
|
|
|
return format.replace(TOKEN_RE, (match): string => {
|
|
switch (match) {
|
|
case 'iYYYY':
|
|
return String(hijriDate.hy).padStart(4, '0');
|
|
case 'iYY':
|
|
return String(hijriDate.hy % 100).padStart(2, '0');
|
|
case 'iMM':
|
|
return String(hijriDate.hm).padStart(2, '0');
|
|
case 'iM':
|
|
return String(hijriDate.hm);
|
|
case 'iMMM':
|
|
// Non-null: hm is validated 1-12 above; index hm-1 is always 0-11, within array bounds.
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
return hmMedium[hijriDate.hm - 1]!;
|
|
case 'iMMMM':
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
return hmLong[hijriDate.hm - 1]!;
|
|
case 'iDD':
|
|
return String(hijriDate.hd).padStart(2, '0');
|
|
case 'iD':
|
|
return String(hijriDate.hd);
|
|
case 'iE':
|
|
case 'iEEE':
|
|
case 'iEEEE': {
|
|
// Luxon weekday: 1=Mon … 7=Sun. Modulo 7: Mon=1 … Sat=6, Sun=0.
|
|
// hwLong/hwShort/hwNumeric arrays: index 0=Sunday, 1=Monday, … 6=Saturday.
|
|
const idx = getGregDt().weekday % 7;
|
|
// Non-null: idx is always 0-6 (weekday%7), within all hw* array bounds.
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
if (match === 'iE') return String(hwNumeric[idx]!);
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
if (match === 'iEEE') return hwShort[idx]!;
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
return hwLong[idx]!;
|
|
}
|
|
case 'iooo':
|
|
case 'ioooo':
|
|
return 'AH';
|
|
default:
|
|
// Delegate time and timezone tokens to Luxon using the Gregorian DateTime.
|
|
return getGregDt().toFormat(match);
|
|
}
|
|
});
|
|
}
|