Die neue Java 8 Zeit-API (JRS-310): Ein kompakter Überblick

Logo: Java
Die bisherige Java Date/Time-API ist veraltet. Warum? Nun, nicht nur weil selbst die Konstruktoren der Klasse als"Deprecated" ausgewiesen sind, sondern weil die API bereits im JDK 1.0 nicht zu gebrauchen war. Seit 1996 warteten deshalb viele Entwickler auf eine überarbeitete Version. Einige mögen bereits an Altersschwäche verstorben sein, doch der Rest kann nun erleichtert Aufatmen. Mit JDK 8 wurde die Date/Time-Api komplett erneuert und bietet nun endlich den Komfort, den man sich von einer ordentlichen API rund um Daten und Zeiten erhofft.

Schon vor Version 8 des JDKs konnten Programmierer jedoch auf eine Vernünftige API zurück greifen. Die Joda-Time von Stephen Colebourne etablierte sich mittlerweile zum Quasi-Standard in der Java-Welt, wenn es um Zeiten ging. Es ist daher nicht verwunderlich, dass dieses Genie mit an Bord geholt wurde. Für die neue Date/Time-API (JRS-310) war er maßgeblich mit verantwortlich und empfiehlt diese auch allen JDK 8 Benutzern auf seinem Blog. Nun aber genug geschwafelt. Schauen wir uns einmal an wie die neue API funktioniert.

Instantiierung

Bevor wir uns mit der Zeit beschäftigen können, muss diese erst einmal erschaffen werden. In der neuen Java-API geht dies überschaubar einfach. Zu bechten ist dabei, dass wir einen include auf "java.time" benötigen. In diesem Paket sind alle notwendigen Klassen enthalten."java.util.Date" und der Rest der alten Klassen sind auch im JDK 8 vorhanden. Diese dienen der Rückwärtskompatibilität und sollten nicht mehr verwendet werden.
Die einfachste Art eine Instanz von DateTime zu erzeugen geht über die aktuelle Systemzeit.
LocalDateTime.now()
Die direkte Initialisierung mittels Konstruktor ist nicht möglich. Dafür gibt es jedoch eine Vielzahl von statischen Methoden, welche Überladungen von of(...) sind.
LocalDateTime.of(2014,Month.JULY, 7,10,0)
Zusätzlich gibt es noch die Methoden
// ISO-Date
LocalDateTime.parse("2007-12-03T10:15:30")
// Zeit in Sekunden und Nanosekunden vom 1 Januar 1970 ab
LocalDateTime.ofEpochSecond(0L,0, ZoneOffset.UTC)
Es ist zwar ziemlich unnötig, der Parameter des letzten Beispiels kann jedoch auch negativ sein, um Zeiten vor 1970 zu generieren.
Das Parsen von ISO Zeitangaben funktioniert, wie in dem Beispiel gezeigt, recht gut. Liegt die Zeit jedoch in einem anderen Format vor, so kann es schnell zu Problemen kommen. Abhilfe schafft hier der "DateTimeFormatter", welcher leicht durch einen String instantiiert werden kann.
DateTimeFormatter Myformatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime myDateTime = LocalDateTime.parse("2014-09-26 22:47:28", Myformatter);
Zu beachten ist, dass es für Datum und Zeit auch eigene Klassen gibt.
  • LocalTime: Für Uhrzeiten von 00:00 bis 23:59:59.999999999
  • LocalDate: Für Daten von -999999999-01-01 bis +999999999-12-31
LocalDateTime ist somit die mächtigste der drei Klassen. In ihr ist sowohl die Zeit wie auch das Datum enthalten. Aus diesem Grund ist es auch möglich diese mit den Methoden "toLocalDate()" und "ToLocalTime()" auszulesen.
LocalTime hat noch zusätzliche statische Member wie LocalTime.MIDNIGHT mit deren Verwendung man bei seinen Kollegen bestimmt Eindruck schinden kann.

Zeitspannen

Alleine eine Zeit zu haben bringt nicht viel. Natürlich sollte man damit auch rechnen können. Einfache Operationen, wie etwa das Hinzufügen von Sekunden oder Stunden können direkt auf der erzeugten Instanz durchgeführt werden.
LocalDateTime now = LocalDateTime.now();
LocalDateTime tomorrow = now.plusDays(1L);
LocalDateTime yesterday = now.minus(24L, ChronoUnit.HOURS);
Wird eine komplexere Zeitspanne als Tage oder Stunden benötigt, so kann auf die Klasse "Period" zurückgegriffen werden. Auch diese Klasse erzeugt Instanzen durch die Methode of(...).
// Auch mit Period.ofDay(1) möglich
Period oneDay = Period.of(0,0,1);
LocalDateTime tomorrow = now.plus(oneDay);
Leider ist es, wie in vielen anderen APIs, nicht möglich die Operatoren + und - zu verwenden. Es muss immer auf die Methoden der Instanz zurückgegriffen werden. Das bedeutet, dass für eine Periode zwischen zwei Zeiten auch eine Methode existieren muss. Diese lautet in unserem Fall between(...) welche zwei Zeiten als Parameter annimmt.
Period oneDay = Period.between(now.toLocalDate(), tomorrow.toLocalDate());
Period.between(...) kann nur mit Parametern vom Typ LocalDate oder ChonoLocalDate befüllt werden. Dies liegt daran, dass Period eine GGenauigkeitvon einem Tag besitzt. Möchte man genauere Zeitspannen haben, dann muss man auf Duration zurück greifen. Diese Klasse funktioniert äquivalent zu Period.
LocalTime inOneHour = now.toLocalTime().plus(Duration.ofHours(1L));

Zeitzonen

Der Alptraum eines jeden Programmierers: Zeitzonen. Neben der Zeichenkodierung gibt es kaum eine lästigere Aufgabe für Programmierer, als das Programmieren mit Zeitzonen. Das dafür eine gute API verwendet werden sollte ist verständlich. Wer ein wenig über das Dilemma mit den Zeitzonen erfahren möchte, der sollte unbedingt dieses Video anschauen. Es lohnt sich.
Mit der Methode "atOffset" lässt sich einer Zeit leicht ein Offset zuweisen. Sobald dies geschehen ist wird daraus der Datentype "OffsetDateTime".
Im Folgenden Beispiel zeige ich, wie man zweimal die gleiche Zeit, nur mit unterschiedlichem Offset vergleicht und dann den Offset wieder heraus rechnet. Um ein wenig kreativ zu sein habe ich beide Zeiten dabei auf unterschiedliche Weiße erstellt.
OffsetDateTime myOffsetDateTime1 = LocalDateTime.of(2001,1,1,0,0).atOffset(ZoneOffset.UTC);
OffsetDateTime myOffsetDateTime2 = OffsetDateTime.of(LocalDateTime.of(2001,1,1,0,0), ZoneOffset.ofHours(2));
System.out.println(Duration.between(myOffsetDateTime1, myOffsetDateTime2));
Ein simpler Offset reicht oft nicht aus um mit Zeiten zu rechnen. Ein Offset kann sich beispielsweise im Laufe eines Halbjahres ändern, wie dies beispielsweise hier in Deutschland der Fall ist. In JRS-310 sind für verschiedenste Orte daher ZoneIds vorhanden. Eine komplette Liste ist über die Methode "ZoneOffset.getAvailableZoneIds()" zu erhalten. Mit diesen Ids lässt sich dann die Zeitzone auf eine Zeit anwenden.
Im folgenden Beispiel habe ich den Unix Zeitstempel für den 1.1.2000 UTC ausgegeben. Wie immer führen mehrere Wege nach Rom.
LocalDateTime myDateTime = LocalDateTime.of(2001,1,1,0,0);
System.out.println(myDateTime.toEpochSecond(ZoneOffset.UTC));
System.out.println(myDateTime.atZone(ZoneId.of("UTC")).toEpochSecond());
Sobald eine Zeit mit einer Zeitzone kombiniert wird, wird daraus der Datentyp "ZonedDateTime". Daraus kann jedoch leicht auch eine "OffsetDateTime" gemacht werden.
myDateTime.atZone(ZoneId.of("Pacific/Kwajalein")).toOffsetDateTime()
Wichtig für Anwender ist es den Unterschied zwischen Offset und Zeitzone zu kennen. Während ein Offset immer ein Offset bleibt kann eine Zeitzone ihren Offset wechseln, je nach Ort und Zeit.

Ausgabe und Formatierung

Möchte man nun mit den Werten der Instanzen arbeiten, so lassen diese sich über eine Vielzahl von Methoden auslesen.
now.get(ChronoField.DAY_OF_WEEK);
now.getDayOfYear();
now.getDayOfMonth();
now.getNano();
Möchte man eine Instanz mit einem einzelnen vorbestimmten Wert abrufen, so ist das mit "with(...)" möglich. Damit kann beispielsweise der Wochentag des Monatsersten erfragt werden.
now.with(ChronoField.DAY_OF_MONTH, 1).getDayOfWeek();
Möchte man eine Zeitangabe abrunden, so geht das mit "truncate(...)". Das folgende Beispiel rundet die aktuelle Uhrzeit beispielsweise auf 12:00 oder 00:00 Uhr des aktuellen Tages
now.truncatedTo(ChronoUnit.HALF_DAYS);
Natürlich ist es auch mit der neuen API möglich einen individuell formatierten String zurück zu geben. Dies geschieht, wie auch beim einlesen eines Strings, mit einem "DateTimeFormatter ".
DateTimeFormatter Myformatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime now = LocalDateTime.now();
System.out.println(now.format(Myformatter));
Damit wäre mein Tutorial auch schon beendet. Natürlich könnte man noch einiges mehr über Date/Time-API erzählen. Insofern ich Zeit dazu finde, werde ich weitere Anwendungsfälle auf meinem Blog vorstellen.
Bookmark and Share

0 Kommentare:

Kommentar veröffentlichen