Java ZonedDateTime
Zeitzonenbezogene Datums- und Zeitangaben in Java mit ZonedDateTime und der Klasse ZoneId darstellen.
ZonedDateTime ist ein LocalDateTime mit einem angehängten ZoneId. Es sagt: „Dieses Kalenderdatum und diese Uhrzeit an diesem Ort." Die Kombination identifiziert einen einzelnen Moment auf der globalen Zeitlinie — 2025-11-04T14:00 [America/New_York] ist genau ein Instant, verschieden von 2025-11-04T14:00 [Europe/Berlin].
Dies ist die Klasse, auf die man zurückgreift, wenn die lokale Wanduhrzeit eines Ereignisses an einem bestimmten Ort wichtig ist. Besprechungskalender. Geplante cron-ähnliche Aufgaben, die um „9 Uhr in der Zeitzone des Benutzers" ausgelöst werden sollen. Alles, was eine Sommerzeit-Umstellung überstehen muss. LocalDateTime kennt nicht genug; Instant ist in UTC und trägt kein für Menschen bedeutungsvolles Zonenbezeichnung. ZonedDateTime ist beides.
ZoneId: der Katalog der Zeitzonen
Vor ZonedDateTime lernen wir ZoneId selbst kennen — eine Zeitzone wird durch eine ZoneId identifiziert, die man mit ZoneId.of(...) erhält:
ZoneId ny = ZoneId.of("America/New_York");
ZoneId de = ZoneId.of("Europe/Berlin");
ZoneId tokyo = ZoneId.of("Asia/Tokyo");
ZoneId utc = ZoneId.of("UTC");
ZoneId sys = ZoneId.systemDefault();Die Zeichenketten sind IANA-Zeitzonendatenbank-Bezeichner (Region/Stadt). Die vollständige Liste ist ZoneId.getAvailableZoneIds() — etwa 600 Einträge, die regelmäßig aktualisiert werden, wenn Länder ihre Zeitzonen oder Sommerzeit-Regeln ändern. ZoneId trägt die historischen Aufzeichnungen der IANA, sodass Datumsangaben aus dem Jahr 1985 die damals geltenden Regeln verwenden.
Vermeiden Sie ZoneOffset (ein festes ±HH:MM), wenn Sie eine echte Zeitzone meinen. ZoneOffset.of("-05:00") ist für New York im November korrekt und im Juni falsch; ZoneId.of("America/New_York") ist das ganze Jahr über korrekt.
Dreibuchstabige Zeitzonennamen wie "EST" und "PST" sind mittlerweile größtenteils Aliase, mehrdeutig (war das Eastern Standard oder Eastern Australia?) und stillschweigend veraltet. Verwenden Sie Region/Stadt. "UTC" und "GMT" sind Sonderfälle und in Ordnung.
Erstellen
ZonedDateTime now = ZonedDateTime.now(); // system zone
ZonedDateTime nowNY = ZonedDateTime.now(ZoneId.of("America/New_York"));
ZonedDateTime made = ZonedDateTime.of(2025, 11, 4, 14, 0, 0, 0, ZoneId.of("America/New_York"));
ZonedDateTime parsed = ZonedDateTime.parse("2025-11-04T14:00:00-05:00[America/New_York]");Der häufigste Erstellungspfad lautet: „Ich habe ein LocalDateTime, ich habe eine ZoneId, verbinde sie":
LocalDateTime ldt = LocalDateTime.of(2025, 11, 4, 14, 0);
ZonedDateTime zdt = ldt.atZone(ZoneId.of("America/New_York"));atZone(zone) ist die einzige Brückenmethode von einer lokalen Uhrzeit zu einem Zeitzonenmoment. Sie behandelt auch die zwei Sonderfälle, die die Sommerzeit mit sich bringt.
Sommerzeit: wenn die Wanduhr springt oder sich wiederholt
Zweimal im Jahr springt die Wanduhr in jeder Zone mit Sommerzeit oder wiederholt sich. Wenn sie vorgestellt wird — in den USA springt die Uhr am Sonntag im März von 02:00 auf 03:00 — existieren die Zeiten zwischen 02:00 und 03:00 an diesem Tag nicht. Wenn sie zurückgestellt wird, passieren die Zeiten zwischen 01:00 und 02:00 zweimal. ZonedDateTime muss in beiden Fällen etwas tun, und was es tut, ist dokumentiert:
- Übersprungene Zeit (Lücke):
atZonegibt die Zeit nach der Umstellung zurück.LocalDateTime.of(2025, 3, 9, 2, 30).atZone(ZoneId.of("America/New_York"))wird zu03:30-04:00— das JDK hat eine Stunde vorgerückt, um auf einer gültigen Wanduhrzeit zu landen. - Wiederholte Zeit (Überschneidung):
atZonegibt den früheren der beiden gültigen Momente zurück (denjenigen vor der Offset-Änderung). Verwenden SiewithEarlierOffsetAtOverlap()oderwithLaterOffsetAtOverlap(), um explizit zu wählen.
ZonedDateTime ambiguous = LocalDateTime.of(2025, 11, 2, 1, 30)
.atZone(ZoneId.of("America/New_York")); // 01:30 EDT (earlier)
ZonedDateTime explicit = ambiguous.withLaterOffsetAtOverlap(); // 01:30 EST (later)Die beiden ZonedDateTime-Objekte haben dasselbe LocalDateTime, aber unterschiedliche Offsets und unterschiedliche Instants. Dies ist der einzige Ort in java.time, an dem dieselbe lokale Uhrzeit legitimerweise auf zwei Momente abgebildet wird — und es ist die Quelle der Sommerzeit-bezogenen Fehler, von denen Sie gehört haben. Seien Sie bewusst, wenn Überschneidungen wichtig sind.
Zerlegung
ZoneId zone = zdt.getZone();
ZoneOffset offset = zdt.getOffset();
LocalDateTime ldt = zdt.toLocalDateTime();
LocalDate date = zdt.toLocalDate();
LocalTime time = zdt.toLocalTime();
Instant inst = zdt.toInstant();
OffsetDateTime odt = zdt.toOffsetDateTime();Die Zugriffsmethoden lassen sich in drei Gruppen einteilen: die Zeitzonenhälfte (getZone, getOffset), die lokale Uhrzeithälfte (toLocalDateTime, toLocalDate, toLocalTime) und die globale Momenthälfte (toInstant). Alle drei sind gleichzeitig für dasselbe ZonedDateTime wahr; Sie wählen die benötigte Projektion.
OffsetDateTime ist ein verwandter Typ — LocalDateTime plus ein ZoneOffset (keine Zeitzone, keine Sommerzeit). Er ist nützlich für die Serialisierung von „2025-11-04T14:00-05:00" ohne Bindung an eine benannte Zone (oft das, was JSON-Zeitstempel wollen); für jeden Code, der sommerzeitbewusste Arithmetik benötigt, behalten Sie das ZonedDateTime.
Zwei Varianten von „nächster Tag"
ZonedDateTime hat zwei Methoden, die ähnlich aussehen, es aber nicht sind:
zdt.plusDays(1); // add 1 day to the local clock reading
zdt.plus(Duration.ofHours(24)); // add exactly 24 hoursAn einem Sommerzeitumstellungstag divergieren die beiden. Am Tag, an dem die Uhren vorgestellt werden, landet plusDays(1) zur gleichen lokalen Zeit von morgen (die nur 23 Stunden realer Zeit entfernt ist). plus(Duration.ofHours(24)) landet eine Stunde später als gestern auf der Wanduhr.
| Ziel | Methode |
|---|---|
| „Gleiche Zeit morgen" (kalendarisch) | plusDays(1) |
| „Genau 24 Stunden von jetzt" (Dauer) | plus(Duration.ofHours(24)) |
Beide sind korrekt; sie beantworten verschiedene Fragen. Wählen Sie bewusst.
Vergleiche und Gleichheit
zdt1.isBefore(zdt2); // compares Instants
zdt1.isAfter(zdt2);
zdt1.isEqual(zdt2); // compares Instants
zdt1.equals(zdt2); // compares LocalDateTime + Zone + OffsetDer Unterschied ist deutlich:
isBefore/isAfter/isEqualvergleichen die zugrunde liegenden Momente (Instants).equalsvergleicht die vollständige Struktur — zweiZonedDateTimes, die denselben Moment darstellen, aber unterschiedliche Zeitzonen haben, sind nichtequal.
Für „ist das derselbe Moment, unabhängig von der Zeitzone" verwenden Sie isEqual oder konvertieren Sie beide zu Instant und vergleichen Sie.
Ein praktisches Beispiel: eine Besprechung über drei Büros hinweg
Das folgende Programm plant eine Besprechung für 14:00 Berliner Zeit und berechnet, welche Zeit das in den Büros in New York und Tokio ist. Anschließend plant es eine wöchentlich wiederkehrende Besprechung, die eine Sommerzeitumstellung übersteht, und demonstriert den Unterschied zwischen plusDays(7) und plus(Duration.ofDays(7)) in einer Umstellungswoche.
Was man aus dem Lauf mitnehmen sollte:
withZoneSameInstant(otherZone)ist die Operation für „Wie spät ist es in ihrem Büro?" — er hält den Moment fest und zeigt die Wanduhr in der neuen Zone an. Sein GeschwisterwithZoneSameLocal(otherZone)behält die Wanduhr bei und ändert den Moment (die Besprechung wird verschoben). Die Namen sind verwechselbar; der Unterschied liegt darin, welches Element gleich bleibt. Lesen Sie sie sorgfältig, wenn Sie sie schreiben.berlin.equals(ny)warfalse, obwohl beide denselben Moment darstellten.equalsvergleicht die vollständige Struktur (lokales Datum/Uhrzeit + Zone). Für „gleicher Moment, unabhängig davon, wie er bezeichnet ist", verwenden SieisEqualoder vergleichen SieInstants. Dies ist genau derselbe Unterschied, denLocalDate.equalsvs.isEqualgemacht hat —equalsfür „gleicher Wertobjekt",isEqualfür „gleicher Zeitpunkt".- Die Sommerzeitlücke (
2025-03-09 02:30in NY) wurde durch Vorspringen auf03:30-04:00aufgelöst. Das JDK hat keine Ausnahme geworfen; es hat den Moment nach der Umstellung gewählt. Wenn Sie unbedingt erkennen müssen, dass Sie eine unmögliche Zeit angegeben haben, verwenden SieZoneRules.getTransition(localDateTime)und prüfen Sie, ob das zurückgegebene Objekt eine Lücke ist. - Die Sommerzeitüberschneidung (
2025-11-02 01:30in NY) hat Ihnen zwei verschiedeneZonedDateTimes mit denselben lokalen Feldern und unterschiedlichen Offsets gegeben —EDTvs.EST, eine Stunde auseinander.withLaterOffsetAtOverlap()undwithEarlierOffsetAtOverlap()sind die Methoden zur Auswahl. Wenn Sie geplante Ereignisse speichern, entscheiden Sie im Voraus, welches der Benutzer meint, und wenden Sie den richtigen Aufruf zur Analysezeit an. plusDays(1)undplus(Duration.ofHours(24))haben am Tag des Vorstellens der Uhren unterschiedliche Ergebnisse produziert — 23 Stunden realer Zeit im Vergleich zu 24 Stunden, mit unterschiedlichen lokalen Uhrzeiten. Verwenden SieplusDays/plusWeeksfür kalendarisch orientierte Planung („gleiche Zeit morgen") undplus(Duration)für die Berechnung vergangener Zeit („Alarm in 24 Stunden"). Die Wahl folgt fast immer der benutzerorientierten Absicht.
Was kommt als Nächstes
ZonedDateTime ist die menschenfreundliche Seite von „ein Moment mit einem Bezeichner". Das nächste Kapitel, Java Instant, ist die maschinenfreundliche Seite — ein Moment als Nanosekunden seit der Epoche, keine Zone, kein Kalender, der Typ, den jedes verteilte System auf der Leitung verwendet.