Behavior changes (lock-step with hijri-core fix/utc-day-boundary): - toHijriDate and all field/format/comparison/arithmetic functions now lift input Dates through localDayToUtcSlot() before calling coreToHijri(), reading the caller's LOCAL calendar day (date-fns convention). Previously passed the raw Date which caused off-by-one results in timezones west of UTC against the new UTC-day core contract. - fromHijriDate now returns local-midnight Dates (new Date(y, m, d)) instead of UTC midnight. Local field accessors and date-fns format() render the intended calendar day on every host timezone. toISOString() is no longer the right API for this value. - addHijriMonths, addHijriYears, startOfHijriMonth, endOfHijriMonth call fromHijriDate directly; the utcMidnightToLocalNoon shim is removed. - Round-trip toHijriDate(fromHijriDate(y, m, d)) is now exact on every timezone. Verified: 58/58 ESM tests, 10/10 CJS tests, 16/16 vitest assertions across TZ=UTC, TZ=America/New_York, and TZ=Pacific/Auckland.
3.5 KiB
date-fns-hijri
date-fns-style functions for Hijri calendar operations. Each function is a pure, stateless utility. Pass a Date, get a result. No classes, no global configuration.
Built on hijri-core. Supports Umm al-Qura (UAQ) and FCNA/ISNA calendar systems.
Installation
pnpm add date-fns-hijri hijri-core
hijri-core is a peer dependency. It provides the underlying calendar engine.
Quick Start
import {
toHijriDate,
fromHijriDate,
formatHijriDate,
addHijriMonths,
getHijriMonthName,
} from 'date-fns-hijri';
// Convert Gregorian to Hijri
const hijri = toHijriDate(new Date(2023, 2, 23, 12));
// { hy: 1444, hm: 9, hd: 1 } (1 Ramadan 1444)
// Format with Hijri tokens
const label = formatHijriDate(new Date(2023, 2, 23, 12), 'iD iMMMM iYYYY ioooo');
// '1 Ramadan 1444 AH'
// Add Hijri months
const eid = addHijriMonths(new Date(2023, 2, 23, 12), 1);
// Date in Shawwal 1444
// Get the month name
getHijriMonthName(9); // 'Ramadan'
Documentation
Full API reference, guides, and examples: Wiki
- API Reference: all 17 functions with signatures and examples
- Architecture: design decisions and hijri-core integration
- Quick Start
Day boundaries and time zones
This package follows date-fns local-time conventions:
- Inputs (
toHijriDate,getHijri*,formatHijriDate, arithmetic, comparisons) — the inputDateis read by its local calendar day (usinggetFullYear/getMonth/getDate). This matches how date-fns' ownformat()and field accessors work. - Outputs (
fromHijriDateand all arithmetic/boundary functions) — returnedDatevalues are local midnight of the equivalent Gregorian day. Local field accessors and date-fns'format()will render the intended date on every timezone.
Round-trips are exact on every host timezone:
toHijriDate(fromHijriDate(1446, 9, 1)); // always { hy: 1446, hm: 9, hd: 1 }
Pitfall: new Date("2025-03-01") parses as UTC midnight. In timezones west of UTC this resolves to the previous local day (Feb 28), giving an off-by-one result. Use the local-date constructor instead:
// Wrong in timezones west of UTC:
toHijriDate(new Date("2025-03-01")); // may return 29 Shaban in some zones
// Correct everywhere:
toHijriDate(new Date(2025, 2, 1)); // always 1 Ramadan 1446
Religious day-start (sunset boundary) is out of scope — this package only handles civil calendar day alignment.
Related
- hijri-core: the calendar engine powering this library
- luxon-hijri: Hijri support for Luxon DateTime objects
- pray-calc: Islamic prayer times
Compatibility
- Node.js 20, 22, 24
- ESM and CJS builds included
- TypeScript definitions bundled
- Works in browsers and all major bundlers
License
MIT. Copyright (c) 2026 Aric Camarata.