Zuletzt aktualisiert am 27.09.2012!
Will ich alle jemals existierenden Ansätze und fertige Bibliotheken auflisten, werde ich sicherlich nicht fertig. Die tatsächliche Anzahl ist auch nicht ermittelbar, weil viele Bibliotheken gar nicht öffentlich verfügbar sind. So gibt es in meiner Firma intern eine eigene Datumsklasse und auch eine eigene Uhrzeitklasse, die jeweils als Zustand Jahr, Monat, Tag und Stunde, Minute, Sekunde in Integer-Variablen halten und die Zeitrechnung an das JDK (java.util.GregorianCalendar) delegieren.
Bibliotheken, die lediglich die JDK-Kalenderklassen ummanteln und so einige unschöne Details verstecken und nach außen verbessern, gibt es in Unmengen. Auf diese Art und Weise kann z.B. die vielfach kritisierte Zählung des JDK von Monaten ab 0 (Januar) statt 1 (in der Umhüllung) leicht geändert werden. Eine unsortierte Mini-Auswahl von Open-Source-Bibliotheken dieses Typs sind zum Beispiel:
- com.google.gdata.data.DateTime (Google Data APIs Client Library (1.41.1))
- jOOCal von Lukas Eder
- CalendarDate von Mark McLaren
- Time and Money Code Library (inaktiv)
- …
Ich möchte daher das Thema etwas anders formulieren: Wieviele eigenständige Bibliotheken gibt es, die alle wesentlichen Aspekte einer Zeitbibliothek selbst behandeln, ohne an das JDK zu delegieren und daher auch stand-alone verwendet werden können? Die folgende Übersicht behandelt genau diese. Es sind überraschend wenige, nämlich (nach meinem Kenntnisstand) nur 6, das JDK eingeschlossen! Wer nach dem Kriterium der Eigenständigkeit noch mehr solche Bibliotheken kennt, möge sie mir gerne mitteilen.
- JDK – java.util.Calendar, sun.util.calendar-Paket etc.
- JodaTime von Brian O’Neill und Stephen Colebourne
- ICU-Projekt von IBM
- JSR 310 (Threeten) von Stephen Colebourne, Michael N. Santos und Roger Riggs
- BigDate von Roedy Green (Quellcode hier)
- DATE4J von John O’Hanley
Zu den Bibliotheken BigDate und DATE4J ist allerdings anzumerken, daß sie nicht ganz eigenständig sind, da ihnen eine eigene Zeitzonenimplementierung fehlt und sie hierzu einfach nur die JDK-Klasse java.util.TimeZone verwenden (und das auch nur als Methodenparameter). Durch Weglassen von Merkmalen wird deren API natürlich leicht sehr schlank. Und das JDK selbst bietet zwar nur relativ wenige öffentliche Klassen an, hat aber im Hintergrund noch viele weitere Klassen und Pakete, ist also deutlich gewichtiger als auf den ersten Augenschein hin.
Merkmale/Eigenschaften | JDK | Joda | ICU | Threeten | BigDate | DATE4J |
---|---|---|---|---|---|---|
Anzahl öffentlicher Klassen (mit spi-Paketen)1) | 14 | 143 | 61 | ~75 | 1 | 3 |
Zugriff auf kalendarische Felder als Integer | ☑ | ☑ | ☑ | ☑ 2) | ☑ | ☑ 3) |
Zugriff auf kalendarische Felder als Objekt (z.B. Monat-Enum) | ☐ | ☐ 4) | ☐ | ☑ | ☐ | ☐ |
Feldbereichsabfrage auf kalendarischen Feldern (Minimum/Maximum) | ☑ | ☑ | ☑ | ☑ | ☐ | ☐ |
>>immutable<< Klassen (Thread-Sicherheit) | ☐ | ☑ | ☐ | ☑ | ☐ | ☑ |
Abfrage der aktuellen Zeit über System.currentTimeMillis() oder now()/today()-Methoden | ☑ | ☑ | ☑ | ☑ | ☑ | ☑ |
Abfrage der aktuellen Zeit über Uhrenabstraktion (injizierbare Clock-Klasse) | ☐ | ☐ | ☐ | ☑ | ☐ | ☐ |
Genauigkeit in Sekunden | Milli | Milli | Milli | Nano | Tag 5) | Nano |
Separate Typen für Datum und Uhrzeit (je Genauigkeitsgrad ein Typ) | ☐ | ☑ | ☐ | ☑ | ☐ 5 | ☐ |
Variable Genauigkeit innerhalb eines Zeitpunkttyps | ☐ | ☐ | ☐ | ☐ | ☐ | ☑ |
Erzeugung von Datum oder Zeit über Konstruktoren | ☑ | ☑ | ☑ | ☐ | ☑ | ☑ |
Erzeugung von Datum oder Zeit über statische Fabrimethoden (ClassName.of(year, month, day) u.ä.) | ☐ | ☐ | ☐ | ☑ | ☐ | ☑ |
Eigene Zeitzonenimplementierung | ☑ | ☑ | ☑ | ☑ | ☐ | ☐ |
Quelle der Zeitzonendaten des JDK pre 8 ({java.home}/lib/zi) | ☑ | ☐ | ☑ | ☐ | ☐ | ☐ |
Eigene Quelle der Zeitzonendaten (TZ-DB) | ☐ | ☑ | ☑ | ☑ | ☐ | ☐ |
Historische Zeitzoneninformationen per UTC-Zeitabfrage | ☑ | ☑ | ☑ | ☑ | ☐ | ☐ |
Historische Zeitzoneninformationen per nextTransition() | ☐ | ☑ 6) | ☐ | ☑ 6) | ☐ | ☐ |
Historische Zeitzoneninformationen per Listing/Iterator | ☐ | ☐ | ☐ | ☑ 6) | ☐ | ☐ |
Erkennung von Zeitzonennamen (z.B. MESZ, PST) | ☑ | ☐ | ☑ | ☐ | ☐ | ☐ |
Erkennung von Windows-Zeitzonen-IDs | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ |
Vordefinierte Konstanten für Standard-Zeitzonen-IDs (z.B. Europe/Paris) | ☐ | ☐ | ☐ | ☐ | ☐ | ☐ |
Konfliktstrategie für Zeitzonenlücken/Überlappungen | ☐ | ☑ 7) | ☐ | ☑ | ☐ | ☐ |
Abstrakte Jahr/Monat/Tag-Basisklasse | ☑ | ☑ | ☑ | ☑ 8) | ☐ | ☐ |
Generisches API mit gemeinsamer Basisimplementierung für Chronologien ohne Jahr/Monat/Tag-Struktur | ☐ | ☐ | ☐ | ☐ 9) | ☐ | ☐ |
Unterstützung des UTC-Standards (Schaltsekunden) | ☐ | ☐ | ☐ | ☐ 10) | ☐ | ☐ |
Addition einer Dauer (add/plus/minus) | ☑ | ☑ | ☑ | ☑ | ☑ 11) | ☑ |
Rolloperation (wie Addition, aber ohne Effekt auf größere Zeitfelder) | ☑ | ☐ | ☑ | ☐ | ☐ | ☐ |
Typsicherheit in der Addition/Subtraktion | ☐ | ☑ 12) | ☐ | ☐ 12) | ☐ | ☐ |
Konfliktstrategie für Monatsaddition 14) | ☐ | ☐ | ☐ | ☐ 15) | ☐ | ☑ |
Dauerberechnung zwischen zwei Zeitpunkten wie in MS-DotNet (t1.subtract(t2)) | ☐ | ☐ | ☐ | ☐ | ☐ | ☑ 16) |
Dauerberechnung zwischen zwei Zeitpunkten ausgehend von der Zeiteinheit (units.between(t1, t2)) | ☐ | ☑ | ☐ | ☑ | ☐ | ☐ |
Dauer mit mehreren Zeitfeldern erzeugen (z.B. P3M1D) | ☑ 17) | ☑ | ☐ | ☑ | ☐ | ☐ |
Addition einer Dauer mit mehreren Zeitfeldern zu einem Zeitpunkt | ☑ 17) | ☑ | ☐ | ☑ | ☐ | ☐ |
XML-konforme Darstellung einer negativen Dauer (z.B. -P2D) | ☑ | ☐ | ☐ | ☐ | ☐ | ☐ |
Intervalle mit je zwei Zeitpunkten (Start/Ende) | ☐ | ☑ | ☐ | ☐ | ☐ | ☐ |
Format-API (eigene konfigurierbare Formatklassen mit CLDR-Unterstützung) | ☑ | ☑ | ☑ | ☑ | ☐ | ☐ |
Zeitformat mit AttributedCharacterIterator 18) | ☑ | ☐ | ☑ 19) | ☐ | ☐ | ☐ |
Lokalisierte Wocheninformationen (erster Tag der Woche etc.) | ☑ | ☐ | ☑ | ☑ | ☐ | ☐ |
Anzahl der vorinstallierten Kalendersysteme inklusive ISO (I18N) | 3 | 6 | 10 | 6 20) | 1 | 1 |
Tagesnummerierungsverfahren (Stichwort: Modified Julian Date) | ☐ | ☐ | ☑ | ☑ | ☑ | ☑ |
Anpassung von kalendarischen Feldern über set/with-Methoden | ☑ | ☑ | ☑ | ☑ | ☑ | ☐ |
Anpassung von kalendarischen Feldern über Adjuster-Verfahren (z.B. letzter Tag dieses Monats, erster Tag der nächsten Woche etc.) | ☐ | ☐ | ☐ | ☑ | ☐ | ☐ |
1) In den gegebenen Zahlen sind auch spi- und Formatpakete wie java.text.* berücksichtigt. Während eine kleine Zahl für eine leichtere Erlernbarkeit spricht, zeigt sie in der Regel aber auch einen Mangel an Merkmalen und Eigenschaften an. Dieses Kriterium eignet sich somit nicht als Aussage zur Qualität einer Bibliothek.
2) Als long-Primitive
3) Feldwert als java.lang.Integer-Objekt
4) Ersatzweise gibt es generische Property-Klassen.
5) Reine Datumsklasse
6) Komplexer uneinheitlicher Zugriff, Aufruf mehrerer Methoden für eine vollständige Historie notwendig
7) Nur für Überlappungen (Wechsel von Sommerzeit zur Winterzeit, wenn eine lokale Zeitangabe zweimal existiert)
8) Das grundlegende Design der Klasse ChronoDate, wie java.util.Calendar auf Jahr/Monat/Tag-Kalender eingeschränkt, ist wenige Monate vor dem vorgesehenen Projektende (Anfang 2013) unter den Projektleitern selbst noch stark umstritten.
9) Es gibt zwar ein AdjustableDateTime-Interface, das aber erstens zuwenig bietet und zweitens keine abstrakte Skelettimplementierung hat.
10) In einem separat hinzulinkbaren Erweiterungsmodul gibt es eine sogenannte UTC-SLS-Implementierung, die UT1-Zeiten zu TAI konvertieren hilft. Eine echte Unterstützung von UTC selbst inklusive Anzeige/Formatierung und Zeitrechnung gibt es leider nicht.
11) Nur Addition von Tagen, nicht Monaten oder Jahren
12 Änderungen sind über Methoden wie plusMonths(), plusSeconds() etc. typsicher, aber die generische Variante plus(3, MONTHS) in Threeten ist es leider nicht, da sie z.B. auch auf eine reine Uhrzeit angewandt werden kann. Der Compiler läßt es durchgehen, zur Laufzeit fliegt dann eine Ausnahme… Joda wirft im Kontrast auch zur Laufzeit keine Ausnahme, ignoriert aber die unpassenden Argumente schweigend – eine fragwürdige Strategie.
14) Was ist zum Beispiel [2012-01-30] + [1M]? => 29. Februar oder 1. März oder Abbruch?
15) Dieses Merkmal war zwischenzeitlich implementiert, ist momentan aber wieder entfernt worden.
16) Eingeschränkt auf Tage oder Sekunden über die Methoden numDaysFrom() und numSecondsFrom()
17) Im JDK möglich über die wenig bekannte Klasse javax.xml.datatype.Duration!
18) Notwendig zur Interoperabilität mit der javax.swing-Klasse JFormattedTextField, um dort ein Spinner-Verhalten zu realisieren. Merkwürdigerweise bietet Threeten im Gegensatz zum aktuellen JDK hier nichts, obwohl es als neue Zeitbibliothek für Java 8 vorgesehen ist.
19) Hier nicht verifiziert, nur vermutet, da sich ICU strukturell eng an das JDK anlehnt.
20 Stand von Anfang September 2012. Es sollen noch mindestens drei dazukommen, angeblich auch der (korrekt schwierig zu implementierende) hebräische Kalender.
Bewertung
Wie aus der Tabelle ersichtlich, ist keine Datums- und Zeitbibliothek perfekt. Auch ist überraschenderweise die vielgelobte Joda-Bibliothek keineswegs immer besser als das JDK. Allerdings können Joda und der designierte Joda-Nachfolger Threeten stark mit der Eigenschaft der Immutability punkten. Mit Sicherheit ist Threeten am ehesten ernst zu nehmen, befindet sich aber immer noch im alpha-Stadium. Weil niemand ernsthaft mit einem instabilen API arbeiten kann, lautet die Empfehlung zur Zeit immer noch Joda, obwohl es einige gravierende Schwächen aufweist, die so manchen trotzdem zum JDK haben greifen lassen. Da wären vor allem die fehlende lokalisierte Wochenunterstützung oder der Mangel an Unterstützung für die Swing-Komponente JFormattedTextField zu nennen. Vorsicht ist auch bei negativen Dauern angebracht (betrifft besonders Joda und leider auch Threeten). Und dann gibt es noch einige Features, auf die wohl auch die größten Threeten-Fans vergeblich warten werden, wie z.B. UTC-Schaltsekunden oder ein generisches Kalendersystem-API u.a. für Spieleentwickler. Es kommt nicht von ungefähr, daß die Anzahl der von Joda unterstützten Kalendersysteme relativ klein ist, obwohl Joda schon seit 2004 auf dem Markt ist. Ohne ein einigermaßen leicht zu handhabendes generisches API ist der Bau von neuen Kalendersystemen einfach zu schwer.
Wie umfangreich darf oder soll eine Datums- und Zeitbibliothek sein?
Der Umgang mit der Zeit und Kalendern ist komplex. Tatsächlich hat das Thema so viele mögliche Facetten, daß die obige Übersicht das nur unvollständig wiederzugeben vermag. Klar ist, daß die Beschränkung auf weniger als 10 Klassen (sogar nur eine im Fall von BigDate) notwendig mit dem Weglassen von vielen Features einhergehen muß. Insofern ist die Kritik mancher Java-Entwickler an Joda überzogen. Aber ein wahrer Kern steckt schon drin. Der abstrakte Überbau von Joda mit Namen wie AbstractReadableInstantFieldProperty überzeugt nicht wirklich. Threeten bringt hier Verbesserungen, aber zum Beispiel sein öffentliches Zeitzonen-API erscheint leider noch unnötig aufgeblasen. Wie wirbt ein gewisser oxbow_lakes für seine Scala-Bibliothek Saturn?
It is a bone-crushingly simple datetime library for those of us that have never needed:
- non-Gregorian calendars
- months that are not Jan-Dec, days of week that are not Mon-Sun etc.
- to schedule a meeting on Klingon during the daylight saving era of Dhu’ruk-ha’a
Erstens ist oxbow_lakes etwas ungerecht, denn seine so simple Bibliothek ist in Wirklichkeit nur eine Hülle um das JDK herum (er verwendet zur Datumsrechnung die äußerst komplexe Klasse java.util.GregorianCalendar). Zweitens gibt es gerade im religiösen Umfeld viele, die andere Kalender brauchen (das sage ich selber als Atheist – eine Ironie), zum Beispiel hebräische Kalender, die auch etwas taugen (die ICU-Variante ist nicht ganz korrekt oder gar vollständig). Und selbst Klingon-Kalender sind für Spiele-Entwickler nicht abwegig. Zu guter Letzt: Wochentage sind selbstverständlich nicht immer von Montag bis Sonntag, in einem ziemlich wichtigen Land wie den USA fängt die Woche mit dem Sonntag an…
Was könnte man Menschen noch sagen, die für sie unwichtige Features gar nicht erst sehen wollen? Wichtig ist in Sachen Schlankheit vor allem, daß das Hauptpaket einer Bibliothek überschaubar bleibt und möglichst nicht mehr als 3 Zeit-Klassen (plus wenige Hilfsklassen) anbietet – eine für Datum, eine für die Uhrzeit und eine für die Kombination. Das JDK und ICU ziehen alles in einer Calendar-Klasse zusammen, was schon zu schlank ist. Andererseits ist Joda hier leider abschreckend mit >50 Klassen. Threeten ist deutlich besser mit 7 Zeit-Klassen, aber man fragt sich schon, was die Daseinsberechtigung einer Klasse wie OffsetDate ausmacht. Der XML-Hintergrund wäre sicherlich in einem xml-Unterpaket besser aufgehoben. Oder warum gibt es zwei Dauer-Implementierungen in Threeten, obwohl die eine Implementierung nur ein Spezialfall der anderen ist? Schlankheit bedeutet auch, daß die Klassen selbst nicht zuviele Methoden anbieten (ZonedDateTime in Threeten hat ca. 100 Methoden).
Fazit
Im Moment ist Joda zu empfehlen, in Zukunft Threeten (ab Java 8). Die alten JDK-Klassen werden dann leider immer noch nicht ausgedient haben, denn sie unterstützen einige Features, die auch Threeten nicht bietet. Es gibt also durchaus noch Raum für eine neue ultimative Datums- und Zeitbibliothek…
Bin durch Deinen Link im java-forum auf diesen Artikel gestoßen. Habe bisher mit den JDK-Klassen ganz gut leben können. Fand die vielen Aspekte aber recht interessant. Einzig die Auswahl Deiner Vergleichskriterien in der Tabelle schien mir zwar sinnvoll, aber etwas willkürlich. Liegt aber evtl. daran, dass ich mich selbst nur mit den Basics beschäftigt habe und auf viele der von Dir geschilderten Probleme noch garnicht gestoßen bin.
Die Ansprüche der Anwender an eine Zeitbibliothek sind natürlich sehr verschieden. Sicherlich kommt man auch mit den alten JDK-Klassen zu brauchbaren Resultaten, wenn man erst mal gelernt hat, mit einigen Fallen umzugehen (z.B. die Klassiker Monatszählung bei 0 für Januar startend oder java.util.Date entgegen seinem Namen nicht als Datum, sondern als UT-Zeitstempel zu interpretieren). Und die JDK-Klassen bieten leider noch viel mehr üble Fallen (siehe z.B. meinen früheren Artikel zur Erkennung von Windows-Zeitzonennamen in diesem Blog). Allein um alle Fallstricke aufzuzählen, könnte ich den Blog locker einige Monate damit füllen.
Wer hingegen täglich mit Datum und Zeit rechnen musste und muss, hat oft auf Joda gesetzt – schon wegen der Immutability, dem stärksten Argument dafür. Aber viele wissen schlicht auch gar nicht, was es noch so alles außerhalb des JDK an Zusatzbibliotheken gibt. Was meine Übersicht ansatzweise zeigt, ist, daß allerdings keine Bibliothek bis jetzt alles kann… auch Joda nicht. Ich persönlich finde außerdem, daß es zu den JDK-Klassen viel mehr gute Tutorials als zu Joda gibt, was ein weiterer Grund für viele ist, bei den JDK-Klassen zu bleiben, die übrigens auch nicht jedem bestens bekannt sind, da sie verstreut in verschiedenen Paketen rumliegen (java.util, java.text, javax.xml.datatype etc).
Ja die Mutability von Date ist schon störend. Löse ich immer so, dass ich als Instanzvariable einen long mit millis speichere. Die Umwandlung in Date und zurück ist ein Einzeiler in den jeweiligen Gettern/Settern. Aber hast schon Recht, wenn man da schon anfangen muss, drumrum zu programmieren, sind komplexere Anforderungen mit anderen Libs sicher besser umsetzbar.
Interessanter Artikel. Mit JodaTime habe ich auch schon ein paar mal gearbeitet und muss sagen, dass die Library sehr hilfreich ist. Habe z.B. mal einen Urlaubskalender damit gebastelt.