Am 4. Dezember 2013 geht der sogenannte “Public Review” des JSR 310 zu Ende. Das ist eine gute Gelegenheit für eine umfassende Rezension. Das vorgeschlagene API hat sich endlich auch stabilisiert. Vorher war es so häufig großen Änderungen unterworfen, daß eine frühe Rezension schnell überholt gewesen wäre. Diese Rezension überläßt Code-Beispiele gerne dem Oracle-Tutorial und fokussiert sich mehr auf das allgemeine Design und die Benutzbarkeit des APIs. Detaillierte Code-Vergleiche und Code-Inspektionen hätten auch den Rahmen dieser Rezension gesprengt. Kalendarisches Spezialwissen ist neben guten Kenntnissen des JSR-310 sicherlich auch nötig, um die Tiefen dieser Rezension zu erfassen. Also eher eine Rezension für anspruchsvolle Entwickler.
Die großen Vorteile auf einen Blick
- Immutable und thread-safe (unveränderlich – sicher in der Parallelverarbeitung)
- Fluent Style (flüssiger Programmierstil)
- Einführung neuer elementarer Datentypen wie: Reines Datum (LocalDate) oder reine Uhrzeit (LocalTime), das ist ein Riesengewinn im Vergleich zum alten JDK
- Extra Formatfabrik zur programmatischen Erstellung individueller Zeitformate
- Nano-Präzision zur Abbildung einiger Datenbankzeitstempel
- Differenzberechnungen (mit Hilfe von java.time.Period und java.time.Duration)
- API für julianische Tage (siehe Klasse java.time.temporal.JulianFields)
- API für zeitzonenrelevante Abfragen (z.B. ZoneOffsetTransition#isGap())
- Umalqra-Kalender für Saudi-Arabien (ein Verdienst von Oracle und Roger Riggs)
- Allgemeiner Mechanismus für benutzerdefinierte Abfragen und Manipulationen via TemporalQuery und TemporalAdjuster
Können wir uns jetzt alle zufrieden zurücklehnen und sagen: Ende gut, alles gut? Für die meisten Business-Entwickler mit eher geringen Ansprüchen und Erwartungen mit Abstrichen ja, für anspruchsvolle Entwickler leider nicht. Ich kann eine noch längere Liste von Nachteilen präsentieren. Schauen wir uns alles noch einmal im Detail an.
Design-Mangel 1: Keine Typsicherheit zur Kompilierzeit
Um Typsicherheit zur Kompilierzeit zu gewährleisten, muß ein API entweder nur konkrete Typen verwenden, oder eben das seit Java 5 (JSR 14) angebotene Sprach-Feature der Generics verwenden. Letzteres fehlt weitgehend. Das liegt hauptsächlich daran, daß der Projektleiter des JSR-310 Stephen Colebourne generell gegenüber Generics feindlich gegenübersteht. Zuletzt schrieb er in der JSR-354-Mailing-Liste:
In JSR-310 I found no way to get generics to actually
work. The problem which 310 tackles involves many more interacting interfaces, and the increased friction between the types nullifies the benefits of generics.
Ich werde noch mit meiner Bibliothek Time4J und mit Hilfe von self-referencing Generics den Beweis antreten, daß es doch geht. Was bedeutet die Nicht-Umsetzung der Typsicherheit konkret in der Praxis? Hier ein Beispiel:
import static java.time.temporal.ChronoUnit.HOURS; LocalDate date = LocalDate.of(2013, 11, 30).plus(30, HOURS); // throws RuntimeException
Das gesamte API ist stark auf Ausnahmen zur Laufzeit gebaut. Das betrifft in erster Linie die Verwendung des Subpakets java.time.temporal. Nicht so toll. Am besten verfahren Anwender als Umgehungslösung, wenn sie so weit wie möglich konkrete Typen und Methoden verwenden, im genannten Beispiel wäre die Alternative:
LocalDate date = LocalDate.of(2013, 11, 30).plusDays(30); // plusHours() gibt es im Gegensatz zu plus(30, HOURS) nicht.
Design-Mangel 2: Zuviele Methoden
Das Fehlen der Typsicherheit führt gleich auch zum zweiten Mangel, nämlich zu einer kaum noch überschaubaren Anzahl von Methoden pro Klasse. So hat etwa die Klasse LocalDate 64 öffentliche Methoden, die Klassen LocalDateTime und OffsetDateTime sogar 80 und die Klasse ZonedDateTime 84 Methoden. Empfohlen wird in der Regel nur max. 30-50 Methoden pro Klasse. Das liegt vor allem daran, daß einer generischen und leicht besser lesbaren Methode wie plus(x, TemporalUnit) noch weitere Methoden wie plusDays(), plusHours() etc. zur Seite gestellt werden. Der JSR 310 versucht, die Unübersichtlichkeit u.a. mit der konsequenten Verwendung von Methodenpräfixen wie plus, minus, of, at, to, with, get usw. auszugleichen. Das ist für sich genommen zwar gut, aber echte Mehrfunktionalität ergibt sich dadurch nicht.
Interessanterweise gibt es dafür keine Konstruktoren. Hier gibt es Vor- und Nachteile. Während in immutable Klassen Fabrikmethoden z.B. wegen besserer Cache-Unterstützung oder wegen eines flüssigen Programmierstils allgemein vorzuziehen sind, sind Konstruktoren zumindest intuitiv leichter zu finden, wenigstens für Einsteiger. Ich meine insgesamt, hie und da hätte der eine oder andere Konstruktor nicht wirklich geschadet.
Design-Mangel 3: Keine klare Zeitskalendefinition
Die Spezifikation des JSR-310 spricht von zwei Zeitskalen: Human time line und Machine time line. Der Unterschied wird daran festgemacht, daß erstere mit Feldern wie Jahr, Monat, Tag, Stunde, Minute, Sekunde etc. arbeitet und die sog. maschinelle Zeit mit einer einzigen fortlaufenden Nummer (Anzahl der verstrichenen Sekunden seit der UNIX-Epoche 1970-01-01T00:00:00Z). Der JSR-310-Guide (Version 0.7) stellt hier insbesondere die Klassen java.time.Instant und java.time.Duration in den Fokus der maschinellen Zeit.
Hier wird schon mal übersehen, daß auch ein Instant durchaus eine Feldsicht bietet und sie sogar definiert:
NANO_OF_SECOND
MICRO_OF_SECOND
MILLI_OF_SECOND
INSTANT_SECONDS
Also ist demnach ein Instant sowohl eine fortlaufende Nummer (INSTANT_SECONDS) als auch eine Sammlung weiterer Felder wie NANO_OF_SECOND etc. Andererseits sind Klassen wie java.time.LocalDate nicht nur per Feldsicht definiert. Neben Jahr, Monat und Tag des Monats bietet ein LocalDate auch eine fortlaufende Nummer! => Die sogenannten Epochentage (im JSR-310-API immer relativ zur UNIX-Epoche). Es handelt sich tatsächlich um eine Variante der julianischen Tage.
Wir sehen, die künstliche Trennung zwischen “Human-scale time” und “Machine-scale time” ist gar keine. Andererseits lesen wir in der Javadoc der Klasse Instant Folgendes über eine “Java time-scale”:
The Java time-scale is used for all date-time classes. This includes
Instant
,LocalDate
,LocalTime
,OffsetDateTime
,ZonedDateTime
andDuration
.
Abgesehen davon, daß hier wahrscheinlich die Klasse LocalDateTime vergessen wurde, fragt man sich schon, wie die “Java time-scale” alle möglichen grundverschiedenen Typen abdecken kann. Was also ist diese Zeitskala?
There are currently, as of 2013, two segments in the Java time-scale.
For the segment from 1972-11-03 (exact boundary discussed below) until further notice, the consensus international time scale is UTC (with leap seconds). In this segment, the Java Time-Scale is identical to UTC-SLS. This is identical to UTC on days that do not have a leap second. On days that do have a leap second, the leap second is spread equally over the last 1000 seconds of the day, maintaining the appearance of exactly 86400 seconds per day.
For the segment prior to 1972-11-03, extending back arbitrarily far, the consensus international time scale is defined to be UT1, applied proleptically, which is equivalent to the (mean) solar time on the prime meridian (Greenwich). In this segment, the Java Time-Scale is identical to the consensus international time scale. The exact boundary between the two segments is the instant where UT1 = UTC between 1972-11-03T00:00 and 1972-11-04T12:00.
Es ist völlig unklar, warum eine solche offensichtlich sekundenbasierte Zeitskala auch für einen Typ wie LocalDate gelten soll. Ich verstehe und definiere eine Zeitskala als eine Abbildung von einer beliebigen Menge von Zeitelementen (Feldsicht) auf eine fortlaufende Nummer. Demnach verwendet ein LocalDate eine Abbildung auf julianische Tage, ein Instant hingegen auf Sekunden und Nanosekunden seit der UNIX-Epoche. Das sind klar zwei verschiedene Zeitskalen.
Aber die Verwirrung geht noch weiter: Während die Trennung einer Zeitskala in Segmente durchaus sinnvoll sein kann (um eine tiefgreifende Änderung der zivilen Zeitskala zu modellieren, z.B. Wechsel von der astronomischen Zeit zur Atomuhrzeit mit der Einführung von UTC zum genauen Zeitpunkt 1972-01-01T00:00:00Z), stellt sich hier die Frage, warum ausgerechnet ein nicht präziser Zeitpunkt wie 1972-11-03/04 angegeben ist. Die Tatsache, daß dieser Zeitpunkt nur mit einer Unsicherheit von 36 Stunden gegeben ist, macht eine präzise Implementierung in einer java.time.Clock quasi zur Unmöglichkeit. Was steckt eigentlich dahinter?
Das JSR-310-Team will auf keinen Fall die UTC-Schaltsekunden dem Anwender präsentieren, sondern sie verstecken (angeblich weil die meisten Entwickler die UTC-Definition nach Ansicht des API-Teams nicht verstehen können). Deshalb ist eine einfache UTC-Implementierung nicht gewollt. Stattdessen hat S. Colebourne den Versuch gemacht, den obsoleten Vorschlag UTC-SLS von Markus Kuhn wiederzubeleben und ihn auf Standard-Java-Software auszuweiten, was vielleicht nicht im Sinn von Markus Kuhn war. Dieser Vorschlag besagt vereinfacht, in den letzten 1000 Sekunden vor einer Schaltsekunde extra Millisekunden so einzuführen, daß die Schaltsekunde selbst nicht mehr im Ergebnis auftaucht. Das ist eine ähnliche Idee, wie sie auch von Googles NTP-Servern als technische Umgehungslösung (über einen ganzen Tag statt über 1000 Sekunden verteilt) realisiert wurde, um Clients zu bedienen, die mit Angaben wie T23:59:60Z nichts anfangen können. Damit ein solcher geglätteter Algorithmus auch beim Segmentwechsel von UT1 zu UTC funktioniert und es keinen plötzlichen Sprung gibt, bezieht sich der JSR 310 auf den ersten Zeitpunkt nach Einführung von UTC, zu dem galt: UT1 = UTC, nämlich ca. 1972-11-03/04.
Zu raffiniert gedacht, um Schaltsekunden zu verstecken, denn aufgrund der vorhandenen IERS-Daten aus dem Jahre 1972 ist ein solcher Segment-Wechsel auf der “Java time-scale” de facto nicht präzise zu definieren (nur numerische Interpolation möglich). Aber offenbar will der JSR-310 auch keine Präzision, denn:
Implementations of the Java time-scale using the JSR-310 API are not required to provide any clock that is sub-second accurate, or that progresses monotonically or smoothly. Implementations are therefore not required to actually perform the UTC-SLS slew or to otherwise be aware of leap seconds.
Ein Offenbarungseid. Wenn die Präzision egal ist, was soll eigentlich das ganze Gerede über UTC-Schaltsekunden noch? Ein neues Standard-API sollte vor allem eine einheitliche Norm für Anwender definieren, tut es hier aber ausdrücklich nicht. Das ist auch deshalb schlimm, weil das API Nanosekundengenauigkeit definiert, die tatsächlich per Design/Spezifikation nicht geliefert wird, so daß oft unklar ist, wie insbesondere Subsekundenteile verschiedener Datentypen zu interpretieren und zu konvertieren sind. Lediglich die Dokumention der gewählten Uhrenimplementierung wird von externen Implementierungen verlangt, mehr nicht. Offenbar ist java.time.Instant keine UTC-SLS-Implementierung (soll es aber sein???). Mehr noch, im JDK 8 wird es gar keine reale Implementierung der Zeitskala UTC-SLS geben.
Interessanterweise ist auch bis jetzt (siehe Issue 343) eine entsprechende Dokumentation der alten aber leider unvermeidlichen Klasse java.util.Date ausgeblieben. Mal sehen, ob wenigstens hier das JSR-310-Team die Dokumentation nachbessert.
Die ominöse “Java time-scale” wird also bis jetzt nur spezifiziert. Deren Implementierung wird nicht verlangt, gilt aber trotzdem für eine Reihen von Datentypen von LocalDate bis Instant (nur SOLL, aber nicht IST). Mehr Konfusion geht kaum. Die Spezifikation ist im Ergebnis beliebig. Ich prophezeie, daß Anwender real sich nicht um die JSR-310-Spezifikation kümmern werden und fröhlich weiter mit dem weit verbreiteten POSIX-Standard arbeiten, so wie sie es schon immer mit der Klasse java.util.Date gemacht haben. Eins ist auch klar, der JSR-310 wird so niemals eine sekundengenaue Brücke zur (kalendarischen) Astronomie schlagen können, weil Zeitskalen wie UTC, UT1, TT, TAI etc. gar nicht erst angeboten werden. Das schon deshalb, weil Oracle sich von der technischen Basis solcher Zeitskalen offiziell verabschiedet hat. Die Vorhaltung einer speziellen Schaltsekundentabelle ist aus dem JDK-Repository wieder entfernt worden (war mal vorhanden!).
Und wer wie Colebourne glaubt, daß sich das Zeitskalenproblem durch eine mögliche Eliminierung der UTC-Schaltsekunden auf einer zukünftigen internationalen Konferenz der ITU 2015 von selbst erledigt, ist gründlich schief gewickelt. Dann wird eben nicht der Schaltsekunden-Mechanismus zur Umrechnung von Zeitskalen herhalten, sondern eine deutlich komplexere Materie rund um den astronomischen Term Delta-T, was teilweise jetzt schon zur Umrechnung von UT1-Angaben notwendig ist.
Letztlich besteht der Kern der Differenz zwischen mir und Colebourne nicht bloß in der Frage der Unterstützung von UTC-Schaltsekunden, sondern vielmehr darin, ob eine Java- Zeitbibliothek eine eigene Zeitskala definieren soll, die außerhalb von Java keine Rolle spielt, oder ob stattdessen die in der externen Realität vorhandenen Zeitskalen modelliert werden sollen.
Design-Mangel 4: Ungenügende Kalenderabstraktion
Das java.time.chrono-Paket definiert Kalendersysteme auf der Basis von Ära, Jahr, Monat, und Tag. Das ist viel zu konkret. Viele Chronologien (inklusive der internationalen Norm ISO-8601!) haben gar keine Ära. Hier hat sich Oracle mit seinen Design-Vorstellungen gegen S. Colebourne durchgesetzt. De facto liegen die gleichen Grundgedanken wie der alten Klasse java.util.Calendar vor. Internationale Kalenderanwendungen sollen gegen einen abstrakten Datumstyp (hier java.time.chrono.ChronoLocalDate) programmiert werden. Colebourne hat zu Recht eingewandt, daß die zugehörigen Abstraktionen wie getYear(), getMonth() etc. dann völlig andere Werte als erwartet liefern können und daher eine reichhaltige Quelle für Programmierfehler darstellen. Zum Beispiel ist das Jahr im buddhistischen Kalender in Thailand derzeit jenseits der Zahl 2500. Oder es kann mal 13 Monate statt 12 Monate geben. Interessenten können sich die Dokumentation der Klasse ChronoLocalDate zu Gemüte führen. Es ist eine einzige große Warnung. Zwischen den Zeilen steht quasi die Aufforderung: Bitte benutze mich nicht. Das sagt eigentlich schon alles über den Gebrauchswert des ganzen Pakets.
Trotzdem hat jemand aus der Java-Community schon versucht, eine Implementierung des diskordianischen Kalenders auf dieser Basis aufzubauen. Es ist nicht besonders gut gelungen, denn als Ersatzkonstrukt für die in diesem Kalendersystem verwendenten 5 “seasons” werden allgemeine Monate benutzt. Mit Verwendung des Format-APIs des JSR-310 ergeben sich dadurch völlig falsche Bezeichnungen in der Formatierung. Kein Wunder, leider gibt es auch kein Tutorial oder Lernbeispiel, wie überhaupt neue Kalendersysteme mit dem JSR-310 zu entwickeln sind. Es werden auch längst nicht alle Kalendersysteme im JDK selbst enthalten sein. So fehlt der im Threeten-Projekt schon fertig implementierte koptische Kalender – und wird wohl nie kommen. Wer also viele Kalendersysteme erwartet, wird durch das JDK nicht bedient werden und muß so oder so eine externe (Zusatz-)Bibliothek verwenden.
Darüberhinaus haben die eingebauten Kalendersysteme dem JSR-310-Team bekannte Mängel, die bewußt in Kauf genommen werden. Beim Umalqra-Kalender fehlt die Möglichkeit, auch einen rein algorithmisch berechneten islamischen Kalender abzufragen. Im buddhistischen Thai-Kalender ist die Datierung vor 1940 (als der Beginn des Jahres von April in den Januar wanderte) nicht korrekt. Der japanische Kalender kann nur ab der Meiji-Ära benutzt werden. Von Oracle angedachte zukünftige Kalender wie der hebräische Kalender oder leider auch heute schon der Umalqura-Kalender unterstützen nicht den korrekten Beginn des islamischen Tages am Vortag zum Sonnenuntergang und liefern daher teilweise falsche Datumsangaben, wenn sie etwa in ChronoLocalDateTime zusammen mit einer Uhrzeit verwendet werden. Verblüffenderweise fehlt im JSR-310 auch ein historischer gregorianischer Kalender mit einer variablen cut-over-Regel wie in java.util.GregorianCalendar, von diversen anderen Lücken in historischen Datumsangaben zu schweigen (Beginn des historischen Jahrs – in England bis 1752 war es der 25. März, in Schweden gab es einmal den 30. Februar etc).
Design-Mangel 5: Einschränkungen in der Dauer-Arithmetik
Jedem Anwender wird die Trennung zwischen den Klassen Period und Duration etwas merkwürdig vorkommen. Ursprünglich war die Trennung durch die Unterscheidung zwischen “Human time-scale” und “Machine time-scale” motiviert, mittlerweile sagt der JSR-310, daß Period datumsbezogen (basiert auf Jahren, Monaten und Tagen) und Duration zeitbezogen (also mit Stunden, Minuten und Sekunden) arbeiten. Das hat schon so manchen Entwickler enttäuscht, die auch eine kombinierte Dauer à la 2 Tage und 12 Stunden erwartet hatten. Würde der JSR-310 letzteres unterstützen, müßte er entweder java.time.Period erweitern oder eine dritte Dauer-Klasse einführen. Wie auch immer, dann wird die Trennung zwischen den Dauer-Klassen zunehmend unverständlicher. Eine Trennung wäre höchstens noch sinnvoll, wenn etwa java.time.Duration ausschließlich mit SI-Sekunden im Gegensatz zu einer ExtendedPeriod-Klasse mit “normalen” Sekunden arbeiten würde. Aber das verbietet sich ja wegen der völlig diffusen Definition der “Java time-scale”.
Und dann gibt es noch das weitere von JodaTime geerbte Ärgernis, daß es immer noch möglich ist, eine Dauer mit gemischten Vorzeichen in den einzelnen Elementen zu erzeugen, zum Beispiel +2 Jahre und -5Monate (in der Notation des JSR-310: P2Y-5M). Das ist mit dem XML-Schema-Element xsd:Duration unvereinbar. Es führt auch zu einem nicht mehr behebbaren Fehler in der Definition der Methode Period#isNegative(), denn P1M-30D ist real nicht ohne weiteres negativ (tatsächlich nicht mehr vernünftig zu definieren). Schlimmer noch: Eine solche Definition einer “Periode” führt auch zu gewissen Inkonsistenzen in der Dauer-Arithmetik, die ausgesprochen unschön sind. Zum Beispiel würde es möglich sein, daß angewandt auf den ersten! Kalendertag eines Monats das Addieren und anschließende Subtrahieren derselben Dauer/Periode ein verändertes Datum herauskommt. Als Umgehungslösung kann man jedem Anwender nur davon abraten, überhaupt solche gemischte “Dauer”-Objekte zu erzeugen. Die einzig richtige Lösung wäre die, das Vorzeichen einer Dauer ausschließlich auf die gesamte Dauer zu beziehen, also z.B. -P2Y5M. Auch hier zeigt sich die unselige Tendenz des JSR-310, eigene Standards einzuführen, statt vorhandene Standards zu modellieren.
Design-Mangel 6: Unsaubere Trennung zwischen Spec und Implementierung
Wenn nur das java.time.temporal-Paket als Spezifikation interpretiert wird, fällt auf, daß z.B. das Interface TemporalUnit die Implementierungsklasse java.time.Duration referenziert. Oder die Hilfsklasse TemporalQueries referenziert Klassen wie Chronology, LocalDate etc. Das hat Werner Keil, Mitglied im JCP-Exekutiv-Komitee richtig beobachtet. Das JSR-310-Team definiert in einer Reaktion auf diese Kritik nun das gesamte API (bzw. die komplette Javadoc) als Spezifikation. Beide Ansätze verhindern, daß eine andere Bibliothek die Chance hat, sich als Implementierung des JSR-310 zu profilieren. Meine Bibliothek Time4J wird daher höchstens prüfen, ob das Interface TemporalAccessor zur Verbesserung der Interoperabilität nützlich sein kann (im Prinzip ja, aber was soll passieren, wenn der JSR-310 in zukünftigen ME-mobile editions nicht enthalten ist?).
Design-Mangel 7: Die Klasse ZonedDateTime
Es war schon in der alten JDK-Klasse GregorianCalendar gefährlich, lokale und globale Aspekte der Zeitarithmetik zu mischen. Die Motivation war und ist verständlich, alles möglichst mit einem einheitlichen Universaltyp zu behandeln. Aber der Preis dafür erscheint mir zu hoch. Wenn z.B. Tage addiert werden, so geschieht das auf der lokalen Zeitachse, die Addition von Stunden aber auf der globalen Zeitachse. Das ist durchaus willkürlich zu nennen (DST-Effekte mal ja, mal nicht einrechnen).
Auch ist die Sortierung und Vergleichbarkeit solcher Universaldatentypen schwierig zu definieren, wenn unterschiedliche Zeitzonen vorliegen. Auf Intuition kann der Standard-Anwender hier nicht mehr bauen.
Noch problematischer ist so ein Datentyp in der Deserialisierung, wenn sich die Zeitzonenregeln in der Zwischenzeit geändert haben. Ich habe hierzu leider keine brauchbare Dokumentation in der Klasse ZonedDateTime gefunden. Es wäre auch eine sehr komplizierte Dokumentation.
Mit anderen Worten: Eine Beschränkung der Klasse ZonedDateTime auf das Interface TemporalAccessor statt Temporal wäre weise gewesen, so daß alle zeitachsenbezogenen Methoden wie Addition, Subtraktion und Berechnung einer Dauer nicht angeboten werden. Das ist letztlich eine Konsequenz aus der Tatsache, daß es keine eindeutige Zeitachse gibt (viele lokale und eine globale Zeitachse gleichzeitig).
Weitere Ärgernisse
a) Uhrzeit 24:00
Überhaupt nimmt es der JSR-310 nicht so ganz genau mit der Beachtung externer Standards. Ein Beispiel ist jüngstens die Meldung einiger Beobachter, daß die Klasse LocalTime nicht den in der ISO-8601-Norm erlaubten Wert T24:00 (Mitternacht zum Ende eines Tages) unterstützt. Lediglich das Format-API bietet minimale und sehr umständliche Unterstützung beim Parsen solcher Angaben.
b) ISO-Klassen mit Ära-Konzept
Siehe den alten Artikel in diesem Blog. Das Gefährliche daran ist, daß die Äras CE und BCE eigentlich nur andere Namen für AD und BC darstellen, hier aber in einem ahistorischen Kontext ohne einen Wechsel zwischen julianischen und gregorianischen Kalenderregeln auch zu anderen Datumsangaben führen können.
c) Konkrete Adjuster und Queries nur statisch eingebaut
Siehe die Klassen java.time.temporal.TemporalQueries oder java.time.temporal.TemporalAdjusters. Alle Methoden sind nur statisch. Das ist kein OO-Design und einfach nur ungeschickt. Viel besser wäre es gewesen, diese API-Elemente an den einzelnen Feldinstanzen aufzuhängen. Das hätte auch Raum für schöne Erweiterungen in der Zukunft gegeben. Ein Beispiel:
// Statisch im JSR-310 LocalDate date = LocalDate.of(2013, 5, 10); date = date.with(TemporalAdjusters.lastDayOfMonth()); System.out.println(date); // 2013-05-31 // OO-Ansatz LocalDate date = LocalDate.of(2013, 5, 10); date = date.with(DAY_OF_MONTH.maximized()); System.out.println(date); // 2013-05-31
d) Keine Intervall-Unterstützung
Ursprünglich bis ca. 2009 immer erwähnt, ist das ab 2010 mit der lapidaren Begründung weggefallen, das sei nicht im Fokus des JSR-310. Eine bittere Enttäuschung auch für so manchen Business-Entwickler. Gerade im Versicherungs- und Bankengewerbe sind Intervalle wichtig, kann ich aus eigener Erfahrung sagen.
e) Keine Schnittstelle zu Wochenenden und Feiertagen
Auch das dürfte viele Business-Entwickler enttäuschen. Eine externe Bibliothek ist gezwungen, nicht nur die Feiertage zu definieren, sondern auch selbst die Arithmetik eines Business-Kalenders zu bauen, anstatt sie dem JDK zu überlassen. Schade.
f) Das Format-API ist beim Parsen direkt nicht wirklich benutzbar.
Der Parse-Vorgang erzeugt zunächst nur ein TemporalAccessor-Objekt. Dieses muß erst als Argument einer hoffentlich vorhandenen statischen from()-Methode im erwarteten Datentyp (z.B. LocalDate) zugewiesen werden, um das eigentliche Ergebnis zu bekommen. Das ist eine erhebliche Umgewöhnung im Vergleich zum alten SimpleDateFormat.
g) Seltsame Felder und Einheiten, die wohl niemand braucht
Eine Auswahl in den Klassen ChronoField und ChronoUnit: ALIGNED_WEEK_OF_YEAR (nicht zu verwechseln mit WeekFields#weekOfYear()!), FOREVER etc.
h) LocalDate ohne DateResolver
Gab es mal im ersten Entwurf und meint die unterschiedlichen Möglichkeiten, zu einem Datum Tage zu addieren – unter der Berücksichtigung der Monatslänge. Ist im Zuge der API-Verschlankung leider rausgefallen.
i) IsoFields versus WeekFields
Teilweise liegen redundante Felder vor – etwa WEEK_OF_WEEK_BASED_YEAR (außerdem ein sehr häßlicher und holpriger Name). Zudem hätte die im Unterpaket temporal versteckte Klasse WeekFields besser in das Hauptpaket java.time gepasst, weil gerade US-Entwickler nicht die ISO-Definition einer Kalenderwoche brauchen und daher ständig die Wochendefinition anpassen müssen.
j) Zeitzonen-IDs im Olson-Format liegen nicht als Enums vor
Eine frühe JavaOne-Foliensammlung versprach noch Schutz gegen Tippfehler wie “Asia/Hongkong” (ohne korrekten Unterstrich). Hier hätte ein Enum für Standard-IDs geholfen. Gibt es aber nicht.
k) Clock als abstrakte Klasse statt als Interface
Das ist auch ärgerlich, weil so externe Implementierungen nur erschwert werden. Noch schlechter: Die Klasse Clock schleppt mit einem eigenen Zeitzonenattribut unnötigen Ballast mit sich herum, der nicht in unmittelbarem Zusammenhang mit der Hauptmethode von Clock steht, nämlich einen Instant für die aktuelle Zeit zu liefern. Nicht jede konkrete Clock müßte eine Zeitzone definieren.
Auch ist der Zugang zur aktuellen Zeit oft nicht nur über die Clock-Klasse erhältlich, sondern auch über now()-Methoden in den Klassen von LocalDate etc. Es ist wegen der impliziten Verwendung von System-Einstellungen nicht so klar, was der Anwender letztlich bekommt (welche System-Zeitzone, welche Uhr?). NodaTime hat diesen Ansatz dankenswerterweise nicht. Ich empfehle, für die aktuelle Zeit entgegen allen “Bequemlichkeitsansätzen” lieber direkt mit der Clock-Instanz anzufangen und sich dann über den Instant bis zur Datumsklasse durchzuhangeln, wenn der heutige Tag gefragt ist.
Fazit: Vieles ist gut, könnte aber viel besser sein. Die meisten möglichen Verbesserungen werden aber nie kommen, das kann man wohl schon jetzt sagen. Letztlich je nach Perspektive voll befriedigend (dann ist der JSR-310 ein guter Ersatz für sowohl Joda Time als auch teilweise die alten JDK-Zeitklassen) bis Mittelmaß (dann kann meine Bibliothek Time4J als vollwertiger Ersatz helfen, die Lücken zu stopfen – ich kämpfe noch darum, das Ding bis Ende des Jahres in einer ersten alpha-Version auszuliefern).