Date, Time and Calendar Localization for Gulf and Levant Audiences
A frontend guide to Intl.DateTimeFormat's calendar option, Hijri-vs-Gregorian display conventions, MENA timezone instability, and 12-hour vs 24-hour formatting across Gulf and Levant locales.
A date field is one of the few pieces of UI that looks finished long before it actually is. new Date().toLocaleDateString('ar') compiles, renders Arabic digits or script, and passes a quick visual check in a demo. It can still be wrong for a production audience in three independent ways at once: the calendar system a user expects isn't the one being shown, the clock format doesn't match local convention, and the timezone math assumes an offset that a government changed with two weeks' notice.
None of these are exotic edge cases. They're the default behavior of the region. This post is about the part of Arabic localization that sits underneath fonts, layout and validation copy — already covered in our companion pieces on RTL design systems and Arabic typography — and instead deals specifically with how JavaScript's Intl namespace handles calendars, clocks and timezones for Gulf and Levant users, and where the platform's defaults do and don't match what those users actually expect.
Key Takeaways
Intl.DateTimeFormat'scalendaroption isn't limited togregory— the platform also supports several Islamic calendar variants (islamic,islamic-umalqura,islamic-civil,islamic-tbla,islamic-rgsa), and they can compute different dates for the same instant because they use different underlying algorithms — astronomical calculation, fixed tabular rules, or Saudi Arabia's Umm al-Qura system.- Saudi Arabia's government moved official business — salaries, contracts, government dealings — to the Gregorian calendar in October 2016 as a cost-cutting measure, according to Arab News's reporting at the time; it did not retire the Hijri calendar, which stays central to religious observance and is still used administratively via the Umm al-Qura system.
- The UAE's official government platform states plainly that "Islamic holidays are determined according to moon sighting," which is why Eid dates can't be hardcoded even a few days out — they're confirmed close to the day itself.
Intl.Locale.prototype.getCalendars()returns the calendars a locale actually recognizes, in preference order, and MDN's own example showsar-EGresolving to["gregory", "coptic", "islamic", "islamic-civil", "islamic-tbla"]— Egypt's calendar preferences include a calendar most non-Egyptian teams wouldn't think to test.- Regional timezone rules changed enough in the past few years that hardcoding UTC offsets is actively dangerous: Jordan permanently abolished daylight saving time in 2022, Egypt reinstated it in 2023 after a seven-year suspension, and Lebanon's government flip-flopped on a DST start date in March 2023 so late that the IANA time zone database needed two emergency releases in five days to model it.
- Most Gulf and Levant Arabic locales default to a 12-hour clock (
hourCycle: "h12") with Arabic AM/PM markers, and several — Saudi Arabia, Egypt, Jordan, Kuwait, Qatar, Bahrain, Oman, Iraq, Lebanon — default to Eastern Arabic-Indic digits (numberingSystem: "arab") rather than the Western digits many teams assume; this doesn't come from a style guide, it's whatIntl.DateTimeFormatresolves to by default for those locales.
Two calendars, and neither is a rounding error
The assumption behind most date pickers — that a date is a date, and localization just swaps the words around it — breaks the moment a Gulf user opens the app. Two calendar systems are genuinely in active use across the region, and a product that only ever shows Gregorian dates has made a decision it probably didn't mean to make.
Saudi Arabia is the clearest documented case. The kingdom ran on the Hijri calendar exclusively since its founding, and in October 2016 its cabinet approved a shift to the Gregorian calendar for government salaries, contracts and official dealings — a decision Arab News reported as a cost-cutting move, since paying civil servants on a roughly 354-day lunar year had quietly cost more than paying them on a 365-day solar one. That switch covered administrative dates, not a rejection of the Hijri calendar itself: religious observance, Ramadan and Hajj timing, and much of everyday cultural life in Saudi Arabia still run on Hijri dates, computed via the kingdom's own Umm al-Qura algorithm.
The UAE shows the same duality from the other side. Its federal government's own information platform states that "Islamic holidays are determined according to moon sighting," and lists Eid al-Fitr, Eid al-Adha and the Hijri New Year by Hijri date even while the rest of the civil calendar runs on Gregorian dates. A product serving Gulf users generally needs both calendars available — not because Gregorian is "wrong," but because someone checking when an offer expires relative to Ramadan, or when a Hijri-dated document is due, gets the wrong number if the app only ever converts everything to Gregorian.
What Intl.DateTimeFormat's calendar option actually supports
This is a solved problem at the platform level — it's just easy to miss, because most Intl.DateTimeFormat usage never sets calendar explicitly. Per MDN's Intl.DateTimeFormat() constructor reference, calendar accepts any value Intl.supportedValuesOf('calendar') returns, and several of those are Islamic-calendar variants, not one monolithic "islamic" option:
Intl.supportedValuesOf('calendar').filter(c => c.startsWith('islamic'));
// ["islamic", "islamic-civil", "islamic-rgsa", "islamic-tbla", "islamic-umalqura"]
These aren't interchangeable labels — they're different algorithms that can disagree on which Hijri date a given Gregorian day falls on. CLDR's design documentation on Islamic calendar types is explicit about this: islamic-civil and islamic-tbla are tabular calendars using fixed arithmetic; plain islamic is "based on astronomical calculation of moon phase based on a certain location" and is flagged there as an approximation of lived practice, not a match to it; and islamic-umalqura is "the algorithm... used by Saudi Arabia for administrative purpose" — deterministic, but astronomically derived rather than purely arithmetic. The same source states the consequence directly: "for a given calendar type, the same instant may fall on to different dates depending on what Islamic algorithm variant is actually used." For a Gulf audience, islamic-umalqura — not the shorter, more obvious-looking islamic — is the variant that matches what Saudi systems and most Gulf-facing products actually use.
Select a calendar through the constructor's calendar option or the -u-ca- Unicode locale extension — both resolve identically:
const hijri = new Intl.DateTimeFormat('ar-SA', {
calendar: 'islamic-umalqura',
year: 'numeric',
month: 'long',
day: 'numeric',
});
hijri.format(new Date('2026-09-14'));
// "٣ ربيع الآخر ١٤٤٨ هـ" — verified against a current V8/ICU implementation
// Equivalent, via the locale extension:
new Intl.DateTimeFormat('ar-SA-u-ca-islamic-umalqura').format(new Date('2026-09-14'));
For a Gulf audience, the practical pattern is showing both calendars wherever the date matters — an expiry, a contract term, an event date — rather than picking one:
function formatBothCalendars(date, locale = 'ar-SA') {
const gregorian = new Intl.DateTimeFormat(locale, {
calendar: 'gregory', day: 'numeric', month: 'long', year: 'numeric',
}).format(date);
const hijri = new Intl.DateTimeFormat(locale, {
calendar: 'islamic-umalqura', day: 'numeric', month: 'long', year: 'numeric',
}).format(date);
return `${gregorian} (${hijri} هـ)`;
}
Letting the locale tell you which calendars are relevant
Hardcoding "Gregorian and Hijri" as the only two options is an improvement over Gregorian-only, but it's still a guess. Intl.Locale.prototype.getCalendars() returns the calendars a locale actually recognizes as commonly used, in preference order — a better source of truth than an assumption about what "Arabic" implies:
new Intl.Locale('ar-SA').getCalendars();
// ["gregory", "islamic-umalqura", "islamic", "islamic-rgsa"]
MDN's own reference for the method shows a more surprising case: ar-EG resolves to ["gregory", "coptic", "islamic", "islamic-civil", "islamic-tbla"]. The Coptic calendar isn't a mistake there — Egyptian Streets reports it's still used popularly by Egyptian farmers to track agricultural seasons and by the Coptic Orthodox Church for its liturgical calendar — exactly the kind of locale-specific detail a team guessing at "the two calendars MENA uses" would never think to test. Building a calendar picker's options from getCalendars() for the active locale, rather than a fixed ['gregory', 'islamic'] array, gives Egypt a Coptic option without anyone special-casing it, and stops an untested locale from silently falling back to the wrong assumption.
Weekend conventions: already covered, worth linking not repeating
The other calendar-adjacent detail that trips up MENA scheduling logic is that the region doesn't share one weekend — the UAE's government-aligned Saturday–Sunday week sits alongside Saudi Arabia's, Kuwait's, Qatar's, Bahrain's, Oman's, Egypt's and Jordan's shared Friday–Saturday week. That split, and how it interacts with the Hijri-dated Ramadan and Eid calendar for launch planning specifically, is covered in full in our guide to timing launches around Ramadan and regional holidays — this post won't repeat that breakdown.
Timezones that don't hold still
Gulf timezones are comparatively stable; the Levant and Egypt are where things move. Comparing each zone's UTC offset in January against July shows it directly — the Gulf zones hold flat, while Beirut and Cairo shift with the season:
function offsetFor(tz, date) {
const parts = new Intl.DateTimeFormat('en-US', { timeZone: tz, timeZoneName: 'shortOffset' }).formatToParts(date);
return parts.find(p => p.type === 'timeZoneName').value;
}
for (const tz of ['Asia/Riyadh', 'Asia/Dubai', 'Asia/Amman', 'Asia/Beirut', 'Africa/Cairo']) {
console.log(tz, offsetFor(tz, new Date('2026-01-15')), '→', offsetFor(tz, new Date('2026-07-15')));
}
// Asia/Riyadh GMT+3 → GMT+3 (flat all year)
// Asia/Dubai GMT+4 → GMT+4 (flat all year)
// Asia/Amman GMT+3 → GMT+3 (flat since Jordan's 2022 change, below)
// Asia/Beirut GMT+2 → GMT+3 (shifts with DST)
// Africa/Cairo GMT+2 → GMT+3 (shifts with DST)
That instability is recent enough that a timezone assumption baked into an app eighteen months ago can already be wrong.
Jordan changed its rule outright: in October 2022, the government announced it would stop observing daylight saving time and remain permanently on UTC+3, a move The National reported the government justified as making "most use of the daytime" without the twice-yearly disruption. Egypt went the other way: after seven years without daylight saving time, it reinstated it in 2023, with clocks moving forward at the end of April and back at the end of October — a schedule Egyptian Streets confirmed has held in the years since.
Lebanon is the sharpest illustration of why this should never be hardcoded as a UTC offset. In March 2023, its government announced — two days before the scheduled clock change — that it was postponing the start of daylight saving time by nearly a month, to keep evenings darker during Ramadan. Institutions disagreed about whether to comply, and the country spent several days running two time zones at once: CNN reported that Beirut's airport departure boards showed two different times for the same flight before the government reversed course. The disruption was sharp enough that the IANA time zone database — the source most operating systems, browsers and Intl itself pull timezone rules from — shipped two emergency releases in five days to model it, the second of which states plainly that its purpose was to "model Lebanon's DST chaos by reverting data to tzdb 2023a" once the first patch's prediction was overtaken by the reversal.
The lesson isn't "Lebanon is unpredictable" — it's that a named IANA timezone identifier, kept current through routine OS and browser updates, absorbed that chaos so application code didn't have to. Anything computing Beirut time as a fixed UTC+2 or UTC+3 offset was wrong for days in 2023. Code that asked Intl.DateTimeFormat for Asia/Beirut got the correct, if temporarily strange, answer once the tz data updated:
// Fragile: assumes a fixed offset that regional governments
// have changed on days' notice more than once in recent years.
const wrongOffset = new Date(utcTimestamp + 3 * 60 * 60 * 1000);
// Resilient: delegates offset and DST rules to the platform's
// timezone database, which absorbs exactly this kind of change.
const correct = new Intl.DateTimeFormat('ar-LB', {
timeZone: 'Asia/Beirut',
hour: 'numeric',
minute: 'numeric',
timeZoneName: 'short',
}).format(new Date(utcTimestamp));
The only obligation this shifts onto you is keeping your runtime's ICU/tz data current — an old Node.js LTS version or stale browser can lag behind a rule change like Jordan's or Lebanon's, which argues for tracking your platform's ICU version, not for hand-rolling offset math instead.
12-hour vs. 24-hour, and the digits underneath
Clock format is the detail most likely to get hardcoded to one global default and shipped that way, and it shouldn't be — Gulf and Levant Arabic locales don't uniformly prefer one format. Running Intl.DateTimeFormat with no explicit hourCycle and reading resolvedOptions() shows the actual default per MDN's documented hourCycle and hour12 options:
for (const loc of ['ar-SA', 'ar-AE', 'ar-EG', 'ar-JO', 'ar-KW', 'ar-QA']) {
const dtf = new Intl.DateTimeFormat(loc, { hour: 'numeric', minute: 'numeric' });
console.log(loc, dtf.resolvedOptions().hourCycle, dtf.format(new Date()));
}
// ar-SA h12 ٦:٣٠ م
// ar-AE h12 6:30 م
// ar-EG h12 ٦:٣٠ م
// ar-JO h12 ٦:٣٠ م
// ar-KW h12 ٦:٣٠ م
// ar-QA h12 ٦:٣٠ م
Every Gulf and Levant Arabic locale tested here defaults to a 12-hour clock with Arabic AM/PM markers (ص for morning, م for evening) rather than 24-hour display — worth knowing before a design system's "use 24-hour time everywhere" rule gets applied unchanged to the Arabic build. If a specific context genuinely calls for 24-hour time (transit schedules, technical dashboards), that's an explicit override, not a safe default:
new Intl.DateTimeFormat('ar-SA', { hourCycle: 'h23', hour: '2-digit', minute: '2-digit' })
.format(new Date()); // "١٨:٣٠" — forced 24-hour, against the locale's own default
The same check surfaces a second, easy-to-miss default: digit style. ar-SA, ar-EG, ar-JO, ar-KW, ar-QA, ar-BH, ar-OM, ar-IQ and ar-LB default to numberingSystem: "arab" — Eastern Arabic-Indic digits (٠١٢٣٤٥٦٧٨٩) — while ar-AE is the outlier among Gulf locales, defaulting to Western (latn) digits. A team that QAs only ar-AE and assumes the rest of the Gulf matches it ships the wrong digit style everywhere else, and vice versa for a team that only checks ar-SA. Neither convention is more "correct"; numberingSystem is the explicit override for cases that need one forced regardless of locale, such as a shared numeric ID that should stay in Western digits inside Arabic text:
new Intl.DateTimeFormat('ar-SA', { numberingSystem: 'latn', year: 'numeric', month: 'numeric', day: 'numeric' })
.format(new Date()); // "14/9/2026" — Western digits forced for a locale that defaults to Eastern Arabic-Indic
A smaller but real third difference sits in Gregorian month names, and it's just as visible from resolvedOptions-style output:
for (const loc of ['ar-SA', 'ar-EG', 'ar-JO', 'ar-LB', 'ar-IQ']) {
console.log(loc, new Intl.DateTimeFormat(loc, { month: 'long' }).format(new Date('2026-09-14')));
}
// ar-SA سبتمبر ar-EG سبتمبر (Latin-transliterated)
// ar-JO أيلول ar-LB أيلول ar-IQ أيلول (Aramaic-derived)
Gulf and Egyptian Arabic default to Latin-transliterated month names, while Levantine and Iraqi Arabic default to the older Aramaic-derived set. Neither is a bug; shipping one month-name convention to every Arabic-speaking market is an implicit dialect choice worth making on purpose.
Common mistakes
Assuming "Arabic" means one calendar. Gregorian-only silently drops Hijri-dependent content, and Hijri-only does the reverse. Build calendar options from getCalendars() per locale rather than guessing.
Using bare islamic instead of islamic-umalqura. The two can resolve to different Hijri dates for the same day. For a Gulf audience, islamic-umalqura is almost always the variant users actually expect.
Hardcoding a UTC offset instead of an IANA timezone identifier. Jordan, Egypt and Lebanon have all changed DST rules in the past few years, one with days' notice. Storing +3 instead of Asia/Amman turns every future change into your bug.
Copying a 24-hour design mandate onto the Arabic build unchanged. Gulf and Levant locales default to 12-hour time with Arabic AM/PM markers; treat 24-hour as a deliberate override, not the neutral default.
Assuming one Gulf locale's digit convention speaks for all of them. ar-AE defaults to Western digits while most of the rest of the Gulf and Levant defaults to Eastern Arabic-Indic digits — QA-ing only one locale misses the other convention entirely.
Putting it together
None of this needs a third-party calendar library or a hand-rolled Hijri converter — Intl.DateTimeFormat and Intl.Locale already carry the correct data for every locale and timezone the region uses. It needs a team that asks the platform instead of assuming: call getCalendars() instead of hardcoding two options, choose islamic-umalqura deliberately over the shorter islamic, address timezones by IANA identifier so a government's next DST decision becomes someone else's patch instead of your bug, and treat 12-hour time and Eastern Arabic-Indic digits as the real Gulf and Levant default rather than a deviation from a 24-hour, Western-digit norm that was never universal. Paired with the layout and typography groundwork in our RTL design system and Arabic typography guides, and the plural-aware copy in our Arabic form validation piece, this closes the part of Arabic localization that's easiest to mistake for "just formatting" — until a date renders in the wrong calendar for a user who was never confused about which one they expected.