date-fns-hijri/test.mjs
Aric Camarata d8c5398fdf feat: initial release of date-fns-hijri v1.0.0
Functional Hijri date utility library in date-fns style. 17 exported
functions: toHijriDate, fromHijriDate, isValidHijriDate, getHijriYear,
getHijriMonth, getHijriDay, getDaysInHijriMonth, getHijriMonthName,
getHijriWeekdayName, formatHijriDate, addHijriMonths, addHijriYears,
startOfHijriMonth, endOfHijriMonth, isSameHijriMonth, isSameHijriYear,
getHijriQuarter. All delegate to hijri-core. UTC-midnight-to-local-noon
correction applied in arithmetic functions to avoid timezone boundary
drift. 55 ESM + 10 CJS tests passing. Dual CJS/ESM build.
2026-02-25 14:15:24 -05:00

383 lines
12 KiB
JavaScript

import assert from 'node:assert/strict';
import {
toHijriDate,
fromHijriDate,
isValidHijriDate,
getHijriYear,
getHijriMonth,
getHijriDay,
getDaysInHijriMonth,
getHijriMonthName,
getHijriWeekdayName,
formatHijriDate,
addHijriMonths,
addHijriYears,
startOfHijriMonth,
endOfHijriMonth,
isSameHijriMonth,
isSameHijriYear,
getHijriQuarter,
} from './dist/index.mjs';
let passed = 0;
let failed = 0;
function test(name, fn) {
try {
fn();
console.log(`[${name}]... PASS`);
passed++;
} catch (err) {
console.error(`[${name}]... FAIL: ${err.message}`);
failed++;
}
}
// ---------------------------------------------------------------------------
// toHijriDate
// ---------------------------------------------------------------------------
test('toHijriDate: 1 Ramadan 1444', () => {
const h = toHijriDate(new Date(2023, 2, 23, 12));
assert.ok(h !== null, 'expected non-null');
assert.equal(h.hy, 1444);
assert.equal(h.hm, 9);
assert.equal(h.hd, 1);
});
test('toHijriDate: 1 Muharram 1446', () => {
const h = toHijriDate(new Date(2024, 6, 7, 12));
assert.ok(h !== null, 'expected non-null');
assert.equal(h.hy, 1446);
assert.equal(h.hm, 1);
assert.equal(h.hd, 1);
});
test('toHijriDate: out of range returns null', () => {
const h = toHijriDate(new Date(1800, 0, 1));
assert.equal(h, null);
});
// ---------------------------------------------------------------------------
// fromHijriDate
// ---------------------------------------------------------------------------
test('fromHijriDate: 1 Ramadan 1444 -> 2023-03-23', () => {
const d = fromHijriDate(1444, 9, 1);
assert.equal(d.getUTCFullYear(), 2023);
assert.equal(d.getUTCMonth(), 2); // March
assert.equal(d.getUTCDate(), 23);
});
test('fromHijriDate: 1 Muharram 1446 -> 2024-07-07', () => {
const d = fromHijriDate(1446, 1, 1);
assert.equal(d.getUTCFullYear(), 2024);
assert.equal(d.getUTCMonth(), 6); // July
assert.equal(d.getUTCDate(), 7);
});
test('fromHijriDate: throws on invalid month', () => {
assert.throws(() => fromHijriDate(1444, 13, 1), /invalid|range/i);
});
// ---------------------------------------------------------------------------
// isValidHijriDate
// ---------------------------------------------------------------------------
test('isValidHijriDate: valid date', () => {
assert.equal(isValidHijriDate(1444, 9, 1), true);
});
test('isValidHijriDate: invalid month 13', () => {
assert.equal(isValidHijriDate(1444, 13, 1), false);
});
test('isValidHijriDate: day 0 is invalid', () => {
assert.equal(isValidHijriDate(1444, 9, 0), false);
});
// ---------------------------------------------------------------------------
// Field getters
// ---------------------------------------------------------------------------
const REF = new Date(2023, 2, 23, 12); // 1 Ramadan 1444
test('getHijriYear', () => {
assert.equal(getHijriYear(REF), 1444);
});
test('getHijriMonth', () => {
assert.equal(getHijriMonth(REF), 9);
});
test('getHijriDay', () => {
assert.equal(getHijriDay(REF), 1);
});
test('getHijriYear: out of range returns null', () => {
assert.equal(getHijriYear(new Date(1800, 0, 1)), null);
});
// ---------------------------------------------------------------------------
// getDaysInHijriMonth
// ---------------------------------------------------------------------------
test('getDaysInHijriMonth: Ramadan 1444', () => {
const days = getDaysInHijriMonth(1444, 9);
// Must be either 29 or 30
assert.ok(days === 29 || days === 30, `expected 29 or 30, got ${days}`);
});
test('getDaysInHijriMonth: month 1 of 1444', () => {
const days = getDaysInHijriMonth(1444, 1);
assert.ok(days === 29 || days === 30, `expected 29 or 30, got ${days}`);
});
// ---------------------------------------------------------------------------
// getHijriMonthName
// ---------------------------------------------------------------------------
test('getHijriMonthName: long (default)', () => {
assert.equal(getHijriMonthName(9), 'Ramadan');
});
test('getHijriMonthName: medium', () => {
assert.equal(getHijriMonthName(9, 'medium'), 'Ramadan');
});
test('getHijriMonthName: short', () => {
assert.equal(getHijriMonthName(9, 'short'), 'Ram');
});
test('getHijriMonthName: Muharram long', () => {
assert.equal(getHijriMonthName(1), 'Muharram');
});
test('getHijriMonthName: Dhul Hijjah long', () => {
assert.equal(getHijriMonthName(12), 'Dhul Hijjah');
});
test('getHijriMonthName: throws on month 0', () => {
assert.throws(() => getHijriMonthName(0), RangeError);
});
test('getHijriMonthName: throws on month 13', () => {
assert.throws(() => getHijriMonthName(13), RangeError);
});
// ---------------------------------------------------------------------------
// getHijriWeekdayName
// ---------------------------------------------------------------------------
// March 23, 2023 was a Thursday (getDay() === 4)
test('getHijriWeekdayName: Thursday long', () => {
assert.equal(getHijriWeekdayName(new Date(2023, 2, 23)), 'Yawm al-Khamis');
});
test('getHijriWeekdayName: Thursday short', () => {
assert.equal(getHijriWeekdayName(new Date(2023, 2, 23), 'short'), 'Kham');
});
// ---------------------------------------------------------------------------
// formatHijriDate
// ---------------------------------------------------------------------------
test('formatHijriDate: iYYYY-iMM-iDD', () => {
assert.equal(formatHijriDate(REF, 'iYYYY-iMM-iDD'), '1444-09-01');
});
test('formatHijriDate: iMMMM', () => {
assert.equal(formatHijriDate(REF, 'iMMMM'), 'Ramadan');
});
test('formatHijriDate: iEEEE', () => {
assert.equal(formatHijriDate(new Date(2023, 2, 23), 'iEEEE'), 'Yawm al-Khamis');
});
test('formatHijriDate: iEEE', () => {
assert.equal(formatHijriDate(new Date(2023, 2, 23), 'iEEE'), 'Kham');
});
test('formatHijriDate: ioooo era', () => {
assert.equal(formatHijriDate(REF, 'ioooo'), 'AH');
});
test('formatHijriDate: iooo era', () => {
assert.equal(formatHijriDate(REF, 'iooo'), 'AH');
});
test('formatHijriDate: iYY two-digit year', () => {
assert.equal(formatHijriDate(REF, 'iYY'), '44');
});
test('formatHijriDate: iMMM medium month', () => {
assert.equal(formatHijriDate(REF, 'iMMM'), 'Ramadan');
});
test('formatHijriDate: iM bare month', () => {
assert.equal(formatHijriDate(REF, 'iM'), '9');
});
test('formatHijriDate: iD bare day', () => {
assert.equal(formatHijriDate(REF, 'iD'), '1');
});
test('formatHijriDate: iE numeric weekday (Thursday = 5)', () => {
// hwNumeric[4] = 5 (Thursday, 0-indexed from Sunday)
assert.equal(formatHijriDate(new Date(2023, 2, 23), 'iE'), '5');
});
test('formatHijriDate: out of range returns empty string', () => {
assert.equal(formatHijriDate(new Date(1800, 0, 1), 'iYYYY-iMM-iDD'), '');
});
test('formatHijriDate: mixed literal and tokens', () => {
const result = formatHijriDate(REF, 'iD iMMMM iYYYY ioooo');
assert.equal(result, '1 Ramadan 1444 AH');
});
// ---------------------------------------------------------------------------
// addHijriMonths
// ---------------------------------------------------------------------------
test('addHijriMonths: +1 from Ramadan -> Shawwal', () => {
const result = toHijriDate(addHijriMonths(REF, 1));
assert.ok(result !== null);
assert.equal(result.hy, 1444);
assert.equal(result.hm, 10); // Shawwal
});
test('addHijriMonths: +3 from month 10 -> wraps to month 1 of next year', () => {
const dec = new Date(2023, 3, 21, 12); // Shawwal 1444 approx
const result = toHijriDate(addHijriMonths(dec, 3));
assert.ok(result !== null);
// Should be in 1445
assert.equal(result.hy, 1445);
});
test('addHijriMonths: +0 is identity', () => {
const result = toHijriDate(addHijriMonths(REF, 0));
assert.ok(result !== null);
assert.equal(result.hy, 1444);
assert.equal(result.hm, 9);
assert.equal(result.hd, 1);
});
test('addHijriMonths: -1 from Ramadan -> Sha\'ban', () => {
const result = toHijriDate(addHijriMonths(REF, -1));
assert.ok(result !== null);
assert.equal(result.hm, 8); // Sha'ban
});
// ---------------------------------------------------------------------------
// addHijriYears
// ---------------------------------------------------------------------------
test('addHijriYears: +1 from Ramadan 1444 -> Ramadan 1445', () => {
const result = toHijriDate(addHijriYears(REF, 1));
assert.ok(result !== null);
assert.equal(result.hy, 1445);
assert.equal(result.hm, 9);
});
test('addHijriYears: -1 from Ramadan 1444 -> Ramadan 1443', () => {
const result = toHijriDate(addHijriYears(REF, -1));
assert.ok(result !== null);
assert.equal(result.hy, 1443);
assert.equal(result.hm, 9);
});
// ---------------------------------------------------------------------------
// startOfHijriMonth / endOfHijriMonth
// ---------------------------------------------------------------------------
test('startOfHijriMonth: 1 Ramadan 1444 = 2023-03-23', () => {
const start = startOfHijriMonth(REF);
// Use local date components — startOfHijriMonth returns a local-noon Date
// to round-trip correctly with toHijriDate across all timezones.
assert.equal(start.getFullYear(), 2023);
assert.equal(start.getMonth(), 2);
assert.equal(start.getDate(), 23);
});
test('endOfHijriMonth: last day of Ramadan 1444', () => {
const end = toHijriDate(endOfHijriMonth(REF));
assert.ok(end !== null);
assert.equal(end.hy, 1444);
assert.equal(end.hm, 9);
// Last day is either 29 or 30
assert.ok(end.hd === 29 || end.hd === 30, `expected 29 or 30, got ${end.hd}`);
});
// ---------------------------------------------------------------------------
// isSameHijriMonth / isSameHijriYear
// ---------------------------------------------------------------------------
// April 10, 2023 is 19 Ramadan 1444 — same Hijri month as March 23, 2023
test('isSameHijriMonth: both in Ramadan 1444', () => {
assert.equal(isSameHijriMonth(new Date(2023, 2, 23, 12), new Date(2023, 3, 10, 12)), true);
});
test('isSameHijriMonth: different months', () => {
assert.equal(isSameHijriMonth(new Date(2023, 2, 23, 12), new Date(2023, 4, 1, 12)), false);
});
test('isSameHijriMonth: out of range returns false', () => {
assert.equal(isSameHijriMonth(new Date(1800, 0, 1), new Date(2023, 2, 23, 12)), false);
});
// March 10, 2024 is in Ramadan 1445 — different year
// But we need same year: 1444 spans roughly April 2022 - April 2023
// 1444 starts ~July 30, 2022. Let's pick two dates in 1444:
// March 23, 2023 = 1 Ramadan 1444
// Feb 10, 2023 = in Jumadal Thani 1444 (still year 1444)
test('isSameHijriYear: both in 1444', () => {
assert.equal(isSameHijriYear(new Date(2023, 2, 23, 12), new Date(2023, 1, 10, 12)), true);
});
test('isSameHijriYear: different years', () => {
assert.equal(isSameHijriYear(new Date(2023, 2, 23, 12), new Date(2024, 6, 7, 12)), false);
});
// ---------------------------------------------------------------------------
// getHijriQuarter
// ---------------------------------------------------------------------------
test('getHijriQuarter: month 9 = Q3', () => {
assert.equal(getHijriQuarter(REF), 3);
});
test('getHijriQuarter: month 1 = Q1', () => {
assert.equal(getHijriQuarter(new Date(2024, 6, 7, 12)), 1); // 1 Muharram 1446
});
test('getHijriQuarter: out of range returns null', () => {
assert.equal(getHijriQuarter(new Date(1800, 0, 1)), null);
});
// ---------------------------------------------------------------------------
// FCNA calendar
// ---------------------------------------------------------------------------
test('toHijriDate: FCNA calendar returns valid HijriDate', () => {
const h = toHijriDate(new Date(2023, 2, 23, 12), { calendar: 'fcna' });
assert.ok(h !== null, 'expected non-null for FCNA');
assert.ok(typeof h.hy === 'number');
assert.ok(h.hm >= 1 && h.hm <= 12);
assert.ok(h.hd >= 1 && h.hd <= 30);
});
test('formatHijriDate: FCNA calendar', () => {
const result = formatHijriDate(new Date(2023, 2, 23, 12), 'iYYYY-iMM-iDD', { calendar: 'fcna' });
assert.ok(result.length > 0, 'expected non-empty string');
});
// ---------------------------------------------------------------------------
// Summary
// ---------------------------------------------------------------------------
const total = passed + failed;
console.log(`\n${passed}/${total} tests passed`);
if (failed > 0) {
process.exit(1);
}