java.time: Der 3. Versuch

von Hubert Schmid vom 2014-01-26

Am Anfang war die Klasse java.util.Date. Aufgrund mangelnder Unterstützung für Internationalisierung wurde sie jedoch bereits wenig später in Teilen durch die Klassen Calendar, GregorianCalendar und TimeZone abgelöst. 17 Jahre später unternimmt nun Java 8 mit java.time den dritten Anlauf zur Unterstützung von Datums- und Zeitangaben. Der Umfang der Erweiterung ist beeindruckend: Über 1 600 Methoden verteilen sich auf knapp 70 öffentliche Klassen, Schnittstellen und Enums aus fünf Paketen. Muss das wirklich sein?

Kalenderrechnung ist intrinsisch komplex, sofern sie lokale Besonderheiten berücksichtigen soll. Die Klasse Calendar bietet zwar ausreichend Funktionalität, doch viele Anforderungen lassen sich mit ihr nur umständlich realisieren. Das macht die Arbeit zeitraubend und fehleranfällig.

Mit der neuen API sind dagegen viele Use-Cases vergleichsweise intuitiv. Im ersten Beispiel geht es um eine Weltzeituhr. Die Klasse Instant repräsentiert einen global eindeutigen Zeitpunkt, der unabhängig vom Ort ist. Mit der Methode atZone lässt sich daraus eine Instanz der Klasse ZonedDateTime erzeugen, die den gleichen Zeitpunkt innerhalb der angegebenen Zeitzone repräsentiert. Schließlich kann man die gleichen Format-Funktionen für die Ausgabe verwenden, wie für Date und Calendar.

public void worldClockDemo(Instant instant) { String[] zones = { "Europe/Berlin", "America/New_York", "Asia/Kolkata", }; for (String zone : zones) { System.out.printf("%-17s %TH:%<TM\n", zone, instant.atZone(ZoneId.of(zone))); } }

Neu ist auch die bessere Trennung zwischen Datum und Uhrzeit. Die Methode toLocalTime extrahiert lediglich die Uhrzeit und liefert sie als Instanz der Klasse LocalTime. Auf diese Weise lässt sie sich relativ einfach mit anderen Werten vergleichen. Im folgenden Listing wird beispielsweise die Grußformel abhängig von der Uhrzeit des Anwenders gewählt.

public void greetingDemo(Instant instant, ZoneId zoneId) { LocalTime localTime = instant.atZone(zoneId).toLocalTime(); if (localTime.isBefore(LocalTime.parse("09:00"))) { System.out.println("Guten Morgen!"); } else if (localTime.isBefore(LocalTime.parse("17:00"))) { System.out.println("Guten Tag!"); } else { System.out.println("Guten Abend!"); } }

Interessanter als Ausgabe und Konvertierung ist das Rechnen mit Zeitangaben. Auch hier trennt die API besser die unterschiedlichen Aspekte: Erstens wird zwischen Zeit- und Kalenderrechnung unterschieden, da es einen Unterschied zwischen 24 Stunden und einem Tag gibt. Und zweitens wird zwischen normalisierter und ortsspezifischer Rechnung unterschieden. Letzteres ist im folgenden Beispiel zu sehen. Die normalisierte Rechnung liefert die Ausgabe 02:30 – die ortsspezifische hingegen aufgrund der Umstellung von Winter- auf Sommerzeit 03:30.

public void timeCalculationDemo() { ZoneId zoneId = ZoneId.of("Europe/Berlin"); LocalDateTime dateTime = LocalDate.parse("2014-03-30").atStartOfDay(); Duration duration = Duration.ofMinutes(150); System.out.printf("%TH:%<TM + 02:30 = %TH:%<TM\n", dateTime, dateTime.plus(duration)); System.out.printf("%TH:%<TM + 02:30 = %TH:%<TM\n", dateTime.atZone(zoneId), dateTime.atZone(zoneId).plus(duration)); }

Das Gleiche gilt auch für andere Zeitumstellungen wie verdoppelte oder ausgelassene Tage. So folgt beispielsweise in der Zeitzone "Pacific/Apia" auf den 29.12.2011 der 31.12.2012 wohingegen es im Standardkalender natürlich der 30.12.2011 ist. Beide Varianten werden daher für unterschiedliche Use-Cases benötigt, wobei die ortsunabhängige allerdings ein wenig intuitiver ist.

Die neue API fühlt sich durch die Trennung in absolute, ortsspezifische und ortsunabhängige Zeitangaben sowie die einfache Konvertierung zwischen diesen Repräsentationen sehr gut an. Auch die explizite Repräsentation einer Uhrzeit ohne Datum erweist sich als sehr hilfreich. Ob dafür wirklich 1 600 Methoden angemessen sind, will ich nicht beurteilen. Doch gegenüber den Klassen Date und Calendar handelt es sich auf jeden Fall um eine sinnvolle und höchst willkommene Erweiterung.