Convert a date
Both ways, in three languages, with the offset a ministry publishes — and it tells you when the answer comes from the table and when it does not.
<div class="demo">
<div class="demo-grid-2">
<div class="demo-field">
<label class="demo-label" for="gregorian">Gregorian</label>
<input id="gregorian" class="demo-input" type="date" value="2026-09-12">
</div>
<div class="demo-field">
<label class="demo-label" for="hijri">Hijri</label>
<input id="hijri" class="demo-input" value="1448-04-01" autocomplete="off">
<span class="demo-hint">yyyy-mm-dd, or “1 Ramadan 1448”</span>
</div>
</div>
<div class="demo-row">
<div class="demo-seg" id="locale">
<button class="is-on" data-locale="en">English</button>
<button data-locale="ar">العربية</button>
<button data-locale="ur">اردو</button>
</div>
<label class="demo-label" for="offset">Offset
<select id="offset" class="demo-input">
<option value="-1">−1 day</option>
<option value="0" selected>Umm al-Qura</option>
<option value="1">+1 day</option>
</select>
</label>
</div>
<p class="demo-out" id="out"></p>
<p class="demo-hint" id="note"></p>
<div class="demo-card">
<p class="demo-hint">Coming up</p>
<div class="demo-kv" id="events"></div>
</div>
</div>
import { events, format, fromHijri, isoOf, parse, toHijri } from 'https://www.devix.pk/cdn/oss/hijri@1.0.0/index.js';
const gregorian = document.getElementById('gregorian');
const hijri = document.getElementById('hijri');
const out = document.getElementById('out');
const note = document.getElementById('note');
const list = document.getElementById('events');
const offsetField = document.getElementById('offset');
let locale = 'en';
const options = () => ({ offset: Number(offsetField.value) });
function show(date) {
const rtl = locale !== 'en';
out.dir = rtl ? 'rtl' : 'ltr';
out.textContent = format(date, 'EEEE d MMMM yyyy G', { locale, weekday: date.weekday, numerals: rtl ? 'arab' : 'latn' });
// The table runs 1300–1600 AH; outside it the tabular calendar answers, and says so.
note.textContent = date.exact
? 'From the published Umm al-Qura table.'
: 'Outside the published table — this is the arithmetical calendar, so treat it as approximate.';
list.textContent = '';
for (const event of events(date.year, { locale, ...options() }).filter((item) => item.inDays >= 0).slice(0, 4)) {
const cell = document.createElement('div');
cell.innerHTML = `<span class="demo-hint">${isoOf(event.gregorian)}</span><br><b>${event.name}</b><br><span class="demo-hint">in ${event.inDays} days</span>`;
list.append(cell);
}
}
function fromGregorian() {
if (!gregorian.value) return;
const date = toHijri(gregorian.value, options());
hijri.value = `${date.year}-${String(date.month).padStart(2, '0')}-${String(date.day).padStart(2, '0')}`;
show(date);
}
function fromHijriField() {
const parsed = parse(hijri.value, locale);
if (!parsed) {
note.textContent = 'That does not look like a Hijri date.';
return;
}
const date = fromHijri(parsed, options());
gregorian.value = isoOf(date.gregorian);
show(date);
}
gregorian.addEventListener('input', fromGregorian);
hijri.addEventListener('change', fromHijriField);
offsetField.addEventListener('change', fromGregorian);
document.getElementById('locale').addEventListener('click', (event) => {
const button = event.target.closest('button');
if (!button) return;
for (const sibling of button.parentElement.children) sibling.classList.toggle('is-on', sibling === button);
locale = button.dataset.locale;
fromGregorian();
});
fromGregorian();
#out { font-size: 17px; font-weight: 600; }
[dir='rtl'] #out { text-align: right; }
#events { gap: 10px 18px; }
#events b { font-weight: 600; }