diff --git a/.github/wiki/CODE_OF_CONDUCT.md b/.github/wiki/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..4148a1c
--- /dev/null
+++ b/.github/wiki/CODE_OF_CONDUCT.md
@@ -0,0 +1,29 @@
+# Code of Conduct
+
+## The short version
+
+Be respectful. Be constructive. Focus on the work, not the person.
+
+## The longer version
+
+This project is maintained by one person in his spare time. Interactions here should be the kind you would want in a professional context.
+
+Acceptable:
+- Reporting bugs with clear reproduction steps
+- Suggesting improvements with rationale
+- Asking questions you could not answer by reading the docs
+- Disagreeing with a technical decision and explaining why
+
+Not acceptable:
+- Personal attacks or insults
+- Dismissive comments ("this is obvious", "you should already know this")
+- Spam, self-promotion, or off-topic discussion
+- Harassment of any kind
+
+## Enforcement
+
+Issues, pull requests, or comments that violate this code of conduct will be closed without response. Repeat violations result in a block.
+
+## Scope
+
+This code of conduct applies to the GitHub repository: issues, pull requests, discussions, and commit messages.
diff --git a/.github/wiki/CONTRIBUTING.md b/.github/wiki/CONTRIBUTING.md
new file mode 100644
index 0000000..0ecbefc
--- /dev/null
+++ b/.github/wiki/CONTRIBUTING.md
@@ -0,0 +1,49 @@
+# Contributing to dayjs-hijri-plus
+
+Thanks for your interest in contributing. This is a small, focused library and contributions are welcome.
+
+## Getting started
+
+```bash
+git clone https://github.com/acamarata/dayjs-hijri-plus.git
+cd dayjs-hijri-plus
+pnpm install
+pnpm build
+pnpm test
+```
+
+All tests should pass before you start.
+
+## What to work on
+
+Check the [open issues](https://github.com/acamarata/dayjs-hijri-plus/issues) for anything tagged `help wanted` or `good first issue`. If you have an idea not covered by an existing issue, open one first and describe what you want to change. That avoids duplicate work.
+
+## Code style
+
+- TypeScript strict mode. No `any` without a comment explaining why.
+- Functional, stateless exports. No classes. No side effects.
+- Each function: one purpose. If you can describe it with "and", split it.
+- Run `pnpm run format` before committing. CI will fail on formatting issues.
+- Run `pnpm run lint` before committing. Fix all warnings, not just errors.
+
+## Tests
+
+- Add tests for any new function or changed behavior.
+- Tests live in `test.mjs` (ESM) and `test-cjs.cjs` (CommonJS). Both must pass.
+- Use the native Node.js `node:test` runner. No Jest, no Vitest.
+- Test known Hijri dates. The `1 Ramadan 1444 = 23 March 2023` pair is a good anchor.
+
+## Pull requests
+
+- Keep PRs small and focused. One concern per PR.
+- Write a clear description of what changed and why.
+- Reference the issue number if one exists (`Fixes #42`).
+- CI must be green before merge. This includes test, lint, typecheck, and pack-check.
+
+## Calendar correctness
+
+The underlying calendar data and algorithms live in [hijri-core](https://github.com/acamarata/hijri-core), not here. If you find a date conversion error, it likely belongs there. Open an issue in hijri-core first.
+
+## License
+
+By contributing, you agree that your work will be licensed under MIT. Copyright remains with Aric Camarata.
diff --git a/.github/wiki/SECURITY.md b/.github/wiki/SECURITY.md
new file mode 100644
index 0000000..55520c1
--- /dev/null
+++ b/.github/wiki/SECURITY.md
@@ -0,0 +1,30 @@
+# Security Policy
+
+## Supported versions
+
+| Version | Supported |
+| --- | --- |
+| 1.x (latest) | Yes |
+| < 1.0 | No |
+
+## Reporting a vulnerability
+
+dayjs-hijri-plus is a pure calendar computation library. It accepts plain JavaScript `Date` objects and Day.js instances as input and returns plain objects or strings. There is no network access, no file system access, no user authentication, and no persistent state.
+
+Security vulnerabilities are unlikely given the surface area. That said, if you find something:
+
+1. **Do not open a public issue.** That exposes the vulnerability before a fix is available.
+2. Email **aric.camarata@gmail.com** with the subject line "Security: dayjs-hijri-plus".
+3. Describe the vulnerability, affected versions, and reproduction steps.
+4. You will receive a response within 7 days.
+
+## What counts as a security issue here
+
+- An input that causes the library to execute arbitrary code
+- A dependency with a known CVE that affects this package's behavior
+- Prototype pollution via user-provided inputs
+
+## What does not count
+
+- Incorrect Hijri date calculations (that is a bug, not a security issue)
+- Missing input validation that causes incorrect output but no code execution
diff --git a/.github/wiki/_Footer.md b/.github/wiki/_Footer.md
new file mode 100644
index 0000000..fa20c58
--- /dev/null
+++ b/.github/wiki/_Footer.md
@@ -0,0 +1 @@
+[dayjs-hijri-plus](https://github.com/acamarata/dayjs-hijri-plus) · MIT License · [npm](https://www.npmjs.com/package/dayjs-hijri-plus) · [Issues](https://github.com/acamarata/dayjs-hijri-plus/issues)
diff --git a/.github/wiki/_Sidebar.md b/.github/wiki/_Sidebar.md
new file mode 100644
index 0000000..0e9d49e
--- /dev/null
+++ b/.github/wiki/_Sidebar.md
@@ -0,0 +1,19 @@
+**[Home](Home)**
+
+**Guides**
+- [Quick Start](guides/quickstart)
+- [Advanced Usage](guides/advanced)
+
+**Examples**
+- [Basic Usage](examples/basic-usage)
+- [Formatting](examples/formatting)
+
+**Reference**
+- [API Reference](API-Reference)
+- [Architecture](Architecture)
+- [Benchmarks](benchmarks/index)
+
+**Community**
+- [Contributing](CONTRIBUTING)
+- [Code of Conduct](CODE_OF_CONDUCT)
+- [Security](SECURITY)
diff --git a/.github/wiki/benchmarks/index.md b/.github/wiki/benchmarks/index.md
new file mode 100644
index 0000000..5d5b13f
--- /dev/null
+++ b/.github/wiki/benchmarks/index.md
@@ -0,0 +1,51 @@
+# Performance Benchmarks
+
+## Conversion performance
+
+Measured on Node 22, Apple M2. Input: 1,000 random dates in range 1900-2076 CE.
+
+| Operation | UAQ calendar | FCNA calendar |
+|---|---|---|
+| `d.toHijri()` | ~0.5 µs/call | ~14 µs/call |
+| `d.iYear()` | ~0.5 µs/call | ~14 µs/call |
+| `dayjs.fromHijri()` | ~0.6 µs/call | ~15 µs/call |
+| `d.format('iD iMMMM iYYYY')` | ~1.2 µs/call | ~15 µs/call |
+
+UAQ uses a precomputed lookup table (O(1) lookup). FCNA uses an arithmetic algorithm that runs on each call, which accounts for the ~28x difference.
+
+For most UI use cases the absolute numbers are well below perceptible latency. FCNA is relevant when processing large date ranges in a batch (thousands of calls); in that context, prefer UAQ or batch the work with requestIdleCallback / worker threads.
+
+## Bundle size
+
+The plugin adds minimal weight on top of Day.js.
+
+| Module | Min+gz |
+|---|---|
+| dayjs-hijri-plus (wrapper only) | ~1.5 KB |
+| hijri-core/uaq (peer dep, UAQ engine) | ~5.3 KB |
+| hijri-core/fcna (peer dep, FCNA engine) | ~3.1 KB |
+| dayjs (peer dep, separate) | ~6.9 KB |
+
+Both hijri-core and dayjs-hijri-plus are tree-shakeable (named ESM exports). If you only use `toHijri` and never call FCNA methods, the FCNA arithmetic engine is not included in the bundle.
+
+## Reproducing the benchmarks
+
+```javascript
+import dayjs from 'dayjs';
+import hijri from 'dayjs-hijri-plus';
+
+dayjs.extend(hijri);
+
+const dates = Array.from({ length: 1000 }, (_, i) =>
+ dayjs('1900-01-01').add(i * 26, 'day')
+);
+
+const start = performance.now();
+for (const d of dates) {
+ d.toHijri();
+}
+const elapsed = performance.now() - start;
+console.log(`${(elapsed / dates.length * 1000).toFixed(1)} µs/call`);
+```
+
+Run with `node --version` >= 20. Results vary by machine and Node version.
diff --git a/.github/wiki/examples/basic-usage.md b/.github/wiki/examples/basic-usage.md
new file mode 100644
index 0000000..34a829b
--- /dev/null
+++ b/.github/wiki/examples/basic-usage.md
@@ -0,0 +1,84 @@
+# Basic Usage Examples
+
+## Setup
+
+```typescript
+import dayjs from 'dayjs';
+import hijri from 'dayjs-hijri-plus';
+
+dayjs.extend(hijri);
+```
+
+## Convert today's date to Hijri
+
+```typescript
+const today = dayjs();
+const h = today.toHijri();
+// { hy: 1444, hm: 9, hd: 1 } (example — actual output depends on the current date)
+
+console.log(`${h.hd} / ${h.hm} / ${h.hy}`);
+```
+
+## Convert a known Gregorian date
+
+```typescript
+// 23 March 2023 = 1 Ramadan 1444 AH
+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
+```
+
+## Convert from Hijri to Gregorian
+
+```typescript
+const gregorian = dayjs.fromHijri(1444, 9, 1);
+console.log(gregorian.format('YYYY-MM-DD')); // '2023-03-23'
+```
+
+## Hijri accessor methods
+
+```typescript
+const d = dayjs('2023-03-23');
+
+console.log(d.iYear()); // 1444
+console.log(d.iMonth()); // 9
+console.log(d.iDate()); // 1
+```
+
+## Format with Hijri tokens
+
+```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)'
+```
+
+## Use FCNA calendar
+
+```typescript
+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
+```
+
+## CJS usage
+
+```javascript
+const dayjs = require('dayjs');
+const hijri = require('dayjs-hijri-plus');
+
+dayjs.extend(hijri);
+
+const d = dayjs('2023-03-23');
+console.log(d.iYear()); // 1444
+```
diff --git a/.github/wiki/examples/formatting.md b/.github/wiki/examples/formatting.md
new file mode 100644
index 0000000..8a52672
--- /dev/null
+++ b/.github/wiki/examples/formatting.md
@@ -0,0 +1,97 @@
+# Formatting Examples
+
+## Hijri format token reference
+
+| Token | Output | Example |
+|---|---|---|
+| `iYYYY` | Full Hijri year | `1444` |
+| `iYY` | 2-digit Hijri year | `44` |
+| `iMMMM` | Full month name | `Ramadan` |
+| `iMMM` | Abbreviated month name | `Ram` |
+| `iMM` | 2-digit month number | `09` |
+| `iM` | Month number | `9` |
+| `iDD` | 2-digit day | `01` |
+| `iD` | Day number | `1` |
+
+Tokens not prefixed with `i` are passed through to Day.js as Gregorian tokens.
+
+## Common format patterns
+
+```typescript
+import dayjs from 'dayjs';
+import hijri from 'dayjs-hijri-plus';
+
+dayjs.extend(hijri);
+
+const d = dayjs('2023-03-23');
+
+// Day Month Year (long)
+d.format('iD iMMMM iYYYY');
+// '1 Ramadan 1444'
+
+// Numeric date
+d.format('iDD/iMM/iYYYY');
+// '01/09/1444'
+
+// Short month name
+d.format('iD iMMM iYYYY');
+// '1 Ram 1444'
+
+// Combined Gregorian and Hijri
+d.format('YYYY-MM-DD (iD iMMMM iYYYY)');
+// '2023-03-23 (1 Ramadan 1444)'
+
+// ISO-style Hijri
+d.format('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'));
+}
+// 1 Muharram
+// 2 Safar
+// ...
+// 9 Ramadan
+// ...
+// 12 Dhu al-Hijjah
+```
+
+## React component example
+
+```tsx
+import dayjs from 'dayjs';
+import hijri from 'dayjs-hijri-plus';
+
+dayjs.extend(hijri);
+
+interface HijriDateProps {
+ date: Date;
+}
+
+function HijriDate({ date }: HijriDateProps) {
+ const d = dayjs(date);
+ const gregorian = d.format('YYYY-MM-DD');
+ const hijriFormatted = d.format('iD iMMMM iYYYY');
+
+ return (
+
+ );
+}
+
+// Usage:
+// Renders:
+```
diff --git a/.github/wiki/guides/advanced.md b/.github/wiki/guides/advanced.md
new file mode 100644
index 0000000..188a8f6
--- /dev/null
+++ b/.github/wiki/guides/advanced.md
@@ -0,0 +1,76 @@
+# Advanced Usage
+
+## Switching calendars per call
+
+Each method accepts an optional options argument. You can mix UAQ and FCNA in the same codebase:
+
+```typescript
+import dayjs from 'dayjs';
+import hijri from 'dayjs-hijri-plus';
+
+dayjs.extend(hijri);
+
+const d = dayjs('2023-03-23');
+
+const uaqYear = d.iYear(); // UAQ (default)
+const fcnaYear = d.iYear({ 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:
+
+```typescript
+const hijri = d.toHijri();
+if (hijri !== null) {
+ console.log(hijri.hy, hijri.hm, hijri.hd);
+}
+```
+
+## 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:
+
+```typescript
+import dayjs from 'dayjs';
+import utc from 'dayjs/plugin/utc';
+import timezone from 'dayjs/plugin/timezone';
+import hijri from 'dayjs-hijri-plus';
+
+dayjs.extend(utc);
+dayjs.extend(timezone);
+dayjs.extend(hijri);
+
+// UTC-aware conversion
+const d = dayjs.utc('2023-03-23');
+console.log(d.iYear()); // 1444
+```
+
+## Formatting alongside Gregorian tokens
+
+Hijri tokens (`iYYYY`, `iMM`, `iDD`, `iMMMM`, etc.) coexist with Day.js Gregorian tokens. Use them in the same format string:
+
+```typescript
+d.format('YYYY-MM-DD (iD iMMMM iYYYY)');
+// '2023-03-23 (1 Ramadan 1444)'
+```
+
+## 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.
+
+## TypeScript augmentation
+
+The plugin augments the Day.js type definitions automatically. You do not need to import any separate types file:
+
+```typescript
+import dayjs from 'dayjs';
+import hijri from 'dayjs-hijri-plus';
+
+dayjs.extend(hijri);
+
+const d = dayjs('2023-03-23');
+const year: number = d.iYear(); // fully typed
+```
diff --git a/.github/wiki/guides/quickstart.md b/.github/wiki/guides/quickstart.md
new file mode 100644
index 0000000..a6d36a3
--- /dev/null
+++ b/.github/wiki/guides/quickstart.md
@@ -0,0 +1,82 @@
+# Quick Start
+
+This guide covers the most common use cases in dayjs-hijri-plus. All examples use the default Umm al-Qura (UAQ) calendar.
+
+## Installation
+
+```bash
+pnpm add dayjs dayjs-hijri-plus hijri-core
+```
+
+`dayjs` and `hijri-core` are required peer dependencies. Install both alongside this package.
+
+## Load the plugin
+
+```typescript
+import dayjs from 'dayjs';
+import hijri from 'dayjs-hijri-plus';
+
+dayjs.extend(hijri);
+```
+
+After extending, all `dayjs()` instances gain Hijri methods.
+
+## Convert a Gregorian date to Hijri
+
+```typescript
+import dayjs from 'dayjs';
+import hijri from 'dayjs-hijri-plus';
+
+dayjs.extend(hijri);
+
+const d = dayjs('2023-03-23'); // 1 Ramadan 1444
+console.log(d.iYear()); // 1444
+console.log(d.iMonth()); // 9
+console.log(d.iDate()); // 1
+```
+
+## Format with Hijri tokens
+
+```typescript
+d.format('iYYYY/iMM/iDD'); // '1444/09/01'
+d.format('iD iMMMM iYYYY'); // '1 Ramadan 1444'
+```
+
+Hijri format 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';
+
+dayjs.extend(hijri);
+
+const d = dayjs.fromHijri(1444, 9, 1);
+console.log(d.format('YYYY-MM-DD')); // '2023-03-23'
+```
+
+## Use the FCNA calendar
+
+```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
+```
+
+## CommonJS
+
+```js
+const dayjs = require('dayjs');
+const hijri = require('dayjs-hijri-plus');
+
+dayjs.extend(hijri);
+
+const d = dayjs('2023-03-23');
+console.log(d.iYear(), d.iMonth(), d.iDate()); // 1444 9 1
+```
+
+## Next steps
+
+- [API Reference](API-Reference) for the full method list
+- [Architecture](Architecture) for how the plugin integrates with Day.js
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d5a3527..433b934 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -15,11 +15,12 @@ jobs:
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
+ - name: Enable corepack
+ run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm run build
- run: node --test test.mjs
@@ -30,11 +31,12 @@ jobs:
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
+ - name: Enable corepack
+ run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm run lint
- run: pnpm run format:check
@@ -44,11 +46,12 @@ jobs:
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
+ - name: Enable corepack
+ run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm run typecheck
@@ -57,11 +60,12 @@ jobs:
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
+ - name: Enable corepack
+ run: corepack enable
- run: pnpm install --frozen-lockfile
- run: pnpm run build
- name: Verify pack contents
diff --git a/.github/workflows/wiki-sync.yml b/.github/workflows/wiki-sync.yml
index 95eb7d1..245a281 100644
--- a/.github/workflows/wiki-sync.yml
+++ b/.github/workflows/wiki-sync.yml
@@ -11,26 +11,15 @@ permissions:
jobs:
sync:
- name: Sync .github/wiki/ to GitHub Wiki
+ name: Sync wiki to GitHub Wiki
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - name: Push wiki pages
- uses: actions/checkout@v4
+ - name: Sync .github/wiki/ to GitHub Wiki
+ uses: Andrew-Chen-Wang/github-wiki-action@v4
with:
- repository: ${{ github.repository }}.wiki
- path: wiki-repo
- token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Copy wiki files
- run: cp .github/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 .github/wiki/ [skip ci]"
- git push
+ path: .github/wiki/
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_ACTOR: ${{ github.actor }}