mirror of
https://github.com/acamarata/date-fns-hijri.git
synced 2026-07-01 11:14:26 +00:00
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.
383 lines
12 KiB
JavaScript
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);
|
|
}
|