mirror of
https://github.com/acamarata/luxon-hijri.git
synced 2026-06-30 18:54:28 +00:00
feat: v2.1.0 - delegate engine to hijri-core
Extract conversion logic to hijri-core. All Hijri algorithms now live in that package and are re-exported with identical signatures. Fix weekday, era, and time token bugs. Dual CJS/ESM build. Full test suite. CI and wiki workflows. Zero breaking API changes.
This commit is contained in:
parent
3332c912f1
commit
20dc36541b
20 changed files with 79 additions and 88 deletions
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm run build
|
||||
- run: node test.mjs
|
||||
- run: node test-cjs.cjs
|
||||
|
|
@ -35,7 +35,7 @@ jobs:
|
|||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm run typecheck
|
||||
|
||||
pack-check:
|
||||
|
|
@ -48,7 +48,7 @@ jobs:
|
|||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm run build
|
||||
- name: Verify pack contents
|
||||
run: |
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ toHijri(new Date(1800, 0, 1), { calendar: 'uaq' }) // null (before table range)
|
|||
Converts a Hijri date to a Gregorian `Date`.
|
||||
|
||||
```typescript
|
||||
function toGregorian(hy: number, hm: number, hd: number, options?: ConversionOptions): Date | null
|
||||
function toGregorian(hy: number, hm: number, hd: number, options?: ConversionOptions): Date
|
||||
```
|
||||
|
||||
**Parameters**
|
||||
|
|
@ -54,9 +54,9 @@ function toGregorian(hy: number, hm: number, hd: number, options?: ConversionOpt
|
|||
| `hd` | `number` | Hijri day (1–29 or 1–30 depending on the month) |
|
||||
| `options` | `ConversionOptions` | Optional. `{ calendar: 'uaq' }` (default) or `{ calendar: 'fcna' }`. |
|
||||
|
||||
**Returns** `Date | null`
|
||||
**Returns** `Date`
|
||||
|
||||
Returns a UTC Date at midnight. Returns `null` if no table entry matches (UAQ only; for valid input that passes `isValidHijriDate` this should not occur).
|
||||
Returns a UTC Date at midnight.
|
||||
|
||||
**Throws** `Error("Invalid Hijri date")` if the date fails validation.
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ Returns a UTC Date at midnight. Returns `null` if no table entry matches (UAQ on
|
|||
toGregorian(1444, 9, 1) // 2023-03-23T00:00:00.000Z
|
||||
toGregorian(1446, 9, 1, { calendar: 'fcna' }) // 2025-03-01T00:00:00.000Z
|
||||
toGregorian(1446, 10, 1, { calendar: 'fcna' }) // 2025-03-30T00:00:00.000Z
|
||||
toGregorian(1444, 0, 1) // throws — month 0 is invalid
|
||||
toGregorian(1444, 0, 1) // throws: month 0 is invalid
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -165,11 +165,11 @@ For FCNA: `hy` must be ≥ 1, `hm` must be 1–12, and `hd` must not exceed the
|
|||
|
||||
```javascript
|
||||
isValidHijriDate(1444, 9, 1) // true
|
||||
isValidHijriDate(1444, 9, 30) // false — Ramadan 1444 has 29 days (UAQ)
|
||||
isValidHijriDate(1317, 1, 1) // false — before table range
|
||||
isValidHijriDate(1501, 1, 1) // false — sentinel boundary
|
||||
isValidHijriDate(1, 1, 1, { calendar: 'fcna' }) // true — FCNA supports all years
|
||||
isValidHijriDate(1600, 1, 1, { calendar: 'fcna' }) // true — beyond UAQ table, FCNA computed
|
||||
isValidHijriDate(1444, 9, 30) // false - Ramadan 1444 has 29 days (UAQ)
|
||||
isValidHijriDate(1317, 1, 1) // false - before table range
|
||||
isValidHijriDate(1501, 1, 1) // false - sentinel boundary
|
||||
isValidHijriDate(1, 1, 1, { calendar: 'fcna' }) // true - FCNA supports all years
|
||||
isValidHijriDate(1600, 1, 1, { calendar: 'fcna' }) // true - beyond UAQ table, FCNA computed
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -184,8 +184,8 @@ interface HijriDate {
|
|||
}
|
||||
|
||||
// Calendar system selector.
|
||||
// 'uaq' — Umm al-Qura (default): table-based, covers 1318–1500 H.
|
||||
// 'fcna' — FCNA/ISNA: astronomical calculation, works for all Hijri years ≥ 1 AH.
|
||||
// 'uaq': Umm al-Qura (default): table-based, covers 1318–1500 H.
|
||||
// 'fcna': FCNA/ISNA: astronomical calculation, works for all Hijri years >= 1 AH.
|
||||
type CalendarSystem = 'uaq' | 'fcna';
|
||||
|
||||
interface ConversionOptions {
|
||||
|
|
@ -218,14 +218,14 @@ import type {
|
|||
|
||||
```javascript
|
||||
import {
|
||||
hDatesTable, // HijriYearRecord[] — 184 entries (183 real years + 1 sentinel)
|
||||
hmLong, // string[12] — full month names
|
||||
hmMedium, // string[12] — medium month names
|
||||
hmShort, // string[12] — abbreviated month names
|
||||
hwLong, // string[7] — full weekday names (Sunday-first order)
|
||||
hwShort, // string[7] — abbreviated weekday names
|
||||
hwNumeric, // number[7] — weekday numbers (1–7, Sunday=1)
|
||||
formatPatterns, // Record<string, string> — token reference map
|
||||
hDatesTable, // HijriYearRecord[] - 184 entries (183 real years + 1 sentinel)
|
||||
hmLong, // string[12] - full month names
|
||||
hmMedium, // string[12] - medium month names
|
||||
hmShort, // string[12] - abbreviated month names
|
||||
hwLong, // string[7] - full weekday names (Sunday-first order)
|
||||
hwShort, // string[7] - abbreviated weekday names
|
||||
hwNumeric, // number[7] - weekday numbers (1-7, Sunday=1)
|
||||
formatPatterns, // Record<string, string> - token reference map
|
||||
} from 'luxon-hijri';
|
||||
```
|
||||
|
||||
|
|
@ -244,7 +244,7 @@ import {
|
|||
| 8 | Ramadan | Ramadan | Ram |
|
||||
| 9 | Shawwal | Shawwal | Shw |
|
||||
| 10 | Dhul Qi'dah | Dhul-Qidah | DhQ |
|
||||
| 11 | Dhul Hijjah | Dhul-Hijah | DhH |
|
||||
| 11 | Dhul Hijjah | Dhul-Hijjah | DhH |
|
||||
|
||||
**Weekday arrays** (index 0 = Sunday, index 6 = Saturday)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
## Overview
|
||||
|
||||
luxon-hijri is a pure table lookup implementation. It does not perform any astronomical calculation at runtime. Instead it ships the official Umm al-Qura calendar table — 183 Hijri years of precomputed data — and does binary search to navigate it.
|
||||
luxon-hijri v2.1 delegates all calendar engine logic to [hijri-core](https://github.com/acamarata/hijri-core), a zero-dependency package that houses the Umm al-Qura table, FCNA algorithm, and calendar registry. This package re-exports the conversion functions and data with identical signatures, then adds Luxon-based date formatting on top.
|
||||
|
||||
Luxon is used only for two things: computing the equivalent Gregorian DateTime when format tokens like `iEEEE` (weekday) or time tokens need it, and for date arithmetic in `toGregorian` when adding days to the Muharram start date.
|
||||
Luxon is used for one thing only: computing the equivalent Gregorian DateTime inside `formatHijriDate` when tokens like `iEEEE` (weekday) or time/timezone tokens are used. All conversion arithmetic (`toHijri`, `toGregorian`, `isValidHijriDate`) runs through hijri-core without Luxon.
|
||||
|
||||
## The Umm al-Qura Table
|
||||
|
||||
The table in [src/hDates.ts](../src/hDates.ts) has 184 rows. The first 183 are real Hijri years (1318–1500). The 184th is a sentinel entry (year 1501, `dpm: 0`) that records the Gregorian start date of 1 Muharram 1501 — used as an upper boundary when converting Gregorian dates near the end of the table.
|
||||
The table lives in `hijri-core` and is re-exported from this package as `hDatesTable`. It has 184 rows. The first 183 are real Hijri years (1318–1500). The 184th is a sentinel entry (year 1501, `dpm: 0`) that records the Gregorian start date of 1 Muharram 1501, used as an upper boundary when converting Gregorian dates near the end of the table.
|
||||
|
||||
Each row stores:
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ Each row stores:
|
|||
| `gm` | number | Gregorian month of 1 Muharram (1-based) |
|
||||
| `gd` | number | Gregorian day of 1 Muharram |
|
||||
|
||||
Example entry: `{ hy: 1444, dpm: 0x0555, gy: 2022, gm: 7, gd: 30 }` — year 1444 started on July 30, 2022 (Gregorian). The `dpm` bitmask tells us which months have 30 days vs 29.
|
||||
Example entry: `{ hy: 1444, dpm: 0x0555, gy: 2022, gm: 7, gd: 30 }`. Year 1444 started on July 30, 2022 (Gregorian). The `dpm` bitmask tells us which months have 30 days vs 29.
|
||||
|
||||
Reading `dpm`: for month `m` (1-based), the day count is `((dpm >> (m - 1)) & 1) ? 30 : 29`.
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ Returns `null` for input before the first entry or if the sentinel is hit (input
|
|||
|
||||
3. Sum the day counts for months 1 through `hm - 1` using the `dpm` bitmask. Add `hd - 1` for the day within the current month. This gives `totalDays` elapsed since 1 Muharram of that year.
|
||||
|
||||
4. Use `DateTime.utc(gy, gm, gd).plus({ days: totalDays }).toJSDate()` to produce the Gregorian date as a UTC Date.
|
||||
4. Sum days elapsed from 1 Muharram, then offset from the entry's UTC start date to produce a UTC midnight `Date`.
|
||||
|
||||
### Validation (`isValidHijriDate`)
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ let _gregDt: DateTime | undefined;
|
|||
function getGregDt(): DateTime {
|
||||
if (!_gregDt) {
|
||||
const greg = toGregorian(hijriDate.hy, hijriDate.hm, hijriDate.hd);
|
||||
_gregDt = DateTime.fromJSDate(greg as Date, { zone: 'UTC' });
|
||||
_gregDt = DateTime.fromJSDate(greg, { zone: 'UTC' });
|
||||
}
|
||||
return _gregDt;
|
||||
}
|
||||
|
|
@ -78,7 +78,7 @@ This avoids the Gregorian lookup entirely when only pure Hijri tokens are used.
|
|||
|
||||
**Weekday index mapping**: Luxon's `weekday` runs 1 (Monday) through 7 (Sunday). The weekday arrays (`hwLong`, `hwShort`, `hwNumeric`) are indexed 0–6 with Sunday at index 0. The mapping is:
|
||||
|
||||
```
|
||||
```text
|
||||
arrayIndex = luxonWeekday % 7
|
||||
// Monday=1 → 1, Tuesday=2 → 2, ..., Saturday=6 → 6, Sunday=7 → 0
|
||||
```
|
||||
|
|
@ -87,11 +87,11 @@ arrayIndex = luxonWeekday % 7
|
|||
|
||||
All three functions (`toHijri`, `toGregorian`, `isValidHijriDate`) use binary search on a 184-entry table. This gives O(log 184) ≈ 8 comparisons worst case, compared to the O(184) linear scan used in v1.
|
||||
|
||||
For typical usage — converting a handful of dates per request — the difference is negligible. For batch workloads converting thousands of dates, the reduction is meaningful.
|
||||
For typical usage, converting a handful of dates per request, the difference is negligible. For batch workloads converting thousands of dates, the reduction is meaningful.
|
||||
|
||||
## FCNA Calendar Engine (`src/fcna.ts`)
|
||||
## FCNA Calendar Engine
|
||||
|
||||
The FCNA/ISNA calendar is computed astronomically rather than looked up from a table. It works for all Hijri years, not just the 1318–1500 range covered by the UAQ table.
|
||||
The FCNA/ISNA calendar is computed astronomically rather than looked up from a table. It works for all Hijri years, not just the 1318–1500 range covered by the UAQ table. The implementation lives in [hijri-core](https://github.com/acamarata/hijri-core) and is accessed through the calendar registry.
|
||||
|
||||
### FCNA Criterion
|
||||
|
||||
|
|
@ -105,15 +105,15 @@ New moon times come from Jean Meeus, *Astronomical Algorithms* (2nd ed.), Chapte
|
|||
|
||||
For years within the UAQ table (1318–1500 H), the UAQ month start date is used as the anchor for the nearest-new-moon search. This ensures the FCNA computation is consistent with the validated UAQ dataset for the date range where both systems overlap.
|
||||
|
||||
For years outside the table, the anchor comes from the Islamic epoch (1 Muharram 1 AH ≈ JDE 1948438.5) plus the mean number of synodic months elapsed. Meeus corrections then adjust the mean estimate to the actual conjunction time.
|
||||
For years outside the table, the anchor comes from the Islamic epoch (1 Muharram 1 AH approximately JDE 1948438.5) plus the mean number of synodic months elapsed. Meeus corrections then adjust the mean estimate to the actual conjunction time.
|
||||
|
||||
### Nearest New Moon Search
|
||||
|
||||
Given an anchor UTC timestamp, the engine estimates k, then checks k−2 through k+2 (five candidates) for the corrected new moon closest to the anchor. This handles any estimation error from the anchor strategy.
|
||||
Given an anchor UTC timestamp, the engine estimates k, then checks k-2 through k+2 (five candidates) for the corrected new moon closest to the anchor. This handles any estimation error from the anchor strategy.
|
||||
|
||||
### Calendar Conversion
|
||||
|
||||
`fcnaToGregorian(hy, hm, hd)`: sum the FCNA month-start offsets and add hd−1 days.
|
||||
`fcnaToGregorian(hy, hm, hd)`: sum the FCNA month-start offsets and add hd-1 days.
|
||||
|
||||
`fcnaToHijri(date)`: shift back ~15 days to ensure kApprox points to the current month's conjunction rather than the next. Try three adjacent k values; for each, compute the FCNA month start and next month start, then check whether the input falls within that window. Map the matching k to (hy, hm) via the K_EPOCH offset, and compute hd from the day offset.
|
||||
|
||||
|
|
@ -121,17 +121,15 @@ FCNA uses UTC date components (`getUTCFullYear`, `getUTCMonth`, `getUTCDate`) be
|
|||
|
||||
### Performance
|
||||
|
||||
FCNA conversion calls `newMoonJDE` (the Meeus formula) 3–5 times per call. Each call is a fixed set of floating-point trig operations — sub-millisecond in any modern JS engine. Month length computation (`fcnaDaysInMonth`) calls it twice more. No caching is done since usage patterns are typically small-batch.
|
||||
FCNA conversion calls `newMoonJDE` (the Meeus formula) 3–5 times per call. Each call is a fixed set of floating-point trig operations, sub-millisecond in any modern JS engine. Month length computation calls it twice more. No caching is done since usage patterns are typically small-batch.
|
||||
|
||||
## Why Luxon
|
||||
|
||||
Luxon is used for two narrow purposes:
|
||||
Luxon is used for one purpose only in this package:
|
||||
|
||||
1. `DateTime.utc(gy, gm, gd).plus({ days: n }).toJSDate()` in `toGregorian` — cleaner than manual day arithmetic across month/year boundaries.
|
||||
`DateTime.fromJSDate(greg, { zone: 'UTC' })` in `formatHijriDate` provides `.weekday` and `.toFormat()` for time/timezone tokens.
|
||||
|
||||
2. `DateTime.fromJSDate(greg, { zone: 'UTC' })` in `formatHijriDate` — provides `.weekday` and `.toFormat()` for time/timezone tokens.
|
||||
|
||||
Neither use requires Luxon's timezone database for standard Hijri date formatting. If you only use Hijri date tokens (no time/timezone tokens), the Gregorian DateTime is never constructed.
|
||||
All conversion arithmetic (`toHijri`, `toGregorian`, `isValidHijriDate`) runs through hijri-core without Luxon. Neither Luxon's timezone database nor its date arithmetic is needed for standard Hijri date formatting. If you only use Hijri date tokens (no time/timezone tokens), the Gregorian DateTime is never constructed.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
The Islamic calendar (also called the Hijri calendar) is a lunar calendar consisting of 12 months in a year of 354 or 355 days. Because the lunar year is roughly 11 days shorter than the solar year, the Islamic calendar cycles through all seasons over a period of approximately 33 years.
|
||||
|
||||
The calendar begins from the year of the Hijra — the migration of the Prophet Muhammad from Mecca to Medina in 622 CE. That year is 1 AH (Anno Hegirae). The Hijri year is written with the suffix "H" or "AH."
|
||||
The calendar begins from the year of the Hijra: the migration of the Prophet Muhammad from Mecca to Medina in 622 CE. That year is 1 AH (Anno Hegirae). The Hijri year is written with the suffix "H" or "AH."
|
||||
|
||||
Each month begins with the sighting of the crescent moon (hilal). Because actual lunar visibility depends on atmospheric conditions and geographic location, two methods exist for determining month start:
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ formatHijriDate({ hy: 1444, hm: 9, hd: 1 }, 'iEEEE, iD iMMMM iYYYY ioooo');
|
|||
- Two calendars: Umm al-Qura (default, table-based, 1318–1500 H) and FCNA/ISNA (astronomical, all years)
|
||||
- FCNA criterion: conjunction before 12:00 UTC → month starts D+1, else D+2 (Meeus Ch.49 algorithm)
|
||||
- Zero runtime dependencies beyond Luxon
|
||||
- Synchronous — no async, no loading delay
|
||||
- Synchronous: no async, no loading delay
|
||||
- Dual CJS and ESM, full TypeScript definitions
|
||||
- Weekday format bug from v1 is fixed in v2 (weekday tokens now use correct Gregorian conversion)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||
### 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.
|
||||
- `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
|
||||
|
|
@ -20,7 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||
|
||||
- **FCNA/ISNA calendar support**: `toHijri`, `toGregorian`, and `isValidHijriDate` now accept an optional `{ calendar: 'fcna' }` option. The FCNA criterion: if the astronomical conjunction occurs before 12:00 UTC the month begins D+1, otherwise D+2. New moon times use the full Meeus Chapter 49 formula (accurate to within a few minutes for 1000–3000 CE). The FCNA calendar works for all Hijri years, not just the 1318–1500 H range covered by the UAQ table.
|
||||
- New exported types: `CalendarSystem` (`'uaq' | 'fcna'`) and `ConversionOptions` (`{ calendar?: CalendarSystem }`).
|
||||
- `src/fcna.ts`: standalone FCNA engine — Meeus Ch.49 new moon algorithm, UAQ anchor lookup, FCNA criterion, and full Hijri↔Gregorian conversion without external dependencies beyond the existing hDatesTable.
|
||||
- `src/fcna.ts`: standalone FCNA engine. Meeus Ch.49 new moon algorithm, UAQ anchor lookup, FCNA criterion, and full Hijri/Gregorian conversion without external dependencies beyond the existing hDatesTable.
|
||||
- Dual CJS and ESM build via tsup (`dist/index.cjs`, `dist/index.mjs`)
|
||||
- Full TypeScript declarations for both module formats (`dist/index.d.ts`, `dist/index.d.mts`)
|
||||
- `src/types.ts` with named exported `HijriDate` and `HijriYearRecord` interfaces
|
||||
|
|
|
|||
30
README.md
30
README.md
|
|
@ -53,7 +53,7 @@ Throws `Error("Invalid Gregorian date")` if `date` is not a valid `Date`.
|
|||
```javascript
|
||||
toHijri(new Date(2024, 6, 7, 12)) // { hy: 1446, hm: 1, hd: 1 } (UAQ)
|
||||
toHijri(new Date(2025, 2, 1, 12), { calendar: 'fcna' }) // { hy: 1446, hm: 9, hd: 1 } (FCNA)
|
||||
toHijri(new Date(1800, 0, 1)) // null — before UAQ table range
|
||||
toHijri(new Date(1800, 0, 1)) // null - before UAQ table range
|
||||
```
|
||||
|
||||
### `toGregorian(hy, hm, hd, options?)`
|
||||
|
|
@ -61,7 +61,7 @@ toHijri(new Date(1800, 0, 1)) // null — before U
|
|||
Converts a Hijri date to a Gregorian `Date` at UTC midnight.
|
||||
|
||||
```typescript
|
||||
function toGregorian(hy: number, hm: number, hd: number, options?: ConversionOptions): Date | null
|
||||
function toGregorian(hy: number, hm: number, hd: number, options?: ConversionOptions): Date
|
||||
```
|
||||
|
||||
Throws `Error("Invalid Hijri date")` if the date is invalid for the selected calendar.
|
||||
|
|
@ -142,14 +142,14 @@ interface HijriYearRecord {
|
|||
|
||||
```javascript
|
||||
import {
|
||||
hDatesTable, // HijriYearRecord[] — the full Umm al-Qura table (184 entries)
|
||||
hmLong, // string[12] — full month names
|
||||
hmMedium, // string[12] — medium month names
|
||||
hmShort, // string[12] — abbreviated month names
|
||||
hwLong, // string[7] — full weekday names (Sunday first)
|
||||
hwShort, // string[7] — abbreviated weekday names
|
||||
hwNumeric, // number[7] — weekday numbers (1–7, Sunday=1)
|
||||
formatPatterns, // Record<string, string> — token reference
|
||||
hDatesTable, // HijriYearRecord[] - the full Umm al-Qura table (184 entries)
|
||||
hmLong, // string[12] - full month names
|
||||
hmMedium, // string[12] - medium month names
|
||||
hmShort, // string[12] - abbreviated month names
|
||||
hwLong, // string[7] - full weekday names (Sunday first)
|
||||
hwShort, // string[7] - abbreviated weekday names
|
||||
hwNumeric, // number[7] - weekday numbers (1-7, Sunday=1)
|
||||
formatPatterns, // Record<string, string> - token reference
|
||||
} from 'luxon-hijri';
|
||||
```
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ import {
|
|||
|
||||
## Architecture
|
||||
|
||||
The UAQ engine is a pure table lookup with binary search (O(log 183)). The FCNA engine computes new moon times astronomically using the Meeus Ch.49 formula — 3 to 5 trigonometric evaluations per call, sub-millisecond on any modern JS engine.
|
||||
The UAQ engine is a pure table lookup with binary search (O(log 183)). The FCNA engine computes new moon times astronomically using the Meeus Ch.49 formula: 3 to 5 trigonometric evaluations per call, sub-millisecond on any modern JS engine.
|
||||
|
||||
For more detail see the [Architecture wiki page](https://github.com/acamarata/luxon-hijri/wiki/Architecture).
|
||||
|
||||
|
|
@ -178,7 +178,7 @@ import { toHijri, toGregorian, formatHijriDate, isValidHijriDate } from 'luxon-h
|
|||
import type { HijriDate, HijriYearRecord, CalendarSystem, ConversionOptions } from 'luxon-hijri';
|
||||
|
||||
const h: HijriDate | null = toHijri(new Date());
|
||||
const g: Date | null = toGregorian(1444, 9, 1, { calendar: 'fcna' });
|
||||
const g: Date = toGregorian(1444, 9, 1, { calendar: 'fcna' });
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
|
@ -188,9 +188,9 @@ Full API reference, architecture notes, calendar background, and format token gu
|
|||
|
||||
## Related
|
||||
|
||||
- [nrel-spa](https://www.npmjs.com/package/nrel-spa) — NREL Solar Position Algorithm (pure JS)
|
||||
- [pray-calc](https://www.npmjs.com/package/pray-calc) — Islamic prayer times, depends on nrel-spa
|
||||
- [solar-spa](https://www.npmjs.com/package/solar-spa) — NREL SPA compiled to WebAssembly
|
||||
- [nrel-spa](https://www.npmjs.com/package/nrel-spa): NREL Solar Position Algorithm (pure JS)
|
||||
- [pray-calc](https://www.npmjs.com/package/pray-calc): Islamic prayer times, depends on nrel-spa
|
||||
- [solar-spa](https://www.npmjs.com/package/solar-spa): NREL SPA compiled to WebAssembly
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@
|
|||
"typescript"
|
||||
],
|
||||
"dependencies": {
|
||||
"hijri-core": "file:../hijri-core",
|
||||
"hijri-core": "^1.0.0",
|
||||
"luxon": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ importers:
|
|||
.:
|
||||
dependencies:
|
||||
hijri-core:
|
||||
specifier: file:../hijri-core
|
||||
version: file:../hijri-core
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
luxon:
|
||||
specifier: ^3.5.0
|
||||
version: 3.7.2
|
||||
|
|
@ -410,8 +410,8 @@ 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}
|
||||
hijri-core@1.0.0:
|
||||
resolution: {integrity: sha512-wImBZLBKbEWEEUE1nrc1CFY/uvx4XjGNWYChImJZlswXIVhrBCzSVaj6DP1AU2gUMJ6KDh2ygXo/u/Qx232CXA==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
joycon@3.1.1:
|
||||
|
|
@ -801,7 +801,7 @@ snapshots:
|
|||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
hijri-core@file:../hijri-core: {}
|
||||
hijri-core@1.0.0: {}
|
||||
|
||||
joycon@3.1.1: {}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,15 +10,14 @@ 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;
|
||||
|
||||
export function formatHijriDate(hijriDate: HijriDate, format: string): string {
|
||||
// Lazy Gregorian DateTime — computed at most once per format call,
|
||||
// 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);
|
||||
// toGregorian throws for invalid input, so greg is non-null here.
|
||||
_gregDt = DateTime.fromJSDate(greg as Date, { zone: 'UTC' });
|
||||
_gregDt = DateTime.fromJSDate(greg, { zone: 'UTC' });
|
||||
}
|
||||
return _gregDt;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,8 +40,10 @@ export const formatPatterns = {
|
|||
|
||||
// Timezone
|
||||
z: 'Timezone (abbreviated)',
|
||||
zz: 'Timezone (full)',
|
||||
Z: 'Timezone offset from UTC',
|
||||
zz: 'Timezone (medium)',
|
||||
zzz: 'Timezone (full)',
|
||||
Z: 'Timezone offset from UTC (+HH:MM)',
|
||||
ZZ: 'Timezone offset from UTC (condensed)',
|
||||
};
|
||||
|
||||
// Export the patterns for use in the rest of the package
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
// hDates.ts — re-exports from hijri-core; table is maintained in the core package
|
||||
// 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,2 +1,2 @@
|
|||
// hMonths.ts — re-exports from hijri-core
|
||||
// hMonths.ts: re-exports from hijri-core
|
||||
export { hmLong, hmMedium, hmShort } from 'hijri-core';
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
// hWeekdays.ts — re-exports from hijri-core
|
||||
// hWeekdays.ts: re-exports from hijri-core
|
||||
export { hwLong, hwShort, hwNumeric } from 'hijri-core';
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"months": ["محرم", "صفر", "..."],
|
||||
"weekdays": ["الأحد", "الاثنين", "..."]
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"months": ["Muharram", "Safar", "..."],
|
||||
"weekdays": ["Sunday", "Monday", "..."]
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
// toGregorian.ts — thin wrapper over hijri-core; preserves throw-on-invalid behavior
|
||||
// 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 {
|
||||
export function toGregorian(hy: number, hm: number, hd: number, options?: ConversionOptions): Date {
|
||||
const result = coreToGregorian(hy, hm, hd, options);
|
||||
if (result === null) throw new Error('Invalid Hijri date');
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
// toHijri.ts — delegates to hijri-core
|
||||
// toHijri.ts: delegates to hijri-core
|
||||
export { toHijri } from 'hijri-core';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// types.ts — re-exports from hijri-core for backward compatibility
|
||||
// types.ts: re-exports from hijri-core for backward compatibility
|
||||
export type { HijriDate, HijriYearRecord, ConversionOptions } from 'hijri-core';
|
||||
|
||||
// CalendarSystem documents the built-in calendar identifiers.
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
// utils.ts — delegates to hijri-core
|
||||
// utils.ts: delegates to hijri-core
|
||||
export { isValidHijriDate } from 'hijri-core';
|
||||
|
|
|
|||
Loading…
Reference in a new issue