LocalDate, LocalTime und LocalDateTime sind die Hauptklassen des Pakets java.time, die die Arbeit mit Datum und Zeit ohne Bindung an eine Zeitzone ermöglichen. Laut Oracle-Dokumentation (Java 17, 2024) sind diese Typen als immutable und thread-safe konzipiert, was sie für Multithread-Anwendungen sicher macht. Sie wurden auf Android ab API 26 durch Desugaring und für ältere Versionen — durch die Bibliothek ThreeTenABP — verfügbar gemacht.
Wichtige Punkte
LocalDate — eine Klasse, die ein Datum im Format Jahr-Monat-Tag ohne Zeit- und Zeitzoneninformationen darstellt. Sie wird verwendet, um Daten wie Geburtstage, Veranstaltungstermine oder Ablaufdaten zu speichern.
LocalDate speichert ein Jahr im Bereich von -999999999 bis +999999999, einen Monat von 1 bis 12 und einen Tag des Monats unter Berücksichtigung von Schaltjahren. Die Klasse ist vollständig immutable — jede Operation gibt ein neues Objekt zurück.
LocalTime repräsentiert die Tageszeit: Stunden, Minuten, Sekunden und Nanosekunden. Die maximale Genauigkeit beträgt eine Nanosekunde. LocalTime enthält keine Datums- oder Zeitzoneninformationen, was es praktisch zum Speichern von Öffnungszeiten oder Prozessdauern macht.
LocalDateTime kombiniert LocalDate und LocalTime in einem Objekt. Dies ist der am häufigsten verwendete Typ, wenn sowohl Datum als auch Uhrzeit gespeichert werden müssen, aber keine Zeitzonenbindung erforderlich ist. Zum Beispiel das Datum und die Uhrzeit eines Konzerts im lokalen Format.
Laut Oracle Java-Dokumentation (2024) sind alle drei Klassen auf der Grundlage von Ideen aus der Joda-Time-Bibliothek konzipiert, jedoch mit verbesserter Architektur und vollständiger Integration in die Standardbibliothek.
Das Paket java.time wurde in Java 8 als Ersatz für die veralteten Klassen Date, Calendar und SimpleDateFormat eingeführt. Seine Architektur basiert auf den Prinzipien von immutable Objekten und einer fließenden Schnittstelle.
Eine wichtige Eigenschaft — alle Hauptklassen sind value-based. Das bedeutet, dass ihre Instanzen nach Wert und nicht nach Referenz verglichen werden und sie nicht vererbt werden können. Zum Vergleichen zweier Objekte wird die equals-Methode verwendet, nicht der ==-Operator.
Das Paket ist in mehrere Kategorien unterteilt. Typen ohne Zeitzone — LocalDate, LocalTime, LocalDateTime — werden für lokale Daten und Zeiten verwendet. Typen mit Zeitzone — ZonedDateTime, OffsetDateTime, OffsetTime — fügen Versatz- oder Zoneninformationen hinzu. Momentane Typen — Instant — stellen einen Punkt auf der Zeitachse in UTC dar.
Diese Unterteilung löst ein Problem, das der alten API innewohnte: Der Entwickler wusste nie, ob ein Date-Objekt Zeitzoneninformationen enthielt oder nicht. In java.time deklariert jeder Typ explizit seine Semantik.
Die Klasse LocalDate bietet viele Methoden zum Erstellen, Lesen und Ändern von Daten. Das aktuelle Datum kann über die statische Methode now() abgerufen werden. Ein bestimmtes Datum — über die Methode of(int year, int month, int dayOfMonth).
Zum Lesen der Datumskomponenten werden Getter verwendet: getYear(), getMonthValue(), getDayOfMonth(), getDayOfWeek(), getDayOfYear(). Die Methode getMonth() gibt die Enumeration Month zurück, und getDayOfWeek() gibt die Enumeration DayOfWeek zurück.
LocalDate unterstützt die Überprüfung von Daten. Die Methoden isBefore(), isAfter() und isEqual() ermöglichen den Vergleich von Daten. Die Methode isLeapYear() prüft, ob das Jahr ein Schaltjahr ist. Die Methode lengthOfMonth() gibt die Anzahl der Tage im Monat zurück, lengthOfYear() die Anzahl der Tage im Jahr.
Zum Ändern werden die Methoden withYear(), withMonth(), withDayOfMonth() verwendet, die ein neues Objekt mit der geänderten Komponente zurückgeben. Die Methoden plusDays(), minusMonths() und ähnliche führen Datumsarithmetik durch.
LocalTime stellt die Tageszeit mit Nanosekundengenauigkeit dar. Das Standardformat ist ISO-8601 (HH:mm:ss.nnnnnnnnn). Der Mindestwert ist 00:00, der Höchstwert ist 23:59:59.999999999.
Ein LocalTime-Objekt kann mit now() für die aktuelle Zeit, of(int hour, int minute), of(int hour, int minute, int second) oder of(int hour, int minute, int second, int nanoOfSecond) erstellt werden. Die Methode parse(CharSequence text) analysiert einen String im ISO-8601-Format.
Zu den Gettern gehören getHour(), getMinute(), getSecond(), getNano(). Die Methode toSecondOfDay() gibt die Anzahl der Sekunden seit Tagesbeginn zurück, und toNanoOfDay() gibt Nanosekunden zurück. Dies ist praktisch für die Berechnung der Dauer innerhalb eines Tages.
LocalTime unterstützt dieselben Vergleichs- und Änderungsoperationen wie LocalDate: plusHours(), minusMinutes(), withHour(), withMinute(). Die Methoden isBefore() und isAfter() funktionieren unter Berücksichtigung, dass die Zeit innerhalb eines Tages zyklisch ist.
LocalDateTime vereint die Fähigkeiten von LocalDate und LocalTime in einer Klasse. Es speichert sowohl Datum als auch Uhrzeit, jedoch ohne Zeitzone. Dies ist der flexibelste lokale Typ, erfordert jedoch Vorsicht bei der Verwendung in verteilten Systemen.
LocalDateTime kann über die statischen Methoden now(), of(LocalDate date, LocalTime time), of(int year, Month month, int dayOfMonth, int hour, int minute) und deren Überladungen erstellt werden. LocalDate und LocalTime können auch über die Methode atTime() kombiniert werden.
LocalDateTime bietet Zugriff auf alle Datums- und Zeitfelder über entsprechende Getter: toLocalDate() und toLocalTime() geben einzelne Komponenten zurück. Die Methode truncatedTo(TemporalUnit unit) ermöglicht das Runden der Zeit auf eine bestimmte Genauigkeit — zum Beispiel auf Minuten.
Für die Konvertierung in eine Zeitzone wird die Methode atZone(ZoneId zone) verwendet, die ZonedDateTime zurückgibt. Dies ist die einzige Möglichkeit, LocalDateTime eine Zeitzone hinzuzufügen.
Alle drei Klassen verwenden ein einheitliches Erstellungsmuster über statische Factory-Methoden. Die Klassenkonstruktoren sind als private deklariert — ein Objekt kann nicht direkt mit new erstellt werden.
Wichtigste Erstellungsmethoden:
Die Methode of hat viele Überladungen. Für LocalDate werden Jahr, Monat und Tag benötigt. Für LocalTime — Stunden und Minuten (optional Sekunden und Nanosekunden). Für LocalDateTime — Jahr, Monat, Tag, Stunden, Minuten. Der Monat kann als int (1-12) oder als Enumeration Month übergeben werden.
val today = LocalDate.now()
val specificDate = LocalDate.of(2026, Month.JULY, 21)
val parsedDate = LocalDate.parse("2026-07-21")
val currentTime = LocalTime.now()
val lunchTime = LocalTime.of(13, 30, 0)
val parsedTime = LocalTime.parse("13:30:00")
val now = LocalDateTime.now()
val meeting = LocalDateTime.of(2026, 7, 21, 15, 0)
Die java.time-Klassen sind für eine bequeme Konvertierung untereinander ausgelegt. LocalDate kann über die Methode atTime(LocalTime) oder atStartOfDay() in LocalDateTime konvertiert werden. LocalTime — über atDate(LocalDate).
LocalDateTime kann über toLocalDate() zurück in LocalDate und über toLocalTime() in LocalTime konvertiert werden. Für die Konvertierung in ZonedDateTime wird die Methode atZone(ZoneId) verwendet.
Die Konvertierung in java.util.Date (zur Kompatibilität mit Altsystemen) erfordert einen Zwischenschritt über Instant und eine Zeitzone. Laut Baeldung (2024) wird diese Operation über Date.from(instant) durchgeführt.
val date = LocalDate.of(2026, 7, 21)
val dateTime = date.atTime(LocalTime.of(10, 30))
val time = LocalTime.of(14, 0)
val dateTimeFromTime = time.atDate(date)
val extractedDate = dateTime.toLocalDate()
val extractedTime = dateTime.toLocalTime()
val zoned = dateTime.atZone(ZoneId.of("Europe/Moscow"))
Für Formatierung und Parsing wird die Klasse DateTimeFormatter verwendet. Sie bietet vordefinierte Formate über Konstanten (ISO_LOCAL_DATE, ISO_LOCAL_TIME, ISO_LOCAL_DATE_TIME) und die Möglichkeit, eigene Formate über Musterstrings zu erstellen.
Formatierungsmuster verwenden Symbole: yyyy — Jahr, MM — Monat (zweistellig), dd — Tag, HH — Stunde (0-23), mm — Minute, ss — Sekunde. Die Methode format() wird auf dem Datums-Zeit-Objekt oder über DateTimeFormatter aufgerufen.
DateTimeFormatter unterstützt auch die Lokalisierung über die statischen Methoden ofLocalizedDate(FormatStyle), ofLocalizedTime(FormatStyle) und ofLocalizedDateTime(FormatStyle). Verfügbare Stile sind SHORT, MEDIUM, LONG und FULL.
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
val formatted = LocalDateTime.now().format(formatter)
val parsed = LocalDate.parse(
"21.07.2026",
DateTimeFormatter.ofPattern("dd.MM.yyyy")
)
Alle drei Klassen implementieren das Interface Comparable, was einen natürlichen Vergleich ermöglicht. Die Methode compareTo() gibt eine negative Zahl, Null oder eine positive Zahl je nach Reihenfolge zurück. Die Methoden isBefore(), isAfter() und isEqual() geben einen booleschen Wert zurück.
Bei LocalDate erfolgt der Vergleich chronologisch — ein früheres Datum gilt als kleiner. Bei LocalTime — nach Tageszeit. Bei LocalDateTime — zuerst nach Datum, dann nach Zeit. Alle Vergleiche berücksichtigen korrekt Schaltjahre und die Anzahl der Tage in Monaten.
Ein wichtiger Unterschied zur alten API: equals() für LocalDate, LocalTime und LocalDateTime vergleicht Werte, nicht Referenzen. Das bedeutet, dass zwei Objekte mit denselben Feldern gleich sind, auch wenn es sich um verschiedene Instanzen handelt.
val d1 = LocalDate.of(2026, 7, 21)
val d2 = LocalDate.of(2026, 12, 25)
if (d1.isBefore(d2)) {
Log.d("Datum", "d1 ist vor d2")
}
val sortedDates = listOf(d2, d1).sorted()
Alle drei Klassen unterstützen arithmetische Operationen über die Methoden plus und minus. Für LocalDate stehen plusDays(), plusWeeks(), plusMonths(), plusYears() und die entsprechenden minus-Methoden zur Verfügung. LocalTime unterstützt plusHours(), plusMinutes(), plusSeconds(), plusNanos().
LocalDateTime erbt alle arithmetischen Operationen beider Typen. Eine bemerkenswerte Eigenschaft von LocalDate: Beim Hinzufügen eines Monats verarbeiten die Ergebnisse korrekt unterschiedliche Monatslängen. Zum Beispiel 31. Januar + 1 Monat = 28. (29. in einem Schaltjahr) Februar.
Für komplexere Operationen gibt es die Klasse Period (für Daten) und Duration (für Zeit). Die Methoden plus(TemporalAmount) und minus(TemporalAmount) akzeptieren diese Objekte.
val today = LocalDate.now()
val nextWeek = today.plusDays(7)
val nextMonth = today.plusMonths(1)
val lastYear = today.minusYears(1)
val now = LocalTime.now()
val inTwoHours = now.plusHours(2)
val halfHourAgo = now.minusMinutes(30)
Betrachten wir ein praktisches Beispiel: eine Anwendung zur Erfassung von Arbeitsschichten. Wir müssen die Schichtdauer berechnen und feststellen, ob sie in die Nachtzeit fällt. Wir verwenden LocalTime für Start- und Endzeit, LocalDate für das Datum und LocalDateTime für die Berechnung von Schichten, die Mitternacht überschreiten.
data class Shift(
val startTime: LocalTime,
val endTime: LocalTime,
val date: LocalDate
) {
fun isOvernight(): Boolean = endTime.isBefore(startTime)
fun durationInMinutes(): Long {
val start = LocalDateTime.of(date, startTime)
val end = LocalDateTime.of(
if (isOvernight()) date.plusDays(1) else date,
endTime
)
return Duration.between(start, end).toMinutes()
}
}
Ein zweites Beispiel — Berechnung des Alters eines Benutzers. Wir verwenden LocalDate für das Geburtsdatum und vergleichen es mit dem aktuellen Datum unter Berücksichtigung von Tag und Monat der Geburt.
fun calculateAge(birthDate: LocalDate): Int {
val today = LocalDate.now()
val period = Period.between(birthDate, today)
return period.years
}
Ein drittes Beispiel — Arbeit mit Benachrichtigungen. LocalDateTime wird zum Planen von Erinnerungen verwendet. Wir überprüfen, ob die geplante Zeit erreicht wurde.
data class Reminder(
val id: Long,
val scheduledAt: LocalDateTime
) {
fun isDue(): Boolean =
LocalDateTime.now().isAfter(scheduledAt)
}
Die integrierte Unterstützung für java.time erschien auf Android ab API 26 (Android 8.0 Oreo). Für Geräte mit älteren Android-Versionen muss Desugaring verwendet werden — ein Mechanismus, der Unterstützung für neue Java-APIs in früheren Versionen hinzufügt.
Desugaring im Android Gradle Plugin wird über compileOptions in build.gradle konfiguriert. Es genügt, isCoreLibraryDesugaringEnabled = true zu setzen und die Bibliothek desugar_jdk_libs hinzuzufügen. Danach ist java.time für alle API-Level ab 14 verfügbar.
Für Projekte, die Desugaring nicht verwenden können (z. B. Legacy-Projekte mit AGP unter 4.0), gibt es die Bibliothek ThreeTenABP — einen Backport von java.time. Sie stellt dieselben Klassen (LocalDate, LocalTime, LocalDateTime) bereit, jedoch im Paket org.threeten.bp.
@Suppress("UnstableApiUsage")
android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
}
dependencies {
"coreLibraryDesugaring"("com.android.tools:desugar_jdk_libs:2.1.4")
}
Der erste häufige Fehler — die Verwendung von LocalDateTime in verteilten Systemen ohne Berücksichtigung von Zeitzonen. Wenn der Server in Europe/Moscow und der Client in Asia/Tokyo ist, wird LocalDateTime unterschiedlich interpretiert. Lösung: Verwenden Sie Instant oder ZonedDateTime für globale Daten.
Der zweite Fehler — falsches String-Parsing. Standardmäßig erwartet LocalDate.parse() das ISO-8601-Format (yyyy-MM-dd). Wenn der String ein anderes Format hat, müssen Sie explizit einen DateTimeFormatter übergeben. Sie sollten auch DateTimeParseException behandeln, damit die Anwendung bei ungültiger Eingabe nicht abstürzt.
Der dritte Fehler — Missachtung der Nullsicherheit. LocalDate, LocalTime und LocalDateTime sind Objekte, die null sein können. In Kotlin wird empfohlen, nullable Typen mit expliziten Prüfungen oder dem Elvis-Operator zu verwenden. In Java — vor dem Aufruf von Methoden auf null prüfen.
Der vierte Fehler — Verwechslung von LocalDateTime und ZonedDateTime. LocalDateTime enthält keine Zeitzoneninformationen. Wenn Sie einen absoluten Zeitpunkt übergeben müssen — verwenden Sie zonale Typen. Wenn die lokale Zeit ausreicht — verwenden Sie lokale Typen.
Häufig gestellte Fragen
Date speichert die Anzahl der Millisekunden seit 1970-01-01 UTC, während LocalDate Jahr, Monat und Tag ohne Zeitzonenbindung speichert. Date ist veränderbar und nicht thread-safe, LocalDate ist immutable und thread-safe. Date ist seit Java 8 veraltet.
Ja, LocalDateTime wird gut auf den SQL-Typ TIMESTAMP WITHOUT TIME ZONE abgebildet. JPA und Room unterstützen es über TypeConverter. Für TIMESTAMP WITH TIME ZONE verwenden Sie ZonedDateTime oder OffsetDateTime.
Verwenden Sie ChronoUnit.DAYS.between(startDate, endDate). Diese Methode gibt einen long-Wert zurück — die Differenz in Tagen. Für eine detailliertere Berechnung verwenden Sie Period.between(), das einen Period mit Jahren, Monaten und Tagen zurückgibt.
LocalTime unterstützt Nanosekundengenauigkeit (9 Dezimalstellen). Wenn Millisekundengenauigkeit ausreicht, verwenden Sie truncateTo(ChronoUnit.MILLIS) vor dem Speichern. Dies verhindert Rundungsprobleme bei der Serialisierung.
Die Methode now() verwendet die Systemuhr und die Standardzeitzone des Geräts. Wenn Geräte sich in unterschiedlichen Zeitzonen befinden, kann das Datum abweichen. Für einen einheitlichen Zeitstempel verwenden Sie Instant.now(), das immer die UTC-Zeit zurückgibt.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch