Einführung in die Java Date and Time API
Einführung in die moderne Java-Datums- und Zeit-API in java.time als Ersatz für die veralteten Klassen Date und Calendar.
Java 8 hat java.time eingeführt, ein neues Paket zur Darstellung von Daten, Uhrzeiten, Dauern, Zeitzonen und der Arithmetik zwischen ihnen. Es ersetzte zwei frühere APIs — java.util.Date und java.util.Calendar —, die zu Recht als der am schlechtesten entworfene Teil des JDK galten. Die neue API wurde von Stephen Colebornes früherer Joda-Time-Bibliothek geprägt; wer Joda bereits kennt, wird java.time vertraut vorkommen.
Die zwei wichtigsten Aspekte des Redesigns:
- Jeder Typ ist unveränderlich. Ein einmal erstelltes
LocalDateändert sich nie. Methoden wieplusDays(7)geben ein neuesLocalDatezurück. Das macht die API konstruktionsbedingt thread-sicher und beseitigt eine ganze Klasse von Fehlern. - Jeder Typ bedeutet eine Sache.
LocalDateist ein Datum ohne Uhrzeit.Instantist ein Moment auf dem Zeitstrahl.Durationist eine Zeitspanne. Das veralteteDatewar irgendwie alles davon gleichzeitig, je nachdem welcher Konstruktor verwendet wurde; die neue API trennt sie, damit der Typ sagt, welche Art von Wert man hat.
Dieses Kapitel ist die Übersicht. Die nächsten zehn Kapitel gehen auf jede Klasse im Detail ein.
Die Kerntypen
"A date" LocalDate 2025-11-04
"A time of day" LocalTime 14:30:00
"Both, no zone" LocalDateTime 2025-11-04T14:30:00
"Both, with zone" ZonedDateTime 2025-11-04T14:30:00-05:00 [America/New_York]
"A moment" Instant 2025-11-04T19:30:00Z (UTC, seconds-since-epoch)
"A length of time" Duration PT1H30M (1 hour 30 minutes)
"A length of date" Period P1Y2M3D (1 year 2 months 3 days)Die horizontale Trennung — Local* vs. Zoned/Instant — ist die wichtigste. Lokale Typen tragen keine Zeitzone. Ein LocalDate von 2025-11-04 ist "der vierte November"; er sagt nicht, ob das der vierte in Tokio oder in Honolulu ist. Er ist der richtige Typ für einen Geburtstag, ein Vertragsdatum oder einen UI-Datepicker.
Zeitzonenbehaftete Typen tragen ihre Zone. ZonedDateTime ist "dieser Kalendermoment an diesem Ort" — genau das, was man für "Meeting angesetzt für 9 Uhr in New York" braucht. Instant ist ein Moment auf dem globalen Zeitstrahl — UTC-Sekunden seit der Epoche — was man für Logging, Nachrichtenzeitstempel oder alles braucht, was global geordnet sein muss, ohne lokale Beschriftungen zu benötigen.
Die horizontale Trennung zwischen Duration und Period ist ebenfalls wichtig. Duration ist eine Zeitspanne, die man in Sekunden vergleichen kann — PT24H sind genau 24 × 3600 Sekunden. Period ist eine Länge in Kalenderbegriffen — P1M (ein Monat) ist in manchen Monaten 30 Tage und in anderen 31. Für Zeitmessungen braucht man Duration. Für "einen Monat zu einem Abrechnungsdatum addieren" braucht man Period.
Die fluente Form
Jeder Typ wird über eine konsistente fluente API erzeugt und verändert:
LocalDate today = LocalDate.now();
LocalDate stardate = LocalDate.of(2025, 11, 4);
LocalDate parsed = LocalDate.parse("2025-11-04");
LocalDate nextWeek = today.plusDays(7); // immutable: returns a NEW LocalDate
LocalDate lastYear = today.minusYears(1);
LocalDate firstOfMonth = today.withDayOfMonth(1); // with* returns a copy with one field changed
boolean before = today.isBefore(stardate);
int year = today.getYear();Drei Muster, die man überall sieht:
now()— aktueller Wert aus der Systemuhr.of(...)— explizite Komponenten.parse(...)— aus einem String (standardmäßig ISO-8601).
Und für Transformationen:
plusX(n)/minusX(n)— Arithmetik.withX(value)— ein einzelnes Feld ersetzen.isBefore(other)/isAfter(other)— Vergleich.
Dieses Muster wiederholt sich in LocalDate, LocalTime, LocalDateTime, ZonedDateTime und Instant. Wer das Muster kennt, liest jede Klasse wie denselben Dialekt mit einem leicht anderen Vokabular.
Zeitzonen sind schwierig, und die API gibt das zu
Der wichtigste Grund, warum java.util.Date so problematisch war, ist, dass es Zeitzonen unsichtbar zu machen versuchte. Das Ergebnis war die berühmte Fehlerklasse: "ein Date speichern, es auf einem Server in einer anderen Zeitzone abrufen, das falsche Kalenderdatum erhalten." java.time löst das, indem die Zone im Typ explizit gemacht wird.
Wenn man ein Datum von einem Benutzer entgegennimmt und nicht weiß, in welcher Zone er sich befindet, speichert man es als LocalDate. Wenn der Benutzer sagt, es ist "9 Uhr seiner Zeit" und man kennt seine Zone, speichert man es als ZonedDateTime mit der Zone. Wenn man ein Server-Ereignis protokolliert, speichert man es als Instant. Man sollte kein LocalDateTime speichern und darauf hoffen, dass die Zeitzone durchkommt; die fehlende Zone ist der eigentliche Fehler.
Instant now = Instant.now(); // unambiguous: a moment in UTC
ZonedDateTime localized = now.atZone(ZoneId.of("Europe/Berlin")); // a label for that moment in BerlinDie Zonenhierarchie:
ZoneOffsetist ein fester±HH:MM-Offset von UTC:+05:30,-08:00. Keine Sommerzeitbehandlung.ZoneIdist eine benannte Zone:Europe/Berlin,America/New_York. Enthält den IANA-Datenbankdatensatz darüber, welchen Offset diese Zone an einem bestimmten Tag hat, einschließlich Sommerzeit-Übergängen und historischen Änderungen.
Immer ZoneId gegenüber ZoneOffset bevorzugen, wenn man die Wahl hat. "America/New_York" ist über die Sommerzeit hinweg korrekt; "−05:00" ist nur außerhalb der Sommerzeit korrekt.
Die veralteten Typen sind nicht verschwunden
java.util.Date, java.util.Calendar und java.text.SimpleDateFormat existieren noch immer. Neuer Code sollte sie nicht verwenden — aber viel alter Code tut es, und man muss mit ihm interoperieren. Die Konvertierungsmethoden sind direkt:
// java.util.Date <-> java.time.Instant
Instant inst = legacyDate.toInstant();
Date back = Date.from(inst);
// java.util.Calendar -> java.time.ZonedDateTime
ZonedDateTime zdt = ZonedDateTime.ofInstant(
cal.toInstant(), cal.getTimeZone().toZoneId());Das Muster ist einseitig: legacy → java.time ist unkompliziert; für alles Neue bleibt man in java.time und konvertiert nur an der API-Grenze, wo der alte Code lebt. Die Kapitel Legacy Date und Calendar am Ende dieses Teils behandeln die Brücke im Detail.
Ein durchgearbeitetes Beispiel: die Typfamilie in einem Programm
Das folgende Programm verwendet jeden Typ, den die obige Übersicht eingeführt hat — LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant, Duration, Period — und zeigt, wie sie ineinander konvertiert werden. Es ist die "Tour"-Version; jeder einzelne Typ bekommt sein eigenes Kapitel von hier an.
Was man aus der Ausgabe mitnehmen sollte:
- Der erste Block hat die Familie durch Komposition aufgebaut:
LocalDate+LocalTime=LocalDateTime;LocalDateTime+ZoneId=ZonedDateTime;ZonedDateTime→Instant. Das ist das Gitter der Konvertierungen, und man führt diese jedes Mal durch, wenn man eine API-Grenze überschreitet. Die Pfeile zeigen für die meisten Paare in beide Richtungen —Instant.atZone(zone)undZonedDateTime.toLocalDateTime()schließen die Schleifen. - Ein
Instanthat drei verschiedene "aussehende" Zeiten ausgegeben, wenn er aus New York, Berlin und Tokio betrachtet wird. Das ist der Sinn vonInstant: er ist der Moment, unabhängig davon, wo man steht. DasZonedDateTimefügt die "wo ich stehe"-Beschriftung hinzu. Die beiden zu verwechseln ist der Fehler des veraltetenDate. Durationhat alsPT1H30MundPeriodalsP3Mausgegeben. Das ISO-8601-Dauerformat istPnYnMnDTnHnMnS— alles vor demTsind Kalendereinheiten (Period), alles danach sind Zeiteinheiten (Duration). Der String ist genau das, wastoString()zurückgibt, und genau das, wasparse(...)akzeptiert.today.plusDays(7)hat ein anderesLocalDateerzeugt. Wenn man danachtodayerneut ausgibt, sieht man, dass das Original unverändert ist — das ist die Unveränderlichkeitsgarantie. Jedesplus/minus/withgibt ein neues Objekt zurück; der Empfänger wird nie verändert. Keine defensiven Kopien, nie Bedenken zur Thread-Sicherheit.ChronoUnit.DAYS.between(today, launch)war die "Abstand"-Operation. Sie gibt einlongzurück, keinPeriod, weil die Antwort in Tagen keine Kalenderambiguität hat (im Gegensatz zu Monaten, die unterschiedlich lang sind). Jedes Kapitel in diesem Teil verwendetChronoUnitirgendwo — es ist der Katalog der Zeiteinheiten, über die die API spricht.
Wie es weitergeht
Das nächste Kapitel, Java LocalDate, beginnt die Vertiefungstour. LocalDate ist der einfachste der fünf "Moment"-Typen und der richtige Ausgangspunkt, um die fluente Form zu lernen, die alle anderen teilen.