Das Aufzeichnen chronologischer Daten, die unabhängig vom Standort des Betrachters identisch bleiben sollen, z. B. Geburtsdaten, tägliche Alarme, Ablaufdaten von Kreditkarten oder monatliche Abrechnungszeiträume, war mit dem alten Date-Objekt in der Vergangenheit fehleranfällig. Da ein Date immer einen bestimmten Zeitpunkt darstellt und in der lokalen Zeitzone des Betrachters angezeigt wird, kann das Speichern eines Datums wie "1990-01-01" dazu führen, dass Nutzer westlich der UTC "1989-12-31" sehen. Beim Modellieren von Teil-Datumsangaben (z. B. nur der Monat des Ablaufdatums einer Karte) werden willkürliche Platzhaltertage erzwungen, die zu Überlauffehlern führen.
Die Temporal API bietet eine Reihe von standortunabhängigen „Plain“-Typen, die keinen Zeitzonenversatz haben und Kalenderdaten und Uhrzeiten genau so darstellen, wie sie von einem Kalender oder einer Uhr abgelesen werden:
| Typ | Was wird modelliert? | Typische Anwendungsfälle |
|---|---|---|
Temporal.PlainDate |
Vollständiges Kalenderdatum (year, month, day) |
Geburtsdaten, Termine, Abrechnungsdaten für Abos |
Temporal.PlainTime |
Echtzeit (hour, minute, second, ...) |
Tägliche Wecker (9:00 Uhr am Standort des Nutzers), Öffnungszeiten |
Temporal.PlainDateTime |
Kalenderdatum + Uhrzeit ohne Zeitzone | Floating-Zeitpläne für die lokale Zeitzone, die einen PlainDate und einen PlainTime kombinieren, bevor eine Zeitzone angehängt wird |
Temporal.PlainMonthDay |
Monat und Tag ohne Jahr | Wiederkehrende jährliche Geburtstage, Jahrestage, feste Feiertage |
Temporal.PlainYearMonth |
Jahr und Monat ohne Tag | Ablaufdatum von Kreditkarten (2027-12), monatliche Abrechnungszeiträume |
Implementierung
- Wählen Sie den spezifischsten
Temporal.Plain*-Typ aus, der zu Ihren Daten passt:- Verwenden Sie
Temporal.PlainDate.from()für vollständige Kalenderdaten undTemporal.PlainTime.from()für Uhrzeiten. - Verwenden Sie
Temporal.PlainDateTime.from()(oderplainDate.toPlainDateTime(plainTime)), wenn Sie ein Kalenderdatum und eine Uhrzeit kombinieren, ohne eine Zeitzone festzulegen. - Verwenden Sie
Temporal.PlainMonthDay.from()für jährliche Datumsangaben ohne Jahr und konvertieren Sie sie mit.toPlainDate({ year })in einenPlainDate, bevor Sie Datumsberechnungen durchführen. - Verwenden Sie
Temporal.PlainYearMonth.from()für Jahres- und Monatswerte, damit keine beliebigen Tageszahlen in Berechnungen einfließen.
- Verwenden Sie
- Beachten Sie den Parameter
calendarbeim Erstellen von Plain-Typen: ISO 8601 ('iso8601') ist der Standardkalender beim Parsen von ISO-Strings oder beim Erstellen mitPlainDate.from({ year, month, day }). Wenn SiePlainMonthDayoderPlainYearMonthaus Property Bags erstellen oder mit Nicht-ISO-Kalendern arbeiten, übergeben Siecalendarexplizit (z.B.{ month: 10, day: 31, calendar: 'iso8601' }). .add()mitoverflow-Optionen für wiederkehrende Kalenderintervalle verwenden: Beim Aufrufen vonplainDate.add({ months: 1 })wird standardmäßig{ overflow: 'constrain' }verwendet, wodurch Monatsenddaten wie der 31. Januar auf den 28./29. Februar beschränkt werden. Übergeben Sie{ overflow: 'reject' }, wenn ungültige Zieldaten einenRangeErrorauslösen sollen, anstatt sie zu klemmen.
Beispiel: Vollständige Datumsangaben, Uhrzeiten und PlainDateTime
// 1. Parse a calendar date and a wall-clock time from input strings
const birthdate = Temporal.PlainDate.from('1990-01-01');
const reminderTime = Temporal.PlainTime.from('09:00');
// Combine into a location-agnostic PlainDateTime without string interpolation
const localAppointment = birthdate.toPlainDateTime(reminderTime);
// 2. Display without time zone conversion; outputs the same date in any time zone
console.log(birthdate.toLocaleString('en-GB'));
console.log(localAppointment.toString()); // "1990-01-01T09:00:00"
// 3. Read calendar fields directly from the Plain type. A PlainDate already *is*
// a calendar date, so there is no need for the formatToParts() workaround that
// legacy Date code uses to recover year/month/day from an instant.
const { year, month, day } = birthdate;
Beispiel: Unvollständige Datumsangaben (PlainMonthDay und PlainYearMonth)
// Annual recurring event (no year component)
// Include explicit calendar when constructing from an object
const annualHoliday = Temporal.PlainMonthDay.from({
month: 10,
day: 31,
calendar: 'iso8601',
});
const today = Temporal.Now.plainDateISO();
const isToday = annualHoliday.equals(today.toPlainMonthDay());
// Convert PlainMonthDay to a PlainDate with a specific year before doing arithmetic,
// because month lengths (such as February) depend on whether the year is a leap year.
const occurrenceThisYear = annualHoliday.toPlainDate({ year: today.year });
// Monthly expiration (no day component, preventing day-of-month overflow bugs)
const cardExpiry = Temporal.PlainYearMonth.from({
year: 2027,
month: 12,
calendar: 'iso8601',
});
const currentMonth = today.toPlainYearMonth();
const isExpired = Temporal.PlainYearMonth.compare(cardExpiry, currentMonth) < 0;
Beispiel: Wiederkehrende monatliche Intervalle (overflow: 'constrain' im Vergleich zu 'reject')
const billingStart = Temporal.PlainDate.from('2024-01-31');
// Default overflow: 'constrain' clamps Jan 31 + 1 month to the last valid day of February
const nextBilling = billingStart.add({ months: 1 }, { overflow: 'constrain' });
console.log(nextBilling.toString()); // "2024-02-29" (2024 is a leap year)
// Use overflow: 'reject' to throw a RangeError when the target day does not exist
try {
billingStart.add({ months: 1 }, { overflow: 'reject' });
} catch (err) {
// RangeError: day 31 does not exist in February
}
Beispiel: Interoperabilität mit Legacy-Date
Ein Legacy-Date ist ein Zeitstempel (ein Zeitpunkt), kein Kalenderdatum. Wenn Sie "1990-01-01" als Date speichern, müssen Sie einen Zeitpunkt auswählen (normalerweise Mitternacht UTC). Wenn Sie ihn in einer Zeitzone westlich von UTC lesen, erhalten Sie "31/12/1989". Wenn eine Bibliothek oder API weiterhin einen Date erfordert, konvertieren Sie explizit über Temporal.Instant, anstatt ISO-Strings manuell zu erstellen:
// PlainDate -> Date: attach a time zone (time of day defaults to the start of
// that day) to get an Instant, then hand the epoch milliseconds to Date.
// 'UTC' is a deliberate choice and must be documented; any zone would do,
// but the stored instant depends on it.
const instant = birthdate.toZonedDateTime('UTC').toInstant();
const legacyDate = new Date(instant.epochMilliseconds);
// Date -> PlainDate: go back through Instant (legacyDate.toTemporalInstant()
// is equivalent), then project into an explicit time zone. The result depends
// on the zone, which is exactly the drift risk.
const viewedDate = Temporal.Instant.fromEpochMilliseconds(legacyDate.getTime())
.toZonedDateTimeISO('America/New_York')
.toPlainDate();
// Compare Plain types with .equals() (or Temporal.PlainDate.compare), not strings.
console.log(viewedDate.equals(birthdate)); // false: the calendar date drifted to 1989-12-31
Strategische Implementierung und Best Practices
- Verwenden Sie
Temporal.PlainDatefür vollständige Kalenderdaten,Temporal.PlainTimefür Uhrzeiten,Temporal.PlainDateTimefür lokale Datums- und Uhrzeitpaare ohne Zeitzone,Temporal.PlainMonthDayfür wiederkehrende jährliche Daten undTemporal.PlainYearMonthfür Ablaufdaten oder Abrechnungszeiträume, die nur aus Jahr und Monat bestehen. - Führen Sie KEINE arithmetischen Operationen direkt für
Temporal.PlainMonthDayaus. Konvertieren Sie den Wert zuerst mit.toPlainDate({ year })in einenTemporal.PlainDate, da die Monatslängen je nach Jahr variieren. - DO use
.add()onTemporal.PlainDatefor recurring calendar intervals, relying onoverflow: 'constrain'(the default) to clamp month-end dates oroverflow: 'reject'to throw aRangeErroron nonexistent dates. - Geben Sie
calendar(z.B.calendar: 'iso8601') an, wenn SiePlainMonthDay- oder Nicht-ISO-Plain-Typen aus Objekten erstellen. - Verwenden Sie KEINE einfachen Typen, wenn Sie einen bestimmten Zeitpunkt in der physischen Zeit (einen Moment) darstellen müssen. Verwenden Sie
Temporal.InstantoderTemporal.ZonedDateTimefür Logs, Ereigniszeitstempel oder die Planung über Zeitzonen hinweg. - Erstellen Sie KEINE
Date-Objekte aus manuell erstellten ISO-Strings (z. B.`${date}T00:00:00Z`). Erstellen Sie einDate-Objekt aus demepochMilliseconds-Wert einesTemporal.Instant- oderTemporal.ZonedDateTime-Objekts und lesen Sie es mitTemporal.Instant.fromEpochMilliseconds(date.getTime())oderdate.toTemporalInstant()aus, damit die Zeitzone explizit angegeben wird. - Verwenden Sie
Intl.DateTimeFormat.prototype.formatToParts()NICHT, um Werte für Jahr, Monat oder Tag zu extrahieren oder zu vergleichen..year,.monthund.daydirekt aus einem einfachen Typ lesen und mit.equals()vergleichen. - DO identify time zones by IANA name (e.g.,
America/New_York) and never by a fixed UTC offset. Die Zeitverschiebung ändert sich mit der Sommerzeit und der Gesetzgebung, sodass Labels wie"UTC-5"für einen Teil des Jahres falsch sind. - DENKEN SIE DARAN, dass
Temporal-Objekte unveränderlich sind. Methoden wie.add(),.subtract()oder.with()geben immer eine neue Instanz mit einer neuen Referenz zurück, anstatt das ursprüngliche Objekt direkt zu ändern. Sie können daher sicher direkt im reaktiven UI-Status gespeichert werden.
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();
})();