Unterstützung globaler Kalendersysteme mit Temporal

Webfunktionen: Temporal

Das herkömmliche JavaScript-Objekt Date basiert auf einem proleptischen gregorianischen Kalender. Das macht es schwierig, Anwendungen für Nutzer zu entwickeln, die andere Kalendersysteme verwenden, z. B. den islamischen (Mondkalender), den hebräischen (lunisolar) oder den chinesischen (lunisolar) Kalender. Bisher mussten Entwickler auf komplexe Bibliotheken von Drittanbietern oder manuelle Berechnungen zurückgreifen, um diese Systeme zu unterstützen.

Die Temporal API bietet erstklassige Unterstützung für mehrere Kalendersysteme. Durch die Verknüpfung einer Kalenderkennung mit Datumsobjekten übernimmt Temporal die komplexe Arithmetik und Formatierung, die für verschiedene kulturelle Kontexte erforderlich ist, nativ.

Implementierung

So unterstützen Sie globale Kalendersysteme mit Temporal:

  1. Kalender verknüpfen (erforderlich): Wenn Sie ein Temporal-Objekt (z. B. Temporal.PlainDate) erstellen oder konvertieren, müssen Sie das gewünschte Kalendersystem mit withCalendar() angeben, um kalenderspezifische Vorgänge auszuführen.
  2. Stabile Kennungen verwenden (für lunisolare Kalender obligatorisch): Du musst monthCode anstelle des numerischen month-Index verwenden, um bestimmte Monate in lunisolaren Kalendern über mehrere Jahre hinweg zu identifizieren. Verwenden Sie diese Option nur für Kalender, die Schaltmonate verwenden, z. B. den hebräischen und den chinesischen Kalender.
  3. Kalenderinvarianten beachten (obligatorisch): Wenn Sie Monate oder Tage durchlaufen, dürfen Sie nicht von festen Werten ausgehen (z. B. 12 Monate in einem Jahr oder 31 Tage in einem Monat). Verwenden Sie Properties wie monthsInYear und daysInMonth, damit Ihr Code in allen Kalendern funktioniert.
  4. Kalenderbezogene Vergleiche verwenden (obligatorisch): Wenn Sie Datumsangaben innerhalb eines bestimmten Kalendersystems vergleichen, müssen Sie Temporal.PlainDate.compare() verwenden, anstatt die Eigenschaften für Jahr/Monat/Tag manuell zu vergleichen. So wird die chronologische Reihenfolge innerhalb der Kalenderregeln korrekt berücksichtigt.

Beispielcode: Kalender konvertieren und iterieren

// 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 Implementierung und Best Practices

  • DO: Verwenden Sie monthsInYear als Obergrenze, wenn Sie Monate durchlaufen, anstatt von 12 auszugehen.
  • DO: Verwenden Sie monthCode, um bestimmte Monate in Kalendern mit Schaltmonaten (z.B. hebräischer oder chinesischer Kalender) unabhängig vom Jahr zu identifizieren.
  • Gehen Sie NICHT davon aus, dass date.month === 12 der letzte Monat des Jahres ist. Verwenden Sie date.month === date.monthsInYear.
  • Gehen Sie NICHT davon aus, dass inLeapYear === true bedeutet, dass das Jahr nur einen Tag länger ist. In Lunisolarkalendern kann ein ganzer Schaltmonat hinzugefügt werden.
  • DO use Temporal.PlainDate.compare() when comparing two dates in a specific calendar system instead of comparing Year/Month/Day properties manually.
  • DO: Verwenden Sie toLocaleString(), um Datumsangaben für Nutzer zu formatieren, anstatt Strings manuell zu verketten.
  • PRÜFEN Sie, ob das Zielkalendersystem von der Umgebung unterstützt wird, indem Sie Intl.supportedValuesOf('calendar') verwenden, bevor Sie kalenderspezifische Temporal-Objekte erstellen.
  • BEACHTEN SIE, dass einige Kalender (z. B. Varianten des islamischen Kalenders) möglicherweise auf visueller Beobachtung und nicht auf festen Berechnungen basieren. Die Temporal API folgt der Intl-Implementierung der Umgebung, die normalerweise berechnete Näherungswerte verwendet. Für wichtige kulturelle oder religiöse Datumsberechnungen sollten Sie sich an Experten wenden oder spezialisierte Bibliotheken verwenden.
  • DO account for era names when using calendars that use eras (e.g., Japanese, Buddhist), using toLocaleString().

Fallback-Strategie

Fallbacks und Browserunterstützung für Temporal

Für Browser, die die native Temporal API noch nicht unterstützen, können Sie die Funktionserkennung verwenden und temporal-polyfill dynamisch laden (~15 KB min+gz).

Beachten Sie, dass das Importieren von temporal-polyfill das Temporal-Objekt nicht automatisch dem globalen Bereich zuweist. Weisen Sie ihn explizit zu, wenn Ihr Code auf globalThis.Temporal basiert.

// 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();
})();