mirror of
https://github.com/acamarata/temporal-hijri.git
synced 2026-06-30 19:04:29 +00:00
fix: build Dates via Date.UTC for hijri-core's UTC-day contract
HijriCalendar.toHijri() previously used new Date(y, m, d) (local-time constructor). Under hijri-core's new UTC-day contract the engine reads the UTC calendar day, so on east-of-UTC hosts (e.g. UTC+5) the local midnight falls on the previous UTC date, producing a one-day-off Hijri result. Fix: use Date.UTC(y, m, d) so PlainDate calendar fields land in the Date's UTC components, matching what hijri-core reads. Lock-step dependency: this fix requires the unreleased hijri-core change on fix/utc-day-boundary (commit 3419378). Both packages will be released together per ADR-013. Tests: round-trip regression added (2025-03-01 = 1 Ramadan 1446 AH); all 37 ESM + 9 CJS tests pass at TZ=UTC, TZ=America/New_York, TZ=Pacific/Auckland.
This commit is contained in:
parent
70bd956179
commit
4dd246f27a
4 changed files with 44 additions and 6 deletions
|
|
@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- Date handed to hijri-core is now built via `Date.UTC()` to match hijri-core's UTC-day
|
||||
contract; fixes previous-day results on east-of-UTC hosts (e.g. UTC+5, UTC+8).
|
||||
Lock-step: requires the matching unreleased hijri-core fix (`fix/utc-day-boundary`).
|
||||
|
||||
## [1.0.2] - 2026-05-30
|
||||
|
||||
### Added
|
||||
|
|
|
|||
11
README.md
11
README.md
|
|
@ -56,6 +56,17 @@ Full reference in the [wiki](https://github.com/acamarata/temporal-hijri/wiki).
|
|||
- [Architecture](https://github.com/acamarata/temporal-hijri/wiki/Architecture)
|
||||
- [Examples](https://github.com/acamarata/temporal-hijri/wiki/examples/basic-usage)
|
||||
|
||||
## Conversion behavior
|
||||
|
||||
Conversions between ISO and Hijri dates are pure calendar-date mappings: the same
|
||||
ISO date always maps to the same Hijri date on every machine, regardless of the host's
|
||||
timezone. `Temporal.PlainDate` carries no time-of-day information, and the underlying
|
||||
hijri-core engine operates on UTC calendar days, so there is no timezone dependency.
|
||||
|
||||
Note: the Islamic calendar begins a new day at sunset, not midnight. This library
|
||||
follows the civil-calendar convention (midnight boundary) used by most software. Sunset
|
||||
day-start determination is out of scope.
|
||||
|
||||
## Related
|
||||
|
||||
- [hijri-core](https://github.com/acamarata/hijri-core): the underlying calendar engine
|
||||
|
|
|
|||
|
|
@ -70,14 +70,14 @@ export class HijriCalendar {
|
|||
/**
|
||||
* Convert a Temporal.PlainDate (ISO calendar) to Hijri coordinates.
|
||||
*
|
||||
* Uses the local-time Date constructor so that the date components passed to
|
||||
* the engine match the calendar date exactly, regardless of host timezone.
|
||||
* The UAQ engine reads local components; the FCNA engine reads UTC components.
|
||||
* Because we construct with new Date(y, m, d) the local date always matches
|
||||
* the intended calendar date.
|
||||
* PlainDate calendar fields are placed in the Date's UTC components via
|
||||
* Date.UTC() because hijri-core reads the UTC calendar day. This ensures
|
||||
* the conversion returns the correct Hijri date on every host timezone:
|
||||
* without Date.UTC, on east-of-UTC hosts (e.g. UTC+5) the local midnight
|
||||
* falls on the previous UTC day, causing a one-day-off result.
|
||||
*/
|
||||
protected toHijri(date: Temporal.PlainDate): { hy: number; hm: number; hd: number } {
|
||||
const jsDate = new Date(date.year, date.month - 1, date.day);
|
||||
const jsDate = new Date(Date.UTC(date.year, date.month - 1, date.day));
|
||||
const hijri = this.engine.toHijri(jsDate);
|
||||
if (!hijri) {
|
||||
throw new RangeError(`Date ${date.toString()} is out of range for the ${this.id} calendar`);
|
||||
|
|
|
|||
22
test.mjs
22
test.mjs
|
|
@ -263,3 +263,25 @@ describe("monthDayFromFields", () => {
|
|||
assert.ok(result);
|
||||
});
|
||||
});
|
||||
|
||||
// ── 14. UTC-day round-trip regression ─────────────────────────────────────────
|
||||
// Verifies that ISO→Hijri→ISO is exact regardless of host timezone.
|
||||
// 2025-03-01 = 1 Ramadan 1446 AH (UAQ).
|
||||
|
||||
describe("UTC-day round-trip regression (ISO → Hijri → ISO)", () => {
|
||||
const isoRamadan1446 = Temporal.PlainDate.from("2025-03-01");
|
||||
|
||||
it("2025-03-01 maps to 1 Ramadan 1446 AH", () => {
|
||||
assert.equal(uaqCalendar.year(isoRamadan1446), 1446);
|
||||
assert.equal(uaqCalendar.month(isoRamadan1446), 9);
|
||||
assert.equal(uaqCalendar.day(isoRamadan1446), 1);
|
||||
});
|
||||
|
||||
it("round-trip: 1446-09-01 → ISO → Hijri returns 1446-09-01", () => {
|
||||
const iso = uaqCalendar.dateFromFields({ year: 1446, month: 9, day: 1 });
|
||||
assert.equal(iso.toString(), "2025-03-01");
|
||||
assert.equal(uaqCalendar.year(iso), 1446);
|
||||
assert.equal(uaqCalendar.month(iso), 9);
|
||||
assert.equal(uaqCalendar.day(iso), 1);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue