Die Berechnung der verstrichenen oder verbleibenden Zeit zwischen Ereignissen (z. B. Ablauf von Testzeiträumen, Countdowns für Abos oder SLA-Fenster) und die Darstellung dieser Zeiträume in einem lokalisierten, menschenlesbaren Format erforderte bisher manuelle Berechnungen oder externe Bibliotheken. Das alte Date-Objekt erfasst nur Millisekunden seit der Epoche, was die Berechnung von Kalendermonaten, die Umstellung auf Sommerzeit und die Einheitenabstimmung fehleranfällig macht.
Die Temporal API kombiniert Temporal.ZonedDateTime mit Temporal.Duration und Intl.DurationFormat, um Zeitdifferenzen zu liefern, die die Sommerzeit berücksichtigen, konfigurierbare Einheitenverteilung, kalenderbasierte Dauervergleiche und lokalisierte Formatierung.
Implementierung
Temporal.ZonedDateTime-Endpunkte abrufen: Verwenden SieTemporal.Now.zonedDateTimeISO()für die aktuelle Zeit in der Systemzeitzone oder übergeben Sie einen expliziten IANA-Zeitzonenstring (Temporal.Now.zonedDateTimeISO(timeZone)), wenn die Zeitzone des Zielereignisses von der System-Standardeinstellung des Nutzers abweicht.- Differenziale mit
.since()und.until()berechnen:- Mit
now.since(start, options)können Sie die Zeit berechnen, die seit einem vergangenen Ereignis verstrichen ist. - Verwenden Sie
now.until(end, options), um die verbleibende Zeit bis zu einem zukünftigen Ereignis zu berechnen. - Übergeben Sie
largestUnit(z.B.'year','month','day','hour') undsmallestUnit(z.B.'minute'), um die resultierendeTemporal.Durationauszugleichen und zu runden.
- Mit
- Eigenständige
Temporal.Duration-Objekte ausgleichen oder vergleichen:- Erstellen Sie eigenständige Dauern mit
Temporal.Duration.from({ minutes: 90 })und gleichen Sie Einheiten mitduration.round({ largestUnit: 'hour' })aus. Geben Sie dabeirelativeToan, wenn Sie über Kalendermonate, Jahre oder Sommerzeitübergänge hinweg ausgleichen. - Vergleichen Sie zwei Dauern mit
Temporal.Duration.compare(d1, d2, { relativeTo }). Wenn eine der Dauern Kalendereinheiten (years,months,weeks) enthält oder die Sommerzeitgrenzen überschreitet, muss einPlainDateoderZonedDateTimealsrelativeToübergeben werden.
- Erstellen Sie eigenständige Dauern mit
- Format für Menschen im Vergleich zu Maschinen:
- Verwenden Sie
duration.toLocaleString(locale, options)(das anIntl.DurationFormatdelegiert) für lokalisierten, für Nutzer sichtbaren Text. - Verwenden Sie
duration.toString()nur für maschinenlesbare ISO 8601-Dauerstrings (z. B.P1M2DT4H).
- Verwenden Sie
Beispiel: Unterschiedliche Ablaufdaten für Testzeiträume und lokalisierte Formatierung
// 1. Get the current time in an explicit target IANA time zone (or omit the
// argument to use the system's current time zone).
const tz = 'America/New_York';
const now = Temporal.Now.zonedDateTimeISO(tz);
// 2. Combine separate date and time strings directly.
// Do not build an ISO string with template literals; chain Temporal methods instead.
const startDateStr = '2025-01-01';
const startTimeStr = '12:00:00';
const endDateStr = '2025-01-31';
const endTimeStr = '12:00:00';
const start = Temporal.PlainDate.from(startDateStr).toPlainDateTime(startTimeStr).toZonedDateTime(tz);
const end = Temporal.PlainDate.from(endDateStr).toPlainDateTime(endTimeStr).toZonedDateTime(tz);
// 3. Calculate differences using .since() and .until()
// ZonedDateTime differences default to largestUnit: 'hour' (days are not a fixed
// length across DST, so Temporal won't balance into days unless asked). Pass
// largestUnit: 'day' or a calendar unit ('week', 'month', 'year') when you want a
// human-scale breakdown instead of e.g. PT720H. smallestUnit rounds away
// seconds and sub-second noise so the formatted string stays readable.
const timeActive = now.since(start, { largestUnit: 'year', smallestUnit: 'minute' });
const timeRemaining = now.until(end, { largestUnit: 'year', smallestUnit: 'minute' });
// 4. Format durations for display with .toLocaleString() (Intl.DurationFormat)
// instead of manually concatenating unit numbers and suffixes. 'en-US' is used
// here for deterministic output; pass undefined to use the user's locale.
console.log(`Active: ${timeActive.toLocaleString('en-US', { style: 'narrow' })}`);
console.log(`Remaining: ${timeRemaining.toLocaleString('en-US', { style: 'long' })}`);
// Use .toString() for ISO 8601 serialization (e.g., API payloads or <time datetime="...">)
console.log(`ISO 8601: ${timeRemaining.toString()}`);
// 5. Check status via .sign, and compare durations with Temporal.Duration.compare()
const isExpired = timeRemaining.sign < 0;
// Pass relativeTo when comparing durations that may include calendar months/years or DST shifts
const isPastHalfway = Temporal.Duration.compare(timeActive, timeRemaining, { relativeTo: now }) > 0;
Beispiel: Eigenständige Dauern ausgleichen (round und Intl.DurationFormat)
// Convert a raw unit count (e.g., 90 minutes) into a balanced breakdown (1 hour, 30 minutes)
const rawDuration = Temporal.Duration.from({ minutes: 90 });
const balanced = rawDuration.round({ largestUnit: 'hour' });
// Format via duration.toLocaleString() or directly with Intl.DurationFormat
const formatter = new Intl.DurationFormat('en-US', { style: 'long' });
console.log(formatter.format(balanced)); // "1 hour, 30 minutes"
Strategische Implementierung und Best Practices
- DO use
Temporal.ZonedDateTimefor calculations involving real-world events that occur in specific time zones (like subscription renewals or event countdowns), and pass an explicittimeZonetoTemporal.Now.zonedDateTimeISO(timeZone)when calculating against a zone other than the system default. - DO combine separate date and time strings with Temporal methods, e.g.
Temporal.PlainDate.from(dateStr).toPlainDateTime(timeStr).toZonedDateTime(tz)orTemporal.PlainDate.from(dateStr).toZonedDateTime({ timeZone: tz, plainTime: timeStr }), instead of interpolating an ISO string like`${dateStr}T${timeStr}`. - ÜBERGEBEN Sie
largestUnit(z. B.'day','month','year') in.since(),.until()oder.round(), wenn Sie Dauern über Stunden hinaus ausgleichen möchten.ZonedDateTime-Differenzmethoden sind standardmäßig auf'hour'eingestellt. - VERWENDEN Sie
Temporal.Duration.compare(d1, d2, { relativeTo }), wenn Sie zwei Zeiträume vergleichen, und geben Sie einenrelativeTo-Anker an, wenn einer der Zeiträume Kalendereinheiten (months,years) oder zeitzonenabhängige Tage enthält. - Formatiere Zeiträume für die Anzeige für Nutzer mit
duration.toLocaleString()odernew Intl.DurationFormat(locale, { style })('long','short','narrow'oder'digital'), anstatt${duration.years}y ${duration.months}m...manuell zu interpolieren. - DO use
duration.toString()for machine-readable ISO 8601 duration strings (e.g.,PT1H30M), never as user-facing copy. - DO render a
PlainTimeasHH:MM(e.g., for<input type="time">) using.toPlainTime().toString({ smallestUnit: 'minute' })rather than string.slice(0, 5). - Ändern Sie Instanzen NICHT direkt.
Temporal-Objekte sind unveränderlich. Vorgänge wie.add(),.subtract(),.round()oder.with()geben eine neue Instanz zurück. - Verwenden Sie für die Differenz NICHT die Arithmetik von
Date/.getTime().Datebietet keine zeitzonenabhängige Kalenderarithmetik und nur eine Genauigkeit im Millisekundenbereich. - DO use the native
.signproperty of the computedTemporal.Duration(orTemporal.ZonedDateTime.compare()) to check if a duration represents a past/expired time point (sign < 0) or a future pending start.
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();
})();