From c59197d7e6f48e34885deb1c63c1fca05c60647b Mon Sep 17 00:00:00 2001 From: Aric Camarata Date: Wed, 25 Feb 2026 15:13:11 -0500 Subject: [PATCH] chore: CR/QA polish for v1.0.0 release Fix documentation style (no em dashes). Update hijri-core devDep from file: path to ^1.0.0. Correct UAQ range to 1318-1500 AH / 1900-2076 CE throughout. --- .wiki/Architecture.md | 12 ++++++------ .wiki/Home.md | 4 ++-- README.md | 20 ++++++++++---------- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- src/index.ts | 26 +++++++++++++++----------- src/types.ts | 2 +- test-cjs.cjs | 5 ++--- test.mjs | 11 ++++------- 9 files changed, 46 insertions(+), 46 deletions(-) diff --git a/.wiki/Architecture.md b/.wiki/Architecture.md index 9ab505e..29a1e16 100644 --- a/.wiki/Architecture.md +++ b/.wiki/Architecture.md @@ -10,7 +10,7 @@ This separation is deliberate. Calendar algorithms are complex, have known edge ``` src/ - index.ts Plugin entry — registers methods on dayjsClass and dayjsFactory + index.ts Plugin entry: registers methods on dayjsClass and dayjsFactory types.ts Type definitions and module augmentation for dayjs ``` @@ -20,8 +20,8 @@ The plugin follows the standard Day.js `PluginFunc` signature: const plugin: PluginFunc = (_option, dayjsClass, dayjsFactory) => { ... }; ``` -- `dayjsClass.prototype.*` — instance methods (`.toHijri`, `.formatHijri`, etc.) -- `(dayjsFactory as any).fromHijri` — static method added to the factory function +- `dayjsClass.prototype.*`: instance methods (`.toHijri`, `.formatHijri`, etc.) +- `(dayjsFactory as any).fromHijri`: static method added to the factory function ## Peer Dependencies @@ -29,7 +29,7 @@ Both `dayjs` and `hijri-core` are peer dependencies. This means: 1. The host application controls which version of `dayjs` is used. No version conflict possible. 2. The host application controls which version of `hijri-core` is used. If hijri-core ships updated tables covering new years, the plugin benefits automatically. -3. The plugin itself has zero runtime dependencies in `node_modules` — only peer resolutions. +3. The plugin itself has zero runtime dependencies in `node_modules`, only peer resolutions. ## Format Token Resolution @@ -49,7 +49,7 @@ This means Hijri tokens and Gregorian tokens can coexist in the same format stri ## Weekday Alignment -Day.js `.day()` returns `0` for Sunday through `6` for Saturday — the same convention as `Date.prototype.getDay()`. +Day.js `.day()` returns `0` for Sunday through `6` for Saturday, the same convention as `Date.prototype.getDay()`. The weekday arrays exported by hijri-core (`hwLong`, `hwShort`, `hwNumeric`) use the same index layout: index `0` = Sunday, index `6` = Saturday. So `hwLong[this.day()]` always yields the correct weekday name with no offset arithmetic. @@ -83,7 +83,7 @@ interface CalendarEngine { ## Build -The package ships a dual CJS/ESM build via tsup. Both `dayjs` and `hijri-core` are marked as `external`, so they are never bundled — consumers provide them via peer dependency resolution. +The package ships a dual CJS/ESM build via tsup. Both `dayjs` and `hijri-core` are marked as `external`, so they are never bundled. Consumers provide them via peer dependency resolution. Output: diff --git a/.wiki/Home.md b/.wiki/Home.md index 4872b83..2c93a7a 100644 --- a/.wiki/Home.md +++ b/.wiki/Home.md @@ -28,8 +28,8 @@ dayjs.fromHijri(1444, 10, 1).format('YYYY-MM-DD'); ## Contents -- [API Reference](API-Reference) — all methods, parameters, return types -- [Architecture](Architecture) — design decisions, delegation model, format token resolution +- [API Reference](API-Reference): all methods, parameters, return types +- [Architecture](Architecture): design decisions, delegation model, format token resolution --- diff --git a/README.md b/README.md index 71837a2..e8d767d 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # dayjs-hijri-plus -[![npm version](https://img.shields.io/npm/v/dayjs-hijri-plus)](https://www.npmjs.com/package/dayjs-hijri-plus) +[![npm version](https://img.shields.io/npm/v/dayjs-hijri-plus.svg)](https://www.npmjs.com/package/dayjs-hijri-plus) [![CI](https://github.com/acamarata/dayjs-hijri-plus/actions/workflows/ci.yml/badge.svg)](https://github.com/acamarata/dayjs-hijri-plus/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) -A Day.js plugin that adds Hijri calendar support. Converts Gregorian dates to and from Hijri, provides Hijri-aware formatting, and delegates all calendar logic to [hijri-core](https://github.com/acamarata/hijri-core) — keeping this package thin and testable. +A Day.js plugin that adds Hijri calendar support. Converts Gregorian dates to and from Hijri, provides Hijri-aware formatting, and delegates all calendar logic to [hijri-core](https://github.com/acamarata/hijri-core). Keeps this package thin and testable. Supports Umm al-Qura (UAQ) and FCNA/ISNA calendars out of the box. Custom calendar engines can be registered at runtime. @@ -63,7 +63,7 @@ Convert the Day.js date to a Hijri date object. | --- | --- | --- | | `opts` | `ConversionOptions` | Optional. `{ calendar: 'uaq' \| 'fcna' \| string }` | -Returns `HijriDate | null`. Returns `null` if the date is outside the supported range (approximately 1900-2077 CE for UAQ). +Returns `HijriDate | null`. Returns `null` if the date is outside the supported range (UAQ: AH 1318-1500 / 1900-2076 CE). ```ts dayjs('2023-03-23').toHijri(); @@ -94,7 +94,7 @@ Format the date using a mix of Hijri tokens and standard Day.js tokens. | Parameter | Type | Description | | --- | --- | --- | -| `formatStr` | `string` | Format string — see token table below | +| `formatStr` | `string` | Format string. See token table below. | | `opts` | `ConversionOptions` | Optional calendar selection | Returns `string`. Returns an empty string if the date is out of range. @@ -142,8 +142,8 @@ Standard Day.js tokens pass through untouched. Square-bracket escaping (`[litera Two calendars ship with hijri-core: -- **`uaq`** (default) — Umm al-Qura, the official calendar of Saudi Arabia. Table-based, covers approximately 1318-1500 AH (1900-2077 CE). -- **`fcna`** — Fiqh Council of North America calendar. Uses an astronomical calculation with fixed criteria, independent of moon sighting. +- **`uaq`** (default): Umm al-Qura, the official calendar of Saudi Arabia. Table-based, covers 1318-1500 AH (1900-2076 CE). +- **`fcna`**: Fiqh Council of North America calendar. Uses an astronomical calculation with fixed criteria, independent of moon sighting. Select a calendar by passing `{ calendar: 'fcna' }` to any method. The default is `'uaq'` when no option is provided. @@ -180,10 +180,10 @@ Full API reference, architecture notes, and calendar system comparisons are on t ## Related -- [hijri-core](https://github.com/acamarata/hijri-core) — the zero-dependency Hijri calendar engine this plugin wraps -- [luxon-hijri](https://github.com/acamarata/luxon-hijri) — the same Hijri conversion for Luxon users -- [pray-calc](https://github.com/acamarata/pray-calc) — Islamic prayer time calculation -- [nrel-spa](https://github.com/acamarata/nrel-spa) — NREL Solar Position Algorithm in pure JavaScript +- [hijri-core](https://github.com/acamarata/hijri-core): zero-dependency Hijri calendar engine this plugin wraps +- [luxon-hijri](https://github.com/acamarata/luxon-hijri): the same Hijri conversion for Luxon users +- [pray-calc](https://github.com/acamarata/pray-calc): Islamic prayer time calculation +- [nrel-spa](https://github.com/acamarata/nrel-spa): NREL Solar Position Algorithm in pure JavaScript ## License diff --git a/package.json b/package.json index 214a855..093696a 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "devDependencies": { "@types/node": "^22.0.0", "dayjs": "^1.11.0", - "hijri-core": "file:../hijri-core", + "hijri-core": "^1.0.0", "tsup": "^8.0.0", "typescript": "^5.5.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ec56175..e3f33fb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: ^1.11.0 version: 1.11.19 hijri-core: - specifier: file:../hijri-core - version: file:../hijri-core + specifier: ^1.0.0 + version: 1.0.0 tsup: specifier: ^8.0.0 version: 8.5.1(typescript@5.9.3) @@ -406,8 +406,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: @@ -793,7 +793,7 @@ snapshots: fsevents@2.3.3: optional: true - hijri-core@file:../hijri-core: {} + hijri-core@1.0.0: {} joycon@3.1.1: {} diff --git a/src/index.ts b/src/index.ts index 2a707f6..59cd631 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,14 +37,11 @@ declare module 'dayjs' { } // Augment the dayjs factory to expose the fromHijri static method. +// Using the function declaration form (same pattern as dayjs timezone plugin) +// because dayjs does not export an IStatic interface for module augmentation. +// import('dayjs').Dayjs is used explicitly to satisfy the tsup DTS emitter. declare module 'dayjs' { - interface IStatic { - /** - * Construct a Day.js instance from a Hijri date. - * Throws if the Hijri date is invalid or outside the supported range. - */ - fromHijri(hy: number, hm: number, hd: number, opts?: ConversionOptions): import('dayjs').Dayjs; - } + function fromHijri(hy: number, hm: number, hd: number, opts?: ConversionOptions): import('dayjs').Dayjs; } // Hijri-specific format tokens, ordered longest-first to prevent partial matches. @@ -58,11 +55,11 @@ const HIJRI_TOKEN_RE = /iYYYY|iYY|iMMMM|iMMM|iMM|iM|iDD|iD|iEEEE|iEEE|iE|ioooo|i * * Day.js uses `[...]` for literal text. A `]` inside such a section would * close it prematurely, so we split on `]` and re-join with `][` (which - * closes the current literal section, outputs a raw `]` — Day.js passes - * unrecognised characters through untouched — then opens a new one). + * closes the current literal section, outputs a raw `]` (Day.js passes + * unrecognised characters through untouched), then opens a new one. */ function lit(value: string): string { - return '[' + value.split(']').join('][') + ']'; + return '[' + value.split(']').join(']][') + ']'; } const plugin: PluginFunc = (_option, dayjsClass, dayjsFactory) => { @@ -146,7 +143,13 @@ const plugin: PluginFunc = (_option, dayjsClass, dayjsFactory) => { if (!greg) { throw new Error(`Invalid or out-of-range Hijri date: ${hy}/${hm}/${hd}`); } - return dayjsFactory(greg); + // Construct from ISO date string to avoid timezone offset issues. + // dayjsFactory(Date) interprets the Date in local time; a UTC-midnight Date + // in western timezones would resolve to the previous local day. + const y = greg.getUTCFullYear(); + const mo = String(greg.getUTCMonth() + 1).padStart(2, '0'); + const dy = String(greg.getUTCDate()).padStart(2, '0'); + return dayjsFactory(`${y}-${mo}-${dy}`); }; }; @@ -154,6 +157,7 @@ export default plugin; // Re-export hijri-core types for consumers who import from dayjs-hijri-plus. export type { HijriDate, ConversionOptions, CalendarSystem } from './types'; +export type { CalendarEngine } from 'hijri-core'; // Re-export the registry API so callers can register custom calendar engines // without adding hijri-core as a direct dependency. diff --git a/src/types.ts b/src/types.ts index 289b2a1..7a75bda 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,5 +9,5 @@ export type CalendarSystem = string; * so callers can switch between 'uaq' (default) and 'fcna'. */ export interface HijriPluginOptions extends ConversionOptions { - // calendar?: string (inherited — 'uaq' | 'fcna' | any registered calendar id) + // calendar?: string (inherited: 'uaq' | 'fcna' | any registered calendar id) } diff --git a/test-cjs.cjs b/test-cjs.cjs index d3bbbe5..fe1d341 100644 --- a/test-cjs.cjs +++ b/test-cjs.cjs @@ -41,10 +41,9 @@ test('toHijri (CJS): 2024-07-07 -> 1 Muharram 1446', () => { assert.deepEqual(h, { hy: 1446, hm: 1, hd: 1 }); }); -test('fromHijri (CJS): 1444/9/1 -> 2023-03-23 (UTC)', () => { +test('fromHijri (CJS): 1444/9/1 -> 2023-03-23', () => { const d = dayjs.fromHijri(1444, 9, 1); - const iso = d.toDate().toISOString(); - assert.ok(iso.startsWith('2023-03-23'), `Expected 2023-03-23, got ${iso}`); + assert.equal(d.format('YYYY-MM-DD'), '2023-03-23'); }); test('formatHijri (CJS): iYYYY-iMM-iDD', () => { diff --git a/test.mjs b/test.mjs index cbb8edb..01c8b67 100644 --- a/test.mjs +++ b/test.mjs @@ -44,17 +44,14 @@ test('toHijri: 2024-07-07 -> 1 Muharram 1446', () => { assert.deepEqual(h, { hy: 1446, hm: 1, hd: 1 }); }); -test('fromHijri: 1444/9/1 -> 2023-03-23 (UTC)', () => { +test('fromHijri: 1444/9/1 -> 2023-03-23', () => { const d = dayjs.fromHijri(1444, 9, 1); - // toGregorian returns midnight UTC; compare using UTC accessors to be timezone-safe. - const iso = d.toDate().toISOString(); - assert.ok(iso.startsWith('2023-03-23'), `Expected 2023-03-23, got ${iso}`); + assert.equal(d.format('YYYY-MM-DD'), '2023-03-23'); }); -test('fromHijri: 1446/1/1 -> 2024-07-07 (UTC)', () => { +test('fromHijri: 1446/1/1 -> 2024-07-07', () => { const d = dayjs.fromHijri(1446, 1, 1); - const iso = d.toDate().toISOString(); - assert.ok(iso.startsWith('2024-07-07'), `Expected 2024-07-07, got ${iso}`); + assert.equal(d.format('YYYY-MM-DD'), '2024-07-07'); }); test('hijriYear/hijriMonth/hijriDay accessors on 1 Ramadan 1444', () => {