feat: initial release of dayjs-hijri-plus v1.0.0

Day.js plugin adding Hijri calendar support via hijri-core. Adds
toHijri(), fromHijri(), hijriYear/Month/Day(), isValidHijri(), and
formatHijri() to all Day.js instances. Supports UAQ and FCNA calendars
via ConversionOptions. Format token escaping wraps substituted values
in Day.js bracket syntax to prevent re-interpretation as format tokens.
14 ESM + 8 CJS tests passing. Dual CJS/ESM build.
This commit is contained in:
Aric Camarata 2026-02-25 14:15:07 -05:00
commit 96dd9c5688
21 changed files with 2082 additions and 0 deletions

14
.editorconfig Normal file
View file

@ -0,0 +1,14 @@
root = true
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{ts,js,mjs,cjs,json,yaml,yml,md}]
indent_style = space
indent_size = 2
[Makefile]
indent_style = tab

62
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,62 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
name: Test (Node ${{ matrix.node }})
runs-on: ubuntu-latest
strategy:
matrix:
node: [20, 22, 24]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm run build
- run: node test.mjs
- run: node test-cjs.cjs
typecheck:
name: Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm run typecheck
pack-check:
name: Pack check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm run build
- name: Verify pack contents
run: |
npm pack --dry-run 2>&1 | tee pack-output.txt
grep "dist/index.cjs" pack-output.txt
grep "dist/index.mjs" pack-output.txt
grep "dist/index.d.ts" pack-output.txt
grep "dist/index.d.mts" pack-output.txt
grep "README.md" pack-output.txt
grep "CHANGELOG.md" pack-output.txt
grep "LICENSE" pack-output.txt

36
.github/workflows/wiki-sync.yml vendored Normal file
View file

@ -0,0 +1,36 @@
name: Wiki Sync
on:
push:
branches: [main]
paths:
- '.wiki/**'
permissions:
contents: write
jobs:
sync:
name: Sync .wiki/ to GitHub Wiki
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Push wiki pages
uses: actions/checkout@v4
with:
repository: ${{ github.repository }}.wiki
path: wiki-repo
token: ${{ secrets.GITHUB_TOKEN }}
- name: Copy wiki files
run: cp .wiki/*.md wiki-repo/
- name: Commit and push
working-directory: wiki-repo
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
git diff --cached --quiet || git commit -m "Sync wiki from .wiki/ [skip ci]"
git push

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
node_modules/
dist/
*.tgz
*.log
.DS_Store
.claude/
.env
.env.*

1
.npmrc Normal file
View file

@ -0,0 +1 @@
package-import-method=hardlink

1
.nvmrc Normal file
View file

@ -0,0 +1 @@
24

205
.wiki/API-Reference.md Normal file
View file

@ -0,0 +1,205 @@
# API Reference
## Setup
```ts
import dayjs from 'dayjs';
import hijriPlugin from 'dayjs-hijri-plus';
dayjs.extend(hijriPlugin);
```
Call `dayjs.extend` once, globally. After that, every Day.js instance has the plugin methods.
---
## Instance Methods
### `.toHijri(opts?)`
Convert the Day.js date to a Hijri date.
**Signature:**
```ts
toHijri(opts?: ConversionOptions): HijriDate | null
```
**Parameters:**
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `opts.calendar` | `string` | `'uaq'` | Calendar engine id. Built-ins: `'uaq'`, `'fcna'` |
**Returns:** `{ hy: number, hm: number, hd: number }` or `null` if the date is outside the table range.
```ts
dayjs('2023-03-23').toHijri();
// => { hy: 1444, hm: 9, hd: 1 }
dayjs('2023-03-23').toHijri({ calendar: 'fcna' });
// => { hy: 1444, hm: 9, hd: 2 }
```
---
### `.isValidHijri(opts?)`
Check whether the date has a valid Hijri representation in the supported range.
**Signature:**
```ts
isValidHijri(opts?: ConversionOptions): boolean
```
Returns `false` for dates outside the coverage range, `true` otherwise.
---
### `.hijriYear(opts?)`
**Signature:**
```ts
hijriYear(opts?: ConversionOptions): number | null
```
Returns the Hijri year, or `null` if out of range.
---
### `.hijriMonth(opts?)`
**Signature:**
```ts
hijriMonth(opts?: ConversionOptions): number | null
```
Returns the Hijri month (1-12), or `null` if out of range.
---
### `.hijriDay(opts?)`
**Signature:**
```ts
hijriDay(opts?: ConversionOptions): number | null
```
Returns the Hijri day (1-30), or `null` if out of range.
---
### `.formatHijri(formatStr, opts?)`
Format the date using a mix of Hijri-specific tokens and standard Day.js tokens.
**Signature:**
```ts
formatHijri(formatStr: string, opts?: ConversionOptions): string
```
**Parameters:**
| Name | Type | Description |
| --- | --- | --- |
| `formatStr` | `string` | Format string containing Hijri tokens, Day.js tokens, or both |
| `opts` | `ConversionOptions` | Optional calendar selection |
Returns an empty string if the date is outside the supported range.
**Hijri tokens:**
| Token | Example | Description |
| --- | --- | --- |
| `iYYYY` | `1444` | 4-digit Hijri year |
| `iYY` | `44` | 2-digit Hijri year |
| `iMMMM` | `Ramadan` | Full month name |
| `iMMM` | `Ramadan` | Medium month name |
| `iMM` | `09` | Zero-padded month number |
| `iM` | `9` | Month number |
| `iDD` | `01` | Zero-padded day |
| `iD` | `1` | Day number |
| `iEEEE` | `Yawm al-Khamis` | Full weekday name |
| `iEEE` | `Kham` | Short weekday name |
| `iE` | `5` | Weekday number (1=Sun ... 7=Sat) |
| `ioooo` | `AH` | Era |
| `iooo` | `AH` | Era (same as ioooo) |
Standard Day.js tokens pass through to `.format()` after Hijri token substitution.
```ts
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'
dayjs('2023-03-23').formatHijri('iYYYY YYYY');
// => '1444 2023'
```
---
## Static Methods
### `dayjs.fromHijri(hy, hm, hd, opts?)`
Construct a Day.js instance from a Hijri date.
**Signature:**
```ts
dayjs.fromHijri(
hy: number,
hm: number,
hd: number,
opts?: ConversionOptions,
): dayjs.Dayjs
```
**Parameters:**
| Name | Type | Description |
| --- | --- | --- |
| `hy` | `number` | Hijri year |
| `hm` | `number` | Hijri month (1-12) |
| `hd` | `number` | Hijri day (1-30) |
| `opts.calendar` | `string` | Calendar engine id (default: `'uaq'`) |
**Throws:** `Error` if the Hijri date is invalid or outside the table range.
```ts
dayjs.fromHijri(1444, 9, 1).format('YYYY-MM-DD');
// => '2023-03-23'
dayjs.fromHijri(1444, 9, 1, { calendar: 'fcna' }).format('YYYY-MM-DD');
// => '2023-03-22'
```
---
## Type Exports
```ts
import type {
HijriDate, // { hy: number, hm: number, hd: number }
ConversionOptions, // { calendar?: string }
CalendarSystem, // string alias for calendar ids
} from 'dayjs-hijri-plus';
```
---
## Registry Exports
These re-export from hijri-core, so consumers can register custom calendar engines without adding hijri-core as a direct dependency:
```ts
import { registerCalendar, getCalendar, listCalendars } from 'dayjs-hijri-plus';
import type { CalendarEngine } from 'dayjs-hijri-plus';
registerCalendar('my-cal', myEngine);
listCalendars(); // => ['uaq', 'fcna', 'my-cal']
```
---
[Home](Home) | [Architecture](Architecture)

99
.wiki/Architecture.md Normal file
View file

@ -0,0 +1,99 @@
# Architecture
## Design Philosophy
dayjs-hijri-plus contains no Hijri calendar arithmetic. Every conversion delegates to [hijri-core](https://github.com/acamarata/hijri-core), which provides a pluggable engine registry with UAQ and FCNA built in.
This separation is deliberate. Calendar algorithms are complex, have known edge cases, and require dedicated testing. Keeping them in hijri-core means both this plugin and future adapters (for Temporal, date-fns, etc.) share a single, well-tested core.
## Plugin Structure
```
src/
index.ts Plugin entry — registers methods on dayjsClass and dayjsFactory
types.ts Type definitions and module augmentation for dayjs
```
The plugin follows the standard Day.js `PluginFunc` signature:
```ts
const plugin: PluginFunc = (_option, dayjsClass, dayjsFactory) => { ... };
```
- `dayjsClass.prototype.*` — instance methods (`.toHijri`, `.formatHijri`, etc.)
- `(dayjsFactory as any).fromHijri` — static method added to the factory function
## Peer Dependencies
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.
## Format Token Resolution
`formatHijri` works in two passes:
**Pass 1:** Replace Hijri tokens using a single regex sweep over the format string.
```ts
const HIJRI_TOKEN_RE = /iYYYY|iYY|iMMMM|iMMM|iMM|iM|iDD|iD|iEEEE|iEEE|iE|ioooo|iooo/g;
```
Tokens are listed longest-first in the alternation. This prevents `iYY` from matching before `iYYYY`, and `iMM` from matching before `iMMMM`. The regex engine tries alternatives left-to-right at each position, so ordering is the only safeguard needed.
**Pass 2:** The modified string is passed to `this.format(result)`. Day.js resolves all remaining tokens (YYYY, MM, DD, HH, mm, ss, etc.) and square-bracket escapes (`[literal]`).
This means Hijri tokens and Gregorian tokens can coexist in the same format string. For example, `'iYYYY YYYY'` produces `'1444 2023'`.
## Weekday Alignment
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.
## fromHijri Error Handling
`dayjs.fromHijri` calls `toGregorian` from hijri-core. If the Hijri date is invalid or outside the table range, `toGregorian` returns `null`. The plugin converts that into a thrown `Error` with the specific Hijri components included in the message, so callers get a useful diagnostic rather than a null-dereference downstream.
## Calendar Extension
The registry is global within a process. Registering a custom calendar once makes it available to all plugin method calls:
```ts
import { registerCalendar } from 'dayjs-hijri-plus';
registerCalendar('tabular', tabularEngine);
dayjs('2023-03-23').toHijri({ calendar: 'tabular' });
```
Custom engines must implement the `CalendarEngine` interface from hijri-core:
```ts
interface CalendarEngine {
readonly id: string;
toHijri(date: Date): HijriDate | null;
toGregorian(hy: number, hm: number, hd: number): Date | null;
isValid(hy: number, hm: number, hd: number): boolean;
daysInMonth(hy: number, hm: number): number;
}
```
## 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.
Output:
| File | Format |
| --- | --- |
| `dist/index.cjs` | CommonJS (Node `require`) |
| `dist/index.mjs` | ESM (`import`) |
| `dist/index.d.ts` | TypeScript declarations for CJS |
| `dist/index.d.mts` | TypeScript declarations for ESM |
---
[Home](Home) | [API Reference](API-Reference)

36
.wiki/Home.md Normal file
View file

@ -0,0 +1,36 @@
# dayjs-hijri-plus
A Day.js plugin for Hijri calendar conversion and formatting. All calendar logic is delegated to [hijri-core](https://github.com/acamarata/hijri-core), making this package a thin, well-typed adapter with no calendar arithmetic of its own.
## Install
```sh
pnpm add dayjs dayjs-hijri-plus hijri-core
```
## Quick Usage
```ts
import dayjs from 'dayjs';
import hijriPlugin from 'dayjs-hijri-plus';
dayjs.extend(hijriPlugin);
dayjs('2023-03-23').toHijri();
// => { hy: 1444, hm: 9, hd: 1 }
dayjs('2023-03-23').formatHijri('iD iMMMM iYYYY');
// => '1 Ramadan 1444'
dayjs.fromHijri(1444, 10, 1).format('YYYY-MM-DD');
// => '2023-04-21'
```
## Contents
- [API Reference](API-Reference) — all methods, parameters, return types
- [Architecture](Architecture) — design decisions, delegation model, format token resolution
---
Part of the [acamarata](https://github.com/acamarata) JavaScript library collection.

16
CHANGELOG.md Normal file
View file

@ -0,0 +1,16 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [1.0.0] - 2026-02-25
### Added
- Day.js plugin with `.toHijri()`, `.fromHijri()`, `.hijriYear()`, `.hijriMonth()`, `.hijriDay()`, `.isValidHijri()`, and `.formatHijri()` methods
- Umm al-Qura (UAQ) calendar support via hijri-core
- FCNA/ISNA calendar support via hijri-core
- Full TypeScript definitions including module augmentation for Day.js types
- Dual CJS/ESM build with separate type declaration files
- Re-exports of `registerCalendar`, `getCalendar`, and `listCalendars` from hijri-core for custom calendar registration

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Aric Camarata
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

190
README.md Normal file
View file

@ -0,0 +1,190 @@
# dayjs-hijri-plus
[![npm version](https://img.shields.io/npm/v/dayjs-hijri-plus)](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.
Supports Umm al-Qura (UAQ) and FCNA/ISNA calendars out of the box. Custom calendar engines can be registered at runtime.
## Installation
```sh
pnpm add dayjs dayjs-hijri-plus hijri-core
```
Both `dayjs` and `hijri-core` are peer dependencies and must be installed alongside this plugin.
## Quick Start
```ts
import dayjs from 'dayjs';
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)
// 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)
```
## 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 (approximately 1900-2077 CE for UAQ).
```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 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.
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.
## Documentation
Full API reference, architecture notes, and calendar system comparisons are on the [GitHub Wiki](https://github.com/acamarata/dayjs-hijri-plus/wiki).
## 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
## License
MIT. Copyright (c) 2026 Aric Camarata.

62
package.json Normal file
View file

@ -0,0 +1,62 @@
{
"name": "dayjs-hijri-plus",
"version": "1.0.0",
"description": "Day.js plugin for Hijri calendar conversion and formatting. Supports Umm al-Qura and FCNA calendars via hijri-core.",
"author": "Aric Camarata",
"license": "MIT",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" },
"require": { "types": "./dist/index.d.ts", "default": "./dist/index.cjs" }
}
},
"sideEffects": false,
"files": [
"dist/index.cjs",
"dist/index.mjs",
"dist/index.d.ts",
"dist/index.d.mts",
"README.md",
"CHANGELOG.md",
"LICENSE"
],
"engines": { "node": ">=20" },
"packageManager": "pnpm@10.30.1",
"scripts": {
"build": "tsup",
"typecheck": "tsc --noEmit",
"pretest": "tsup",
"test": "node test.mjs && node test-cjs.cjs",
"prepublishOnly": "tsup"
},
"keywords": [
"dayjs",
"plugin",
"hijri",
"islamic",
"calendar",
"umm-al-qura",
"fcna",
"gregorian",
"converter",
"typescript"
],
"peerDependencies": {
"dayjs": "^1.0.0",
"hijri-core": "^1.0.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"dayjs": "^1.11.0",
"hijri-core": "file:../hijri-core",
"tsup": "^8.0.0",
"typescript": "^5.5.0"
},
"publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" },
"repository": { "type": "git", "url": "git+https://github.com/acamarata/dayjs-hijri-plus.git" },
"homepage": "https://github.com/acamarata/dayjs-hijri-plus#readme",
"bugs": { "url": "https://github.com/acamarata/dayjs-hijri-plus/issues" }
}

942
pnpm-lock.yaml Normal file
View file

@ -0,0 +1,942 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
devDependencies:
'@types/node':
specifier: ^22.0.0
version: 22.19.11
dayjs:
specifier: ^1.11.0
version: 1.11.19
hijri-core:
specifier: file:../hijri-core
version: file:../hijri-core
tsup:
specifier: ^8.0.0
version: 8.5.1(typescript@5.9.3)
typescript:
specifier: ^5.5.0
version: 5.9.3
packages:
'@esbuild/aix-ppc64@0.27.3':
resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
'@esbuild/android-arm64@0.27.3':
resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm@0.27.3':
resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
'@esbuild/android-x64@0.27.3':
resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
'@esbuild/darwin-arm64@0.27.3':
resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-x64@0.27.3':
resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
'@esbuild/freebsd-arm64@0.27.3':
resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-x64@0.27.3':
resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
'@esbuild/linux-arm64@0.27.3':
resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm@0.27.3':
resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
'@esbuild/linux-ia32@0.27.3':
resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-loong64@0.27.3':
resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-mips64el@0.27.3':
resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-ppc64@0.27.3':
resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-riscv64@0.27.3':
resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-s390x@0.27.3':
resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-x64@0.27.3':
resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
'@esbuild/netbsd-arm64@0.27.3':
resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
'@esbuild/netbsd-x64@0.27.3':
resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
'@esbuild/openbsd-arm64@0.27.3':
resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
'@esbuild/openbsd-x64@0.27.3':
resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
'@esbuild/openharmony-arm64@0.27.3':
resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
'@esbuild/sunos-x64@0.27.3':
resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
'@esbuild/win32-arm64@0.27.3':
resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-ia32@0.27.3':
resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==}
engines: {node: '>=18'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-x64@0.27.3':
resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==}
engines: {node: '>=18'}
cpu: [x64]
os: [win32]
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@rollup/rollup-android-arm-eabi@4.59.0':
resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.59.0':
resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.59.0':
resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.59.0':
resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-freebsd-arm64@4.59.0':
resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==}
cpu: [arm64]
os: [freebsd]
'@rollup/rollup-freebsd-x64@4.59.0':
resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==}
cpu: [x64]
os: [freebsd]
'@rollup/rollup-linux-arm-gnueabihf@4.59.0':
resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.59.0':
resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.59.0':
resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.59.0':
resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.59.0':
resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==}
cpu: [loong64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.59.0':
resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==}
cpu: [loong64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.59.0':
resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.59.0':
resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==}
cpu: [ppc64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.59.0':
resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.59.0':
resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==}
cpu: [riscv64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.59.0':
resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.59.0':
resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.59.0':
resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-openbsd-x64@4.59.0':
resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==}
cpu: [x64]
os: [openbsd]
'@rollup/rollup-openharmony-arm64@4.59.0':
resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==}
cpu: [arm64]
os: [openharmony]
'@rollup/rollup-win32-arm64-msvc@4.59.0':
resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.59.0':
resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-gnu@4.59.0':
resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==}
cpu: [x64]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.59.0':
resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==}
cpu: [x64]
os: [win32]
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/node@22.19.11':
resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==}
acorn@8.16.0:
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
engines: {node: '>=0.4.0'}
hasBin: true
any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
bundle-require@5.1.0:
resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
peerDependencies:
esbuild: '>=0.18'
cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
chokidar@4.0.3:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
confbox@0.1.8:
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
consola@3.4.2:
resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
engines: {node: ^14.18.0 || >=16.10.0}
dayjs@1.11.19:
resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
esbuild@0.27.3:
resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==}
engines: {node: '>=18'}
hasBin: true
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
fix-dts-default-cjs-exports@1.0.1:
resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
hijri-core@file:../hijri-core:
resolution: {directory: ../hijri-core, type: directory}
engines: {node: '>=20'}
joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
lilconfig@3.1.3:
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
engines: {node: '>=14'}
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
load-tsconfig@0.2.5:
resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
mlly@1.8.0:
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
picomatch@4.0.3:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
pirates@4.0.7:
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
engines: {node: '>= 6'}
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
postcss-load-config@6.0.1:
resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
engines: {node: '>= 18'}
peerDependencies:
jiti: '>=1.21.0'
postcss: '>=8.0.9'
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
jiti:
optional: true
postcss:
optional: true
tsx:
optional: true
yaml:
optional: true
readdirp@4.1.2:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
resolve-from@5.0.0:
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
engines: {node: '>=8'}
rollup@4.59.0:
resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
source-map@0.7.6:
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
engines: {node: '>= 12'}
sucrase@3.35.1:
resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
engines: {node: '>=16 || 14 >=14.17'}
hasBin: true
thenify-all@1.6.0:
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
engines: {node: '>=0.8'}
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
tinyglobby@0.2.15:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'}
tree-kill@1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
tsup@8.5.1:
resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==}
engines: {node: '>=18'}
hasBin: true
peerDependencies:
'@microsoft/api-extractor': ^7.36.0
'@swc/core': ^1
postcss: ^8.4.12
typescript: '>=4.5.0'
peerDependenciesMeta:
'@microsoft/api-extractor':
optional: true
'@swc/core':
optional: true
postcss:
optional: true
typescript:
optional: true
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'}
hasBin: true
ufo@1.6.3:
resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
snapshots:
'@esbuild/aix-ppc64@0.27.3':
optional: true
'@esbuild/android-arm64@0.27.3':
optional: true
'@esbuild/android-arm@0.27.3':
optional: true
'@esbuild/android-x64@0.27.3':
optional: true
'@esbuild/darwin-arm64@0.27.3':
optional: true
'@esbuild/darwin-x64@0.27.3':
optional: true
'@esbuild/freebsd-arm64@0.27.3':
optional: true
'@esbuild/freebsd-x64@0.27.3':
optional: true
'@esbuild/linux-arm64@0.27.3':
optional: true
'@esbuild/linux-arm@0.27.3':
optional: true
'@esbuild/linux-ia32@0.27.3':
optional: true
'@esbuild/linux-loong64@0.27.3':
optional: true
'@esbuild/linux-mips64el@0.27.3':
optional: true
'@esbuild/linux-ppc64@0.27.3':
optional: true
'@esbuild/linux-riscv64@0.27.3':
optional: true
'@esbuild/linux-s390x@0.27.3':
optional: true
'@esbuild/linux-x64@0.27.3':
optional: true
'@esbuild/netbsd-arm64@0.27.3':
optional: true
'@esbuild/netbsd-x64@0.27.3':
optional: true
'@esbuild/openbsd-arm64@0.27.3':
optional: true
'@esbuild/openbsd-x64@0.27.3':
optional: true
'@esbuild/openharmony-arm64@0.27.3':
optional: true
'@esbuild/sunos-x64@0.27.3':
optional: true
'@esbuild/win32-arm64@0.27.3':
optional: true
'@esbuild/win32-ia32@0.27.3':
optional: true
'@esbuild/win32-x64@0.27.3':
optional: true
'@jridgewell/gen-mapping@0.3.13':
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
'@jridgewell/trace-mapping': 0.3.31
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/sourcemap-codec@1.5.5': {}
'@jridgewell/trace-mapping@0.3.31':
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@rollup/rollup-android-arm-eabi@4.59.0':
optional: true
'@rollup/rollup-android-arm64@4.59.0':
optional: true
'@rollup/rollup-darwin-arm64@4.59.0':
optional: true
'@rollup/rollup-darwin-x64@4.59.0':
optional: true
'@rollup/rollup-freebsd-arm64@4.59.0':
optional: true
'@rollup/rollup-freebsd-x64@4.59.0':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.59.0':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.59.0':
optional: true
'@rollup/rollup-linux-arm64-gnu@4.59.0':
optional: true
'@rollup/rollup-linux-arm64-musl@4.59.0':
optional: true
'@rollup/rollup-linux-loong64-gnu@4.59.0':
optional: true
'@rollup/rollup-linux-loong64-musl@4.59.0':
optional: true
'@rollup/rollup-linux-ppc64-gnu@4.59.0':
optional: true
'@rollup/rollup-linux-ppc64-musl@4.59.0':
optional: true
'@rollup/rollup-linux-riscv64-gnu@4.59.0':
optional: true
'@rollup/rollup-linux-riscv64-musl@4.59.0':
optional: true
'@rollup/rollup-linux-s390x-gnu@4.59.0':
optional: true
'@rollup/rollup-linux-x64-gnu@4.59.0':
optional: true
'@rollup/rollup-linux-x64-musl@4.59.0':
optional: true
'@rollup/rollup-openbsd-x64@4.59.0':
optional: true
'@rollup/rollup-openharmony-arm64@4.59.0':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.59.0':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.59.0':
optional: true
'@rollup/rollup-win32-x64-gnu@4.59.0':
optional: true
'@rollup/rollup-win32-x64-msvc@4.59.0':
optional: true
'@types/estree@1.0.8': {}
'@types/node@22.19.11':
dependencies:
undici-types: 6.21.0
acorn@8.16.0: {}
any-promise@1.3.0: {}
bundle-require@5.1.0(esbuild@0.27.3):
dependencies:
esbuild: 0.27.3
load-tsconfig: 0.2.5
cac@6.7.14: {}
chokidar@4.0.3:
dependencies:
readdirp: 4.1.2
commander@4.1.1: {}
confbox@0.1.8: {}
consola@3.4.2: {}
dayjs@1.11.19: {}
debug@4.4.3:
dependencies:
ms: 2.1.3
esbuild@0.27.3:
optionalDependencies:
'@esbuild/aix-ppc64': 0.27.3
'@esbuild/android-arm': 0.27.3
'@esbuild/android-arm64': 0.27.3
'@esbuild/android-x64': 0.27.3
'@esbuild/darwin-arm64': 0.27.3
'@esbuild/darwin-x64': 0.27.3
'@esbuild/freebsd-arm64': 0.27.3
'@esbuild/freebsd-x64': 0.27.3
'@esbuild/linux-arm': 0.27.3
'@esbuild/linux-arm64': 0.27.3
'@esbuild/linux-ia32': 0.27.3
'@esbuild/linux-loong64': 0.27.3
'@esbuild/linux-mips64el': 0.27.3
'@esbuild/linux-ppc64': 0.27.3
'@esbuild/linux-riscv64': 0.27.3
'@esbuild/linux-s390x': 0.27.3
'@esbuild/linux-x64': 0.27.3
'@esbuild/netbsd-arm64': 0.27.3
'@esbuild/netbsd-x64': 0.27.3
'@esbuild/openbsd-arm64': 0.27.3
'@esbuild/openbsd-x64': 0.27.3
'@esbuild/openharmony-arm64': 0.27.3
'@esbuild/sunos-x64': 0.27.3
'@esbuild/win32-arm64': 0.27.3
'@esbuild/win32-ia32': 0.27.3
'@esbuild/win32-x64': 0.27.3
fdir@6.5.0(picomatch@4.0.3):
optionalDependencies:
picomatch: 4.0.3
fix-dts-default-cjs-exports@1.0.1:
dependencies:
magic-string: 0.30.21
mlly: 1.8.0
rollup: 4.59.0
fsevents@2.3.3:
optional: true
hijri-core@file:../hijri-core: {}
joycon@3.1.1: {}
lilconfig@3.1.3: {}
lines-and-columns@1.2.4: {}
load-tsconfig@0.2.5: {}
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
mlly@1.8.0:
dependencies:
acorn: 8.16.0
pathe: 2.0.3
pkg-types: 1.3.1
ufo: 1.6.3
ms@2.1.3: {}
mz@2.7.0:
dependencies:
any-promise: 1.3.0
object-assign: 4.1.1
thenify-all: 1.6.0
object-assign@4.1.1: {}
pathe@2.0.3: {}
picocolors@1.1.1: {}
picomatch@4.0.3: {}
pirates@4.0.7: {}
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
mlly: 1.8.0
pathe: 2.0.3
postcss-load-config@6.0.1:
dependencies:
lilconfig: 3.1.3
readdirp@4.1.2: {}
resolve-from@5.0.0: {}
rollup@4.59.0:
dependencies:
'@types/estree': 1.0.8
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.59.0
'@rollup/rollup-android-arm64': 4.59.0
'@rollup/rollup-darwin-arm64': 4.59.0
'@rollup/rollup-darwin-x64': 4.59.0
'@rollup/rollup-freebsd-arm64': 4.59.0
'@rollup/rollup-freebsd-x64': 4.59.0
'@rollup/rollup-linux-arm-gnueabihf': 4.59.0
'@rollup/rollup-linux-arm-musleabihf': 4.59.0
'@rollup/rollup-linux-arm64-gnu': 4.59.0
'@rollup/rollup-linux-arm64-musl': 4.59.0
'@rollup/rollup-linux-loong64-gnu': 4.59.0
'@rollup/rollup-linux-loong64-musl': 4.59.0
'@rollup/rollup-linux-ppc64-gnu': 4.59.0
'@rollup/rollup-linux-ppc64-musl': 4.59.0
'@rollup/rollup-linux-riscv64-gnu': 4.59.0
'@rollup/rollup-linux-riscv64-musl': 4.59.0
'@rollup/rollup-linux-s390x-gnu': 4.59.0
'@rollup/rollup-linux-x64-gnu': 4.59.0
'@rollup/rollup-linux-x64-musl': 4.59.0
'@rollup/rollup-openbsd-x64': 4.59.0
'@rollup/rollup-openharmony-arm64': 4.59.0
'@rollup/rollup-win32-arm64-msvc': 4.59.0
'@rollup/rollup-win32-ia32-msvc': 4.59.0
'@rollup/rollup-win32-x64-gnu': 4.59.0
'@rollup/rollup-win32-x64-msvc': 4.59.0
fsevents: 2.3.3
source-map@0.7.6: {}
sucrase@3.35.1:
dependencies:
'@jridgewell/gen-mapping': 0.3.13
commander: 4.1.1
lines-and-columns: 1.2.4
mz: 2.7.0
pirates: 4.0.7
tinyglobby: 0.2.15
ts-interface-checker: 0.1.13
thenify-all@1.6.0:
dependencies:
thenify: 3.3.1
thenify@3.3.1:
dependencies:
any-promise: 1.3.0
tinyexec@0.3.2: {}
tinyglobby@0.2.15:
dependencies:
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
tree-kill@1.2.2: {}
ts-interface-checker@0.1.13: {}
tsup@8.5.1(typescript@5.9.3):
dependencies:
bundle-require: 5.1.0(esbuild@0.27.3)
cac: 6.7.14
chokidar: 4.0.3
consola: 3.4.2
debug: 4.4.3
esbuild: 0.27.3
fix-dts-default-cjs-exports: 1.0.1
joycon: 3.1.1
picocolors: 1.1.1
postcss-load-config: 6.0.1
resolve-from: 5.0.0
rollup: 4.59.0
source-map: 0.7.6
sucrase: 3.35.1
tinyexec: 0.3.2
tinyglobby: 0.2.15
tree-kill: 1.2.2
optionalDependencies:
typescript: 5.9.3
transitivePeerDependencies:
- jiti
- supports-color
- tsx
- yaml
typescript@5.9.3: {}
ufo@1.6.3: {}
undici-types@6.21.0: {}

2
pnpm-workspace.yaml Normal file
View file

@ -0,0 +1,2 @@
onlyBuiltDependencies:
- esbuild

160
src/index.ts Normal file
View file

@ -0,0 +1,160 @@
import type { PluginFunc } from 'dayjs';
import {
toHijri,
toGregorian,
hmLong,
hmMedium,
hwLong,
hwShort,
hwNumeric,
} from 'hijri-core';
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. */
toHijri(opts?: ConversionOptions): HijriDate | null;
/** Check whether the date maps to a valid Hijri date in the supported range. */
isValidHijri(opts?: ConversionOptions): boolean;
/** Hijri year component, or null if out of range. */
hijriYear(opts?: ConversionOptions): number | null;
/** Hijri month component (1-12), or null if out of range. */
hijriMonth(opts?: ConversionOptions): number | null;
/** Hijri day component (1-30), or null if out of range. */
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.
*/
formatHijri(formatStr: string, opts?: ConversionOptions): string;
}
}
// Augment the dayjs factory to expose the fromHijri static method.
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;
}
}
// Hijri-specific format tokens, ordered longest-first to prevent partial matches.
// After replacement, the remaining string is passed to Day.js .format() for
// standard tokens (YYYY, MM, DD, HH, mm, ss, etc.).
const HIJRI_TOKEN_RE = /iYYYY|iYY|iMMMM|iMMM|iMM|iM|iDD|iD|iEEEE|iEEE|iE|ioooo|iooo/g;
/**
* Wrap a plain string value in Day.js bracket-escape syntax so that
* `.format()` treats every character as a literal.
*
* 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).
*/
function lit(value: string): string {
return '[' + value.split(']').join('][') + ']';
}
const plugin: PluginFunc = (_option, dayjsClass, dayjsFactory) => {
// ------------------------------------------------------------------ //
// Instance methods //
// ------------------------------------------------------------------ //
dayjsClass.prototype.toHijri = function (opts?: ConversionOptions): HijriDate | null {
return toHijri(this.toDate(), opts);
};
dayjsClass.prototype.isValidHijri = function (opts?: ConversionOptions): boolean {
return this.toHijri(opts) !== null;
};
dayjsClass.prototype.hijriYear = function (opts?: ConversionOptions): number | null {
return this.toHijri(opts)?.hy ?? null;
};
dayjsClass.prototype.hijriMonth = function (opts?: ConversionOptions): number | null {
return this.toHijri(opts)?.hm ?? null;
};
dayjsClass.prototype.hijriDay = function (opts?: ConversionOptions): number | null {
return this.toHijri(opts)?.hd ?? null;
};
dayjsClass.prototype.formatHijri = function (
formatStr: string,
opts?: ConversionOptions,
): string {
const hijri = this.toHijri(opts);
if (!hijri) return '';
// Day.js .day() returns 0 (Sunday) ... 6 (Saturday), matching the index
// layout of hwLong, hwShort, and hwNumeric from hijri-core.
const dow = this.day();
const replaced = formatStr.replace(HIJRI_TOKEN_RE, (token) => {
switch (token) {
case 'iYYYY': return lit(String(hijri.hy).padStart(4, '0'));
case 'iYY': return lit(String(hijri.hy % 100).padStart(2, '0'));
case 'iMMMM': return lit(hmLong[hijri.hm - 1]);
case 'iMMM': return lit(hmMedium[hijri.hm - 1]);
case 'iMM': return lit(String(hijri.hm).padStart(2, '0'));
case 'iM': return lit(String(hijri.hm));
case 'iDD': return lit(String(hijri.hd).padStart(2, '0'));
case 'iD': return lit(String(hijri.hd));
case 'iEEEE': return lit(hwLong[dow]);
case 'iEEE': return lit(hwShort[dow]);
case 'iE': return lit(String(hwNumeric[dow]));
case 'ioooo':
case 'iooo': return lit('AH');
default: return token;
}
});
// Pass the processed string to Day.js .format() so standard tokens
// (YYYY, MM, DD, HH, mm, ss, etc.) resolve correctly. Hijri values are
// already wrapped in bracket-escaped literals and pass through untouched.
return this.format(replaced);
};
// ------------------------------------------------------------------ //
// Static method: dayjs.fromHijri(hy, hm, hd, opts?) //
// ------------------------------------------------------------------ //
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(dayjsFactory as any).fromHijri = (
hy: number,
hm: number,
hd: number,
opts?: ConversionOptions,
) => {
let greg: Date | null;
try {
greg = toGregorian(hy, hm, hd, opts);
} catch {
throw new Error(`Invalid or out-of-range Hijri date: ${hy}/${hm}/${hd}`);
}
if (!greg) {
throw new Error(`Invalid or out-of-range Hijri date: ${hy}/${hm}/${hd}`);
}
return dayjsFactory(greg);
};
};
export default plugin;
// Re-export hijri-core types for consumers who import from dayjs-hijri-plus.
export type { HijriDate, ConversionOptions, CalendarSystem } from './types';
// Re-export the registry API so callers can register custom calendar engines
// without adding hijri-core as a direct dependency.
export { registerCalendar, getCalendar, listCalendars } from 'hijri-core';

13
src/types.ts Normal file
View file

@ -0,0 +1,13 @@
import type { HijriDate, ConversionOptions } from 'hijri-core';
export type { HijriDate, ConversionOptions };
/** A registered calendar identifier. The built-in values are 'uaq' and 'fcna'. */
export type CalendarSystem = string;
/**
* Options passed to plugin methods. Inherits `calendar` from ConversionOptions
* so callers can switch between 'uaq' (default) and 'fcna'.
*/
export interface HijriPluginOptions extends ConversionOptions {
// calendar?: string (inherited — 'uaq' | 'fcna' | any registered calendar id)
}

68
test-cjs.cjs Normal file
View file

@ -0,0 +1,68 @@
'use strict';
const assert = require('node:assert/strict');
const dayjs = require('dayjs');
const { default: plugin } = require('./dist/index.cjs');
dayjs.extend(plugin);
let passed = 0;
let total = 0;
function test(name, fn) {
total++;
try {
fn();
console.log(`[${name}]... PASS`);
passed++;
} catch (err) {
console.error(`[${name}]... FAIL: ${err.message}`);
process.exit(1);
}
}
const D_RAMADAN_1444 = new Date(2023, 2, 23, 12);
const D_MUHARRAM_1446 = new Date(2024, 6, 7, 12);
test('plugin registers (CJS)', () => {
const d = dayjs(D_RAMADAN_1444);
assert.equal(typeof d.toHijri, 'function');
assert.equal(typeof d.formatHijri, 'function');
assert.equal(typeof dayjs.fromHijri, 'function');
});
test('toHijri (CJS): 2023-03-23 -> 1 Ramadan 1444', () => {
const h = dayjs(D_RAMADAN_1444).toHijri();
assert.deepEqual(h, { hy: 1444, hm: 9, hd: 1 });
});
test('toHijri (CJS): 2024-07-07 -> 1 Muharram 1446', () => {
const h = dayjs(D_MUHARRAM_1446).toHijri();
assert.deepEqual(h, { hy: 1446, hm: 1, hd: 1 });
});
test('fromHijri (CJS): 1444/9/1 -> 2023-03-23 (UTC)', () => {
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}`);
});
test('formatHijri (CJS): iYYYY-iMM-iDD', () => {
const result = dayjs(D_RAMADAN_1444).formatHijri('iYYYY-iMM-iDD');
assert.equal(result, '1444-09-01');
});
test('formatHijri (CJS): iMMMM -> Ramadan', () => {
const result = dayjs(D_RAMADAN_1444).formatHijri('iMMMM');
assert.equal(result, 'Ramadan');
});
test('isValidHijri (CJS): true for in-range date', () => {
assert.equal(dayjs(D_RAMADAN_1444).isValidHijri(), true);
});
test('fromHijri (CJS): throws for out-of-range date', () => {
assert.throws(() => dayjs.fromHijri(1301, 1, 1), /Invalid or out-of-range/);
});
console.log(`\n${passed}/${total} tests passed`);

113
test.mjs Normal file
View file

@ -0,0 +1,113 @@
import assert from 'node:assert/strict';
import dayjs from 'dayjs';
import plugin from './dist/index.mjs';
dayjs.extend(plugin);
let passed = 0;
let total = 0;
function test(name, fn) {
total++;
try {
fn();
console.log(`[${name}]... PASS`);
passed++;
} catch (err) {
console.error(`[${name}]... FAIL: ${err.message}`);
process.exit(1);
}
}
// Use noon to avoid UTC midnight boundary issues across timezones.
const D_RAMADAN_1444 = new Date(2023, 2, 23, 12); // 1 Ramadan 1444
const D_MUHARRAM_1446 = new Date(2024, 6, 7, 12); // 1 Muharram 1446
test('plugin registers on dayjs', () => {
const d = dayjs(D_RAMADAN_1444);
assert.equal(typeof d.toHijri, 'function');
assert.equal(typeof d.formatHijri, 'function');
assert.equal(typeof d.isValidHijri, 'function');
assert.equal(typeof d.hijriYear, 'function');
assert.equal(typeof d.hijriMonth, 'function');
assert.equal(typeof d.hijriDay, 'function');
assert.equal(typeof dayjs.fromHijri, 'function');
});
test('toHijri: 2023-03-23 -> 1 Ramadan 1444', () => {
const h = dayjs(D_RAMADAN_1444).toHijri();
assert.deepEqual(h, { hy: 1444, hm: 9, hd: 1 });
});
test('toHijri: 2024-07-07 -> 1 Muharram 1446', () => {
const h = dayjs(D_MUHARRAM_1446).toHijri();
assert.deepEqual(h, { hy: 1446, hm: 1, hd: 1 });
});
test('fromHijri: 1444/9/1 -> 2023-03-23 (UTC)', () => {
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}`);
});
test('fromHijri: 1446/1/1 -> 2024-07-07 (UTC)', () => {
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}`);
});
test('hijriYear/hijriMonth/hijriDay accessors on 1 Ramadan 1444', () => {
const d = dayjs(D_RAMADAN_1444);
assert.equal(d.hijriYear(), 1444);
assert.equal(d.hijriMonth(), 9);
assert.equal(d.hijriDay(), 1);
});
test('formatHijri: iYYYY-iMM-iDD on 1 Ramadan 1444', () => {
const result = dayjs(D_RAMADAN_1444).formatHijri('iYYYY-iMM-iDD');
assert.equal(result, '1444-09-01');
});
test('formatHijri: iMMMM -> Ramadan', () => {
const result = dayjs(D_RAMADAN_1444).formatHijri('iMMMM');
assert.equal(result, 'Ramadan');
});
test('formatHijri: iEEEE on 2023-03-23 (Thursday)', () => {
const result = dayjs(D_RAMADAN_1444).formatHijri('iEEEE');
// 2023-03-23 is a Thursday; hwLong[4] = 'Yawm al-Khamis'
assert.equal(result, 'Yawm al-Khamis');
});
test('formatHijri: ioooo -> AH', () => {
const result = dayjs(D_RAMADAN_1444).formatHijri('ioooo');
assert.equal(result, 'AH');
});
test('FCNA calendar: toHijri returns a valid HijriDate', () => {
const h = dayjs(D_RAMADAN_1444).toHijri({ calendar: 'fcna' });
assert.notEqual(h, null);
assert.equal(typeof h.hy, 'number');
assert.equal(typeof h.hm, 'number');
assert.equal(typeof h.hd, 'number');
});
test('isValidHijri returns true for in-range date', () => {
const valid = dayjs(D_RAMADAN_1444).isValidHijri();
assert.equal(valid, true);
});
test('fromHijri throws for out-of-range UAQ date', () => {
// 1301 is before the UAQ table begins (coverage starts at 1318)
assert.throws(() => dayjs.fromHijri(1301, 1, 1), /Invalid or out-of-range/);
});
test('formatHijri passthrough: iYYYY YYYY contains both Hijri and Gregorian year', () => {
const result = dayjs(D_RAMADAN_1444).formatHijri('iYYYY YYYY');
// Should contain '1444' (Hijri) and '2023' (Gregorian)
assert.ok(result.includes('1444'), `Expected Hijri year 1444 in: ${result}`);
assert.ok(result.includes('2023'), `Expected Gregorian year 2023 in: ${result}`);
});
console.log(`\n${passed}/${total} tests passed`);

16
tsconfig.json Normal file
View file

@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"rootDir": "src",
"types": ["node"]
},
"include": ["src"]
}

17
tsup.config.ts Normal file
View file

@ -0,0 +1,17 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
clean: true,
outDir: 'dist',
splitting: false,
sourcemap: true,
target: 'es2020',
platform: 'node',
external: ['dayjs', 'hijri-core'],
outExtension({ format }) {
return { js: format === 'esm' ? '.mjs' : '.cjs' };
},
});