hijri-core/README.md
Aric Camarata 34193780f3 fix: interpret Dates by UTC calendar day in UAQ engine for exact round-trips
Bug: uaqToHijri read local date components (getFullYear/getMonth/getDate)
and passed them to Date.UTC(), producing a UTC midnight that does not match
the input's intended Gregorian day on hosts west of UTC. Concretely, a Date
returned by toGregorian() (UTC midnight) would map to the *previous* Hijri
day on UTC-5 or UTC-13 hosts, breaking the toHijri(toGregorian(hy,hm,hd))
round-trip.

Fix: switch line 44 to read UTC components (getUTCFullYear/getUTCMonth/
getUTCDate), matching how the FCNA engine already worked. Both engines now
share the same UTC-day contract: toHijri reads the UTC calendar day of the
input, and toGregorian returns a UTC-midnight Date. Round-trips are exact;
results are host-timezone-independent.

Behavior change: on non-UTC hosts, toHijri results may shift to the UTC
calendar day rather than the local calendar day. Users passing local
wall-clock dates should use new Date(Date.UTC(y, m-1, d)).

Also:
- Fix misleading comment in uaqToHijri (previously claimed local components
  were "timezone-safe")
- Add UTC-day contract to toHijri JSDoc in src/index.ts
- Fix wrong constraint comment in hijri-core.test.ts header
- Add "day boundaries (UTC contract)" describe block to vitest suite
- Convert LOCAL Date constructors in test.mjs and test-cjs.cjs to
  Date.UTC() form; add UAQ round-trip assertion to test.mjs
- Add "Day boundaries and time zones" section to README.md
- Add [Unreleased] Fixed entry to CHANGELOG.md
2026-06-10 16:32:01 -04:00

87 lines
3.4 KiB
Markdown

# hijri-core
[![npm](https://img.shields.io/npm/v/hijri-core.svg)](https://www.npmjs.com/package/hijri-core)
[![CI](https://github.com/acamarata/hijri-core/actions/workflows/ci.yml/badge.svg)](https://github.com/acamarata/hijri-core/actions/workflows/ci.yml)
[![license](https://img.shields.io/npm/l/hijri-core.svg)](LICENSE)
[![wiki](https://img.shields.io/badge/docs-wiki-blue)](https://github.com/acamarata/hijri-core/wiki)
Zero-dependency Hijri calendar engine for JavaScript and TypeScript. Supports the Umm al-Qura (UAQ) and FCNA/ISNA calendars out of the box. A pluggable registry lets you add custom calendar implementations at runtime.
## Installation
```bash
npm install hijri-core
```
## Quick Start
```typescript
import { toHijri, toGregorian, isValidHijriDate, daysInHijriMonth } from 'hijri-core';
// Gregorian to Hijri (UAQ, default)
const hijri = toHijri(new Date(2025, 2, 1));
// { hy: 1446, hm: 9, hd: 1 }
// Hijri to Gregorian
const greg = toGregorian(1446, 9, 1);
// Date: 2025-03-01
// FCNA/ISNA calendar
toHijri(new Date('2025-03-01'), { calendar: 'fcna' });
toGregorian(1446, 9, 1, { calendar: 'fcna' });
// Validation and month length
isValidHijriDate(1444, 9, 1); // true
daysInHijriMonth(1444, 9); // 29
```
## Day boundaries and time zones
hijri-core maps civil calendar days one-to-one (tabular UAQ, computed FCNA). The religious Hijri day beginning at sunset is intentionally out of scope.
`toHijri` reads the input Date's UTC calendar day (`getUTCFullYear`, `getUTCMonth`, `getUTCDate`). `toGregorian` returns a UTC-midnight Date. This means round-trips are exact and results are identical on every machine regardless of local time zone:
```typescript
// Safe on any host — UTC-explicit construction
const greg = toGregorian(1446, 9, 1); // 2025-03-01T00:00:00.000Z
const back = toHijri(greg!); // { hy: 1446, hm: 9, hd: 1 } — always exact
// ISO date-only strings parse as UTC midnight — correct
toHijri(new Date('2025-03-01')); // { hy: 1446, hm: 9, hd: 1 }
// For a local wall-clock date, construct explicitly in UTC
toHijri(new Date(Date.UTC(2025, 2, 1))); // { hy: 1446, hm: 9, hd: 1 }
// Avoid local Date constructor for date-only conversions — breaks on UTC+13
// toHijri(new Date(2025, 2, 1)) ← do NOT do this
```
## Custom Calendars
Implement `CalendarEngine` and call `registerCalendar('my-id', engine)`. Pass `{ calendar: 'my-id' }` to any conversion function.
## TypeScript
```typescript
import type { HijriDate, HijriYearRecord, CalendarEngine, ConversionOptions } from 'hijri-core';
```
## Documentation
Full API reference, architecture notes, and calendar background: [GitHub Wiki](https://github.com/acamarata/hijri-core/wiki)
## Related
- [luxon-hijri](https://github.com/acamarata/luxon-hijri): Hijri formatting with Luxon
- [dayjs-hijri-plus](https://github.com/acamarata/dayjs-hijri-plus): Day.js Hijri plugin
- [date-fns-hijri](https://github.com/acamarata/date-fns-hijri): date-fns Hijri helpers
- [moment-hijri-plus](https://github.com/acamarata/moment-hijri-plus): Moment.js Hijri plugin
- [temporal-hijri](https://github.com/acamarata/temporal-hijri): Temporal API Hijri support
## Acknowledgments
The Umm al-Qura table is derived from data published by the King Abdulaziz City for Science and Technology (KACST). The FCNA new moon algorithm follows Jean Meeus, "Astronomical Algorithms," 2nd ed., Chapter 49.
## License
MIT. Copyright (c) 2024-2026 Aric Camarata.