luxon-hijri/.github/wiki/guides/advanced.md

3.5 KiB
Raw Blame History

Advanced Usage

Format tokens

All Hijri-specific tokens use the i prefix. For the full token table, see the API Reference.

Common tokens:

Token Example Description
iD 130 Hijri day, no padding
iDD 0130 Hijri day, zero-padded
iM 112 Hijri month number, no padding
iMM 0112 Hijri month number, zero-padded
iMMMM Ramadan Full Hijri month name
iYY 46 Last two digits of Hijri year
iYYYY 1446 Full Hijri year
ioooo AH Hijri era
import { toHijri, formatHijriDate } from 'luxon-hijri';

const h = toHijri(new Date('2025-03-20'));

console.log(formatHijriDate(h, 'iDD/iMM/iYYYY'));         // 20/09/1446
console.log(formatHijriDate(h, 'iD iMMMM iYYYY'));        // 20 Ramadan 1446
console.log(formatHijriDate(h, 'iD iMMMM iYYYY ioooo')); // 20 Ramadan 1446 AH

Hijri date arithmetic with Luxon

Luxon handles Gregorian arithmetic. Use toGregorian to convert Hijri endpoints, then work in Gregorian:

import { DateTime } from 'luxon';
import { toHijri, toGregorian } from 'luxon-hijri';

// Find when Eid al-Fitr (1 Shawwal) starts for this year
const today = new Date();
const h = toHijri(today);

if (h) {
  const eidStart = toGregorian(h.hy, 10, 1); // 1 Shawwal
  const eid = DateTime.fromJSDate(eidStart);
  console.log(`Eid al-Fitr ${h.hy}: ${eid.toFormat('MMMM d, yyyy')}`);
}

Generating a Hijri month calendar

The UAQ table encodes day counts per month in a bitmask. To iterate a month, convert each Hijri day to Gregorian and stop when toGregorian throws:

import { toGregorian } from 'luxon-hijri';
import { DateTime } from 'luxon';

const HY = 1446;
const HM = 9; // Ramadan

// Determine the month length (29 or 30 days)
let days = 29;
try { toGregorian(HY, HM, 30); days = 30; } catch (_) {}

const NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

console.log(`Ramadan ${HY}\n`);
console.log(NAMES.join('  '));

const firstGreg = DateTime.fromJSDate(toGregorian(HY, HM, 1));
let line = '     '.repeat(firstGreg.weekday % 7); // Sunday = 0

for (let d = 1; d <= days; d++) {
  const greg = DateTime.fromJSDate(toGregorian(HY, HM, d));
  line += String(d).padStart(3) + '  ';
  if (greg.weekday === 6) { // Saturday ends row
    console.log(line);
    line = '';
  }
}
if (line.trim()) console.log(line);

FCNA vs UAQ differences

FCNA and UAQ can differ by a day around month transitions:

import { toHijri } from 'luxon-hijri';

const borderDates = [
  new Date('2025-02-28'),
  new Date('2025-03-01'),
  new Date('2025-03-02'),
];

for (const d of borderDates) {
  const uaq  = toHijri(d, { calendar: 'uaq' });
  const fcna = toHijri(d, { calendar: 'fcna' });

  const fmt = h => h ? `${h.hd}/${h.hm}/${h.hy}` : 'null';
  console.log(`${d.toISOString().slice(0, 10)}  UAQ: ${fmt(uaq)}  FCNA: ${fmt(fcna)}`);
}

Batch conversion

import { toHijri } from 'luxon-hijri';

const isoList = [
  '2025-01-01', '2025-03-01', '2025-03-30',
  '2025-06-06', '2025-12-31',
];

for (const iso of isoList) {
  const h = toHijri(new Date(iso));
  const result = h ? `${h.hd}/${h.hm}/${h.hy} AH` : 'out of range';
  console.log(`${iso}${result}`);
}