chore: E6 polish wiki content + ADR-015 CI updates (P1)

This commit is contained in:
Aric Camarata 2026-05-29 07:15:47 -04:00
parent 4c7ab92727
commit ff3b681238
12 changed files with 533 additions and 22 deletions

29
.github/wiki/CODE_OF_CONDUCT.md vendored Normal file
View file

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

49
.github/wiki/CONTRIBUTING.md vendored Normal file
View file

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

30
.github/wiki/SECURITY.md vendored Normal file
View file

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

1
.github/wiki/_Footer.md vendored Normal file
View file

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

19
.github/wiki/_Sidebar.md vendored Normal file
View file

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

51
.github/wiki/benchmarks/index.md vendored Normal file
View file

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

84
.github/wiki/examples/basic-usage.md vendored Normal file
View file

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

97
.github/wiki/examples/formatting.md vendored Normal file
View file

@ -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 (
<time dateTime={gregorian}>
{hijriFormatted}
</time>
);
}
// Usage: <HijriDate date={new Date('2023-03-23')} />
// Renders: <time datetime="2023-03-23">1 Ramadan 1444</time>
```

76
.github/wiki/guides/advanced.md vendored Normal file
View file

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

82
.github/wiki/guides/quickstart.md vendored Normal file
View file

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

View file

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

View file

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