Die neue Date/Time-API in Java 8

Sprachen  –  12 Kommentare

Seit 18. März ist Java 8 verfügbar. Die bekanntesten Neuerungen sind Lambda-Ausdrücke und Streams. Aber es gibt noch mehr Nützliches, zum Beispiel die neue Date/Time-API. Zeit wird's dafür, denn die alten Java-APIs für Zeit- und Datumsrechnung sind nun doch wirklich in die Jahre gekommen.

Warum eine neue Date/Time-API? Nun, es muss wohl eher heißen: Warum erst jetzt? Die alten Java-APIs zur Zeit- und Datumrechnung haben einige Mängel aufzuweisen, und das nicht erst seit gestern, sondern schon seit Zeiten des JDK 1.0 und 1.1. Im JEP 150 (JDK Enhancement Proposal) heißt es als Motivation: "The existing Java date and time classes are poor, mutable, and have unpredictable performance." Diverse Programmier-Blogs beschweren sich seit Jahren über Design-Fehler und konzeptionelle sowie sonstige Probleme.

Seit dem JDK 1.0 gibt es die Klasse java.util.Date, die von Beginn an folgende Probleme mitbringt (ohne den Anspruch auf Vollständigkeit):

  1. Date ist konzeptionell kein Datum, sondern ein Datum plus Zeit.
  2. Die Granularität von Date ist auf Millisekunden festgeschrieben (als Wrapper um die Millisekunden seit 1.1.1970 00:00:00 UTC, also um epoch.
  3. Date ist leider "mutable". Dabei kannte man das Immutability-Pattern schon Mitte der 90er-Jahre, als das JDK implementiert wurde – vergleiche java.lang.String.
  4. Date kennt keine Zeitzone.
  5. Date kennt auch keine Sommer-/Winterzeit.
  6. Fragt man ein Date nach seiner Jahreszahl, wird sie als Integer-Wert y minus 1900 ausgegeben. Man merkt, dass die API im letzten Jahrhundert geschrieben wurde.
  7. Der Januar ist der 0. Monat. Dezember ist demnach die 11 – nicht gerade intuitiv.
  8. Die Wertebereiche sind nicht fixiert. Zum Beispiel wird ein 32. Januar stillschweigend als 1. Februar interpretiert.

Konsequenterweise sind in Java 8 insgesamt 22 Methoden oder Konstruktoren von Date als "deprecated" markiert.

Das JDK 1.1 führte java.util.Calendar ein, um die Designprobleme von Date zu umgehen (während Date wegen der Abwärtskompatibilität erhalten blieb). Das ist leider nicht gelungen, stattdessen schuf
Calendar altbekannte und auch neue Probleme:

  1. Calendar ist konzeptionell kein Kalendersystem, sondern ebenfalls ein Datum plus Zeit.
  2. Auch Calendar ist "mutable".
  3. Der Januar ist immer noch der Monat 0.
  4. Die Zugriffs-API ist wenig intuitiv. So greift man auf den Stundenwert mit get(Calendar.HOUR) zu und nicht per getHour(), wie man das erwarten würde. Datumsrechnungen werden nur mangelhaft unterstützt.
  5. Calendar selbst ist abstrakt. Die einzige JDK-Unterklasse war lange Zeit der GregorianCalendar (bis mit JDK 1.6 der in westlichen Gefilden vermutlich selten benutzte JapaneseImperialCalendar dazukam). Calendar führte schon zu Beginn selten benötigte Erweiterungen ein, beispielsweise einen dreizehnten Monat. (Zitat aus JavaDoc für die Konstante UNDECIMBER aus Calendar: "Value of the MONTH field indicating the thirteenth month of the year. Although GregorianCalendar does not use this value, lunar calendars do."
  6. Calendar hat einige zweifelhafte Implementierungen, etwa boolean Calendar.after(Object o) und after(Object o), die auf "vorher?" und "nachher?" prüfen. Übergibt man ein Objekt, das nicht vom Typ Calendar ist, kommt als Ergebnis immer "false" zurück. Eine IllegalArgumentException wäre wohl naheliegender gewesen – und noch besser sollte die API den Typ des Parameters als Calendar schlicht erzwingen. (Vermutlich wollte man ermöglichen, dass Unterklassen auch ein Date oder eine andere Datumsrepräsentation verarbeiten können, aber das ist so nicht gelungen.)

Auch Nachbarklassen bereiten Probleme – beispielsweise java.text.DateFormat:

  1. DateFormat ist leider kein zustandsloser Formatter (vgl. dazu DateFormat.setCalendar).
  2. DateFormat ist nicht "thread-safe. (Zitat aus JavaDoc: "It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.")
  3. DateFormat.parse ignoriert eine Zeitzone, die mit übergeben wird. Diese ist nachträglich extra zu setzen.

Und als wäre das nicht genug, gibt's noch die SQL-Klassen java.sql.Date, Time, TimeStamp und außerdem javax.xml.datatype.Duration und XMLGregorianCalendar.