Skip to content
Devix Open Source

Guide

Getting started

npm install @devix-labs/hijri
import { toHijri, fromHijri, format } from '@devix-labs/hijri';

const hijri = toHijri(new Date());
// { year: 1448, month: 4, day: 1, weekday: 6, gregorian: {…}, dayNumber: 20708, exact: true }

format(hijri, 'd MMMM yyyy G');   // 1 Rabi' al-Thani 1448 AH

toHijri takes a Date, an ISO string, or a plain { year, month, day }.

Back again

const eid = fromHijri({ year: 1448, month: 10, day: 1 });
eid.gregorian;    // { year: 2027, month: 3, day: 9 }

exact is the field to watch

The Umm al-Qura calendar is a published table, not a formula, and it runs from 1300 to 1600 AH. Outside that the tabular calendar answers, and the result says so:

toHijri('2026-09-12').exact;              // true  — from the table
fromHijri({ year: 1700, month: 1, day: 1 }).exact;   // false — arithmetic

Show that distinction if the date matters. Nothing else in this space tells you.

When your country sights the moon a day later

toHijri(new Date(), { offset: 1 });   // one day ahead of Umm al-Qura

Whole-day offsets are how ministries publish their difference, and the offset applies to the conversion both ways, so the arithmetic stays consistent.

For the arithmetical calendar on its own:

toHijri(new Date(), { calendar: 'tabular' });

Formatting

format(hijri, 'd MMMM yyyy G');                                  // 1 Rabi' al-Thani 1448 AH
format(hijri, 'd MMMM yyyy G', { locale: 'ar' });                // 1 ربيع الآخر 1448 هـ
format(hijri, 'd MMMM yyyy', { locale: 'ar', numerals: 'arab' }); // ١ ربيع الآخر ١٤٤٨
format(hijri, 'EEEE d MMMM', { locale: 'ur', weekday: hijri.weekday });
format(hijri, 'yyyy-MM-dd');                                     // 1448-04-01
Token Gives
d dd day
M MM month number
MMM MMMM month name
yy yyyy year
EEEE weekday (pass weekday)
G era — AH, هـ, ھ

registerNames('fa', { months: […], weekdays: […], era: 'ه‍.ق' }) adds a language.

Parsing

import { parse } from '@devix-labs/hijri';

parse('1448-09-01');            // { year: 1448, month: 9, day: 1 }
parse('1/9/1448');
parse('1 Ramadan 1448');
parse('1 رمضان 1448');
parse('١٠ ذی الحجہ ١٤٤٨');

Month names are matched across all three languages, so an Arabic month name typed into an English-locale field still works.

Arithmetic

import { addDays, addMonths, diffDays, daysInMonth, monthDays } from '@devix-labs/hijri';

addDays({ year: 1448, month: 1, day: 1 }, 30);
addMonths({ year: 1448, month: 12, day: 30 }, 1);   // clamps to the next month's length
diffDays({ year: 1448, month: 1, day: 1 }, { year: 1449, month: 1, day: 1 });   // 355
daysInMonth(1448, 9);                                // 29 or 30, from the table
monthDays(1448, 9);                                  // every day, for drawing a calendar

The dates people ask for

import { ramadan, events } from '@devix-labs/hijri';

ramadan(1448);
// { start: …2027-02-08, end: …2027-03-08, days: 29 }

events(1448, { locale: 'en' });
// [{ key: 'new-year', name: 'Islamic New Year', hijri, gregorian, inDays }, … ]

Nine of them: the new year, Ashura, Mawlid al-Nabi, Isra and Mi'raj, the first of Ramadan, Laylat al-Qadr, Eid al-Fitr, Arafah and Eid al-Adha. inDays counts from today, so a countdown is one field.

These are calendar dates. In most countries the moon decides and the announcement can move a day — use offset to follow a particular authority, and say "expected" in your interface.

Today, somewhere

import { today } from '@devix-labs/hijri';

today({ timeZone: 'Asia/Dubai' });

Without a zone it uses the machine's. The date changes at local midnight, which is why the parameter exists.

Updated 15 Sep 2026