mirror of
https://github.com/acamarata/moment-hijri-plus.git
synced 2026-06-30 18:54:29 +00:00
fix: convert the displayed calendar date in toHijri for hijri-core's UTC-day contract
moment.fn.toHijri now passes new Date(Date.UTC(this.year(), this.month(), this.date())) to hijri-core instead of the raw instant (this.toDate()). This converts the calendar date the moment instance displays, respecting utc() mode, rather than the underlying millisecond value — eliminating wrong-Hijri-day results around UTC-midnight for hosts east or west of UTC. Lock-step with hijri-core fix/utc-day-boundary (commit 3419378). fromHijri path was already correct; its comment updated for clarity.
This commit is contained in:
parent
1e3044c859
commit
c6e9a49d19
5 changed files with 71 additions and 5 deletions
|
|
@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- `.toHijri()` now converts the calendar date the moment instance displays (year/month/day
|
||||
components, respecting `.utc()` mode) rather than passing the raw instant to hijri-core.
|
||||
This eliminates wrong-Hijri-day results around UTC-midnight for hosts east or west of UTC.
|
||||
Lock-step with the unreleased hijri-core fix on `fix/utc-day-boundary`.
|
||||
|
||||
## [1.0.2] - 2026-05-30
|
||||
|
||||
### Changed
|
||||
|
|
|
|||
|
|
@ -50,6 +50,15 @@ Pass `{ calendar: 'fcna' }` to switch from the default Umm al-Qura calendar to F
|
|||
Full API reference, format token table, and examples are in the
|
||||
[project wiki](https://github.com/acamarata/moment-hijri-plus/wiki).
|
||||
|
||||
## Day boundaries and time zones
|
||||
|
||||
Conversions use the calendar date the moment instance displays, not the underlying UTC
|
||||
instant. A `moment("2025-03-01")` parsed in any local timezone returns the Hijri date
|
||||
for March 1st, 2025. A moment created with `.utc()` uses its UTC components.
|
||||
|
||||
Religious day-start at sunset is outside the scope of this package; it depends on
|
||||
location and madhab, and must be handled at the application layer.
|
||||
|
||||
## Note on Moment.js
|
||||
|
||||
Moment.js is in maintenance mode. For new projects,
|
||||
|
|
|
|||
19
src/index.ts
19
src/index.ts
|
|
@ -28,8 +28,13 @@ declare module "moment" {
|
|||
/**
|
||||
* Convert this moment to a Hijri date.
|
||||
*
|
||||
* Passes the underlying `Date` to hijri-core's `toHijri()`. The calendar engine
|
||||
* performs a table lookup (UAQ) or astronomical calculation (FCNA).
|
||||
* Converts the calendar date this moment instance displays (year/month/day) to a
|
||||
* Hijri date via hijri-core's `toHijri()`. The conversion is independent of the
|
||||
* host machine's timezone: a moment in UTC mode uses its UTC components, and a
|
||||
* moment in local mode uses its local components. The raw instant (milliseconds
|
||||
* since epoch) is never passed directly to the calendar engine.
|
||||
*
|
||||
* The calendar engine performs a table lookup (UAQ) or astronomical calculation (FCNA).
|
||||
*
|
||||
* @param options - Calendar selection. Default: `{ calendar: 'uaq' }`.
|
||||
* @returns A `HijriDate` object `{ hy, hm, hd }`, or `null` if the date is outside
|
||||
|
|
@ -149,7 +154,11 @@ function escapeLiteral(value: string): string {
|
|||
*/
|
||||
function install(momentInstance: typeof moment): void {
|
||||
momentInstance.fn.toHijri = function (opts?: ConversionOptions): HijriDate | null {
|
||||
return toHijri(this.toDate(), opts);
|
||||
// Use the calendar date this instance displays rather than the raw instant.
|
||||
// this.year()/.month()/.date() respect utc mode automatically, so a moment
|
||||
// created with .utc() uses UTC components and a local moment uses local components.
|
||||
// moment.month() is 0-based, matching Date.UTC's month parameter exactly.
|
||||
return toHijri(new Date(Date.UTC(this.year(), this.month(), this.date())), opts);
|
||||
};
|
||||
|
||||
momentInstance.fn.hijriYear = function (opts?: ConversionOptions): number | null {
|
||||
|
|
@ -236,8 +245,8 @@ function install(momentInstance: typeof moment): void {
|
|||
if (!greg) {
|
||||
throw new Error(`Invalid or out-of-range Hijri date: ${hy}/${hm}/${hd}`);
|
||||
}
|
||||
// Construct from explicit year/month/day to avoid UTC-to-local timezone
|
||||
// shift when the Date object represents midnight UTC.
|
||||
// Construct from explicit year/month/day components so the moment displays
|
||||
// the correct calendar date regardless of the host timezone offset.
|
||||
return momentInstance([greg.getUTCFullYear(), greg.getUTCMonth(), greg.getUTCDate()]);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
19
test-cjs.cjs
19
test-cjs.cjs
|
|
@ -61,3 +61,22 @@ describe('CJS: isValidHijri', () => {
|
|||
assert.equal(moment(new Date(2023, 2, 23, 12)).isValidHijri(), true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CJS: UTC-day boundary (regression)', () => {
|
||||
it('fromHijri → toHijri round-trip: 1446/9/1', () => {
|
||||
const m = moment.fromHijri(1446, 9, 1);
|
||||
const h = m.toHijri();
|
||||
assert.notEqual(h, null);
|
||||
assert.equal(h.hy, 1446);
|
||||
assert.equal(h.hm, 9);
|
||||
assert.equal(h.hd, 1);
|
||||
});
|
||||
|
||||
it('moment("2025-03-01") toHijri => 1446/9/1 (timezone-invariant)', () => {
|
||||
const h = moment('2025-03-01').toHijri();
|
||||
assert.notEqual(h, null);
|
||||
assert.equal(h.hy, 1446);
|
||||
assert.equal(h.hm, 9);
|
||||
assert.equal(h.hd, 1);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
23
test.mjs
23
test.mjs
|
|
@ -110,3 +110,26 @@ describe('FCNA calendar', () => {
|
|||
assert.equal(typeof h.hd, 'number');
|
||||
});
|
||||
});
|
||||
|
||||
describe('UTC-day boundary (regression)', () => {
|
||||
it('fromHijri → toHijri round-trip: 1446/9/1', () => {
|
||||
// Construct from Hijri, convert back — must be exact regardless of host TZ.
|
||||
const m = moment.fromHijri(1446, 9, 1);
|
||||
const h = m.toHijri();
|
||||
assert.notEqual(h, null);
|
||||
assert.equal(h.hy, 1446);
|
||||
assert.equal(h.hm, 9);
|
||||
assert.equal(h.hd, 1);
|
||||
});
|
||||
|
||||
it('moment("2025-03-01") toHijri => 1446/9/1 (timezone-invariant)', () => {
|
||||
// moment parses date-only ISO strings as LOCAL midnight.
|
||||
// toHijri must convert the displayed calendar date (2025-03-01), not the raw
|
||||
// instant, so the result is the same regardless of the host timezone offset.
|
||||
const h = moment('2025-03-01').toHijri();
|
||||
assert.notEqual(h, null);
|
||||
assert.equal(h.hy, 1446);
|
||||
assert.equal(h.hm, 9);
|
||||
assert.equal(h.hd, 1);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue