Supporting Global Calendar Systems with Temporal
The traditional JavaScript Date object is based on a proleptic Gregorian calendar, making it challenging to build applications for users who rely on other calendar systems, such as the Islamic (lunar), Hebrew (lunisolar), or Chinese (lunisolar) calendars. Developers previously had to rely on complex third-party libraries or manual calculations to support these systems.
The Temporal API provides first-class support for multiple calendar systems. By associating a calendar identifier with date objects, Temporal handles the complex arithmetic and formatting required for different cultural contexts natively.
How to Implement
Section titled “How to Implement”To support global calendar systems using Temporal:
- Associate a Calendar (Mandatory): When creating or converting a Temporal object (like
Temporal.PlainDate), you must specify the desired calendar system usingwithCalendar()to perform calendar-sensitive operations. - Use Stable Identifiers (Mandatory for Lunisolar Calendars): You must use
monthCoderather than the numericmonthindex to identify specific months across years in lunisolar calendars. Only use this for calendars that use leap months, such as the Hebrew and Chinese calendars. - Respect Calendar Invariants (Mandatory): When iterating through months or days, you must not assume fixed values (like 12 months in a year or 31 days in a month). Use properties like
monthsInYearanddaysInMonthto ensure your code works across all calendars. - Use Calendar-Aware Comparisons (Mandatory): When comparing dates within a specific calendar system, you must use
Temporal.PlainDate.compare()instead of comparing year/month/day properties manually. This correctly handles chronological ordering within the calendar rules.
Example Code: Converting and Iterating Calendars
Section titled “Example Code: Converting and Iterating Calendars”// 1. Helper to check calendar supportfunction isCalendarSupported(calendarId) { try { return Intl.supportedValuesOf('calendar').includes(calendarId); } catch { // Fallback for environments where supportedValuesOf is not available return false; }}
// 2. Get current date in default ISO 8601 calendarconst isoDate = Temporal.Now.plainDateISO();
// 3. Convert to Hebrew calendar if supportedconst calendarId = 'hebrew';const targetDate = isCalendarSupported(calendarId) ? isoDate.withCalendar(calendarId) : isoDate; // Fallback to ISO if not supported
if (targetDate.calendar.id !== calendarId) { console.warn(`Calendar ${calendarId} not supported; falling back to ISO 8601`);}
// 4. Log properties specific to the calendarconsole.log(`Calendar: ${targetDate.calendar.id}`);console.log(`Year: ${targetDate.year}`);console.log(`Month Code: ${targetDate.monthCode}`); // Stable across leap years
// 5. Safely iterate through months in the current yearfor (let m = 1; m <= targetDate.monthsInYear; m++) { console.log(`Month ${m} has ${targetDate.with({ month: m }).daysInMonth} days.`);}
// 6. Compare dates within the same calendarconst today = Temporal.Now.plainDateISO().withCalendar(calendarId);const comparison = Temporal.PlainDate.compare(targetDate, today);const relative = comparison < 0 ? 'Past' : comparison > 0 ? 'Future' : 'Today';console.log(`Timeline: ${relative}`);
// 7. Format for display using toLocaleString (including era for era-based calendars like 'japanese')const localizedDisplay = targetDate.toLocaleString('en-u-ca-hebrew', { day: 'numeric', month: 'long', year: 'numeric', era: 'long'});Strategic Implementation & Best Practices
Section titled “Strategic Implementation & Best Practices”- DO use
monthsInYearas the upper bound when looping through months, rather than assuming 12. - DO use
monthCodefor identifying specific months in calendars that use leap months (e.g., Hebrew or Chinese), regardless of the year. - DO NOT assume
date.month === 12is the last month of the year. Usedate.month === date.monthsInYear. - DO NOT assume
inLeapYear === trueimplies the year is only one day longer. In lunisolar calendars, it may add a full leap month. - DO use
Temporal.PlainDate.compare()when comparing two dates in a specific calendar system instead of comparing Year/Month/Day properties manually. - DO use
toLocaleString()to format dates for users instead of manual string concatenation. - DO verify that the target calendar system is supported by the environment using
Intl.supportedValuesOf('calendar')before creating calendar-specific Temporal objects. - DO be aware that some calendars (like variants of the Islamic calendar) may rely on visual observation rather than fixed calculations. The
TemporalAPI follows the environment’sIntlimplementation, which usually uses calculated approximations. For critical cultural or religious date calculations, verify with domain experts or use specialized libraries. - DO account for era names when using calendars that use eras (e.g., Japanese, Buddhist), using
toLocaleString().
Fallback Strategy
Section titled “Fallback Strategy”Baseline: temporal
Compatibility notes: Badges like Baseline: … mark web features referenced by this guide. Baseline status describes availability across the core browser set — it does not mean every browser or older version supports the feature. Feature data as published upstream, synced 2026-11-12.
Live demo
Section titled “Live demo”Open the demo standalone · Demo source on GitHub
Counter-example (negative demo) — what this guide tells you to avoid.
Source: GoogleChrome/modern-web-guidance-src (Apache-2.0), upstream commit 179dbc6, synced 2026-11-12. Local changes: macros expanded to links/badges, first heading de-duplicated, demo and this footer added. This is an independent, unofficial site — not a Google publication.