Wereldwijde agendasystemen ondersteunen met Temporal

Webfuncties: Temporal

Het traditionele JavaScript-object Date is gebaseerd op een proleptische Gregoriaanse kalender. Hierdoor is het lastig om apps te maken voor gebruikers die andere kalendersystemen gebruiken, zoals de islamitische (maan), Hebreeuwse (lunisolar) of Chinese (lunisolar) kalender. Ontwikkelaars moesten eerder vertrouwen op complexe bibliotheken van derden of handmatige berekeningen om deze systemen te ondersteunen.

De Temporal API biedt eersteklas ondersteuning voor meerdere agendasystemen. Door een agenda-ID aan datumobjecten te koppelen, verwerkt Temporal de complexe berekeningen en opmaak die vereist zijn voor verschillende culturele contexten.

Implementeren

Zo ondersteun je wereldwijde agendasystemen met Temporal:

  1. Een kalender koppelen (verplicht): Als je een Temporal-object (zoals Temporal.PlainDate) maakt of converteert, moet je het gewenste kalendersysteem opgeven met withCalendar() om kalendergevoelige bewerkingen uit te voeren.
  2. Gebruik stabiele ID's (verplicht voor lunisolaire kalenders): Gebruik monthCode in plaats van de numerieke index month om specifieke maanden in verschillende jaren in lunisolaire kalenders te identificeren. Gebruik dit alleen voor kalenders die schrikkelmaanden gebruiken, zoals de Hebreeuwse en Chinese kalenders.
  3. Respecteer kalenderinvarianten (verplicht): Als je maanden of dagen doorloopt, mag je niet uitgaan van vaste waarden (zoals 12 maanden in een jaar of 31 dagen in een maand). Gebruik property's zoals monthsInYear en daysInMonth om te zorgen dat je code werkt in alle agenda's.
  4. Gebruik kalenderbewuste vergelijkingen (verplicht): Als je datums binnen een specifiek kalendersysteem vergelijkt, moet je Temporal.PlainDate.compare() gebruiken in plaats van de eigenschappen jaar/maand/dag handmatig te vergelijken. Zo wordt de chronologische volgorde binnen de agendaregels correct verwerkt.

Voorbeeldcode: Agenda's converteren en herhalen

// 1. Helper to check calendar support
function 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 calendar
const isoDate = Temporal.Now.plainDateISO();

// 3. Convert to Hebrew calendar if supported
const 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 calendar
console.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 year
for (let m = 1; m <= targetDate.monthsInYear; m++) {
  console.log(`Month ${m} has ${targetDate.with({ month: m }).daysInMonth} days.`);
}

// 6. Compare dates within the same calendar
const 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'
});

Strategische implementatie en best practices

  • Gebruik WEL monthsInYear als de bovengrens als u door maanden loopt, in plaats van uit te gaan van 12.
  • Gebruik WEL monthCode om specifieke maanden te identificeren in kalenders die schrikkelmaanden gebruiken (bijvoorbeeld de Hebreeuwse of Chinese kalender), ongeacht het jaar.
  • Ga er NIET van uit dat date.month === 12 de laatste maand van het jaar is. Gebruik date.month === date.monthsInYear.
  • Ga er NIET van uit dat inLeapYear === true impliceert dat het jaar maar één dag langer is. In lunisolaire kalenders kan een hele schrikkelmaand worden toegevoegd.
  • Gebruik WEL Temporal.PlainDate.compare() als je 2 datums in een specifiek agendasysteem vergelijkt in plaats van de eigenschappen Jaar/Maand/Dag handmatig te vergelijken.
  • Gebruik WEL toLocaleString() om datums voor gebruikers op te maken in plaats van handmatige tekenreekssamenvoeging.
  • WEL: Controleer of het doelagendasysteem wordt ondersteund door de omgeving met Intl.supportedValuesOf('calendar') voordat je agendaspecifieke Temporal-objecten maakt.
  • WEL: Houd er rekening mee dat sommige kalenders (zoals varianten van de islamitische kalender) afhankelijk zijn van visuele observatie in plaats van vaste berekeningen. De Temporal API volgt de Intl-implementatie van de omgeving, die meestal berekende benaderingen gebruikt. Voor belangrijke culturele of religieuze datumberekeningen kun je het beste contact opnemen met domeinexperts of gespecialiseerde bibliotheken gebruiken.
  • WEL: Houd rekening met tijdperknamen als je agenda's gebruikt die tijdperken gebruiken (bijvoorbeeld de Japanse of boeddhistische agenda) door toLocaleString() te gebruiken.

Reservestrategie

Fallbacks en browserondersteuning voor Temporal

Voor browsers die de native Temporal API nog niet ondersteunen, gebruik je functiedetectie en laad je temporal-polyfill dynamisch (~15 KB min+gz).

Houd er rekening mee dat als je temporal-polyfill importeert, het Temporal-object niet automatisch wordt toegewezen aan het globale bereik. Wijs de waarde expliciet toe als je code afhankelijk is van globalThis.Temporal.

// Check if Temporal is supported natively
(async () => {
  if (typeof Temporal === 'undefined') {
    // Load the polyfill conditionally
    const module = await import('https://esm.sh/temporal-polyfill');
    globalThis.Temporal = module.Temporal;
    // Extend Date.prototype if needed
    Date.prototype.toTemporalInstant = module.toTemporalInstant;
  }
  initializeApp();
})();