mirror of
https://github.com/acamarata/luxon-hijri.git
synced 2026-06-30 18:54:28 +00:00
feat: v2.1.0 — delegate engine logic to hijri-core
All Hijri conversion logic (UAQ table, FCNA algorithm, month/weekday names, isValidHijriDate) now lives in hijri-core and is re-exported from this package with identical signatures. Zero breaking changes: the public API surface, type exports, and behavior are unchanged. src/fcna.ts removed — FCNA engine is in hijri-core. src/hDates.ts, hMonths.ts, hWeekdays.ts, utils.ts, toHijri.ts, toGregorian.ts all rewritten as thin re-exports or single-line wrappers. hijri-core added as a runtime dependency.
This commit is contained in:
parent
8b734dd777
commit
30afd3c8a7
12 changed files with 42 additions and 731 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -1,9 +1,19 @@
|
|||
# Changelog
|
||||
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [2.1.0] - 2026-02-25
|
||||
|
||||
### Changed
|
||||
|
||||
- Engine logic extracted to `hijri-core` (new dependency). All Hijri conversion algorithms now live in that package and are re-exported from `luxon-hijri` with identical signatures. Zero breaking changes to the public API.
|
||||
- `src/hDates.ts`, `src/hMonths.ts`, `src/hWeekdays.ts`, `src/fcna.ts`, `src/utils.ts`, `src/toHijri.ts`, `src/toGregorian.ts` — all now delegate to `hijri-core`. The UAQ table, FCNA engine, month/weekday names, and conversion functions have a single source of truth across the hijri ecosystem.
|
||||
- `hijri-core ^1.0.0` added as a runtime dependency. `luxon` stays as a runtime dependency (still needed by `formatHijriDate` for time and timezone tokens).
|
||||
|
||||
## [2.0.0] - 2026-02-25
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "luxon-hijri",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"description": "Hijri/Gregorian date conversion and formatting using the Umm al-Qura calendar. Built on Luxon. Supports toHijri, toGregorian, formatHijriDate, and isValidHijriDate.",
|
||||
"author": "Aric Camarata",
|
||||
"license": "MIT",
|
||||
|
|
@ -53,6 +53,7 @@
|
|||
"typescript"
|
||||
],
|
||||
"dependencies": {
|
||||
"hijri-core": "file:../hijri-core",
|
||||
"luxon": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ importers:
|
|||
|
||||
.:
|
||||
dependencies:
|
||||
hijri-core:
|
||||
specifier: file:../hijri-core
|
||||
version: file:../hijri-core
|
||||
luxon:
|
||||
specifier: ^3.5.0
|
||||
version: 3.7.2
|
||||
|
|
@ -407,6 +410,10 @@ packages:
|
|||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
hijri-core@file:../hijri-core:
|
||||
resolution: {directory: ../hijri-core, type: directory}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
joycon@3.1.1:
|
||||
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -794,6 +801,8 @@ snapshots:
|
|||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
hijri-core@file:../hijri-core: {}
|
||||
|
||||
joycon@3.1.1: {}
|
||||
|
||||
lilconfig@3.1.3: {}
|
||||
|
|
|
|||
301
src/fcna.ts
301
src/fcna.ts
|
|
@ -1,301 +0,0 @@
|
|||
// fcna.ts — FCNA/ISNA Hijri calendar engine
|
||||
//
|
||||
// The Fiqh Council of North America (FCNA) uses a global astronomical criterion:
|
||||
// if the new moon conjunction occurs before 12:00 noon UTC on day D, the new
|
||||
// Hijri month begins at midnight starting day D+1 (next calendar day); if at or
|
||||
// after 12:00 UTC, the month begins at midnight starting day D+2.
|
||||
//
|
||||
// For years in the Umm al-Qura table (1318–1500 H), the UAQ month start date
|
||||
// serves as the anchor for locating the nearest new moon. For years outside that
|
||||
// range the anchor comes from the Islamic epoch plus mean synodic months.
|
||||
//
|
||||
// New moon times come from Jean Meeus, Astronomical Algorithms (2nd ed.),
|
||||
// Chapter 49 — accurate to within a few minutes for 1000 CE to 3000 CE.
|
||||
|
||||
import { hDatesTable } from './hDates';
|
||||
import type { HijriDate } from './types';
|
||||
|
||||
// ─── Constants ───────────────────────────────────────────────────────────────
|
||||
|
||||
const SYNODIC = 29.530588861; // Mean synodic month (days)
|
||||
const JDE0 = 2451550.09766; // Meeus k=0: mean new moon ~2000-01-06
|
||||
const JDE_UNIX = 2440587.5; // JDE of Unix epoch 1970-01-01 00:00 UTC
|
||||
const MS_PER_DAY = 86_400_000;
|
||||
const TO_RAD = Math.PI / 180;
|
||||
|
||||
// Approximate k index of 1 Muharram 1 AH in Meeus numbering.
|
||||
// Derived: Islamic epoch JDE ≈ 1948438.5 → k ≈ (1948438.5 − JDE0) / SYNODIC ≈ −17037.
|
||||
const K_EPOCH = -17037;
|
||||
|
||||
// ─── Meeus Chapter 49: corrected new moon JDE ────────────────────────────────
|
||||
|
||||
function newMoonJDE(k: number): number {
|
||||
const T = k / 1236.85;
|
||||
const T2 = T * T;
|
||||
const T3 = T2 * T;
|
||||
const T4 = T3 * T;
|
||||
|
||||
// Mean JDE (Meeus Eq. 49.1)
|
||||
let jde = JDE0
|
||||
+ SYNODIC * k
|
||||
+ 0.00015437 * T2
|
||||
- 0.000000150 * T3
|
||||
+ 0.00000000073 * T4;
|
||||
|
||||
// Sun's mean anomaly M (degrees)
|
||||
const M = (2.5534
|
||||
+ 29.10535670 * k
|
||||
- 0.0000014 * T2
|
||||
- 0.00000011 * T3) % 360;
|
||||
|
||||
// Moon's mean anomaly M' (degrees)
|
||||
const Mprime = (201.5643
|
||||
+ 385.81693528 * k
|
||||
+ 0.0107582 * T2
|
||||
+ 0.00001238 * T3
|
||||
- 0.000000058 * T4) % 360;
|
||||
|
||||
// Moon's argument of latitude F (degrees)
|
||||
const F = (160.7108
|
||||
+ 390.67050284 * k
|
||||
- 0.0016118 * T2
|
||||
- 0.00000227 * T3
|
||||
+ 0.000000011 * T4) % 360;
|
||||
|
||||
// Longitude of ascending node Omega (degrees)
|
||||
const Omega = (124.7746
|
||||
- 1.56375588 * k
|
||||
+ 0.0020672 * T2
|
||||
+ 0.00000215 * T3) % 360;
|
||||
|
||||
// Eccentricity correction factor E
|
||||
const E = 1 - 0.002516 * T - 0.0000074 * T2;
|
||||
const E2 = E * E;
|
||||
|
||||
// Angles to radians
|
||||
const Mrad = M * TO_RAD;
|
||||
const Mprad = Mprime * TO_RAD;
|
||||
const Frad = F * TO_RAD;
|
||||
const Orad = Omega * TO_RAD;
|
||||
|
||||
// Planetary correction (Meeus Table 49.a — new moon phase)
|
||||
jde +=
|
||||
- 0.40720 * Math.sin(Mprad)
|
||||
+ 0.17241 * E * Math.sin(Mrad)
|
||||
+ 0.01608 * Math.sin(2 * Mprad)
|
||||
+ 0.01039 * Math.sin(2 * Frad)
|
||||
+ 0.00739 * E * Math.sin(Mprad - Mrad)
|
||||
- 0.00514 * E * Math.sin(Mprad + Mrad)
|
||||
+ 0.00208 * E2 * Math.sin(2 * Mrad)
|
||||
- 0.00111 * Math.sin(Mprad - 2 * Frad)
|
||||
- 0.00057 * Math.sin(Mprad + 2 * Frad)
|
||||
+ 0.00056 * E * Math.sin(2 * Mprad + Mrad)
|
||||
- 0.00042 * Math.sin(3 * Mprad)
|
||||
+ 0.00042 * E * Math.sin(Mrad + 2 * Frad)
|
||||
+ 0.00038 * E * Math.sin(Mrad - 2 * Frad)
|
||||
- 0.00024 * E * Math.sin(2 * Mprad - Mrad)
|
||||
- 0.00017 * Math.sin(Orad)
|
||||
- 0.00007 * Math.sin(Mprad + 2 * Mrad)
|
||||
+ 0.00004 * Math.sin(2 * Mprad - 2 * Frad)
|
||||
+ 0.00004 * Math.sin(3 * Mrad)
|
||||
+ 0.00003 * Math.sin(Mprad + Mrad - 2 * Frad)
|
||||
+ 0.00003 * Math.sin(2 * Mprad + 2 * Frad)
|
||||
- 0.00003 * Math.sin(Mprad + Mrad + 2 * Frad)
|
||||
+ 0.00003 * Math.sin(Mprad - Mrad + 2 * Frad)
|
||||
- 0.00002 * Math.sin(Mprad - Mrad - 2 * Frad)
|
||||
- 0.00002 * Math.sin(3 * Mprad + Mrad)
|
||||
+ 0.00002 * Math.sin(4 * Mprad);
|
||||
|
||||
// Additional planetary corrections (Meeus Table 49.b)
|
||||
const A1 = (299.77 + 0.107408 * k - 0.009173 * T2) * TO_RAD;
|
||||
const A2 = (251.88 + 0.016321 * k) * TO_RAD;
|
||||
const A3 = (251.83 + 26.651886 * k) * TO_RAD;
|
||||
const A4 = (349.42 + 36.412478 * k) * TO_RAD;
|
||||
const A5 = ( 84.66 + 18.206239 * k) * TO_RAD;
|
||||
const A6 = (141.74 + 53.303771 * k) * TO_RAD;
|
||||
const A7 = (207.14 + 2.453732 * k) * TO_RAD;
|
||||
const A8 = (154.84 + 7.306860 * k) * TO_RAD;
|
||||
const A9 = ( 34.52 + 27.261239 * k) * TO_RAD;
|
||||
const A10 = (207.19 + 0.121824 * k) * TO_RAD;
|
||||
const A11 = (291.34 + 1.844379 * k) * TO_RAD;
|
||||
const A12 = (161.72 + 24.198154 * k) * TO_RAD;
|
||||
const A13 = (239.56 + 25.513099 * k) * TO_RAD;
|
||||
const A14 = (331.55 + 3.592518 * k) * TO_RAD;
|
||||
|
||||
jde +=
|
||||
+ 0.000325 * Math.sin(A1)
|
||||
+ 0.000165 * Math.sin(A2)
|
||||
+ 0.000164 * Math.sin(A3)
|
||||
+ 0.000126 * Math.sin(A4)
|
||||
+ 0.000110 * Math.sin(A5)
|
||||
+ 0.000062 * Math.sin(A6)
|
||||
+ 0.000060 * Math.sin(A7)
|
||||
+ 0.000056 * Math.sin(A8)
|
||||
+ 0.000047 * Math.sin(A9)
|
||||
+ 0.000042 * Math.sin(A10)
|
||||
+ 0.000040 * Math.sin(A11)
|
||||
+ 0.000037 * Math.sin(A12)
|
||||
+ 0.000035 * Math.sin(A13)
|
||||
+ 0.000023 * Math.sin(A14);
|
||||
|
||||
return jde;
|
||||
}
|
||||
|
||||
// ─── JDE / UTC conversion ─────────────────────────────────────────────────────
|
||||
|
||||
function jdeToUtcMs(jde: number): number {
|
||||
return (jde - JDE_UNIX) * MS_PER_DAY;
|
||||
}
|
||||
|
||||
function utcMsToKApprox(ms: number): number {
|
||||
const jde = ms / MS_PER_DAY + JDE_UNIX;
|
||||
return (jde - JDE0) / SYNODIC;
|
||||
}
|
||||
|
||||
// ─── Find nearest corrected new moon ─────────────────────────────────────────
|
||||
|
||||
// Returns the UTC ms of the corrected new moon closest to anchorMs.
|
||||
// Searches k0-2 through k0+2 (5 candidates) to handle any estimation error.
|
||||
function nearestNewMoonMs(anchorMs: number): number {
|
||||
const k0 = Math.round(utcMsToKApprox(anchorMs));
|
||||
let bestMs = 0;
|
||||
let bestDist = Infinity;
|
||||
|
||||
for (let k = k0 - 2; k <= k0 + 2; k++) {
|
||||
const ms = jdeToUtcMs(newMoonJDE(k));
|
||||
const dist = Math.abs(ms - anchorMs);
|
||||
if (dist < bestDist) {
|
||||
bestDist = dist;
|
||||
bestMs = ms;
|
||||
}
|
||||
}
|
||||
|
||||
return bestMs;
|
||||
}
|
||||
|
||||
// ─── FCNA criterion ──────────────────────────────────────────────────────────
|
||||
|
||||
// Given a conjunction UTC ms, return the midnight UTC ms that starts the
|
||||
// new FCNA Hijri month: D+1 if conjunction before 12:00 UTC, D+2 otherwise.
|
||||
function fcnaCriterionMs(conjMs: number): number {
|
||||
const midnight = Math.floor(conjMs / MS_PER_DAY) * MS_PER_DAY;
|
||||
const noon = midnight + 12 * 3_600_000;
|
||||
return conjMs < noon ? midnight + MS_PER_DAY : midnight + 2 * MS_PER_DAY;
|
||||
}
|
||||
|
||||
// ─── UAQ anchor ──────────────────────────────────────────────────────────────
|
||||
|
||||
// Return the UTC ms of the UAQ month start for (hy, hm).
|
||||
// For years 1318–1500 H: binary-search hDatesTable, sum dpm day counts.
|
||||
// For years outside that range: estimate from Islamic epoch + mean month count.
|
||||
export function uaqAnchorMs(hy: number, hm: number): number {
|
||||
// Binary search for hy in table.
|
||||
let lo = 0, hi = hDatesTable.length - 1, 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 && hDatesTable[found].dpm !== 0) {
|
||||
// In-range: sum prior-month day counts from table start date.
|
||||
const r = hDatesTable[found];
|
||||
let days = 0;
|
||||
for (let i = 0; i < hm - 1; i++) {
|
||||
days += (r.dpm >> i) & 1 ? 30 : 29;
|
||||
}
|
||||
return Date.UTC(r.gy, r.gm - 1, r.gd) + days * MS_PER_DAY;
|
||||
}
|
||||
|
||||
// Out of range: estimate from Islamic epoch + mean months elapsed.
|
||||
const monthsFromEpoch = (hy - 1) * 12 + (hm - 1);
|
||||
const kApprox = K_EPOCH + monthsFromEpoch;
|
||||
return jdeToUtcMs(newMoonJDE(kApprox));
|
||||
}
|
||||
|
||||
// ─── FCNA month start ─────────────────────────────────────────────────────────
|
||||
|
||||
// Return UTC ms of midnight beginning the given FCNA Hijri month.
|
||||
export function fcnaMonthStartMs(hy: number, hm: number): number {
|
||||
const anchor = uaqAnchorMs(hy, hm);
|
||||
const conjMs = nearestNewMoonMs(anchor);
|
||||
return fcnaCriterionMs(conjMs);
|
||||
}
|
||||
|
||||
// ─── FCNA month length ───────────────────────────────────────────────────────
|
||||
|
||||
export function fcnaDaysInMonth(hy: number, hm: number): number {
|
||||
const thisStart = fcnaMonthStartMs(hy, hm);
|
||||
const nextHy = hm < 12 ? hy : hy + 1;
|
||||
const nextHm = hm < 12 ? hm + 1 : 1;
|
||||
const nextStart = fcnaMonthStartMs(nextHy, nextHm);
|
||||
return Math.round((nextStart - thisStart) / MS_PER_DAY);
|
||||
}
|
||||
|
||||
// ─── FCNA Gregorian → Hijri ──────────────────────────────────────────────────
|
||||
|
||||
export function fcnaToHijri(gregorianDate: Date): HijriDate | null {
|
||||
if (!(gregorianDate instanceof Date) || isNaN(gregorianDate.getTime())) {
|
||||
throw new Error('Invalid Gregorian date');
|
||||
}
|
||||
|
||||
// Use UTC date components. The FCNA criterion is UTC-based (conjunction before
|
||||
// 12:00 UTC), so month boundaries are defined in UTC. Using UTC methods ensures
|
||||
// fcnaToGregorian ↔ fcnaToHijri round-trips correctly in any host timezone.
|
||||
const inputMs = Date.UTC(
|
||||
gregorianDate.getUTCFullYear(),
|
||||
gregorianDate.getUTCMonth(),
|
||||
gregorianDate.getUTCDate(),
|
||||
);
|
||||
|
||||
// Shift back ~15 days before estimating k so that kApprox resolves to the
|
||||
// current month's conjunction rather than possibly the next month's.
|
||||
const kApprox = utcMsToKApprox(inputMs - 15 * MS_PER_DAY);
|
||||
const k0 = Math.floor(kApprox);
|
||||
|
||||
// Search k0-1, k0, k0+1 for the FCNA month containing inputMs.
|
||||
for (let ki = k0 - 1; ki <= k0 + 1; ki++) {
|
||||
const conjMs = jdeToUtcMs(newMoonJDE(ki));
|
||||
const monthStart = fcnaCriterionMs(conjMs);
|
||||
|
||||
if (monthStart > inputMs) continue;
|
||||
|
||||
const nextConjMs = jdeToUtcMs(newMoonJDE(ki + 1));
|
||||
const nextMonthStart = fcnaCriterionMs(nextConjMs);
|
||||
|
||||
if (inputMs < nextMonthStart) {
|
||||
// inputMs falls in the month that began at monthStart (k = ki).
|
||||
// Map ki → Hijri (hy, hm) via K_EPOCH offset.
|
||||
const monthsFromEpoch = ki - K_EPOCH;
|
||||
let hy = Math.floor(monthsFromEpoch / 12) + 1;
|
||||
let hm = (monthsFromEpoch % 12) + 1;
|
||||
// JavaScript % can return negative; normalize to 1–12.
|
||||
if (hm <= 0) { hm += 12; hy--; }
|
||||
if (hy < 1) return null; // before 1 AH
|
||||
|
||||
const hd = Math.round((inputMs - monthStart) / MS_PER_DAY) + 1;
|
||||
return { hy, hm, hd };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ─── FCNA Hijri → Gregorian ──────────────────────────────────────────────────
|
||||
|
||||
export function fcnaToGregorian(hy: number, hm: number, hd: number): Date | null {
|
||||
if (hy < 1 || hm < 1 || hm > 12 || hd < 1) return null;
|
||||
const days = fcnaDaysInMonth(hy, hm);
|
||||
if (hd > days) return null;
|
||||
const startMs = fcnaMonthStartMs(hy, hm);
|
||||
return new Date(startMs + (hd - 1) * MS_PER_DAY);
|
||||
}
|
||||
|
||||
// ─── FCNA validation ─────────────────────────────────────────────────────────
|
||||
|
||||
export function fcnaIsValid(hy: number, hm: number, hd: number): boolean {
|
||||
if (hy < 1 || hm < 1 || hm > 12 || hd < 1) return false;
|
||||
return hd <= fcnaDaysInMonth(hy, hm);
|
||||
}
|
||||
194
src/hDates.ts
194
src/hDates.ts
|
|
@ -1,191 +1,3 @@
|
|||
// hDates.ts
|
||||
// Hijri Dates Reference Table
|
||||
import { HijriYearRecord } from './types';
|
||||
|
||||
export type { HijriYearRecord };
|
||||
|
||||
export const hDatesTable: HijriYearRecord[] = [
|
||||
{ hy: 1318, dpm: 0x02EA, gy: 1900, gm: 4, gd: 30 },
|
||||
{ hy: 1319, dpm: 0x06E9, gy: 1901, gm: 4, gd: 19 },
|
||||
{ hy: 1320, dpm: 0x0ED2, gy: 1902, gm: 4, gd: 9 },
|
||||
{ hy: 1321, dpm: 0x0EA4, gy: 1903, gm: 3, gd: 30 },
|
||||
{ hy: 1322, dpm: 0x0D4A, gy: 1904, gm: 3, gd: 18 },
|
||||
{ hy: 1323, dpm: 0x0A96, gy: 1905, gm: 3, gd: 7 },
|
||||
{ hy: 1324, dpm: 0x0536, gy: 1906, gm: 2, gd: 24 },
|
||||
{ hy: 1325, dpm: 0x0AB5, gy: 1907, gm: 2, gd: 13 },
|
||||
{ hy: 1326, dpm: 0x0DAA, gy: 1908, gm: 2, gd: 3 },
|
||||
{ hy: 1327, dpm: 0x0BA4, gy: 1909, gm: 1, gd: 23 },
|
||||
{ hy: 1328, dpm: 0x0B49, gy: 1910, gm: 1, gd: 12 },
|
||||
{ hy: 1329, dpm: 0x0A93, gy: 1911, gm: 1, gd: 1 },
|
||||
{ hy: 1330, dpm: 0x052B, gy: 1911, gm: 12, gd: 21 },
|
||||
{ hy: 1331, dpm: 0x0A57, gy: 1912, gm: 12, gd: 9 },
|
||||
{ hy: 1332, dpm: 0x04B6, gy: 1913, gm: 11, gd: 29 },
|
||||
{ hy: 1333, dpm: 0x0AB5, gy: 1914, gm: 11, gd: 18 },
|
||||
{ hy: 1334, dpm: 0x05AA, gy: 1915, gm: 11, gd: 8 },
|
||||
{ hy: 1335, dpm: 0x0D55, gy: 1916, gm: 10, gd: 27 },
|
||||
{ hy: 1336, dpm: 0x0D2A, gy: 1917, gm: 10, gd: 17 },
|
||||
{ hy: 1337, dpm: 0x0A56, gy: 1918, gm: 10, gd: 6 },
|
||||
{ hy: 1338, dpm: 0x04AE, gy: 1919, gm: 9, gd: 25 },
|
||||
{ hy: 1339, dpm: 0x095D, gy: 1920, gm: 9, gd: 13 },
|
||||
{ hy: 1340, dpm: 0x02EC, gy: 1921, gm: 9, gd: 3 },
|
||||
{ hy: 1341, dpm: 0x06D5, gy: 1922, gm: 8, gd: 23 },
|
||||
{ hy: 1342, dpm: 0x06AA, gy: 1923, gm: 8, gd: 13 },
|
||||
{ hy: 1343, dpm: 0x0555, gy: 1924, gm: 8, gd: 1 },
|
||||
{ hy: 1344, dpm: 0x04AB, gy: 1925, gm: 7, gd: 21 },
|
||||
{ hy: 1345, dpm: 0x095B, gy: 1926, gm: 7, gd: 10 },
|
||||
{ hy: 1346, dpm: 0x02BA, gy: 1927, gm: 6, gd: 30 },
|
||||
{ hy: 1347, dpm: 0x0575, gy: 1928, gm: 6, gd: 18 },
|
||||
{ hy: 1348, dpm: 0x0BB2, gy: 1929, gm: 6, gd: 8 },
|
||||
{ hy: 1349, dpm: 0x0764, gy: 1930, gm: 5, gd: 29 },
|
||||
{ hy: 1350, dpm: 0x0749, gy: 1931, gm: 5, gd: 18 },
|
||||
{ hy: 1351, dpm: 0x0655, gy: 1932, gm: 5, gd: 6 },
|
||||
{ hy: 1352, dpm: 0x02AB, gy: 1933, gm: 4, gd: 25 },
|
||||
{ hy: 1353, dpm: 0x055B, gy: 1934, gm: 4, gd: 14 },
|
||||
{ hy: 1354, dpm: 0x0ADA, gy: 1935, gm: 4, gd: 4 },
|
||||
{ hy: 1355, dpm: 0x06D4, gy: 1936, gm: 3, gd: 24 },
|
||||
{ hy: 1356, dpm: 0x0EC9, gy: 1937, gm: 3, gd: 13 },
|
||||
{ hy: 1357, dpm: 0x0D92, gy: 1938, gm: 3, gd: 3 },
|
||||
{ hy: 1358, dpm: 0x0D25, gy: 1939, gm: 2, gd: 20 },
|
||||
{ hy: 1359, dpm: 0x0A4D, gy: 1940, gm: 2, gd: 9 },
|
||||
{ hy: 1360, dpm: 0x02AD, gy: 1941, gm: 1, gd: 28 },
|
||||
{ hy: 1361, dpm: 0x056D, gy: 1942, gm: 1, gd: 17 },
|
||||
{ hy: 1362, dpm: 0x0B6A, gy: 1943, gm: 1, gd: 7 },
|
||||
{ hy: 1363, dpm: 0x0B52, gy: 1943, gm: 12, gd: 28 },
|
||||
{ hy: 1364, dpm: 0x0AA5, gy: 1944, gm: 12, gd: 16 },
|
||||
{ hy: 1365, dpm: 0x0A4B, gy: 1945, gm: 12, gd: 5 },
|
||||
{ hy: 1366, dpm: 0x0497, gy: 1946, gm: 11, gd: 24 },
|
||||
{ hy: 1367, dpm: 0x0937, gy: 1947, gm: 11, gd: 13 },
|
||||
{ hy: 1368, dpm: 0x02B6, gy: 1948, gm: 11, gd: 2 },
|
||||
{ hy: 1369, dpm: 0x0575, gy: 1949, gm: 10, gd: 22 },
|
||||
{ hy: 1370, dpm: 0x0D6A, gy: 1950, gm: 10, gd: 12 },
|
||||
{ hy: 1371, dpm: 0x0D52, gy: 1951, gm: 10, gd: 2 },
|
||||
{ hy: 1372, dpm: 0x0A96, gy: 1952, gm: 9, gd: 20 },
|
||||
{ hy: 1373, dpm: 0x092D, gy: 1953, gm: 9, gd: 9 },
|
||||
{ hy: 1374, dpm: 0x025D, gy: 1954, gm: 8, gd: 29 },
|
||||
{ hy: 1375, dpm: 0x04DD, gy: 1955, gm: 8, gd: 18 },
|
||||
{ hy: 1376, dpm: 0x0ADA, gy: 1956, gm: 8, gd: 7 },
|
||||
{ hy: 1377, dpm: 0x05D4, gy: 1957, gm: 7, gd: 28 },
|
||||
{ hy: 1378, dpm: 0x0DA9, gy: 1958, gm: 7, gd: 17 },
|
||||
{ hy: 1379, dpm: 0x0D52, gy: 1959, gm: 7, gd: 7 },
|
||||
{ hy: 1380, dpm: 0x0AAA, gy: 1960, gm: 6, gd: 25 },
|
||||
{ hy: 1381, dpm: 0x04D6, gy: 1961, gm: 6, gd: 14 },
|
||||
{ hy: 1382, dpm: 0x09B6, gy: 1962, gm: 6, gd: 3 },
|
||||
{ hy: 1383, dpm: 0x0374, gy: 1963, gm: 5, gd: 24 },
|
||||
{ hy: 1384, dpm: 0x0769, gy: 1964, gm: 5, gd: 12 },
|
||||
{ hy: 1385, dpm: 0x0752, gy: 1965, gm: 5, gd: 2 },
|
||||
{ hy: 1386, dpm: 0x06A5, gy: 1966, gm: 4, gd: 21 },
|
||||
{ hy: 1387, dpm: 0x054B, gy: 1967, gm: 4, gd: 10 },
|
||||
{ hy: 1388, dpm: 0x0AAB, gy: 1968, gm: 3, gd: 29 },
|
||||
{ hy: 1389, dpm: 0x055A, gy: 1969, gm: 3, gd: 19 },
|
||||
{ hy: 1390, dpm: 0x0AD5, gy: 1970, gm: 3, gd: 8 },
|
||||
{ hy: 1391, dpm: 0x0DD2, gy: 1971, gm: 2, gd: 26 },
|
||||
{ hy: 1392, dpm: 0x0DA4, gy: 1972, gm: 2, gd: 16 },
|
||||
{ hy: 1393, dpm: 0x0D49, gy: 1973, gm: 2, gd: 4 },
|
||||
{ hy: 1394, dpm: 0x0A95, gy: 1974, gm: 1, gd: 24 },
|
||||
{ hy: 1395, dpm: 0x052D, gy: 1975, gm: 1, gd: 13 },
|
||||
{ hy: 1396, dpm: 0x0A5D, gy: 1976, gm: 1, gd: 2 },
|
||||
{ hy: 1397, dpm: 0x055A, gy: 1976, gm: 12, gd: 22 },
|
||||
{ hy: 1398, dpm: 0x0AD5, gy: 1977, gm: 12, gd: 11 },
|
||||
{ hy: 1399, dpm: 0x06AA, gy: 1978, gm: 12, gd: 1 },
|
||||
{ hy: 1400, dpm: 0x0695, gy: 1979, gm: 11, gd: 20 },
|
||||
{ hy: 1401, dpm: 0x052B, gy: 1980, gm: 11, gd: 8 },
|
||||
{ hy: 1402, dpm: 0x0A57, gy: 1981, gm: 10, gd: 28 },
|
||||
{ hy: 1403, dpm: 0x04AE, gy: 1982, gm: 10, gd: 18 },
|
||||
{ hy: 1404, dpm: 0x0976, gy: 1983, gm: 10, gd: 7 },
|
||||
{ hy: 1405, dpm: 0x056C, gy: 1984, gm: 9, gd: 26 },
|
||||
{ hy: 1406, dpm: 0x0B55, gy: 1985, gm: 9, gd: 15 },
|
||||
{ hy: 1407, dpm: 0x0AAA, gy: 1986, gm: 9, gd: 5 },
|
||||
{ hy: 1408, dpm: 0x0A55, gy: 1987, gm: 8, gd: 25 },
|
||||
{ hy: 1409, dpm: 0x04AD, gy: 1988, gm: 8, gd: 13 },
|
||||
{ hy: 1410, dpm: 0x095D, gy: 1989, gm: 8, gd: 2 },
|
||||
{ hy: 1411, dpm: 0x02DA, gy: 1990, gm: 7, gd: 23 },
|
||||
{ hy: 1412, dpm: 0x05D9, gy: 1991, gm: 7, gd: 12 },
|
||||
{ hy: 1413, dpm: 0x0DB2, gy: 1992, gm: 7, gd: 1 },
|
||||
{ hy: 1414, dpm: 0x0BA4, gy: 1993, gm: 6, gd: 21 },
|
||||
{ hy: 1415, dpm: 0x0B4A, gy: 1994, gm: 6, gd: 10 },
|
||||
{ hy: 1416, dpm: 0x0A55, gy: 1995, gm: 5, gd: 30 },
|
||||
{ hy: 1417, dpm: 0x02B5, gy: 1996, gm: 5, gd: 18 },
|
||||
{ hy: 1418, dpm: 0x0575, gy: 1997, gm: 5, gd: 7 },
|
||||
{ hy: 1419, dpm: 0x0B6A, gy: 1998, gm: 4, gd: 27 },
|
||||
{ hy: 1420, dpm: 0x0BD2, gy: 1999, gm: 4, gd: 17 },
|
||||
{ hy: 1421, dpm: 0x0BC4, gy: 2000, gm: 4, gd: 6 },
|
||||
{ hy: 1422, dpm: 0x0B89, gy: 2001, gm: 3, gd: 26 },
|
||||
{ hy: 1423, dpm: 0x0A95, gy: 2002, gm: 3, gd: 15 },
|
||||
{ hy: 1424, dpm: 0x052D, gy: 2003, gm: 3, gd: 4 },
|
||||
{ hy: 1425, dpm: 0x05AD, gy: 2004, gm: 2, gd: 21 },
|
||||
{ hy: 1426, dpm: 0x0B6A, gy: 2005, gm: 2, gd: 10 },
|
||||
{ hy: 1427, dpm: 0x06D4, gy: 2006, gm: 1, gd: 31 },
|
||||
{ hy: 1428, dpm: 0x0DC9, gy: 2007, gm: 1, gd: 20 },
|
||||
{ hy: 1429, dpm: 0x0D92, gy: 2008, gm: 1, gd: 10 },
|
||||
{ hy: 1430, dpm: 0x0AA6, gy: 2008, gm: 12, gd: 29 },
|
||||
{ hy: 1431, dpm: 0x0956, gy: 2009, gm: 12, gd: 18 },
|
||||
{ hy: 1432, dpm: 0x02AE, gy: 2010, gm: 12, gd: 7 },
|
||||
{ hy: 1433, dpm: 0x056D, gy: 2011, gm: 11, gd: 26 },
|
||||
{ hy: 1434, dpm: 0x036A, gy: 2012, gm: 11, gd: 15 },
|
||||
{ hy: 1435, dpm: 0x0B55, gy: 2013, gm: 11, gd: 4 },
|
||||
{ hy: 1436, dpm: 0x0AAA, gy: 2014, gm: 10, gd: 25 },
|
||||
{ hy: 1437, dpm: 0x094D, gy: 2015, gm: 10, gd: 14 },
|
||||
{ hy: 1438, dpm: 0x049D, gy: 2016, gm: 10, gd: 2 },
|
||||
{ hy: 1439, dpm: 0x095D, gy: 2017, gm: 9, gd: 21 },
|
||||
{ hy: 1440, dpm: 0x02BA, gy: 2018, gm: 9, gd: 11 },
|
||||
{ hy: 1441, dpm: 0x05B5, gy: 2019, gm: 8, gd: 31 },
|
||||
{ hy: 1442, dpm: 0x05AA, gy: 2020, gm: 8, gd: 20 },
|
||||
{ hy: 1443, dpm: 0x0D55, gy: 2021, gm: 8, gd: 9 },
|
||||
{ hy: 1444, dpm: 0x0A9A, gy: 2022, gm: 7, gd: 30 },
|
||||
{ hy: 1445, dpm: 0x092E, gy: 2023, gm: 7, gd: 19 },
|
||||
{ hy: 1446, dpm: 0x026E, gy: 2024, gm: 7, gd: 7 },
|
||||
{ hy: 1447, dpm: 0x055D, gy: 2025, gm: 6, gd: 26 },
|
||||
{ hy: 1448, dpm: 0x0ADA, gy: 2026, gm: 6, gd: 16 },
|
||||
{ hy: 1449, dpm: 0x06D4, gy: 2027, gm: 6, gd: 6 },
|
||||
{ hy: 1450, dpm: 0x06A5, gy: 2028, gm: 5, gd: 25 },
|
||||
{ hy: 1451, dpm: 0x054B, gy: 2029, gm: 5, gd: 14 },
|
||||
{ hy: 1452, dpm: 0x0A97, gy: 2030, gm: 5, gd: 3 },
|
||||
{ hy: 1453, dpm: 0x054E, gy: 2031, gm: 4, gd: 23 },
|
||||
{ hy: 1454, dpm: 0x0AAE, gy: 2032, gm: 4, gd: 11 },
|
||||
{ hy: 1455, dpm: 0x05AC, gy: 2033, gm: 4, gd: 1 },
|
||||
{ hy: 1456, dpm: 0x0BA9, gy: 2034, gm: 3, gd: 21 },
|
||||
{ hy: 1457, dpm: 0x0D92, gy: 2035, gm: 3, gd: 11 },
|
||||
{ hy: 1458, dpm: 0x0B25, gy: 2036, gm: 2, gd: 28 },
|
||||
{ hy: 1459, dpm: 0x064B, gy: 2037, gm: 2, gd: 16 },
|
||||
{ hy: 1460, dpm: 0x0CAB, gy: 2038, gm: 2, gd: 5 },
|
||||
{ hy: 1461, dpm: 0x055A, gy: 2039, gm: 1, gd: 26 },
|
||||
{ hy: 1462, dpm: 0x0B55, gy: 2040, gm: 1, gd: 15 },
|
||||
{ hy: 1463, dpm: 0x06D2, gy: 2041, gm: 1, gd: 4 },
|
||||
{ hy: 1464, dpm: 0x0EA5, gy: 2041, gm: 12, gd: 24 },
|
||||
{ hy: 1465, dpm: 0x0E4A, gy: 2042, gm: 12, gd: 14 },
|
||||
{ hy: 1466, dpm: 0x0A95, gy: 2043, gm: 12, gd: 3 },
|
||||
{ hy: 1467, dpm: 0x052D, gy: 2044, gm: 11, gd: 21 },
|
||||
{ hy: 1468, dpm: 0x0AAD, gy: 2045, gm: 11, gd: 10 },
|
||||
{ hy: 1469, dpm: 0x036C, gy: 2046, gm: 10, gd: 31 },
|
||||
{ hy: 1470, dpm: 0x0759, gy: 2047, gm: 10, gd: 20 },
|
||||
{ hy: 1471, dpm: 0x06D2, gy: 2048, gm: 10, gd: 9 },
|
||||
{ hy: 1472, dpm: 0x0695, gy: 2049, gm: 9, gd: 28 },
|
||||
{ hy: 1473, dpm: 0x052D, gy: 2050, gm: 9, gd: 17 },
|
||||
{ hy: 1474, dpm: 0x0A5B, gy: 2051, gm: 9, gd: 6 },
|
||||
{ hy: 1475, dpm: 0x04BA, gy: 2052, gm: 8, gd: 26 },
|
||||
{ hy: 1476, dpm: 0x09BA, gy: 2053, gm: 8, gd: 15 },
|
||||
{ hy: 1477, dpm: 0x03B4, gy: 2054, gm: 8, gd: 5 },
|
||||
{ hy: 1478, dpm: 0x0B69, gy: 2055, gm: 7, gd: 25 },
|
||||
{ hy: 1479, dpm: 0x0B52, gy: 2056, gm: 7, gd: 14 },
|
||||
{ hy: 1480, dpm: 0x0AA6, gy: 2057, gm: 7, gd: 3 },
|
||||
{ hy: 1481, dpm: 0x04B6, gy: 2058, gm: 6, gd: 22 },
|
||||
{ hy: 1482, dpm: 0x096D, gy: 2059, gm: 6, gd: 11 },
|
||||
{ hy: 1483, dpm: 0x02EC, gy: 2060, gm: 5, gd: 31 },
|
||||
{ hy: 1484, dpm: 0x06D9, gy: 2061, gm: 5, gd: 20 },
|
||||
{ hy: 1485, dpm: 0x0EB2, gy: 2062, gm: 5, gd: 10 },
|
||||
{ hy: 1486, dpm: 0x0D54, gy: 2063, gm: 4, gd: 30 },
|
||||
{ hy: 1487, dpm: 0x0D2A, gy: 2064, gm: 4, gd: 18 },
|
||||
{ hy: 1488, dpm: 0x0A56, gy: 2065, gm: 4, gd: 7 },
|
||||
{ hy: 1489, dpm: 0x04AE, gy: 2066, gm: 3, gd: 27 },
|
||||
{ hy: 1490, dpm: 0x096D, gy: 2067, gm: 3, gd: 16 },
|
||||
{ hy: 1491, dpm: 0x0D6A, gy: 2068, gm: 3, gd: 5 },
|
||||
{ hy: 1492, dpm: 0x0B54, gy: 2069, gm: 2, gd: 23 },
|
||||
{ hy: 1493, dpm: 0x0B29, gy: 2070, gm: 2, gd: 12 },
|
||||
{ hy: 1494, dpm: 0x0A93, gy: 2071, gm: 2, gd: 1 },
|
||||
{ hy: 1495, dpm: 0x052B, gy: 2072, gm: 1, gd: 21 },
|
||||
{ hy: 1496, dpm: 0x0A57, gy: 2073, gm: 1, gd: 9 },
|
||||
{ hy: 1497, dpm: 0x0536, gy: 2073, gm: 12, gd: 30 },
|
||||
{ hy: 1498, dpm: 0x0AB5, gy: 2074, gm: 12, gd: 19 },
|
||||
{ hy: 1499, dpm: 0x06AA, gy: 2075, gm: 12, gd: 9 },
|
||||
{ hy: 1500, dpm: 0x0E93, gy: 2076, gm: 11, gd: 27 },
|
||||
{ hy: 1501, dpm: 0, gy: 2077, gm: 11, gd: 17 }];
|
||||
// hDates.ts — re-exports from hijri-core; table is maintained in the core package
|
||||
export { hDatesTable } from 'hijri-core';
|
||||
export type { HijriYearRecord } from 'hijri-core';
|
||||
|
|
|
|||
|
|
@ -1,46 +1,2 @@
|
|||
// hMonths.ts
|
||||
// Hijri Month Names
|
||||
export const hmLong = [
|
||||
"Muharram", // 1st month
|
||||
"Safar", // 2nd month
|
||||
"Rabi'l Awwal", // 3rd month
|
||||
"Rabi'l Thani", // 4th month
|
||||
"Jumadal Awwal", // 5th month
|
||||
"Jumadal Thani", // 6th month
|
||||
"Rajab", // 7th month
|
||||
"Sha'ban", // 8th month
|
||||
"Ramadan", // 9th month
|
||||
"Shawwal", // 10th month
|
||||
"Dhul Qi'dah", // 11th month
|
||||
"Dhul Hijjah" // 12th month
|
||||
];
|
||||
|
||||
export const hmMedium = [
|
||||
"Muharram",
|
||||
"Safar",
|
||||
"Rabi1",
|
||||
"Rabi2",
|
||||
"Jumada1",
|
||||
"Jumada2",
|
||||
"Rajab",
|
||||
"Shaban",
|
||||
"Ramadan",
|
||||
"Shawwal",
|
||||
"Dhul-Qidah",
|
||||
"Dhul-Hijah"
|
||||
];
|
||||
|
||||
export const hmShort = [
|
||||
"Muh",
|
||||
"Saf",
|
||||
"Ra1",
|
||||
"Ra2",
|
||||
"Ju1",
|
||||
"Ju2",
|
||||
"Raj",
|
||||
"Shb",
|
||||
"Ram",
|
||||
"Shw",
|
||||
"DhQ",
|
||||
"DhH"
|
||||
];
|
||||
// hMonths.ts — re-exports from hijri-core
|
||||
export { hmLong, hmMedium, hmShort } from 'hijri-core';
|
||||
|
|
|
|||
|
|
@ -1,25 +1,2 @@
|
|||
// hWeekdays.ts
|
||||
// Full names of Hijri weekdays
|
||||
export const hwLong = [
|
||||
"Yawm al-Ahad", // Sunday
|
||||
"Yawm al-Ithnayn", // Monday
|
||||
"Yawm ath-Thulatha'", // Tuesday
|
||||
"Yawm al-Arba`a'", // Wednesday
|
||||
"Yawm al-Khamis", // Thursday
|
||||
"Yawm al-Jum`a", // Friday
|
||||
"Yawm as-Sabt" // Saturday
|
||||
];
|
||||
|
||||
// Abbreviated names of Hijri weekdays
|
||||
export const hwShort = [
|
||||
"Ahad", // Sunday
|
||||
"Ithn", // Monday
|
||||
"Thul", // Tuesday
|
||||
"Arba", // Wednesday
|
||||
"Kham", // Thursday
|
||||
"Jum`a", // Friday
|
||||
"Sabt" // Saturday
|
||||
];
|
||||
|
||||
// Numeric representation of Hijri weekdays (1 for Sunday, 2 for Monday, etc.)
|
||||
export const hwNumeric = [1, 2, 3, 4, 5, 6, 7];
|
||||
// hWeekdays.ts — re-exports from hijri-core
|
||||
export { hwLong, hwShort, hwNumeric } from 'hijri-core';
|
||||
|
|
|
|||
|
|
@ -1,50 +1,9 @@
|
|||
// toGregorian.ts
|
||||
import { DateTime } from 'luxon';
|
||||
import { hDatesTable } from './hDates';
|
||||
import { fcnaToGregorian } from './fcna';
|
||||
import { isValidHijriDate } from './utils';
|
||||
// toGregorian.ts — thin wrapper over hijri-core; preserves throw-on-invalid behavior
|
||||
import { toGregorian as coreToGregorian } from 'hijri-core';
|
||||
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();
|
||||
const result = coreToGregorian(hy, hm, hd, options);
|
||||
if (result === null) throw new Error('Invalid Hijri date');
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,62 +1,2 @@
|
|||
// toHijri.ts
|
||||
import { hDatesTable } from './hDates';
|
||||
import { fcnaToHijri } from './fcna';
|
||||
import type { HijriDate, HijriYearRecord, ConversionOptions } from './types';
|
||||
|
||||
export function toHijri(gregorianDate: Date, options?: ConversionOptions): HijriDate | null {
|
||||
if (options?.calendar === 'fcna') {
|
||||
return fcnaToHijri(gregorianDate);
|
||||
}
|
||||
|
||||
if (!(gregorianDate instanceof Date) || isNaN(gregorianDate.getTime())) {
|
||||
throw new Error('Invalid Gregorian date');
|
||||
}
|
||||
|
||||
// Normalize input to UTC midnight so comparisons are timezone-independent.
|
||||
const inputUtc = Date.UTC(
|
||||
gregorianDate.getFullYear(),
|
||||
gregorianDate.getMonth(),
|
||||
gregorianDate.getDate(),
|
||||
);
|
||||
|
||||
// Binary search: find the last table entry whose Gregorian date <= input.
|
||||
// Table is sorted ascending by (gy, gm, gd).
|
||||
let lo = 0;
|
||||
let hi = hDatesTable.length - 1;
|
||||
let found = -1;
|
||||
|
||||
while (lo <= hi) {
|
||||
const mid = (lo + hi) >>> 1;
|
||||
const entry = hDatesTable[mid];
|
||||
const entryUtc = Date.UTC(entry.gy, entry.gm - 1, entry.gd);
|
||||
|
||||
if (entryUtc <= inputUtc) {
|
||||
found = mid;
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
hi = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// dpm === 0 means sentinel entry (marks end-of-table boundary, not a real year).
|
||||
if (found === -1 || hDatesTable[found].dpm === 0) return null;
|
||||
|
||||
const record: HijriYearRecord = hDatesTable[found];
|
||||
const startUtc = Date.UTC(record.gy, record.gm - 1, record.gd);
|
||||
let remainingDays = Math.round((inputUtc - startUtc) / 86_400_000);
|
||||
let hijriMonth = 0;
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const daysInThisMonth = (record.dpm >> i) & 1 ? 30 : 29;
|
||||
if (remainingDays < daysInThisMonth) {
|
||||
hijriMonth = i + 1;
|
||||
break;
|
||||
}
|
||||
remainingDays -= daysInThisMonth;
|
||||
}
|
||||
|
||||
// hijriMonth remains 0 if the date falls beyond the last table entry's year.
|
||||
if (hijriMonth === 0) return null;
|
||||
|
||||
return { hy: record.hy, hm: hijriMonth, hd: remainingDays + 1 };
|
||||
}
|
||||
// toHijri.ts — delegates to hijri-core
|
||||
export { toHijri } from 'hijri-core';
|
||||
|
|
|
|||
25
src/types.ts
25
src/types.ts
|
|
@ -1,23 +1,6 @@
|
|||
// types.ts
|
||||
export interface HijriDate {
|
||||
hy: number; // Hijri year
|
||||
hm: number; // Hijri month (1–12)
|
||||
hd: number; // Hijri day (1–30)
|
||||
}
|
||||
// types.ts — re-exports from hijri-core for backward compatibility
|
||||
export type { HijriDate, HijriYearRecord, ConversionOptions } from 'hijri-core';
|
||||
|
||||
export interface HijriYearRecord {
|
||||
hy: number; // Hijri year
|
||||
dpm: number; // days-per-month bitmask (bit 0 = month 1: 1→30 days, 0→29 days)
|
||||
gy: number; // Gregorian year of 1 Muharram
|
||||
gm: number; // Gregorian month of 1 Muharram
|
||||
gd: number; // Gregorian day of 1 Muharram
|
||||
}
|
||||
|
||||
// Calendar system selector.
|
||||
// 'uaq' — Umm al-Qura (default): table-based, covers 1318–1500 H.
|
||||
// 'fcna' — FCNA/ISNA: astronomical calculation, works for all Hijri years.
|
||||
// CalendarSystem documents the built-in calendar identifiers.
|
||||
// hijri-core accepts any string via registerCalendar(); this type covers the defaults.
|
||||
export type CalendarSystem = 'uaq' | 'fcna';
|
||||
|
||||
export interface ConversionOptions {
|
||||
calendar?: CalendarSystem;
|
||||
}
|
||||
|
|
|
|||
39
src/utils.ts
39
src/utils.ts
|
|
@ -1,37 +1,2 @@
|
|||
// utils.ts
|
||||
import { hDatesTable } from './hDates';
|
||||
import { fcnaIsValid } from './fcna';
|
||||
import type { ConversionOptions } from './types';
|
||||
|
||||
export function isValidHijriDate(hy: number, hm: number, hd: number, options?: ConversionOptions): boolean {
|
||||
if (options?.calendar === 'fcna') {
|
||||
return fcnaIsValid(hy, hm, hd);
|
||||
}
|
||||
|
||||
// Binary search on hy.
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// dpm === 0 means sentinel entry (marks end-of-table boundary, not a real year).
|
||||
if (found === -1 || hDatesTable[found].dpm === 0) return false;
|
||||
|
||||
const record = hDatesTable[found];
|
||||
if (hm < 1 || hm > 12 || hd < 1) return false;
|
||||
const daysInMonth = (record.dpm >> (hm - 1)) & 1 ? 30 : 29;
|
||||
return hd <= daysInMonth;
|
||||
}
|
||||
// utils.ts — delegates to hijri-core
|
||||
export { isValidHijriDate } from 'hijri-core';
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export default defineConfig({
|
|||
sourcemap: true,
|
||||
target: 'es2020',
|
||||
platform: 'node',
|
||||
external: ['luxon'],
|
||||
external: ['luxon', 'hijri-core'],
|
||||
outExtension({ format }) {
|
||||
return { js: format === 'cjs' ? '.cjs' : '.mjs' };
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue