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

This commit is contained in:
Aric Camarata 2026-05-29 07:15:50 -04:00
parent 2289dd3718
commit 250dda20d4
11 changed files with 453 additions and 5 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.

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

@ -0,0 +1,54 @@
# Contributing to temporal-hijri
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/temporal-hijri.git
cd temporal-hijri
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/temporal-hijri/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.
- Class-based implementation following the Temporal Calendar Protocol interface.
- Each class method: 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 method 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.
- The polyfill (`@js-temporal/polyfill`) must be installed for tests to run.
## Temporal specification
This package implements the [TC39 Temporal proposal](https://tc39.es/proposal-temporal/) Calendar Protocol (Stage 3). Before adding or changing behavior, read the relevant section of the specification. Deviations from the spec are not accepted unless the spec itself is ambiguous.
## 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
temporal-hijri is a pure calendar computation library. It implements the Temporal Calendar Protocol over Hijri calendar data. 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: temporal-hijri".
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 @@
[temporal-hijri](https://github.com/acamarata/temporal-hijri) · MIT License · [npm](https://www.npmjs.com/package/temporal-hijri) · [Issues](https://github.com/acamarata/temporal-hijri/issues)

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

@ -0,0 +1,18 @@
**[Home](Home)**
**Guides**
- [Quick Start](guides/quickstart)
- [Advanced Usage](guides/advanced)
**Examples**
- [Basic Usage](examples/basic-usage)
**Reference**
- [API Reference](API-Reference)
- [Architecture](Architecture)
- [Benchmarks](benchmarks/index)
**Community**
- [Contributing](CONTRIBUTING)
- [Code of Conduct](CODE_OF_CONDUCT)
- [Security](SECURITY)

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

@ -0,0 +1,47 @@
# 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 |
|---|---|---|
| `uaqCalendar.year(date)` | ~0.5 µs/call | ~14 µs/call |
| `uaqCalendar.dateFromFields(fields)` | ~0.7 µs/call | ~15 µs/call |
| `uaqCalendar.dateUntil(d1, d2)` | ~1.1 µs/call | ~16 µs/call |
| `uaqCalendar.dateAdd(date, duration)` | ~1.3 µs/call | ~17 µs/call |
UAQ uses a precomputed lookup table (O(1) lookup). FCNA uses an arithmetic algorithm per call, which accounts for the ~26x difference.
The Temporal polyfill itself adds overhead on top of these numbers. With native Temporal support (future Node.js versions and browsers), the overhead will be lower.
## Bundle size
| Module | Min+gz |
|---|---|
| temporal-hijri (wrapper only) | ~1.4 KB |
| hijri-core/uaq (peer dep, UAQ engine) | ~5.3 KB |
| hijri-core/fcna (peer dep, FCNA engine) | ~3.1 KB |
| @js-temporal/polyfill (peer dep, optional) | ~39 KB |
When native `Temporal` is available in the runtime, the polyfill is not needed, which removes its bundle cost entirely.
## Reproducing the benchmarks
```javascript
import { Temporal } from '@js-temporal/polyfill';
import { uaqCalendar } from 'temporal-hijri';
const dates = Array.from({ length: 1000 }, (_, i) =>
Temporal.PlainDate.from('1900-01-01').add({ days: i * 26 })
);
const start = performance.now();
for (const d of dates) {
uaqCalendar.year(d);
}
const elapsed = performance.now() - start;
console.log(`${(elapsed / dates.length * 1000).toFixed(1)} µs/call`);
```
Run with `node --version` >= 20.

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

@ -0,0 +1,76 @@
# Basic Usage Examples
## Setup
```typescript
import { Temporal } from '@js-temporal/polyfill';
import { uaqCalendar } from 'temporal-hijri';
```
## Convert a Gregorian date to Hijri
```typescript
// 23 March 2023 = 1 Ramadan 1444 AH
const isoDate = Temporal.PlainDate.from('2023-03-23');
console.log(uaqCalendar.year(isoDate)); // 1444
console.log(uaqCalendar.month(isoDate)); // 9 (Ramadan is the 9th month)
console.log(uaqCalendar.day(isoDate)); // 1
```
## Read today's Hijri date
```typescript
import { Temporal } from '@js-temporal/polyfill';
import { uaqCalendar } from 'temporal-hijri';
const today = Temporal.Now.plainDateISO();
const hy = uaqCalendar.year(today);
const hm = uaqCalendar.month(today);
const hd = uaqCalendar.day(today);
console.log(`${hd} / ${hm} / ${hy}`);
```
## Create a Hijri date and convert to ISO
```typescript
const ramadan1 = uaqCalendar.dateFromFields({ year: 1444, month: 9, day: 1 });
console.log(ramadan1.toString()); // '2023-03-23'
```
## Add Hijri months
```typescript
const start = uaqCalendar.dateFromFields({ year: 1444, month: 9, day: 1 });
const twoMonthsLater = uaqCalendar.dateAdd(start, new Temporal.Duration(0, 0, 0, 2 * 29));
// Durations use days — calculate from expected month length
// Or use dateUntil to measure between two dates
const end = Temporal.PlainDate.from('2023-05-20');
const diff = uaqCalendar.dateUntil(start, end, { largestUnit: 'months' });
console.log(diff.months, diff.days);
```
## Use FCNA calendar
```typescript
import { fcnaCalendar } from 'temporal-hijri';
const isoDate = Temporal.PlainDate.from('2023-03-23');
console.log(fcnaCalendar.year(isoDate)); // 1444
console.log(fcnaCalendar.month(isoDate)); // 9
console.log(fcnaCalendar.day(isoDate)); // 1
// Near month boundaries, UAQ and FCNA may differ by one day
```
## CJS usage
```javascript
const { Temporal } = require('@js-temporal/polyfill');
const { uaqCalendar } = require('temporal-hijri');
const d = Temporal.PlainDate.from('2023-03-23');
console.log(uaqCalendar.year(d)); // 1444
```

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

@ -0,0 +1,91 @@
# Advanced Usage
## Custom calendar engines
Any engine registered in hijri-core can be wrapped in a Temporal calendar:
```typescript
import { HijriCalendar } from 'temporal-hijri';
import { registerCalendar, getCalendar } from 'hijri-core';
import type { CalendarEngine } from 'hijri-core';
const myEngine: CalendarEngine = {
id: 'local-sighting',
toHijri(date) { /* ... */ return { hy, hm, hd }; },
toGregorian(hy, hm, hd) { /* ... */ return new Date(...); },
isValid(hy, hm, hd) { /* ... */ return true; },
daysInMonth(hy, hm) { /* ... */ return 29; },
};
registerCalendar('local-sighting', myEngine);
const cal = new HijriCalendar(getCalendar('local-sighting'));
// cal.id === 'hijri-local-sighting'
```
## DateUntil with different largestUnit values
`dateUntil` respects the `largestUnit` option:
```typescript
import { Temporal } from '@js-temporal/polyfill';
import { uaqCalendar } from 'temporal-hijri';
const start = Temporal.PlainDate.from('2023-01-01');
const end = Temporal.PlainDate.from('2023-12-31');
const inYears = uaqCalendar.dateUntil(start, end, { largestUnit: 'years' });
const inMonths = uaqCalendar.dateUntil(start, end, { largestUnit: 'months' });
const inDays = uaqCalendar.dateUntil(start, end, { largestUnit: 'days' });
console.log(inYears.years, inYears.months, inYears.days);
console.log(inMonths.months, inMonths.days);
console.log(inDays.days);
```
Note: the result measures in Hijri units. One Hijri year is 354 or 355 days, so `inYears.days` may differ from what you would expect in Gregorian.
## PlainYearMonth and PlainMonthDay
```typescript
import { Temporal } from '@js-temporal/polyfill';
import { uaqCalendar } from 'temporal-hijri';
// Year-month in Hijri
const ym = uaqCalendar.yearMonthFromFields({ year: 1444, month: 9 });
console.log(ym.toString()); // ISO year-month of 1 Ramadan 1444
// Month-day in Hijri
const md = uaqCalendar.monthDayFromFields({ month: 9, day: 1 });
console.log(md.toString()); // ISO month-day of Ramadan 1st
```
## Out-of-range behavior
UAQ covers 1318-1500 AH (1900-2076 CE). Requesting dates outside that range throws `RangeError`:
```typescript
const earlyDate = Temporal.PlainDate.from('1800-01-01');
try {
uaqCalendar.year(earlyDate); // throws RangeError
} catch (e) {
if (e instanceof RangeError) {
// Use FCNA for unbounded coverage
import { fcnaCalendar } from 'temporal-hijri';
console.log(fcnaCalendar.year(earlyDate));
}
}
```
## Using with native Temporal
When native `Temporal` is available (future Node.js or browsers), you can use it directly without the polyfill:
```typescript
// No import from @js-temporal/polyfill
import { uaqCalendar } from 'temporal-hijri';
const isoDate = Temporal.PlainDate.from('2023-03-23');
console.log(uaqCalendar.year(isoDate)); // 1444
```
The `uaqCalendar` and `fcnaCalendar` objects implement the `Temporal.CalendarProtocol` interface and work with any spec-conforming implementation.

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

@ -0,0 +1,98 @@
# Quick Start
This guide covers the most common use cases in temporal-hijri. All examples use `uaqCalendar` (Umm al-Qura). For FCNA/ISNA output, substitute `fcnaCalendar`.
## Installation
```bash
pnpm add temporal-hijri hijri-core @js-temporal/polyfill
```
`hijri-core` is required. `@js-temporal/polyfill` is required in environments without native `Temporal` support. In environments with native Temporal (Node 22+ with the flag, or future standard support), omit the polyfill.
## Import
```typescript
import { Temporal } from '@js-temporal/polyfill'; // or use native Temporal
import { uaqCalendar } from 'temporal-hijri';
```
## Convert an ISO date to Hijri
```typescript
const isoDate = Temporal.PlainDate.from('2023-03-23');
console.log(uaqCalendar.year(isoDate)); // 1444
console.log(uaqCalendar.month(isoDate)); // 9
console.log(uaqCalendar.day(isoDate)); // 1
console.log(uaqCalendar.monthCode(isoDate)); // 'M09'
```
## Convert Hijri coordinates to ISO
```typescript
const ramadan = uaqCalendar.dateFromFields({ year: 1444, month: 9, day: 1 });
console.log(ramadan.toString()); // '2023-03-23'
```
## Date arithmetic in Hijri space
```typescript
const { Duration } = Temporal;
const isoDate = Temporal.PlainDate.from('2023-03-23');
// Add one Hijri month
const nextMonth = uaqCalendar.dateAdd(isoDate, new Duration(0, 1));
console.log(uaqCalendar.month(nextMonth)); // 10 (Shawwal)
console.log(nextMonth.toString()); // '2023-04-21'
// Get the difference between two dates
const earlier = Temporal.PlainDate.from('2023-01-01');
const later = Temporal.PlainDate.from('2023-03-23');
const diff = uaqCalendar.dateUntil(earlier, later, { largestUnit: 'months' });
console.log(diff.months); // 2 (in Hijri months)
```
## Use the FCNA calendar
```typescript
import { fcnaCalendar } from 'temporal-hijri';
const isoDate = Temporal.PlainDate.from('2023-03-23');
console.log(fcnaCalendar.year(isoDate)); // 1444
console.log(fcnaCalendar.month(isoDate)); // 9 or may differ by 1 near month start
```
## Singletons vs classes
The package exports convenience singletons for the common case:
```typescript
import { uaqCalendar, fcnaCalendar } from 'temporal-hijri';
```
If you need to construct a calendar from a custom hijri-core engine:
```typescript
import { HijriCalendar } from 'temporal-hijri';
import { registerCalendar, getCalendar } from 'hijri-core';
registerCalendar('my-engine', myEngine);
const cal = new HijriCalendar(getCalendar('my-engine'));
```
## CommonJS
```js
const { Temporal } = require('@js-temporal/polyfill');
const { uaqCalendar } = require('temporal-hijri');
const isoDate = Temporal.PlainDate.from('2023-03-23');
console.log(uaqCalendar.year(isoDate)); // 1444
```
## Next steps
- [API Reference](API-Reference) for all calendar protocol methods
- [Architecture](Architecture) for how the Temporal Calendar Protocol is implemented

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

@ -4,7 +4,7 @@
# temporal-hijri
Temporal Calendar Protocol implementation for the Hijri calendar. Works with the TC39 Temporal proposal and `@js-temporal/polyfill`.
Temporal Calendar Protocol implementation for the Hijri calendar. Works with the TC39 Temporal proposal (Stage 3) and `@js-temporal/polyfill`.
Provides `UaqCalendar` (Umm al-Qura) and `FcnaCalendar` (FCNA/ISNA) as plug-in calendars for `Temporal.PlainDate` and related types. The underlying conversion logic comes from [hijri-core](https://github.com/acamarata/hijri-core), a zero-dependency Hijri engine with table-driven UAQ data and astronomical FCNA calculations.