mirror of
https://github.com/acamarata/luxon-hijri.git
synced 2026-07-02 03:30:42 +00:00
Core fixes:
- Fix critical weekday bug: iE/iEEE/iEEEE tokens used Hijri year as Gregorian,
returning weekdays ~580 years wrong. Now converts via toGregorian() first.
- Fix era tokens iooo/ioooo: were returning Gregorian era, now always return "AH".
- Fix toGregorian timezone sensitivity: was using DateTime.local(), now DateTime.utc().
- Fix format token regex: word-boundary approach caused partial matches.
New: FCNA/ISNA calendar support:
- toHijri, toGregorian, isValidHijriDate now accept { calendar: 'fcna' } option.
- FCNA criterion: conjunction before 12:00 UTC → month starts D+1, else D+2.
- New moon times from Meeus Ch.49 full formula (accurate to within minutes, 1000–3000 CE).
- Works for all Hijri years, not just the 1318–1500 UAQ table range.
- Anchor: UAQ table for in-range years, Islamic epoch estimate for out-of-range.
- Exports: CalendarSystem, ConversionOptions types.
Build and infrastructure:
- pnpm replaces npm; tsup replaces tsc for dual CJS/ESM output.
- Exports map with types-first conditional exports for import/require.
- Binary search O(log 183) replaces linear O(n) scan in all three functions.
- Luxon upgraded from ^2.5.2 to ^3.5.0; TypeScript from ^4 to ^5.5.
- CI: Node 20/22/24 matrix, typecheck, and pack-check jobs.
- GitHub Wiki: four pages synced via Actions on push.
- Test suite: 81 ESM tests + 24 CJS tests, verified against ISNA 2023–2025 calendars.
- Exports hwLong, hwShort, hwNumeric weekday arrays.
Breaking changes:
- Dual ESM/CJS exports map (CJS consumers: no change via main field).
- HijriYearRecord replaces hDates interface name.
- Luxon peer dep bumped to ^3.5.0.
- Node >=20 required.
50 lines
1.3 KiB
TypeScript
50 lines
1.3 KiB
TypeScript
// toGregorian.ts
|
|
import { DateTime } from 'luxon';
|
|
import { hDatesTable } from './hDates';
|
|
import { fcnaToGregorian } from './fcna';
|
|
import { isValidHijriDate } from './utils';
|
|
import type { ConversionOptions } from './types';
|
|
|
|
export function toGregorian(hy: number, hm: number, hd: number, options?: ConversionOptions): Date | null {
|
|
if (options?.calendar === 'fcna') {
|
|
const result = fcnaToGregorian(hy, hm, hd);
|
|
if (result === null) throw new Error('Invalid Hijri date');
|
|
return result;
|
|
}
|
|
|
|
if (!isValidHijriDate(hy, hm, hd)) {
|
|
throw new Error('Invalid Hijri date');
|
|
}
|
|
|
|
// Binary search on hy (table is sorted ascending by Hijri year).
|
|
let lo = 0;
|
|
let hi = hDatesTable.length - 1;
|
|
let found = -1;
|
|
|
|
while (lo <= hi) {
|
|
const mid = (lo + hi) >>> 1;
|
|
const midHy = hDatesTable[mid].hy;
|
|
|
|
if (midHy === hy) {
|
|
found = mid;
|
|
break;
|
|
} else if (midHy < hy) {
|
|
lo = mid + 1;
|
|
} else {
|
|
hi = mid - 1;
|
|
}
|
|
}
|
|
|
|
if (found === -1) return null;
|
|
|
|
const record = hDatesTable[found];
|
|
let totalDays = 0;
|
|
|
|
for (let i = 0; i < hm - 1; i++) {
|
|
totalDays += (record.dpm >> i) & 1 ? 30 : 29;
|
|
}
|
|
totalDays += hd - 1;
|
|
|
|
const startDate = DateTime.utc(record.gy, record.gm, record.gd);
|
|
return startDate.plus({ days: totalDays }).toJSDate();
|
|
}
|