mirror of
https://github.com/acamarata/dayjs-hijri-plus.git
synced 2026-06-30 18:54:26 +00:00
chore: E6 polish wiki + CI + TypeDoc integration (P1)
This commit is contained in:
parent
c89dbaf5c7
commit
546ec2d302
9 changed files with 266 additions and 271 deletions
11
.github/wiki/_Sidebar.md
vendored
11
.github/wiki/_Sidebar.md
vendored
|
|
@ -13,6 +13,17 @@
|
|||
- [Architecture](Architecture)
|
||||
- [Benchmarks](benchmarks/index)
|
||||
|
||||
**API Pages**
|
||||
- [plugin (default)](api/plugin)
|
||||
- [toHijri](api/toHijri)
|
||||
- [isValidHijri](api/isValidHijri)
|
||||
- [hijriYear](api/hijriYear)
|
||||
- [hijriMonth](api/hijriMonth)
|
||||
- [hijriDay](api/hijriDay)
|
||||
- [formatHijri](api/formatHijri)
|
||||
- [fromHijri](api/fromHijri)
|
||||
- [registerCalendar](api/registerCalendar)
|
||||
|
||||
**Community**
|
||||
- [Contributing](CONTRIBUTING)
|
||||
- [Code of Conduct](CODE_OF_CONDUCT)
|
||||
|
|
|
|||
2
.github/wiki/api/variables/default.md
vendored
2
.github/wiki/api/variables/default.md
vendored
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
> `const` **default**: `PluginFunc`
|
||||
|
||||
Defined in: [src/index.ts:164](https://github.com/acamarata/dayjs-hijri-plus/blob/599c7481510f5ea625b79c98a7030b9c8d90e4f7/src/index.ts#L164)
|
||||
Defined in: [src/index.ts:164](https://github.com/acamarata/dayjs-hijri-plus/blob/033ca475760db2b9e4c6edd69cc9dc1f013b8d8f/src/index.ts#L164)
|
||||
|
||||
Day.js plugin that adds Hijri calendar support.
|
||||
|
||||
|
|
|
|||
44
.github/wiki/examples/basic-usage.md
vendored
44
.github/wiki/examples/basic-usage.md
vendored
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
```typescript
|
||||
import dayjs from 'dayjs';
|
||||
import hijri from 'dayjs-hijri-plus';
|
||||
import hijriPlugin from 'dayjs-hijri-plus';
|
||||
|
||||
dayjs.extend(hijri);
|
||||
dayjs.extend(hijriPlugin);
|
||||
```
|
||||
|
||||
## Convert today's date to Hijri
|
||||
|
|
@ -14,9 +14,11 @@ dayjs.extend(hijri);
|
|||
```typescript
|
||||
const today = dayjs();
|
||||
const h = today.toHijri();
|
||||
// { hy: 1444, hm: 9, hd: 1 } (example — actual output depends on the current date)
|
||||
// { hy: 1446, hm: 11, hd: 3 } (example — actual output depends on today's date)
|
||||
|
||||
console.log(`${h.hd} / ${h.hm} / ${h.hy}`);
|
||||
if (h !== null) {
|
||||
console.log(`${h.hd} / ${h.hm} / ${h.hy}`);
|
||||
}
|
||||
```
|
||||
|
||||
## Convert a known Gregorian date
|
||||
|
|
@ -26,16 +28,16 @@ console.log(`${h.hd} / ${h.hm} / ${h.hy}`);
|
|||
const d = dayjs('2023-03-23');
|
||||
const h = d.toHijri();
|
||||
|
||||
console.log(h.hy); // 1444
|
||||
console.log(h.hm); // 9 (Ramadan is the 9th month)
|
||||
console.log(h.hd); // 1
|
||||
console.log(h?.hy); // 1444
|
||||
console.log(h?.hm); // 9 (Ramadan is the 9th month)
|
||||
console.log(h?.hd); // 1
|
||||
```
|
||||
|
||||
## Convert from Hijri to Gregorian
|
||||
|
||||
```typescript
|
||||
const gregorian = dayjs.fromHijri(1444, 9, 1);
|
||||
console.log(gregorian.format('YYYY-MM-DD')); // '2023-03-23'
|
||||
console.log(gregorian.format('YYYY-MM-DD')); // '2023-03-23'
|
||||
```
|
||||
|
||||
## Hijri accessor methods
|
||||
|
|
@ -43,9 +45,9 @@ console.log(gregorian.format('YYYY-MM-DD')); // '2023-03-23'
|
|||
```typescript
|
||||
const d = dayjs('2023-03-23');
|
||||
|
||||
console.log(d.iYear()); // 1444
|
||||
console.log(d.iMonth()); // 9
|
||||
console.log(d.iDate()); // 1
|
||||
console.log(d.hijriYear()); // 1444
|
||||
console.log(d.hijriMonth()); // 9
|
||||
console.log(d.hijriDay()); // 1
|
||||
```
|
||||
|
||||
## Format with Hijri tokens
|
||||
|
|
@ -53,10 +55,10 @@ console.log(d.iDate()); // 1
|
|||
```typescript
|
||||
const d = dayjs('2023-03-23');
|
||||
|
||||
d.format('iD iMMMM iYYYY'); // '1 Ramadan 1444'
|
||||
d.format('iDD/iMM/iYYYY'); // '01/09/1444'
|
||||
d.format('YYYY-MM-DD'); // '2023-03-23' (Gregorian tokens still work)
|
||||
d.format('YYYY (iYYYY/iM/iD)'); // '2023 (1444/9/1)'
|
||||
d.formatHijri('iD iMMMM iYYYY'); // '1 Ramadan 1444'
|
||||
d.formatHijri('iDD/iMM/iYYYY'); // '01/09/1444'
|
||||
d.formatHijri('YYYY-MM-DD'); // 'YYYY-MM-DD' — use .format() for Gregorian-only
|
||||
d.formatHijri('YYYY (iYYYY/iM/iD)'); // '2023 (1444/9/1)'
|
||||
```
|
||||
|
||||
## Use FCNA calendar
|
||||
|
|
@ -65,20 +67,20 @@ d.format('YYYY (iYYYY/iM/iD)'); // '2023 (1444/9/1)'
|
|||
const d = dayjs('2023-03-23');
|
||||
const fcna = d.toHijri({ calendar: 'fcna' });
|
||||
|
||||
console.log(fcna.hy); // 1444
|
||||
console.log(fcna.hm); // 9
|
||||
console.log(fcna.hd); // 1
|
||||
// Near month boundaries, UAQ and FCNA may differ by one day
|
||||
console.log(fcna?.hy); // 1444
|
||||
console.log(fcna?.hm); // 9
|
||||
console.log(fcna?.hd); // 1 or 2 depending on the month boundary
|
||||
```
|
||||
|
||||
## CJS usage
|
||||
|
||||
```javascript
|
||||
const dayjs = require('dayjs');
|
||||
const hijri = require('dayjs-hijri-plus');
|
||||
const hijriPlugin = require('dayjs-hijri-plus');
|
||||
|
||||
dayjs.extend(hijri);
|
||||
dayjs.extend(hijriPlugin);
|
||||
|
||||
const d = dayjs('2023-03-23');
|
||||
console.log(d.iYear()); // 1444
|
||||
console.log(d.hijriYear()); // 1444
|
||||
```
|
||||
|
|
|
|||
51
.github/wiki/examples/formatting.md
vendored
51
.github/wiki/examples/formatting.md
vendored
|
|
@ -7,61 +7,60 @@
|
|||
| `iYYYY` | Full Hijri year | `1444` |
|
||||
| `iYY` | 2-digit Hijri year | `44` |
|
||||
| `iMMMM` | Full month name | `Ramadan` |
|
||||
| `iMMM` | Abbreviated month name | `Ram` |
|
||||
| `iMMM` | Abbreviated month name | `Ramadan` |
|
||||
| `iMM` | 2-digit month number | `09` |
|
||||
| `iM` | Month number | `9` |
|
||||
| `iDD` | 2-digit day | `01` |
|
||||
| `iD` | Day number | `1` |
|
||||
| `iEEEE` | Full weekday name | `Yawm al-Khamis` |
|
||||
| `iEEE` | Short weekday name | `Kham` |
|
||||
| `iE` | Weekday number | `5` (1=Sunday) |
|
||||
| `ioooo` | Era | `AH` |
|
||||
|
||||
Tokens not prefixed with `i` are passed through to Day.js as Gregorian tokens.
|
||||
Tokens not prefixed with `i` pass through to Day.js `.format()` as Gregorian tokens.
|
||||
|
||||
## Common format patterns
|
||||
|
||||
```typescript
|
||||
import dayjs from 'dayjs';
|
||||
import hijri from 'dayjs-hijri-plus';
|
||||
import hijriPlugin from 'dayjs-hijri-plus';
|
||||
|
||||
dayjs.extend(hijri);
|
||||
dayjs.extend(hijriPlugin);
|
||||
|
||||
const d = dayjs('2023-03-23');
|
||||
|
||||
// Day Month Year (long)
|
||||
d.format('iD iMMMM iYYYY');
|
||||
d.formatHijri('iD iMMMM iYYYY');
|
||||
// '1 Ramadan 1444'
|
||||
|
||||
// Numeric date
|
||||
d.format('iDD/iMM/iYYYY');
|
||||
d.formatHijri('iDD/iMM/iYYYY');
|
||||
// '01/09/1444'
|
||||
|
||||
// Short month name
|
||||
d.format('iD iMMM iYYYY');
|
||||
// '1 Ram 1444'
|
||||
d.formatHijri('iD iMMM iYYYY');
|
||||
// '1 Ramadan 1444'
|
||||
|
||||
// Combined Gregorian and Hijri
|
||||
d.format('YYYY-MM-DD (iD iMMMM iYYYY)');
|
||||
d.formatHijri('YYYY-MM-DD (iD iMMMM iYYYY)');
|
||||
// '2023-03-23 (1 Ramadan 1444)'
|
||||
|
||||
// ISO-style Hijri
|
||||
d.format('iYYYY-iMM-iDD');
|
||||
d.formatHijri('iYYYY-iMM-iDD');
|
||||
// '1444-09-01'
|
||||
```
|
||||
|
||||
## Hijri month names
|
||||
|
||||
```typescript
|
||||
const months = [
|
||||
'Muharram', 'Safar', "Rabi' al-Awwal", "Rabi' al-Thani",
|
||||
"Jumada al-Awwal", "Jumada al-Thani", 'Rajab', "Sha'ban",
|
||||
'Ramadan', 'Shawwal', "Dhu al-Qa'dah", "Dhu al-Hijjah"
|
||||
];
|
||||
|
||||
// iMMMM returns the standard transliteration for each month
|
||||
for (let m = 1; m <= 12; m++) {
|
||||
const d = dayjs.fromHijri(1444, m, 1);
|
||||
console.log(d.format('iM iMMMM'));
|
||||
console.log(d.formatHijri('iM iMMMM'));
|
||||
}
|
||||
// 1 Muharram
|
||||
// 2 Safar
|
||||
// 3 Rabi' al-Awwal
|
||||
// ...
|
||||
// 9 Ramadan
|
||||
// ...
|
||||
|
|
@ -72,26 +71,26 @@ for (let m = 1; m <= 12; m++) {
|
|||
|
||||
```tsx
|
||||
import dayjs from 'dayjs';
|
||||
import hijri from 'dayjs-hijri-plus';
|
||||
import hijriPlugin from 'dayjs-hijri-plus';
|
||||
|
||||
dayjs.extend(hijri);
|
||||
dayjs.extend(hijriPlugin);
|
||||
|
||||
interface HijriDateProps {
|
||||
interface HijriDateDisplayProps {
|
||||
date: Date;
|
||||
}
|
||||
|
||||
function HijriDate({ date }: HijriDateProps) {
|
||||
function HijriDateDisplay({ date }: HijriDateDisplayProps) {
|
||||
const d = dayjs(date);
|
||||
const gregorian = d.format('YYYY-MM-DD');
|
||||
const hijriFormatted = d.format('iD iMMMM iYYYY');
|
||||
const gregorianIso = d.format('YYYY-MM-DD');
|
||||
const hijriFormatted = d.formatHijri('iD iMMMM iYYYY');
|
||||
|
||||
return (
|
||||
<time dateTime={gregorian}>
|
||||
{hijriFormatted}
|
||||
<time dateTime={gregorianIso}>
|
||||
{hijriFormatted || gregorianIso}
|
||||
</time>
|
||||
);
|
||||
}
|
||||
|
||||
// Usage: <HijriDate date={new Date('2023-03-23')} />
|
||||
// Usage: <HijriDateDisplay date={new Date('2023-03-23')} />
|
||||
// Renders: <time datetime="2023-03-23">1 Ramadan 1444</time>
|
||||
```
|
||||
|
|
|
|||
45
.github/wiki/guides/advanced.md
vendored
45
.github/wiki/guides/advanced.md
vendored
|
|
@ -6,71 +6,74 @@ Each method accepts an optional options argument. You can mix UAQ and FCNA in th
|
|||
|
||||
```typescript
|
||||
import dayjs from 'dayjs';
|
||||
import hijri from 'dayjs-hijri-plus';
|
||||
import hijriPlugin from 'dayjs-hijri-plus';
|
||||
|
||||
dayjs.extend(hijri);
|
||||
dayjs.extend(hijriPlugin);
|
||||
|
||||
const d = dayjs('2023-03-23');
|
||||
|
||||
const uaqYear = d.iYear(); // UAQ (default)
|
||||
const fcnaYear = d.iYear({ calendar: 'fcna' }); // FCNA
|
||||
const uaqYear = d.hijriYear(); // UAQ (default)
|
||||
const fcnaYear = d.hijriYear({ calendar: 'fcna' }); // FCNA
|
||||
```
|
||||
|
||||
Near month boundaries, UAQ and FCNA may differ by one day. The calendar argument is per-call, not session-wide.
|
||||
|
||||
## Null safety
|
||||
|
||||
`d.toHijri()` (if the package exposes it) returns `null` for dates outside UAQ range (approximately 1900-2076 CE). Guard before using:
|
||||
`toHijri()` returns `null` for dates outside the UAQ range (approximately 1900-2076 CE). Guard before using the result:
|
||||
|
||||
```typescript
|
||||
const hijri = d.toHijri();
|
||||
if (hijri !== null) {
|
||||
console.log(hijri.hy, hijri.hm, hijri.hd);
|
||||
const h = d.toHijri();
|
||||
if (h !== null) {
|
||||
console.log(h.hy, h.hm, h.hd);
|
||||
}
|
||||
```
|
||||
|
||||
Use `isValidHijri()` when you only need a boolean check.
|
||||
|
||||
## Combining with Day.js plugins
|
||||
|
||||
dayjs-hijri-plus works alongside other Day.js plugins. The order of `extend()` calls matters when two plugins patch the same method:
|
||||
dayjs-hijri-plus works alongside other Day.js plugins. The order of `extend()` calls matters when two plugins patch the same method. Register `dayjs-hijri-plus` after plugins that also modify `.format()`:
|
||||
|
||||
```typescript
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
import hijri from 'dayjs-hijri-plus';
|
||||
import hijriPlugin from 'dayjs-hijri-plus';
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(hijri);
|
||||
dayjs.extend(hijriPlugin);
|
||||
|
||||
// UTC-aware conversion
|
||||
const d = dayjs.utc('2023-03-23');
|
||||
console.log(d.iYear()); // 1444
|
||||
console.log(d.hijriYear()); // 1444
|
||||
```
|
||||
|
||||
## Formatting alongside Gregorian tokens
|
||||
## Mixing Gregorian and Hijri tokens
|
||||
|
||||
Hijri tokens (`iYYYY`, `iMM`, `iDD`, `iMMMM`, etc.) coexist with Day.js Gregorian tokens. Use them in the same format string:
|
||||
Hijri tokens (`iYYYY`, `iMM`, `iDD`, `iMMMM`, etc.) coexist with Day.js Gregorian tokens in `formatHijri`:
|
||||
|
||||
```typescript
|
||||
d.format('YYYY-MM-DD (iD iMMMM iYYYY)');
|
||||
d.formatHijri('YYYY-MM-DD (iD iMMMM iYYYY)');
|
||||
// '2023-03-23 (1 Ramadan 1444)'
|
||||
```
|
||||
|
||||
Standard Day.js `.format()` still handles Gregorian-only strings normally. Use `formatHijri` only when you need Hijri tokens.
|
||||
|
||||
## Tree-shaking
|
||||
|
||||
The package ships both ESM and CJS builds. In ESM bundlers (Vite, esbuild, Rollup), unused code is eliminated. The plugin itself is ~2 KB min+gz on top of Day.js.
|
||||
The package ships both ESM and CJS builds. ESM bundlers (Vite, esbuild, Rollup) eliminate unused code. The plugin is approximately 2 KB min+gz on top of Day.js.
|
||||
|
||||
## TypeScript augmentation
|
||||
|
||||
The plugin augments the Day.js type definitions automatically. You do not need to import any separate types file:
|
||||
The plugin augments the Day.js type definitions automatically. No separate type import is needed:
|
||||
|
||||
```typescript
|
||||
import dayjs from 'dayjs';
|
||||
import hijri from 'dayjs-hijri-plus';
|
||||
import hijriPlugin from 'dayjs-hijri-plus';
|
||||
|
||||
dayjs.extend(hijri);
|
||||
dayjs.extend(hijriPlugin);
|
||||
|
||||
const d = dayjs('2023-03-23');
|
||||
const year: number = d.iYear(); // fully typed
|
||||
const year: number = d.hijriYear()!; // fully typed; assert non-null if date is in range
|
||||
```
|
||||
|
|
|
|||
41
.github/wiki/guides/quickstart.md
vendored
41
.github/wiki/guides/quickstart.md
vendored
|
|
@ -1,6 +1,6 @@
|
|||
# Quick Start
|
||||
|
||||
This guide covers the most common use cases in dayjs-hijri-plus. All examples use the default Umm al-Qura (UAQ) calendar.
|
||||
This guide covers the most common use cases. All examples use the default Umm al-Qura (UAQ) calendar.
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -14,9 +14,9 @@ pnpm add dayjs dayjs-hijri-plus hijri-core
|
|||
|
||||
```typescript
|
||||
import dayjs from 'dayjs';
|
||||
import hijri from 'dayjs-hijri-plus';
|
||||
import hijriPlugin from 'dayjs-hijri-plus';
|
||||
|
||||
dayjs.extend(hijri);
|
||||
dayjs.extend(hijriPlugin);
|
||||
```
|
||||
|
||||
After extending, all `dayjs()` instances gain Hijri methods.
|
||||
|
|
@ -25,32 +25,32 @@ After extending, all `dayjs()` instances gain Hijri methods.
|
|||
|
||||
```typescript
|
||||
import dayjs from 'dayjs';
|
||||
import hijri from 'dayjs-hijri-plus';
|
||||
import hijriPlugin from 'dayjs-hijri-plus';
|
||||
|
||||
dayjs.extend(hijri);
|
||||
dayjs.extend(hijriPlugin);
|
||||
|
||||
const d = dayjs('2023-03-23'); // 1 Ramadan 1444
|
||||
console.log(d.iYear()); // 1444
|
||||
console.log(d.iMonth()); // 9
|
||||
console.log(d.iDate()); // 1
|
||||
console.log(d.hijriYear()); // 1444
|
||||
console.log(d.hijriMonth()); // 9
|
||||
console.log(d.hijriDay()); // 1
|
||||
```
|
||||
|
||||
## Format with Hijri tokens
|
||||
|
||||
```typescript
|
||||
d.format('iYYYY/iMM/iDD'); // '1444/09/01'
|
||||
d.format('iD iMMMM iYYYY'); // '1 Ramadan 1444'
|
||||
d.formatHijri('iYYYY/iMM/iDD'); // '1444/09/01'
|
||||
d.formatHijri('iD iMMMM iYYYY'); // '1 Ramadan 1444'
|
||||
```
|
||||
|
||||
Hijri format tokens are prefixed with `i` to avoid conflicts with Day.js Gregorian tokens.
|
||||
Hijri tokens are prefixed with `i` to avoid conflicts with Day.js Gregorian tokens.
|
||||
|
||||
## Convert a Hijri date to a Day.js object
|
||||
|
||||
```typescript
|
||||
import dayjs from 'dayjs';
|
||||
import hijri from 'dayjs-hijri-plus';
|
||||
import hijriPlugin from 'dayjs-hijri-plus';
|
||||
|
||||
dayjs.extend(hijri);
|
||||
dayjs.extend(hijriPlugin);
|
||||
|
||||
const d = dayjs.fromHijri(1444, 9, 1);
|
||||
console.log(d.format('YYYY-MM-DD')); // '2023-03-23'
|
||||
|
|
@ -60,23 +60,24 @@ console.log(d.format('YYYY-MM-DD')); // '2023-03-23'
|
|||
|
||||
```typescript
|
||||
const d = dayjs('2023-03-23');
|
||||
console.log(d.iYear({ calendar: 'fcna' })); // 1444
|
||||
console.log(d.iMonth({ calendar: 'fcna' })); // 9 or differs by a day near month start
|
||||
const h = d.toHijri({ calendar: 'fcna' });
|
||||
// Near month boundaries, UAQ and FCNA may differ by one day
|
||||
console.log(h?.hy, h?.hm, h?.hd);
|
||||
```
|
||||
|
||||
## CommonJS
|
||||
|
||||
```js
|
||||
const dayjs = require('dayjs');
|
||||
const hijri = require('dayjs-hijri-plus');
|
||||
const hijriPlugin = require('dayjs-hijri-plus');
|
||||
|
||||
dayjs.extend(hijri);
|
||||
dayjs.extend(hijriPlugin);
|
||||
|
||||
const d = dayjs('2023-03-23');
|
||||
console.log(d.iYear(), d.iMonth(), d.iDate()); // 1444 9 1
|
||||
console.log(d.hijriYear(), d.hijriMonth(), d.hijriDay()); // 1444 9 1
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
- [API Reference](API-Reference) for the full method list
|
||||
- [Architecture](Architecture) for how the plugin integrates with Day.js
|
||||
- [API Reference](../API-Reference) for the full method list
|
||||
- [Architecture](../Architecture) for how the plugin integrates with Day.js
|
||||
|
|
|
|||
21
.github/workflows/ci.yml
vendored
21
.github/workflows/ci.yml
vendored
|
|
@ -78,3 +78,24 @@ jobs:
|
|||
grep "README.md" pack-output.txt
|
||||
grep "CHANGELOG.md" pack-output.txt
|
||||
grep "LICENSE" pack-output.txt
|
||||
|
||||
coverage:
|
||||
name: Coverage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Enable corepack
|
||||
run: corepack enable
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm run build
|
||||
- run: pnpm run coverage
|
||||
- name: Upload to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./coverage/lcov.info
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: false
|
||||
|
|
|
|||
182
README.md
182
README.md
|
|
@ -4,9 +4,9 @@
|
|||
[](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). Keeps 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).
|
||||
|
||||
Supports Umm al-Qura (UAQ) and FCNA/ISNA calendars out of the box. Custom calendar engines can be registered at runtime.
|
||||
Supports Umm al-Qura (UAQ) and FCNA/ISNA calendars. Custom engines can be registered at runtime.
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ Supports Umm al-Qura (UAQ) and FCNA/ISNA calendars out of the box. Custom calend
|
|||
pnpm add dayjs dayjs-hijri-plus hijri-core
|
||||
```
|
||||
|
||||
Both `dayjs` and `hijri-core` are peer dependencies and must be installed alongside this plugin.
|
||||
Both `dayjs` and `hijri-core` are peer dependencies.
|
||||
|
||||
## Quick Start
|
||||
|
||||
|
|
@ -24,183 +24,23 @@ import hijriPlugin from 'dayjs-hijri-plus';
|
|||
|
||||
dayjs.extend(hijriPlugin);
|
||||
|
||||
// Convert a Gregorian date to Hijri
|
||||
const d = dayjs(new Date(2023, 2, 23));
|
||||
const hijri = d.toHijri();
|
||||
// => { hy: 1444, hm: 9, hd: 1 } (1 Ramadan 1444 AH)
|
||||
const d = dayjs('2023-03-23');
|
||||
d.toHijri(); // { hy: 1444, hm: 9, hd: 1 }
|
||||
d.formatHijri('iD iMMMM iYYYY'); // '1 Ramadan 1444'
|
||||
d.formatHijri('iYYYY-iMM-iDD'); // '1444-09-01'
|
||||
|
||||
// Format using Hijri tokens mixed with standard Day.js tokens
|
||||
d.formatHijri('iYYYY-iMM-iDD'); // => '1444-09-01'
|
||||
d.formatHijri('iD iMMMM iYYYY'); // => '1 Ramadan 1444'
|
||||
d.formatHijri('iD iMMMM iYYYY [at] HH:mm'); // => '1 Ramadan 1444 at 00:00'
|
||||
|
||||
// Individual Hijri components
|
||||
d.hijriYear(); // => 1444
|
||||
d.hijriMonth(); // => 9
|
||||
d.hijriDay(); // => 1
|
||||
|
||||
// Construct a Day.js instance from a Hijri date
|
||||
const eid = dayjs.fromHijri(1444, 10, 1);
|
||||
eid.format('YYYY-MM-DD'); // => '2023-04-21'
|
||||
|
||||
// FCNA/ISNA calendar variant
|
||||
d.toHijri({ calendar: 'fcna' }); // => { hy: 1444, hm: 9, hd: 2 } (varies by month)
|
||||
dayjs.fromHijri(1444, 10, 1).format('YYYY-MM-DD'); // '2023-04-21'
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### dayjs.extend(hijriPlugin)
|
||||
|
||||
Register the plugin with your Day.js instance. Call once before using any plugin methods.
|
||||
|
||||
### Instance Methods
|
||||
|
||||
#### `.toHijri(opts?)`
|
||||
|
||||
Convert the Day.js date to a Hijri date object.
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `opts` | `ConversionOptions` | Optional. `{ calendar: 'uaq' \| 'fcna' \| string }` |
|
||||
|
||||
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();
|
||||
// => { hy: 1444, hm: 9, hd: 1 }
|
||||
```
|
||||
|
||||
#### `.isValidHijri(opts?)`
|
||||
|
||||
Check whether the date maps to a valid Hijri date in the supported range.
|
||||
|
||||
Returns `boolean`.
|
||||
|
||||
#### `.hijriYear(opts?)`
|
||||
|
||||
Returns the Hijri year as a `number`, or `null` if out of range.
|
||||
|
||||
#### `.hijriMonth(opts?)`
|
||||
|
||||
Returns the Hijri month (1-12) as a `number`, or `null` if out of range.
|
||||
|
||||
#### `.hijriDay(opts?)`
|
||||
|
||||
Returns the Hijri day (1-30) as a `number`, or `null` if out of range.
|
||||
|
||||
#### `.formatHijri(formatStr, opts?)`
|
||||
|
||||
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. |
|
||||
| `opts` | `ConversionOptions` | Optional calendar selection |
|
||||
|
||||
Returns `string`. Returns an empty string if the date is out of range.
|
||||
|
||||
Hijri tokens are replaced first. The resulting string is then passed to Day.js `.format()`, so all standard tokens (YYYY, MM, DD, HH, mm, ss, etc.) resolve normally.
|
||||
|
||||
### Static Methods
|
||||
|
||||
#### `dayjs.fromHijri(hy, hm, hd, opts?)`
|
||||
|
||||
Construct a Day.js instance from a Hijri date.
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `hy` | `number` | Hijri year |
|
||||
| `hm` | `number` | Hijri month (1-12) |
|
||||
| `hd` | `number` | Hijri day (1-30) |
|
||||
| `opts` | `ConversionOptions` | Optional calendar selection |
|
||||
|
||||
Returns a `dayjs.Dayjs` instance. Throws `Error` if the Hijri date is invalid or outside the table range.
|
||||
|
||||
## Format Tokens
|
||||
|
||||
All Hijri-specific tokens use the `i` prefix.
|
||||
|
||||
| Token | Example | Description |
|
||||
| --- | --- | --- |
|
||||
| `iYYYY` | `1444` | 4-digit Hijri year |
|
||||
| `iYY` | `44` | 2-digit Hijri year |
|
||||
| `iMMMM` | `Ramadan` | Full Hijri month name |
|
||||
| `iMMM` | `Ramadan` | Medium Hijri month name |
|
||||
| `iMM` | `09` | Zero-padded Hijri month number |
|
||||
| `iM` | `9` | Hijri month number |
|
||||
| `iDD` | `01` | Zero-padded Hijri day |
|
||||
| `iD` | `1` | Hijri day number |
|
||||
| `iEEEE` | `Yawm al-Khamis` | Full weekday name |
|
||||
| `iEEE` | `Kham` | Short weekday name |
|
||||
| `iE` | `5` | Weekday number (1=Sunday ... 7=Saturday) |
|
||||
| `ioooo` | `AH` | Era (Anno Hegirae) |
|
||||
| `iooo` | `AH` | Era (short form, same as ioooo) |
|
||||
|
||||
Standard Day.js tokens pass through untouched. Square-bracket escaping (`[literal text]`) also works as expected.
|
||||
|
||||
## Calendar Systems
|
||||
|
||||
Two calendars ship with hijri-core:
|
||||
|
||||
- **`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.
|
||||
|
||||
Custom calendar engines can be registered:
|
||||
|
||||
```ts
|
||||
import { registerCalendar } from 'dayjs-hijri-plus';
|
||||
import type { CalendarEngine } from 'dayjs-hijri-plus';
|
||||
|
||||
const myEngine: CalendarEngine = { ... };
|
||||
registerCalendar('my-calendar', myEngine);
|
||||
|
||||
dayjs().toHijri({ calendar: 'my-calendar' });
|
||||
```
|
||||
|
||||
See the [hijri-core CalendarEngine interface](https://github.com/acamarata/hijri-core) for the full contract.
|
||||
|
||||
## TypeScript
|
||||
|
||||
Full TypeScript support is included. The plugin augments the Day.js module to add types for all instance and static methods.
|
||||
|
||||
```ts
|
||||
import type { HijriDate, ConversionOptions } from 'dayjs-hijri-plus';
|
||||
|
||||
const h: HijriDate = dayjs().toHijri()!;
|
||||
const opts: ConversionOptions = { calendar: 'fcna' };
|
||||
```
|
||||
|
||||
No `@types` package is needed.
|
||||
|
||||
## Architecture
|
||||
|
||||
A thin plugin wrapper over [hijri-core](https://github.com/acamarata/hijri-core). The plugin augments the Day.js prototype with Hijri methods, each delegating to the registered calendar engine. Zero global state: calendar selection is passed per call.
|
||||
|
||||
For more detail see the [Architecture wiki page](https://github.com/acamarata/dayjs-hijri-plus/wiki/Architecture).
|
||||
|
||||
## Documentation
|
||||
|
||||
Full API reference, architecture notes, and calendar system comparisons are on the [GitHub Wiki](https://github.com/acamarata/dayjs-hijri-plus/wiki).
|
||||
Full API reference, examples, and architecture notes are on the [GitHub Wiki](https://github.com/acamarata/dayjs-hijri-plus/wiki).
|
||||
|
||||
## Related
|
||||
|
||||
- [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
|
||||
- [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 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
|
||||
|
||||
## Compatibility
|
||||
|
||||
- Node.js 20, 22, 24
|
||||
- Day.js 1.x (peer dependency)
|
||||
- ESM and CJS builds included
|
||||
- TypeScript definitions bundled
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Calendar data and algorithms provided by [hijri-core](https://github.com/acamarata/hijri-core). The Umm al-Qura table is derived from data published by the King Abdulaziz City for Science and Technology (KACST). FCNA new moon calculations follow Jean Meeus, "Astronomical Algorithms," 2nd ed., Chapter 49.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
140
src/index.ts
140
src/index.ts
|
|
@ -5,24 +5,83 @@ import type { ConversionOptions, HijriDate } from './types';
|
|||
// Augment Day.js to expose plugin methods on the instance type.
|
||||
declare module 'dayjs' {
|
||||
interface Dayjs {
|
||||
/** Convert to a Hijri date. Returns null if outside the supported range. */
|
||||
/**
|
||||
* Convert the Day.js date to a Hijri date.
|
||||
*
|
||||
* @param opts - Optional calendar selection. Defaults to `{ calendar: 'uaq' }`.
|
||||
* @returns `{ hy, hm, hd }` on success, or `null` if the date is outside the
|
||||
* supported range (UAQ: 1318-1500 AH / approximately 1900-2076 CE).
|
||||
* @example
|
||||
* dayjs('2023-03-23').toHijri();
|
||||
* // => { hy: 1444, hm: 9, hd: 1 }
|
||||
*/
|
||||
toHijri(opts?: ConversionOptions): HijriDate | null;
|
||||
|
||||
/** Check whether the date maps to a valid Hijri date in the supported range. */
|
||||
/**
|
||||
* Check whether the date maps to a valid Hijri date in the supported range.
|
||||
*
|
||||
* Equivalent to `d.toHijri(opts) !== null`.
|
||||
*
|
||||
* @param opts - Optional calendar selection.
|
||||
* @returns `true` if the date is in range, `false` otherwise.
|
||||
* @example
|
||||
* dayjs('2023-03-23').isValidHijri(); // true
|
||||
* dayjs('1800-01-01').isValidHijri(); // false
|
||||
*/
|
||||
isValidHijri(opts?: ConversionOptions): boolean;
|
||||
|
||||
/** Hijri year component, or null if out of range. */
|
||||
/**
|
||||
* Return the Hijri year of the date.
|
||||
*
|
||||
* @param opts - Optional calendar selection.
|
||||
* @returns The Hijri year as a `number`, or `null` if out of range.
|
||||
* @example
|
||||
* dayjs('2023-03-23').hijriYear(); // 1444
|
||||
*/
|
||||
hijriYear(opts?: ConversionOptions): number | null;
|
||||
|
||||
/** Hijri month component (1-12), or null if out of range. */
|
||||
/**
|
||||
* Return the Hijri month (1-12) of the date.
|
||||
*
|
||||
* Month 1 = Muharram, month 9 = Ramadan, month 12 = Dhu al-Hijjah.
|
||||
*
|
||||
* @param opts - Optional calendar selection.
|
||||
* @returns The Hijri month in the range 1-12, or `null` if out of range.
|
||||
* @example
|
||||
* dayjs('2023-03-23').hijriMonth(); // 9 (Ramadan)
|
||||
*/
|
||||
hijriMonth(opts?: ConversionOptions): number | null;
|
||||
|
||||
/** Hijri day component (1-30), or null if out of range. */
|
||||
/**
|
||||
* Return the Hijri day of month (1-30) of the date.
|
||||
*
|
||||
* @param opts - Optional calendar selection.
|
||||
* @returns The Hijri day in the range 1-30, or `null` if out of range.
|
||||
* @example
|
||||
* dayjs('2023-03-23').hijriDay(); // 1
|
||||
*/
|
||||
hijriDay(opts?: ConversionOptions): number | null;
|
||||
|
||||
/**
|
||||
* Format the date using Hijri tokens (i-prefixed) and standard Day.js tokens.
|
||||
* Returns an empty string if the date is outside the supported range.
|
||||
* Format the date using a mix of Hijri tokens (`i`-prefixed) and standard
|
||||
* Day.js tokens.
|
||||
*
|
||||
* Hijri tokens are replaced first. The resulting string is then passed to
|
||||
* Day.js `.format()`, so all standard tokens (YYYY, MM, DD, HH, mm, ss, etc.)
|
||||
* resolve normally.
|
||||
*
|
||||
* @param formatStr - Format string containing Hijri tokens, Day.js tokens, or both.
|
||||
* @param opts - Optional calendar selection.
|
||||
* @returns The formatted string, or an empty string if the date is out of range.
|
||||
* @example
|
||||
* dayjs('2023-03-23').formatHijri('iD iMMMM iYYYY');
|
||||
* // => '1 Ramadan 1444'
|
||||
*
|
||||
* dayjs('2023-03-23').formatHijri('iYYYY-iMM-iDD');
|
||||
* // => '1444-09-01'
|
||||
*
|
||||
* dayjs('2023-03-23').formatHijri('iD iMMMM iYYYY [at] HH:mm');
|
||||
* // => '1 Ramadan 1444 at 00:00'
|
||||
*/
|
||||
formatHijri(formatStr: string, opts?: ConversionOptions): string;
|
||||
}
|
||||
|
|
@ -33,6 +92,26 @@ declare module 'dayjs' {
|
|||
// 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' {
|
||||
/**
|
||||
* Construct a Day.js instance from a Hijri date.
|
||||
*
|
||||
* The result is built from an ISO date string (`YYYY-MM-DD`) to avoid
|
||||
* UTC midnight converting to the previous local day in western timezones.
|
||||
*
|
||||
* @param hy - Hijri year.
|
||||
* @param hm - Hijri month (1-12).
|
||||
* @param hd - Hijri day (1-30).
|
||||
* @param opts - Optional calendar selection. Defaults to `{ calendar: 'uaq' }`.
|
||||
* @returns A `dayjs.Dayjs` instance at midnight local time on the corresponding
|
||||
* Gregorian date.
|
||||
* @throws {Error} If the Hijri date is invalid or outside the table range.
|
||||
* @example
|
||||
* dayjs.fromHijri(1444, 9, 1).format('YYYY-MM-DD');
|
||||
* // => '2023-03-23'
|
||||
*
|
||||
* dayjs.fromHijri(1444, 10, 1).format('YYYY-MM-DD');
|
||||
* // => '2023-04-21' (Eid al-Fitr 1444)
|
||||
*/
|
||||
function fromHijri(
|
||||
hy: number,
|
||||
hm: number,
|
||||
|
|
@ -53,12 +132,35 @@ 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.
|
||||
* unrecognised characters through untouched), then opens a new one).
|
||||
*
|
||||
* @param value - Plain string to escape.
|
||||
* @returns The bracket-escaped string.
|
||||
*/
|
||||
function lit(value: string): string {
|
||||
return '[' + value.split(']').join(']][') + ']';
|
||||
}
|
||||
|
||||
/**
|
||||
* Day.js plugin that adds Hijri calendar support.
|
||||
*
|
||||
* Register once with `dayjs.extend(hijriPlugin)`. After that, all `dayjs()`
|
||||
* instances expose `.toHijri()`, `.isValidHijri()`, `.hijriYear()`,
|
||||
* `.hijriMonth()`, `.hijriDay()`, and `.formatHijri()`. The static factory
|
||||
* `dayjs.fromHijri(hy, hm, hd)` is also added.
|
||||
*
|
||||
* All calendar arithmetic is delegated to hijri-core. This plugin adds no
|
||||
* conversion logic of its own.
|
||||
*
|
||||
* @example
|
||||
* import dayjs from 'dayjs';
|
||||
* import hijriPlugin from 'dayjs-hijri-plus';
|
||||
*
|
||||
* dayjs.extend(hijriPlugin);
|
||||
*
|
||||
* dayjs('2023-03-23').toHijri();
|
||||
* // => { hy: 1444, hm: 9, hd: 1 }
|
||||
*/
|
||||
const plugin: PluginFunc = (_option, dayjsClass, dayjsFactory) => {
|
||||
// ------------------------------------------------------------------ //
|
||||
// Instance methods //
|
||||
|
|
@ -172,10 +274,26 @@ const plugin: PluginFunc = (_option, dayjsClass, dayjsFactory) => {
|
|||
|
||||
export default plugin;
|
||||
|
||||
// Re-export hijri-core types for consumers who import from dayjs-hijri-plus.
|
||||
/**
|
||||
* Re-exported from hijri-core for consumers who import from dayjs-hijri-plus.
|
||||
* Avoids requiring hijri-core as a direct dependency just to use these types.
|
||||
*/
|
||||
export type { HijriDate, ConversionOptions } from './types';
|
||||
|
||||
/**
|
||||
* Re-exported CalendarEngine interface from hijri-core.
|
||||
* Use this type to implement custom calendar engines for `registerCalendar`.
|
||||
*/
|
||||
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.
|
||||
/**
|
||||
* Re-exported registry API from hijri-core.
|
||||
* Register, retrieve, or list custom calendar engines without adding
|
||||
* hijri-core as a direct dependency.
|
||||
*
|
||||
* @example
|
||||
* import { registerCalendar, listCalendars } from 'dayjs-hijri-plus';
|
||||
* registerCalendar('my-cal', myEngine);
|
||||
* listCalendars(); // ['uaq', 'fcna', 'my-cal']
|
||||
*/
|
||||
export { registerCalendar, getCalendar, listCalendars } from 'hijri-core';
|
||||
|
|
|
|||
Loading…
Reference in a new issue