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.
This commit is contained in:
Aric Camarata 2026-02-25 15:13:11 -05:00
parent 0943b99495
commit c59197d7e6
9 changed files with 46 additions and 46 deletions

View file

@ -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:

View file

@ -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
---

View file

@ -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

View file

@ -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"
},

View file

@ -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: {}

View file

@ -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.

View file

@ -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)
}

View file

@ -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', () => {

View file

@ -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', () => {