mirror of
https://github.com/acamarata/dayjs-hijri-plus.git
synced 2026-06-30 18:54:26 +00:00
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:
parent
0943b99495
commit
c59197d7e6
9 changed files with 46 additions and 46 deletions
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
20
README.md
20
README.md
|
|
@ -1,10 +1,10 @@
|
|||
# dayjs-hijri-plus
|
||||
|
||||
[](https://www.npmjs.com/package/dayjs-hijri-plus)
|
||||
[](https://www.npmjs.com/package/dayjs-hijri-plus)
|
||||
[](https://github.com/acamarata/dayjs-hijri-plus/actions/workflows/ci.yml)
|
||||
[](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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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: {}
|
||||
|
||||
|
|
|
|||
26
src/index.ts
26
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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
11
test.mjs
11
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', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue