From 62c247b9427e1605d1fd1c49a8191dd1ff955877 Mon Sep 17 00:00:00 2001 From: Aric Camarata Date: Fri, 29 May 2026 07:15:49 -0400 Subject: [PATCH] chore: E6 polish wiki content + ADR-015 CI updates (P1) --- .github/wiki/CODE_OF_CONDUCT.md | 29 ++++++++++ .github/wiki/CONTRIBUTING.md | 53 +++++++++++++++++ .github/wiki/SECURITY.md | 30 ++++++++++ .github/wiki/_Footer.md | 1 + .github/wiki/_Sidebar.md | 19 +++++++ .github/wiki/benchmarks/index.md | 49 ++++++++++++++++ .github/wiki/examples/basic-usage.md | 75 ++++++++++++++++++++++++ .github/wiki/examples/formatting.md | 83 +++++++++++++++++++++++++++ .github/wiki/guides/advanced.md | 61 ++++++++++++++++++++ .github/wiki/guides/quickstart.md | 85 ++++++++++++++++++++++++++++ .github/workflows/ci.yml | 12 ++-- .github/workflows/wiki-sync.yml | 8 +-- README.md | 4 ++ 13 files changed, 501 insertions(+), 8 deletions(-) create mode 100644 .github/wiki/CODE_OF_CONDUCT.md create mode 100644 .github/wiki/CONTRIBUTING.md create mode 100644 .github/wiki/SECURITY.md create mode 100644 .github/wiki/_Footer.md create mode 100644 .github/wiki/_Sidebar.md create mode 100644 .github/wiki/benchmarks/index.md create mode 100644 .github/wiki/examples/basic-usage.md create mode 100644 .github/wiki/examples/formatting.md create mode 100644 .github/wiki/guides/advanced.md create mode 100644 .github/wiki/guides/quickstart.md 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..f36ee96 --- /dev/null +++ b/.github/wiki/CONTRIBUTING.md @@ -0,0 +1,53 @@ +# Contributing to moment-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/moment-hijri-plus.git +cd moment-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/moment-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. + +## Note on Moment.js + +Moment.js is in maintenance mode. The authors recommend Luxon, Day.js, or date-fns for new projects. This package targets existing codebases already using Moment.js. Bug fixes are welcome; new features that require significant new Moment.js integration are unlikely to be accepted. + +## 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..38a36a0 --- /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 + +moment-hijri-plus is a pure calendar computation library. It accepts Moment.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: moment-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..62ce76f --- /dev/null +++ b/.github/wiki/_Footer.md @@ -0,0 +1 @@ +[moment-hijri-plus](https://github.com/acamarata/moment-hijri-plus) · MIT License · [npm](https://www.npmjs.com/package/moment-hijri-plus) · [Issues](https://github.com/acamarata/moment-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..0c47e5b --- /dev/null +++ b/.github/wiki/benchmarks/index.md @@ -0,0 +1,49 @@ +# 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 | +|---|---|---| +| `m.iYear()` | ~0.6 µs/call | ~14 µs/call | +| `m.toHijri()` | ~0.6 µs/call | ~14 µs/call | +| `moment.fromHijri()` | ~0.7 µs/call | ~15 µs/call | +| `m.format('iD iMMMM iYYYY')` | ~1.4 µs/call | ~15 µs/call | + +UAQ uses a precomputed lookup table (O(1) lookup). FCNA uses an arithmetic algorithm per call, which accounts for the ~24x difference. + +For UI rendering the numbers are well below perceptible latency. In batch-processing scenarios (thousands of calls), prefer UAQ or run the work off the main thread. + +## Bundle size + +| Module | Min+gz | +|---|---| +| moment-hijri-plus (wrapper only) | ~1.6 KB | +| hijri-core/uaq (peer dep, UAQ engine) | ~5.3 KB | +| hijri-core/fcna (peer dep, FCNA engine) | ~3.1 KB | +| moment (peer dep, separate) | ~72 KB | + +Moment.js itself is the dominant bundle cost. The plugin adds a negligible ~1.6 KB. If bundle size is a concern for a new project, Day.js + [dayjs-hijri-plus](https://github.com/acamarata/dayjs-hijri-plus) delivers the same Hijri API with a much smaller footprint. + +## Reproducing the benchmarks + +```javascript +import moment from 'moment'; +import { installHijri } from 'moment-hijri-plus'; + +installHijri(moment); + +const dates = Array.from({ length: 1000 }, (_, i) => + moment('1900-01-01').add(i * 26, 'days') +); + +const start = performance.now(); +for (const m of dates) { + m.iYear(); +} +const elapsed = performance.now() - start; +console.log(`${(elapsed / dates.length * 1000).toFixed(1)} µs/call`); +``` + +Run with `node --version` >= 20. diff --git a/.github/wiki/examples/basic-usage.md b/.github/wiki/examples/basic-usage.md new file mode 100644 index 0000000..c7a6bdc --- /dev/null +++ b/.github/wiki/examples/basic-usage.md @@ -0,0 +1,75 @@ +# Basic Usage Examples + +## Setup + +```typescript +import moment from 'moment'; +import { installHijri } from 'moment-hijri-plus'; + +installHijri(moment); +``` + +## Convert today's date to Hijri + +```typescript +const today = moment(); +const h = today.toHijri(); +// Returns null if date is outside UAQ range; guard before using + +if (h !== null) { + console.log(`${h.hd} / ${h.hm} / ${h.hy}`); +} +``` + +## Convert a known Gregorian date + +```typescript +// 23 March 2023 = 1 Ramadan 1444 AH +const m = moment('2023-03-23'); + +console.log(m.iYear()); // 1444 +console.log(m.iMonth()); // 9 (Ramadan is the 9th month) +console.log(m.iDate()); // 1 +``` + +## Convert from Hijri to Gregorian + +```typescript +const gregorian = moment.fromHijri(1444, 9, 1); +console.log(gregorian.format('YYYY-MM-DD')); // '2023-03-23' +``` + +## Format with Hijri tokens + +```typescript +const m = moment('2023-03-23'); + +m.format('iD iMMMM iYYYY'); // '1 Ramadan 1444' +m.format('iDD/iMM/iYYYY'); // '01/09/1444' +m.format('YYYY-MM-DD'); // '2023-03-23' (Gregorian tokens still work) +m.format('YYYY (iYYYY/iM/iD)'); // '2023 (1444/9/1)' +``` + +## Use FCNA calendar + +```typescript +const m = moment('2023-03-23'); + +const uaqYear = m.iYear(); // UAQ (default) +const fcnaYear = m.iYear({ calendar: 'fcna' }); // FCNA + +console.log(uaqYear, fcnaYear); +// Near month boundaries, UAQ and FCNA may differ by one day +``` + +## CJS usage + +```javascript +const moment = require('moment'); +const { installHijri } = require('moment-hijri-plus'); + +installHijri(moment); + +const m = moment('2023-03-23'); +console.log(m.iYear()); // 1444 +``` diff --git a/.github/wiki/examples/formatting.md b/.github/wiki/examples/formatting.md new file mode 100644 index 0000000..ec4298f --- /dev/null +++ b/.github/wiki/examples/formatting.md @@ -0,0 +1,83 @@ +# Formatting Examples + +## Hijri format token reference + +| Token | Output | Example | +|---|---|---| +| `iYYYY` | Full Hijri year | `1444` | +| `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 Moment.js as Gregorian tokens. + +## Common format patterns + +```typescript +import moment from 'moment'; +import { installHijri } from 'moment-hijri-plus'; + +installHijri(moment); + +const m = moment('2023-03-23'); + +// Day Month Year (long) +m.format('iD iMMMM iYYYY'); +// '1 Ramadan 1444' + +// Numeric date +m.format('iDD/iMM/iYYYY'); +// '01/09/1444' + +// Combined Gregorian and Hijri +m.format('YYYY-MM-DD (iD iMMMM iYYYY)'); +// '2023-03-23 (1 Ramadan 1444)' + +// ISO-style Hijri +m.format('iYYYY-iMM-iDD'); +// '1444-09-01' +``` + +## Hijri month names + +The `iMMMM` token returns the standard English transliteration for each Hijri month: + +| Number | Full name | Abbreviated | +|---|---|---| +| 1 | Muharram | Muh | +| 2 | Safar | Saf | +| 3 | Rabi' al-Awwal | Rab1 | +| 4 | Rabi' al-Thani | Rab2 | +| 5 | Jumada al-Awwal | Jum1 | +| 6 | Jumada al-Thani | Jum2 | +| 7 | Rajab | Raj | +| 8 | Sha'ban | Sha | +| 9 | Ramadan | Ram | +| 10 | Shawwal | Shaw | +| 11 | Dhu al-Qa'dah | DhuQ | +| 12 | Dhu al-Hijjah | DhuH | + +## Note on locales + +Moment.js locale settings affect how Gregorian tokens are formatted but have no effect on Hijri tokens. The `iMMMM` token always produces the English transliterations shown above. To localize Hijri month names, build a lookup table with your own translations and use the `iM` (numeric) token to index into it. + +## React example + +```tsx +import moment from 'moment'; +import { installHijri } from 'moment-hijri-plus'; + +installHijri(moment); + +function HijriDate({ date }: { date: Date }) { + const m = moment(date); + return ( + + ); +} +``` diff --git a/.github/wiki/guides/advanced.md b/.github/wiki/guides/advanced.md new file mode 100644 index 0000000..539f36c --- /dev/null +++ b/.github/wiki/guides/advanced.md @@ -0,0 +1,61 @@ +# Advanced Usage + +## Switching calendars per call + +Each method accepts an optional options argument: + +```typescript +import moment from 'moment'; +import hijri from 'moment-hijri-plus'; + +moment.extend(hijri); + +const m = moment('2023-03-23'); + +const uaqYear = m.iYear(); // UAQ (default) +const fcnaYear = m.iYear({ calendar: 'fcna' }); // FCNA +``` + +Near month boundaries, UAQ and FCNA may differ by one day. + +## Null safety + +`m.toHijri()` returns `null` for dates outside UAQ range (approximately 1900-2076 CE). Guard before using: + +```typescript +const hijri = m.toHijri(); +if (hijri !== null) { + console.log(hijri.hy, hijri.hm, hijri.hd); +} +``` + +## Combining with Moment.js locales + +Moment.js locale settings affect Gregorian formatting but not Hijri tokens. Hijri tokens always produce the same English output regardless of locale. To localize Hijri month names, use `getHijriMonthName` from `date-fns-hijri` or build your own translation layer. + +## Formatting alongside Gregorian tokens + +Hijri tokens (`iYYYY`, `iMM`, `iDD`, `iMMMM`, etc.) coexist with Moment Gregorian tokens: + +```typescript +m.format('YYYY-MM-DD (iD iMMMM iYYYY)'); +// '2023-03-23 (1 Ramadan 1444)' +``` + +## Moment.js tree-shaking + +Moment.js does not tree-shake well. If bundle size is a concern in a new project, consider migrating to Day.js + [dayjs-hijri-plus](https://github.com/acamarata/dayjs-hijri-plus) for the same Hijri API with significantly smaller bundles. + +## TypeScript augmentation + +The plugin augments Moment.js type definitions automatically: + +```typescript +import moment from 'moment'; +import hijri from 'moment-hijri-plus'; + +moment.extend(hijri); + +const m = moment('2023-03-23'); +const year: number = m.iYear(); // fully typed +``` diff --git a/.github/wiki/guides/quickstart.md b/.github/wiki/guides/quickstart.md new file mode 100644 index 0000000..e88e73e --- /dev/null +++ b/.github/wiki/guides/quickstart.md @@ -0,0 +1,85 @@ +# Quick Start + +This guide covers the most common use cases in moment-hijri-plus. All examples use the default Umm al-Qura (UAQ) calendar. + +## Installation + +```bash +pnpm add moment moment-hijri-plus hijri-core +``` + +`moment` and `hijri-core` are required peer dependencies. Install both alongside this package. + +## Load the plugin + +```typescript +import moment from 'moment'; +import hijri from 'moment-hijri-plus'; + +moment.extend(hijri); +``` + +After extending, all `moment()` instances gain Hijri methods. + +## Convert a Gregorian date to Hijri + +```typescript +import moment from 'moment'; +import hijri from 'moment-hijri-plus'; + +moment.extend(hijri); + +const m = moment('2023-03-23'); // 1 Ramadan 1444 +console.log(m.iYear()); // 1444 +console.log(m.iMonth()); // 9 +console.log(m.iDate()); // 1 +``` + +## Format with Hijri tokens + +```typescript +m.format('iYYYY/iMM/iDD'); // '1444/09/01' +m.format('iD iMMMM iYYYY'); // '1 Ramadan 1444' +``` + +Hijri format tokens are prefixed with `i` to avoid conflicts with Moment.js Gregorian tokens. + +## Convert a Hijri date to a Moment object + +```typescript +import moment from 'moment'; +import hijri from 'moment-hijri-plus'; + +moment.extend(hijri); + +const m = moment.fromHijri(1444, 9, 1); +console.log(m.format('YYYY-MM-DD')); // '2023-03-23' +``` + +## Use the FCNA calendar + +```typescript +const m = moment('2023-03-23'); +console.log(m.iYear({ calendar: 'fcna' })); // 1444 +``` + +## Note on Moment.js + +Moment.js is in maintenance mode. The Moment team recommends Luxon, Day.js, or date-fns for new projects. If you are starting fresh, consider [dayjs-hijri-plus](https://github.com/acamarata/dayjs-hijri-plus) as a compatible alternative. + +## CommonJS + +```js +const moment = require('moment'); +const hijri = require('moment-hijri-plus'); + +moment.extend(hijri); + +const m = moment('2023-03-23'); +console.log(m.iYear(), m.iMonth(), m.iDate()); // 1444 9 1 +``` + +## Next steps + +- [API Reference](API-Reference) for the full method list +- [Architecture](Architecture) for how the plugin integrates with Moment.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 9fa1709..245a281 100644 --- a/.github/workflows/wiki-sync.yml +++ b/.github/workflows/wiki-sync.yml @@ -11,15 +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: Sync wiki pages + - name: Sync .github/wiki/ to GitHub Wiki uses: Andrew-Chen-Wang/github-wiki-action@v4 with: path: .github/wiki/ env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_REPOSITORY: ${{ github.repository }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_ACTOR: ${{ github.actor }} diff --git a/README.md b/README.md index 640831e..0f099b3 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,10 @@ installHijri(moment); const hijri: HijriDate | null = moment().toHijri(); ``` +## Note on Moment.js + +Moment.js is in maintenance mode. The authors recommend Luxon, Day.js, or date-fns for new projects. This package targets existing codebases already using Moment.js. If you are starting a new project, [dayjs-hijri-plus](https://github.com/acamarata/dayjs-hijri-plus) is a compatible alternative that works with Day.js. + ## Architecture A thin plugin wrapper over [hijri-core](https://github.com/acamarata/hijri-core). The plugin augments the Moment.js prototype with Hijri methods, each delegating to the registered calendar engine. Zero global state.