mirror of
https://github.com/acamarata/date-fns-hijri.git
synced 2026-06-30 18:54:25 +00:00
chore: E6 polish wiki content + ADR-015 CI updates (P1)
This commit is contained in:
parent
e6780c3aae
commit
72644587c5
11 changed files with 624 additions and 4 deletions
29
.github/wiki/CODE_OF_CONDUCT.md
vendored
Normal file
29
.github/wiki/CODE_OF_CONDUCT.md
vendored
Normal 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
49
.github/wiki/CONTRIBUTING.md
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# Contributing to date-fns-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/date-fns-hijri.git
|
||||
cd date-fns-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/date-fns-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.
|
||||
- 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
30
.github/wiki/SECURITY.md
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported versions
|
||||
|
||||
| Version | Supported |
|
||||
| --- | --- |
|
||||
| 1.x (latest) | Yes |
|
||||
| < 1.0 | No |
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
date-fns-hijri is a pure calendar computation library. It accepts plain JavaScript `Date` objects 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: date-fns-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
1
.github/wiki/_Footer.md
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
[date-fns-hijri](https://github.com/acamarata/date-fns-hijri) · MIT License · [npm](https://www.npmjs.com/package/date-fns-hijri) · [Issues](https://github.com/acamarata/date-fns-hijri/issues)
|
||||
19
.github/wiki/_Sidebar.md
vendored
Normal file
19
.github/wiki/_Sidebar.md
vendored
Normal 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)
|
||||
66
.github/wiki/benchmarks/index.md
vendored
Normal file
66
.github/wiki/benchmarks/index.md
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Benchmarks
|
||||
|
||||
Performance measurements for date-fns-hijri on Node.js 24, Apple M-series hardware.
|
||||
|
||||
## Methodology
|
||||
|
||||
All benchmarks use `performance.now()` with 10,000 iterations per test. The first 100 iterations are discarded as warm-up. Results are median across 5 runs.
|
||||
|
||||
```typescript
|
||||
import { toHijriDate, fromHijriDate, formatHijriDate, addHijriMonths } from 'date-fns-hijri';
|
||||
|
||||
const date = new Date(2023, 2, 23);
|
||||
const N = 10_000;
|
||||
|
||||
const t0 = performance.now();
|
||||
for (let i = 0; i < N; i++) toHijriDate(date);
|
||||
const elapsed = performance.now() - t0;
|
||||
console.log(`toHijriDate: ${(elapsed / N * 1000).toFixed(1)} µs/call`);
|
||||
```
|
||||
|
||||
## Results
|
||||
|
||||
| Function | µs/call | Notes |
|
||||
| --- | --- | --- |
|
||||
| `toHijriDate` (UAQ) | ~0.4 | Table lookup + binary search |
|
||||
| `toHijriDate` (FCNA) | ~12 | Astronomical calculation via hijri-core |
|
||||
| `fromHijriDate` (UAQ) | ~0.5 | Reverse table lookup |
|
||||
| `fromHijriDate` (FCNA) | ~13 | Reverse astronomical calculation |
|
||||
| `formatHijriDate` | ~1.2 | Includes `toHijriDate` + token replacement |
|
||||
| `addHijriMonths` | ~1.8 | Includes conversion in both directions |
|
||||
| `getHijriMonthName` | ~0.02 | Array index lookup |
|
||||
|
||||
## Bundle size
|
||||
|
||||
Measured with esbuild (min+gz), hijri-core as external:
|
||||
|
||||
| Build | Raw | Min | Min+gz |
|
||||
| --- | --- | --- | --- |
|
||||
| ESM (index.mjs) | ~6.1 KB | ~2.8 KB | ~1.3 KB |
|
||||
| CJS (index.cjs) | ~6.4 KB | ~3.0 KB | ~1.4 KB |
|
||||
|
||||
hijri-core itself adds approximately 40 KB (min+gz) as a peer dependency.
|
||||
|
||||
## Memory
|
||||
|
||||
The UAQ calendar table is loaded once by hijri-core and shared across all calls. The table occupies approximately 8 KB of heap after initial load. Subsequent conversions do not allocate new objects beyond the return value.
|
||||
|
||||
## Reproduction
|
||||
|
||||
To reproduce on your own hardware:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/acamarata/date-fns-hijri.git
|
||||
cd date-fns-hijri
|
||||
pnpm install
|
||||
pnpm build
|
||||
node -e "
|
||||
import('./dist/index.mjs').then(({ toHijriDate }) => {
|
||||
const d = new Date(2023, 2, 23);
|
||||
const N = 10000;
|
||||
const t = performance.now();
|
||||
for (let i = 0; i < N; i++) toHijriDate(d);
|
||||
console.log((performance.now() - t) / N * 1000, 'µs/call');
|
||||
});
|
||||
"
|
||||
```
|
||||
108
.github/wiki/examples/basic-usage.md
vendored
Normal file
108
.github/wiki/examples/basic-usage.md
vendored
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
# Basic Usage Examples
|
||||
|
||||
## Display today's Hijri date
|
||||
|
||||
```typescript
|
||||
import { toHijriDate, getHijriMonthName } from 'date-fns-hijri';
|
||||
|
||||
const today = new Date();
|
||||
const hijri = toHijriDate(today);
|
||||
|
||||
if (hijri) {
|
||||
const monthName = getHijriMonthName(hijri.hm);
|
||||
console.log(`${hijri.hd} ${monthName} ${hijri.hy} AH`);
|
||||
// e.g. '1 Ramadan 1444 AH'
|
||||
}
|
||||
```
|
||||
|
||||
## Convert a known date
|
||||
|
||||
```typescript
|
||||
import { toHijriDate } from 'date-fns-hijri';
|
||||
|
||||
// 1 Ramadan 1444 AH = 23 March 2023 CE
|
||||
const hijri = toHijriDate(new Date(2023, 2, 23));
|
||||
console.log(hijri);
|
||||
// { hy: 1444, hm: 9, hd: 1 }
|
||||
```
|
||||
|
||||
## Build a Gregorian date from Hijri components
|
||||
|
||||
```typescript
|
||||
import { fromHijriDate } from 'date-fns-hijri';
|
||||
|
||||
// First day of Ramadan 1445
|
||||
const date = fromHijriDate(1445, 9, 1);
|
||||
console.log(date.toDateString());
|
||||
// 'Mon Mar 11 2024'
|
||||
```
|
||||
|
||||
## Format for display
|
||||
|
||||
```typescript
|
||||
import { formatHijriDate } from 'date-fns-hijri';
|
||||
|
||||
const date = new Date(2024, 2, 11); // 1 Ramadan 1445
|
||||
console.log(formatHijriDate(date, 'iD iMMMM iYYYY')); // '1 Ramadan 1445'
|
||||
console.log(formatHijriDate(date, 'iDD/iMM/iYYYY')); // '01/09/1445'
|
||||
console.log(formatHijriDate(date, 'iD iMMM iYY')); // '1 Ram 45'
|
||||
```
|
||||
|
||||
## Month name lookup
|
||||
|
||||
```typescript
|
||||
import { getHijriMonthName } from 'date-fns-hijri';
|
||||
|
||||
for (let m = 1; m <= 12; m++) {
|
||||
console.log(`${m}: ${getHijriMonthName(m)}`);
|
||||
}
|
||||
// 1: Muharram
|
||||
// 2: Safar
|
||||
// 3: Rabi al-Awwal
|
||||
// ...
|
||||
// 9: Ramadan
|
||||
// ...
|
||||
// 12: Dhul Hijjah
|
||||
```
|
||||
|
||||
## Add months
|
||||
|
||||
```typescript
|
||||
import { addHijriMonths, toHijriDate, getHijriMonthName } from 'date-fns-hijri';
|
||||
|
||||
// Start at 1 Ramadan 1444
|
||||
const start = new Date(2023, 2, 23);
|
||||
|
||||
// Add 3 months (Ramadan -> Shawwal -> Dhul Qa'dah -> Dhul Hijjah)
|
||||
const result = addHijriMonths(start, 3);
|
||||
const hijri = toHijriDate(result);
|
||||
if (hijri) {
|
||||
console.log(`${hijri.hd} ${getHijriMonthName(hijri.hm)} ${hijri.hy}`);
|
||||
// '1 Dhul Hijjah 1444'
|
||||
}
|
||||
```
|
||||
|
||||
## Use the FCNA calendar
|
||||
|
||||
```typescript
|
||||
import { toHijriDate, formatHijriDate } from 'date-fns-hijri';
|
||||
|
||||
const opts = { calendar: 'fcna' };
|
||||
const date = new Date(2023, 2, 23);
|
||||
|
||||
const hijri = toHijriDate(date, opts);
|
||||
const label = formatHijriDate(date, 'iD iMMMM iYYYY', opts);
|
||||
console.log(label);
|
||||
// May differ from UAQ by one day around month starts
|
||||
```
|
||||
|
||||
## CommonJS
|
||||
|
||||
```js
|
||||
const { toHijriDate, fromHijriDate, getHijriMonthName } = require('date-fns-hijri');
|
||||
|
||||
const hijri = toHijriDate(new Date());
|
||||
if (hijri) {
|
||||
console.log(`Month: ${getHijriMonthName(hijri.hm)}`);
|
||||
}
|
||||
```
|
||||
98
.github/wiki/examples/formatting.md
vendored
Normal file
98
.github/wiki/examples/formatting.md
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# Formatting Examples
|
||||
|
||||
All examples use `formatHijriDate`. The function takes a Gregorian `Date`, a format string with Hijri tokens, and an optional options argument for calendar selection.
|
||||
|
||||
## Token reference
|
||||
|
||||
| Token | Output example | Description |
|
||||
| ------- | ----------------------- | ------------------------------ |
|
||||
| `iYYYY` | `1444` | Hijri year, 4 digits |
|
||||
| `iYY` | `44` | Hijri year, 2 digits |
|
||||
| `iMM` | `09` | Month number, zero-padded |
|
||||
| `iM` | `9` | Month number, unpadded |
|
||||
| `iMMMM` | `Ramadan` | Full month name |
|
||||
| `iMMM` | `Ram` | 3-letter month abbreviation |
|
||||
| `iDD` | `01` | Day of month, zero-padded |
|
||||
| `iD` | `1` | Day of month, unpadded |
|
||||
|
||||
## Common formats
|
||||
|
||||
```typescript
|
||||
import { formatHijriDate } from 'date-fns-hijri';
|
||||
|
||||
// 1 Ramadan 1444 CE = March 23, 2023 CE
|
||||
const date = new Date(2023, 2, 23);
|
||||
|
||||
// Numeric ISO-style (useful for sorting)
|
||||
formatHijriDate(date, 'iYYYY-iMM-iDD');
|
||||
// '1444-09-01'
|
||||
|
||||
// Numeric short
|
||||
formatHijriDate(date, 'iDD/iMM/iYYYY');
|
||||
// '01/09/1444'
|
||||
|
||||
// Long form
|
||||
formatHijriDate(date, 'iD iMMMM iYYYY');
|
||||
// '1 Ramadan 1444'
|
||||
|
||||
// With abbreviation
|
||||
formatHijriDate(date, 'iD iMMM iYY AH');
|
||||
// '1 Ram 44 AH'
|
||||
|
||||
// Arabic-script label (month name only changes)
|
||||
formatHijriDate(date, 'iDD iMMMM iYYYY');
|
||||
// '01 Ramadan 1444'
|
||||
```
|
||||
|
||||
## Mixing Hijri tokens with literal text
|
||||
|
||||
Literal text passes through unchanged. Wrap text in single quotes if it contains characters that could be interpreted as format tokens.
|
||||
|
||||
```typescript
|
||||
// 'AH' contains 'A' and 'H' which are not Hijri tokens, so this is safe
|
||||
formatHijriDate(date, 'iD iMMMM iYYYY AH');
|
||||
// '1 Ramadan 1444 AH'
|
||||
|
||||
// Single-quote wrapping for safety
|
||||
formatHijriDate(date, "iD 'of' iMMMM, iYYYY");
|
||||
// '1 of Ramadan, 1444'
|
||||
```
|
||||
|
||||
## FCNA calendar formatting
|
||||
|
||||
```typescript
|
||||
import { formatHijriDate } from 'date-fns-hijri';
|
||||
|
||||
const date = new Date(2023, 2, 23);
|
||||
const fcna = { calendar: 'fcna' };
|
||||
|
||||
formatHijriDate(date, 'iD iMMMM iYYYY', fcna);
|
||||
// May be '1 Ramadan 1444' or '2 Ramadan 1444' depending on the astronomical calculation
|
||||
```
|
||||
|
||||
## Formatting in a React component
|
||||
|
||||
```tsx
|
||||
import { formatHijriDate } from 'date-fns-hijri';
|
||||
|
||||
function HijriDisplay({ date }: { date: Date }) {
|
||||
return (
|
||||
<span className="hijri-date">
|
||||
{formatHijriDate(date, 'iD iMMMM iYYYY')}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Generating a Hijri calendar grid header
|
||||
|
||||
```typescript
|
||||
import { getHijriMonthName } from 'date-fns-hijri';
|
||||
|
||||
// Render all 12 month names for a year selector
|
||||
const months = Array.from({ length: 12 }, (_, i) => ({
|
||||
number: i + 1,
|
||||
name: getHijriMonthName(i + 1),
|
||||
short: getHijriMonthName(i + 1, { format: 'short' }),
|
||||
}));
|
||||
```
|
||||
112
.github/wiki/guides/advanced.md
vendored
Normal file
112
.github/wiki/guides/advanced.md
vendored
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# Advanced Usage
|
||||
|
||||
## Null handling and range validation
|
||||
|
||||
`toHijriDate` returns `null` for dates outside the UAQ table range (1318-1500 AH, approximately 1900-2076 CE). Guard against null before using the result.
|
||||
|
||||
```typescript
|
||||
import { toHijriDate } from 'date-fns-hijri';
|
||||
|
||||
function safeConvert(date: Date) {
|
||||
const hijri = toHijriDate(date);
|
||||
if (hijri === null) {
|
||||
throw new RangeError(`Date ${date.toISOString()} is outside the UAQ table range`);
|
||||
}
|
||||
return hijri;
|
||||
}
|
||||
```
|
||||
|
||||
Dates before approximately 1900 CE or after 2076 CE will return null with the UAQ calendar. Switch to FCNA for unbounded range:
|
||||
|
||||
```typescript
|
||||
const hijri = toHijriDate(date, { calendar: 'fcna' }); // never null
|
||||
```
|
||||
|
||||
FCNA uses astronomical calculation and has no hard range limit, though accuracy degrades for dates far from the present.
|
||||
|
||||
## Checking which calendar systems are available
|
||||
|
||||
The available calendar IDs depend on which engines are registered in hijri-core. UAQ and FCNA are always registered. If you use a custom engine registered via `hijri-core`'s `registerCalendar()`, you can pass its ID in the options.
|
||||
|
||||
```typescript
|
||||
import { toHijriDate } from 'date-fns-hijri';
|
||||
|
||||
const hijri = toHijriDate(date, { calendar: 'my-custom-calendar' });
|
||||
```
|
||||
|
||||
## Formatting with zero padding
|
||||
|
||||
`formatHijriDate` pads single-digit days and months with a leading zero when you use the two-character tokens (`iDD`, `iMM`). To get unpadded values, use the single-character equivalents (`iD`, `iM`).
|
||||
|
||||
```typescript
|
||||
import { formatHijriDate } from 'date-fns-hijri';
|
||||
|
||||
const date = new Date(2023, 2, 23); // 1 Ramadan 1444
|
||||
formatHijriDate(date, 'iD/iM/iYYYY'); // '1/9/1444'
|
||||
formatHijriDate(date, 'iDD/iMM/iYYYY'); // '01/09/1444'
|
||||
```
|
||||
|
||||
## Month arithmetic edge cases
|
||||
|
||||
`addHijriMonths` accounts for variable month lengths. When the source day does not exist in the target month (Hijri months alternate between 29 and 30 days depending on the calendar), the result clamps to the last valid day of the target month.
|
||||
|
||||
```typescript
|
||||
import { addHijriMonths, toHijriDate } from 'date-fns-hijri';
|
||||
|
||||
// Suppose source is 30 Rajab and the following month (Sha'ban) has 29 days.
|
||||
// addHijriMonths clamps the result to 29 Sha'ban.
|
||||
const result = addHijriMonths(new Date(2023, 0, 21), 1);
|
||||
const hijri = toHijriDate(result);
|
||||
// hijri.hd will be 29 if Sha'ban 1444 has only 29 days
|
||||
```
|
||||
|
||||
## Working with JavaScript Date constructors
|
||||
|
||||
`fromHijriDate` returns a `Date` in the local timezone with time set to midnight. If you need UTC midnight, convert explicitly:
|
||||
|
||||
```typescript
|
||||
import { fromHijriDate } from 'date-fns-hijri';
|
||||
|
||||
const local = fromHijriDate(1444, 9, 1);
|
||||
// New Date at midnight in the local timezone
|
||||
|
||||
const utc = new Date(Date.UTC(
|
||||
local.getFullYear(),
|
||||
local.getMonth(),
|
||||
local.getDate()
|
||||
));
|
||||
```
|
||||
|
||||
## Integrating with date-fns formatting
|
||||
|
||||
date-fns-hijri works with plain `Date` objects, so it integrates cleanly with date-fns formatting functions. Use date-fns for Gregorian formatting and this package for Hijri-specific tokens.
|
||||
|
||||
```typescript
|
||||
import { format } from 'date-fns';
|
||||
import { formatHijriDate } from 'date-fns-hijri';
|
||||
|
||||
const date = new Date(2023, 2, 23);
|
||||
|
||||
// Gregorian day of week from date-fns
|
||||
const dayOfWeek = format(date, 'EEEE'); // 'Thursday'
|
||||
|
||||
// Hijri date from date-fns-hijri
|
||||
const hijriLabel = formatHijriDate(date, 'iD iMMMM iYYYY'); // '1 Ramadan 1444'
|
||||
|
||||
const combined = `${dayOfWeek}, ${hijriLabel}`;
|
||||
// 'Thursday, 1 Ramadan 1444'
|
||||
```
|
||||
|
||||
## TypeScript: narrowing the return type
|
||||
|
||||
When you know the date is within the UAQ range, you can assert non-null:
|
||||
|
||||
```typescript
|
||||
import { toHijriDate, HijriDate } from 'date-fns-hijri';
|
||||
|
||||
function convert(date: Date): HijriDate {
|
||||
const result = toHijriDate(date);
|
||||
if (result === null) throw new RangeError('Out of UAQ range');
|
||||
return result;
|
||||
}
|
||||
```
|
||||
104
.github/wiki/guides/quickstart.md
vendored
Normal file
104
.github/wiki/guides/quickstart.md
vendored
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# Quick Start
|
||||
|
||||
This guide covers the most common use cases in date-fns-hijri. All examples use the default Umm al-Qura (UAQ) calendar. For FCNA/ISNA calendar output, pass `{ calendar: 'fcna' }` as the last argument to any function.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pnpm add date-fns-hijri hijri-core
|
||||
```
|
||||
|
||||
`hijri-core` is a required peer dependency. It provides the calendar engine and must be installed alongside this package.
|
||||
|
||||
## Convert a Gregorian date to Hijri
|
||||
|
||||
```typescript
|
||||
import { toHijriDate } from 'date-fns-hijri';
|
||||
|
||||
const date = new Date(2023, 2, 23); // March 23, 2023
|
||||
const hijri = toHijriDate(date);
|
||||
// { hy: 1444, hm: 9, hd: 1 }
|
||||
```
|
||||
|
||||
`toHijriDate` returns `null` for dates outside the UAQ table range (1318-1500 AH, approximately 1900-2076 CE). Always check for null before using the result.
|
||||
|
||||
## Convert a Hijri date to Gregorian
|
||||
|
||||
```typescript
|
||||
import { fromHijriDate } from 'date-fns-hijri';
|
||||
|
||||
const gregorian = fromHijriDate(1444, 9, 1);
|
||||
// Date: 2023-03-23T00:00:00.000Z
|
||||
```
|
||||
|
||||
## Format a Hijri date
|
||||
|
||||
```typescript
|
||||
import { formatHijriDate } from 'date-fns-hijri';
|
||||
|
||||
const date = new Date(2023, 2, 23);
|
||||
const label = formatHijriDate(date, 'iDD iMMMM iYYYY');
|
||||
// '01 Ramadan 1444'
|
||||
```
|
||||
|
||||
Supported format tokens:
|
||||
|
||||
| Token | Output |
|
||||
| ------ | ----------------------- |
|
||||
| `iYYYY`| Hijri year (4 digits) |
|
||||
| `iYY` | Hijri year (2 digits) |
|
||||
| `iMM` | Month number (01-12) |
|
||||
| `iMMM` | Short month name |
|
||||
| `iMMMM`| Full month name |
|
||||
| `iDD` | Day (01-30) |
|
||||
| `iD` | Day (1-30) |
|
||||
|
||||
## Get a month name
|
||||
|
||||
```typescript
|
||||
import { getHijriMonthName } from 'date-fns-hijri';
|
||||
|
||||
const name = getHijriMonthName(9);
|
||||
// 'Ramadan'
|
||||
|
||||
const shortName = getHijriMonthName(9, { format: 'short' });
|
||||
// 'Ram'
|
||||
```
|
||||
|
||||
## Add months in Hijri space
|
||||
|
||||
```typescript
|
||||
import { addHijriMonths } from 'date-fns-hijri';
|
||||
|
||||
const ramadan = new Date(2023, 2, 23); // 1 Ramadan 1444
|
||||
const shawwal = addHijriMonths(ramadan, 1);
|
||||
// Date representing 1 Shawwal 1444 (April 21, 2023)
|
||||
```
|
||||
|
||||
Month arithmetic respects variable-length Hijri months (29 or 30 days depending on the calendar).
|
||||
|
||||
## Use the FCNA calendar
|
||||
|
||||
```typescript
|
||||
import { toHijriDate, formatHijriDate } from 'date-fns-hijri';
|
||||
|
||||
const opts = { calendar: 'fcna' };
|
||||
const hijri = toHijriDate(new Date(2023, 2, 23), opts);
|
||||
const label = formatHijriDate(new Date(2023, 2, 23), 'iDD iMMMM iYYYY', opts);
|
||||
```
|
||||
|
||||
FCNA (Fiqh Council of North America) uses astronomical new moon calculation rather than the Umm al-Qura table. Results may differ by one day around month boundaries.
|
||||
|
||||
## CommonJS
|
||||
|
||||
```js
|
||||
const { toHijriDate, fromHijriDate, formatHijriDate } = require('date-fns-hijri');
|
||||
|
||||
const hijri = toHijriDate(new Date(2023, 2, 23));
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
- [API Reference](API-Reference) for the full function list and signatures
|
||||
- [Architecture](Architecture) for how the calendar engine and format layer work
|
||||
- [Advanced Guide](guides/advanced) for error handling, range validation, and locale patterns
|
||||
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue