JS
() =>
JavaScript · Temporal API · Zeitzonen · Datum
JavaScript Temporal API
Datum und Uhrzeit endlich richtig handhaben

Das Date-Objekt ist einer der ältesten und fehleranfälligsten Teile von JavaScript. Die Temporal API bricht mit allen Designfehlern: immutable Typen, explizite Zeitzonensemantik, korrekte Kalenderarithmetik und eine klare API — ohne externe Bibliotheken wie Moment.js oder date-fns.

14 Min. Lesezeit PlainDate · ZonedDateTime · Instant · Duration · Zeitzonen TC39 Stage 3 · Polyfill verfügbar

1. Warum das Date-Objekt scheitert

Das JavaScript Date-Objekt wurde 1995 in wenigen Tagen entwickelt und hat seitdem kaum bedeutsame Verbesserungen erfahren. Die Designprobleme sind gut dokumentiert und treffen jeden Entwickler früher oder später: Monate sind 0-basiert (Januar ist 0, Dezember ist 11), getYear() gibt die Zahl der Jahre seit 1900 zurück, und Date-Objekte sind mutierbar — date.setMonth(date.getMonth() + 1) verändert das ursprüngliche Objekt. Das führt zu schwer auffindbaren Bugs, wenn dasselbe Datum-Objekt an mehrere Stellen weitergegeben wird.

Das gravierendste Problem ist jedoch die fehlende Zeitzonenunterstützung: Date kennt nur UTC und die lokale Systemzeitzone des Geräts. Jede andere Zeitzone muss mit manuellen Offset-Berechnungen simuliert werden — ein fehleranfälliger Ansatz, der Sommer- und Winterzeitwechsel ignoriert. Genau das ist der Grund, warum Bibliotheken wie Moment.js, Luxon und date-fns entstanden sind. Die Temporal API macht all das überflüssig: Sie ist das Ergebnis jahrelanger Arbeit der TC39-Arbeitsgruppe und adressiert jeden bekannten Designfehler des Date-Objekts mit einer modernen, unveränderlichen API.

2. Die Temporal-Typen: PlainDate, Instant, ZonedDateTime und mehr

Der wichtigste konzeptionelle Unterschied der Temporal API gegenüber Date ist die Trennung verschiedener Konzepte in eigene Typen. Date versucht, alle Szenarien mit einem einzigen Typ abzudecken — Datum, Uhrzeit, Zeitzone, UTC-Timestamp — und schlägt dabei bei allen. Die Temporal API bietet stattdessen spezialisierte Typen: Temporal.PlainDate für ein Datum ohne Zeit und Zeitzone, Temporal.PlainTime für eine Uhrzeit ohne Datum und Zeitzone, Temporal.PlainDateTime für Datum und Zeit ohne Zeitzone, Temporal.Instant für einen exakten UTC-Zeitpunkt, und Temporal.ZonedDateTime für den vollständigen Typ mit Zeitzone.

Diese Spezialisierung ist keine akademische Übung — sie erzwingt, dass Entwickler explizit angeben, welches Konzept sie meinen. Ein Geburtstag ist ein PlainDate — er hat keine Zeitzone, weil ein Geburtstag in Tokyo derselbe Geburtstag wie in Berlin ist. Ein Serverlog-Eintrag ist ein Instant — er muss in UTC gespeichert werden, unabhängig von der lokalen Zeit des Servers. Eine Kalenderveranstaltung ist ein ZonedDateTime — sie hat eine explizite Zeitzone, weil das Meeting um 10 Uhr in Berlin bei Sommerzeit eine andere UTC-Zeit hat als im Winter. Die Temporal API macht diesen Unterschied zur Pflicht, nicht zur Option.


// Temporal API — distinct types for distinct concepts
import { Temporal } from "@js-temporal/polyfill";

// PlainDate: a date without time or timezone — e.g., a birthday
const birthday = Temporal.PlainDate.from("1990-03-15");
console.log(birthday.year);  // 1990
console.log(birthday.month); // 3  — 1-based, unlike Date!
console.log(birthday.day);   // 15

// Instant: exact UTC point in time — e.g., log timestamps
const logEntry = Temporal.Now.instant();
console.log(logEntry.epochSeconds); // Unix timestamp
console.log(logEntry.toString());   // "2026-05-10T14:30:00Z"

// ZonedDateTime: date, time AND explicit timezone — e.g., calendar events
const meeting = Temporal.ZonedDateTime.from({
  year: 2026, month: 6, day: 15,
  hour: 10, minute: 0,
  timeZone: "Europe/Berlin"
});
console.log(meeting.toString());
// "2026-06-15T10:00:00+02:00[Europe/Berlin]"

// Convert to another timezone — same instant, different wall clock
const meetingInTokyo = meeting.withTimeZone("Asia/Tokyo");
console.log(meetingInTokyo.hour); // 17 (UTC+9, CEST is UTC+2)

3. PlainDate und PlainTime: Datum ohne Zeitzone

Temporal.PlainDate repräsentiert ein Datum ohne Uhrzeit und ohne Zeitzone. Das klingt simpel, löst aber eine erhebliche Quelle von Bugs: Wenn man mit Date nur das Datum eines Ereignisses speichern will, erzeugt man trotzdem einen UTC-Timestamp mit einer impliziten Uhrzeit (meistens Mitternacht UTC). Das führt dazu, dass dasselbe Datum — je nach Zeitzone des Betrachters — als Vortag angezeigt wird. Temporals PlainDate speichert dagegen explizit nur Jahr, Monat und Tag, ohne jegliche Uhrzeit- oder Zeitzoneninformation.

Ebenso wichtig: In der Temporal API sind alle Objekte immutabel. birthday.add({ years: 1 }) gibt ein neues PlainDate-Objekt zurück — das ursprüngliche Objekt bleibt unverändert. Das eliminiert eine ganze Klasse von Bugs, bei denen Datum-Objekte unbeabsichtigt verändert werden. PlainTime funktioniert analog für Uhrzeiten ohne Datum und Zeitzone. PlainDateTime kombiniert Datum und Uhrzeit, ist aber ebenfalls zeitzonenlos — nützlich für lokale Zeitpläne oder Rezept-Zeitangaben, wo die Zeitzone keine Rolle spielt.

4. Instant: der exakte Zeitpunkt in UTC

Temporal.Instant entspricht konzeptionell dem, was Date eigentlich darstellt: einen exakten Zeitpunkt auf der universellen Zeitachse, ausgedrückt als Nanosekunden seit der Unix-Epoche (1. Januar 1970, 00:00:00 UTC). Der Unterschied: Instant ist immutabel, kennt keine Zeitzone und hat keine Methoden, um lokale Datumseigenschaften abzufragen — das ist absichtlich. Ein Instant hat nur einen Wert: einen exakten Zeitpunkt. Alles andere — Stunde, Tag, Monat — ist eine Interpretation dieses Zeitpunkts in einer bestimmten Zeitzone und damit Sache von ZonedDateTime.

Die Auflösung von Instant ist Nanosekunden, nicht Millisekunden wie bei Date. Das ist für High-Performance-Anwendungen relevant — Temporal.Now.instant() liefert einen nanosekunden-genauen Zeitstempel, sofern die Plattform das unterstützt. Für Datenbankzeitstempel, API-Payloads und Log-Einträge ist Instant der richtige Typ: instant.toString() gibt immer einen ISO-8601-String in UTC aus, unabhängig von der Systemzeitzone des ausführenden Geräts — ein fundamentaler Unterschied zu new Date().toISOString(), das ebenfalls UTC ausgibt, aber auf einem mutierbaren Objekt basiert.


// Instant: precise UTC timestamps with nanosecond resolution
import { Temporal } from "@js-temporal/polyfill";

// Current instant — nanosecond precision
const now = Temporal.Now.instant();
console.log(now.epochNanoseconds); // BigInt: nanoseconds since Unix epoch
console.log(now.epochMilliseconds); // compatible with Date.now()

// Parse from ISO string — always UTC
const ts = Temporal.Instant.from("2026-05-10T14:30:00.123456789Z");
console.log(ts.epochNanoseconds);

// Measuring durations precisely
const start = Temporal.Now.instant();
// ... do work ...
const end = Temporal.Now.instant();
const elapsed = end.since(start);
console.log(`Elapsed: ${elapsed.total("milliseconds")}ms`);

// Convert Instant to ZonedDateTime for human-readable output
const berlin = ts.toZonedDateTimeISO("Europe/Berlin");
console.log(berlin.hour);   // 16 (UTC+2 in summer)
console.log(berlin.day);    // 10

// Sorting: Instant supports compare()
const timestamps = [ts2, ts1, ts3].sort(Temporal.Instant.compare);

5. ZonedDateTime: Zeitzone explizit und unveränderlich

Temporal.ZonedDateTime ist der mächtigste und vollständigste Typ der Temporal API. Er repräsentiert einen Zeitpunkt mit expliziter Zeitzone und allen Datumseigenschaften. Im Gegensatz zu Instant kennt ZonedDateTime die Zeitzone und berechnet korrekt Sommer- und Winterzeiten, Übergangszeiten und historische Zeitzonenanpassungen. Erstellt man einen ZonedDateTime für den 27. Oktober 2024 um 2:30 Uhr in Europa/Berlin — also genau zur Sommerzeituhr-Umstellung — behandelt die Temporal API diese Mehrdeutigkeit explizit und kann konfiguriert werden, die Sommerzeit- oder Winterzeit-Variante zu wählen.

Für Kalender-Anwendungen, Buchungssysteme und internationale Terminplanung ist ZonedDateTime unverzichtbar. Ein Meeting um 10 Uhr in Berlin bleibt um 10 Uhr, wenn die Sommerzeitumstellung dazwischenliegt — die Temporal API berechnet automatisch die korrekte UTC-Zeit. Das Gegenteil — ein fester UTC-Zeitpunkt — würde das Meeting auf 11 Uhr verschieben, was häufig falsch ist. Die Temporal API macht diesen Unterschied explizit zur Designentscheidung, die der Entwickler treffen muss.

6. Datumsarithmetik: Duration und Differenzen

Datumsarithmetik ist mit Date eine Fehlerquelle: Monate haben unterschiedliche Längen, Schaltjahre, Sommerzeitumstellungen mit 23 oder 25 Stunden. Die Temporal API abstrahiert all das mit dem Temporal.Duration-Typ. Eine Duration kann Jahre, Monate, Wochen, Tage, Stunden, Minuten, Sekunden und Nanosekunden enthalten — und sie werden bei der Arithmetik korrekt auf einen konkreten Datumswert angewendet. date.add({ months: 1 }) auf einem 31-Januar-Datum gibt den 28. Februar zurück — weil der 31. Februar nicht existiert. Das konfigurierbare Overflow-Verhalten erlaubt, stattdessen den letzten Tag des Monats zu wählen.

Differenzen zwischen Datumswerten sind ebenfalls präzise: date1.until(date2, { largestUnit: "months" }) berechnet die Differenz in vollen Monaten und Resttagen — nicht in rohen Millisekunden, die der Entwickler dann manuell in Monate umrechnen müsste. Für Alterungsberechnungen, Vertragslaufzeiten und Zahlungsfristen ist das der korrekte Weg. Die Temporal API unterstützt auch nicht-gregorianische Kalender wie den islamischen, hebräischen und japanischen Kalender — alle mit derselben API, aber kalender-spezifisch korrekter Arithmetik.


// Duration and date arithmetic with Temporal API
import { Temporal } from "@js-temporal/polyfill";

const start = Temporal.PlainDate.from("2026-01-31");

// Adding months: handles end-of-month correctly
const nextMonth = start.add({ months: 1 });
console.log(nextMonth.toString()); // "2026-02-28" — not Feb 31

// Configurable overflow behavior
const constrained = start.add({ months: 1 }, { overflow: "constrain" }); // Feb 28
const rejected = start.add({ months: 1 }, { overflow: "reject" });        // throws

// Calculate age in years, months, days
const birthDate = Temporal.PlainDate.from("1990-03-15");
const today = Temporal.Now.plainDateISO();
const age = birthDate.until(today, { largestUnit: "years" });
console.log(`Age: ${age.years} years, ${age.months} months, ${age.days} days`);

// Contract duration — from signing to expiry
const signed = Temporal.PlainDate.from("2026-02-15");
const expiry = signed.add({ years: 2, months: 3 });
const remaining = Temporal.Now.plainDateISO().until(expiry, { largestUnit: "days" });
console.log(`Contract expires in ${remaining.days} days`);

// DST-aware duration: 2 hours after a time, across DST boundary
const beforeDST = Temporal.ZonedDateTime.from("2026-10-25T01:00:00[Europe/Berlin]");
const afterDST = beforeDST.add({ hours: 2 });
// Correctly accounts for the extra hour during fall-back

7. Temporal vs. Date: der direkte Vergleich

Der Unterschied zwischen Date und der Temporal API ist nicht nur syntaktisch — er ist konzeptionell. Date ist ein einziger, mutierbarer Typ, der alle Zeitkonzepte in einem unzureichenden Interface vereint. Die Temporal API trennt die Konzepte in spezialisierte, immutierbare Typen, die exakt das ausdrücken, was gemeint ist.

Aspekt Date (alt) Temporal API (neu) Bedeutung
Mutierbarkeit Mutierbar (setMonth etc.) Immer immutierbar Kein unbeabsichtigtes Ändern
Monatsindizierung 0-basiert (Jan = 0) 1-basiert (Jan = 1) Keine off-by-one Bugs
Zeitzonen Nur UTC + Systemzone Jede IANA-Zeitzone Korrekte DST-Handhabung
Auflösung Millisekunden Nanosekunden Präzise Performance-Messung
Datumsarithmetik Manuell in ms rechnen Duration-Typ, add/until Schaltjahr/DST automatisch

Ein besonders kritisches Beispiel: new Date("2026-05-10") parst das Datum als UTC Mitternacht. Zeigt man dieses Datum einem Nutzer in UTC-5 an, erscheint es als 9. Mai — ein Tag zu früh. Die Temporal API löst das mit Temporal.PlainDate.from("2026-05-10") — ein Datum ohne Zeitzone, das immer als 10. Mai dargestellt wird, egal wo der Nutzer ist. Dieser Bug ist in jeder großen Web-Anwendung mindestens einmal aufgetreten und kostet Teams regelmäßig Debugging-Zeit.

8. Formatierung und Internationalisierung mit Intl

Die Temporal API ist eng mit der Intl-API verknüpft, dem eingebauten JavaScript-Internationalisierungsstandard. Statt eigene Formatierungslogik zu implementieren, delegiert Temporal die Ausgabe an Intl.DateTimeFormat. Das bedeutet, dass dieselbe Temporal API-Basis für alle Sprachen und Regionen korrekte Datumsformate liefert: date.toLocaleString("de-DE", { dateStyle: "long" }) für "10. Mai 2026" auf Deutsch, date.toLocaleString("en-US") für "May 10, 2026" auf Englisch.

Besonders leistungsfähig ist die Kombination mit nicht-gregorianischen Kalendern: Temporal.PlainDate.from({ calendar: "islamic", year: 1447, month: 11, day: 1 }) erzeugt ein Datum im islamischen Kalender, das mit denselben Methoden formatiert und für Arithmetik genutzt werden kann. Für internationale Anwendungen, die mehrere Kalender unterstützen müssen, ist die Temporal API der einzige saubere Weg in nativem JavaScript — ohne externe Bibliotheken mit hunderten Kilobyte Gewicht.

9. Migration von Date zu Temporal

Die Temporal API ist noch kein offizieller Standard, befindet sich aber seit Jahren in TC39 Stage 3 und ist für viele aktuelle Browser-Versionen polyfillbar. Der offizielle Polyfill @js-temporal/polyfill implementiert die vollständige API und ist für Node.js und Browser verfügbar. Für neue Projekte empfiehlt sich der sofortige Einsatz des Polyfills; für bestehende Projekte gibt es eine schrittweise Migrationsstrategie.

Der erste Schritt bei der Migration: Alle Stellen identifizieren, an denen new Date() oder Date.now() verwendet wird. Dann entscheiden, welcher Temporal-Typ das Konzept korrekt modelliert — PlainDate für reine Datumsangaben, Instant für Timestamps, ZonedDateTime für lokal interpretierte Zeiten. An der Grenze zur bestehenden Infrastruktur (Datenbank, externe APIs) hilft die Konvertierung: Temporal.Instant.fromEpochMilliseconds(date.getTime()) wandelt ein altes Date-Objekt in einen Instant um. In die andere Richtung: new Date(instant.epochMilliseconds).


// Migration helpers: bridging Date and Temporal API
import { Temporal } from "@js-temporal/polyfill";

// Date -> Temporal: convert legacy timestamps
function fromLegacyDate(date, timeZone = "UTC") {
  return Temporal.Instant
    .fromEpochMilliseconds(date.getTime())
    .toZonedDateTimeISO(timeZone);
}

// Temporal -> Date: interop with APIs expecting Date
function toLegacyDate(temporalValue) {
  if (temporalValue instanceof Temporal.Instant) {
    return new Date(temporalValue.epochMilliseconds);
  }
  if (temporalValue instanceof Temporal.ZonedDateTime) {
    return new Date(temporalValue.toInstant().epochMilliseconds);
  }
  throw new TypeError("Expected Instant or ZonedDateTime");
}

// Practical: parse API response date strings safely
function parseApiDate(isoString) {
  // API sends "2026-05-10" — store as PlainDate, never as Date
  return Temporal.PlainDate.from(isoString);
}

// Practical: format for display in user's local timezone
function formatForUser(instant, locale, timeZone) {
  const zdt = instant.toZonedDateTimeISO(timeZone);
  return zdt.toLocaleString(locale, {
    dateStyle: "long",
    timeStyle: "short"
  });
}

const ts = Temporal.Now.instant();
console.log(formatForUser(ts, "de-DE", "Europe/Berlin"));
// e.g., "10. Mai 2026, 16:30 Uhr"

10. Zusammenfassung

Die Temporal API ist der lang erwartete Ersatz für das fehlerhafte JavaScript Date-Objekt. Mit spezialisierten, immutablen Typen — PlainDate, PlainTime, PlainDateTime, Instant und ZonedDateTime — löst sie alle bekannten Probleme: 0-basierte Monate, Mutierbarkeit, fehlende Zeitzonenunterstützung und ungenaue Datumsarithmetik. Jeder Typ modelliert exakt das Konzept, das der Entwickler meint — kein implizites UTC-Mitternacht mehr bei reinen Datumsangaben, keine fehlenden DST-Korrekturen bei Zeitzonenoperationen.

Mit dem offiziellen Polyfill @js-temporal/polyfill ist die Temporal API heute in jedem Projekt einsetzbar. Die Migration bestehender Date-Nutzung ist schrittweise möglich: Neue Teile der Anwendung nutzen sofort Temporal; Konvertierungsfunktionen überbrücken die Grenze zu alten Teilen und externer Infrastruktur. Wer heute anfängt, Temporal API zu nutzen, schreibt Code, der bei der nativen Unterstützung in allen Browsern ohne Polyfill weiterläuft — die API selbst bleibt stabil, nur der Polyfill-Import entfällt.

Mironsoft

JavaScript-Modernisierung, API-Migration und internationale Web-Anwendungen

Datum-Bugs im Produktivsystem? Wir helfen.

Zeitzonenfehler, Off-by-one-Bugs bei Monaten und DST-Probleme in bestehenden Anwendungen — wir migrieren Date-basierten Code zur Temporal API und beseitigen die Ursache, nicht nur die Symptome.

Fehleranalyse

Zeitzonenfehler, DST-Bugs und Datumsarithmetik-Probleme in bestehenden Anwendungen identifizieren

Temporal-Migration

Schrittweise Migration von Date-Objekten zur Temporal API mit vollständiger Regressionstests

Internationalisierung

Mehrsprachige Datumsformatierung und Kalenderunterstützung mit Temporal + Intl

JavaScript Temporal API — Das Wichtigste auf einen Blick

Typen-Übersicht

PlainDate/Time: ohne Zeitzone. Instant: exakter UTC-Punkt. ZonedDateTime: vollständig mit IANA-Zeitzone. Duration: für Arithmetik. Alle immutierbar.

Kritische Verbesserungen

1-basierte Monate. Kein UTC-Mitternacht-Bug bei PlainDate. DST-korrekte Arithmetik. Nanosekunden-Auflösung. Nicht-gregorianische Kalender nativ.

Einstieg heute

npm install @js-temporal/polyfill — vollständige API, stabile Implementierung. Bei nativer Browser-Unterstützung nur den Import entfernen.

Migration

Instant.fromEpochMilliseconds(date.getTime()) für den Übergang. new Date(instant.epochMilliseconds) zurück. Schrittweise pro Modul migrieren.

11. FAQ: JavaScript Temporal API

1Was ist die JavaScript Temporal API?
Offizieller Nachfolger des Date-Objekts mit spezialisierten, immutablen Typen: PlainDate, Instant, ZonedDateTime, Duration. Löst alle bekannten Designfehler von Date.
2Kann ich Temporal API heute schon verwenden?
Ja — npm install @js-temporal/polyfill. Vollständige Implementierung, für Produktion geeignet. Bei nativer Browser-Unterstützung nur den Import entfernen.
3PlainDate vs. ZonedDateTime?
PlainDate: ohne Zeitzone, für Geburtstage und Feiertage. ZonedDateTime: mit expliziter IANA-Zeitzone, DST-korrekte Arithmetik, für Kalenderevents.
4Warum 1-basierte Monate?
Date hat 0-basierte Monate aus historischen Java-Kompatibilitätsgründen. Temporal korrigiert das: Januar = 1, Dezember = 12. Kein off-by-one mehr.
5Wann Temporal.Instant nutzen?
Für Log-Timestamps, API-Payloads und Performance-Messungen. Nanosekunden-Auflösung, immer UTC, kein Zeitzoneninterpretationsproblem.
6Wie funktioniert Datumsarithmetik?
date.add({ months: 1 }) — kein manuelles Millisekunden-Rechnen. End-of-month mit overflow: 'constrain' behandeln. DST-Übergänge bei ZonedDateTime automatisch.
7Ersetzt Temporal Moment.js und date-fns?
Ja — nativ, ohne Bundlegewicht. Moment.js ist veraltet. date-fns hat deutlich mehr Bundle-Größe. Temporal + Intl decken die meisten Anwendungsfälle ab.
8Wie von Date zu Temporal migrieren?
Temporal.Instant.fromEpochMilliseconds(date.getTime()) konvertiert Date zu Temporal. new Date(instant.epochMilliseconds) zurück. Schrittweise pro Modul.
9Nicht-gregorianische Kalender?
Temporal unterstützt islamisch, hebräisch, japanisch, persisch, chinesisch nativ. Gleiche API, kalender-spezifisch korrekte Arithmetik und Formatierung.
10Häufigster Date-Bug den Temporal löst?
UTC-Mitternacht-Bug: new Date('2026-05-10') wird in UTC-5 als 9. Mai angezeigt. PlainDate.from('2026-05-10') hat keine Zeitzone — immer der 10. Mai.