Die neue Date/Time-API in Java 8
Seite 2: JSR 310
Projekt Joda wird JSR 310: Konzepte und Designprinzipien
Irgendwie hat sich die Java-Community in der Vergangenheit mit diesen Java-APIs arrangiert. Oder aber sie hat sich mit externen Bibliotheken beholfen, von denen Joda Time die bekannteste sein dürfte: Deren Designideen sind die Basis für die neue Date/Time-API in Java 8, die im JSR 310 (Java Specification Request) beschrieben ist. Joda Time wurde dabei nicht direkt übernommen, wie Spec-Lead Stephen Colebourne begründet, sondern leicht überarbeitet (v. a. neue Package-Namen, Null-Behandlung und Exception-Hierarchie). In einer Umfrage auf java.net schätzten 75 Prozent der Teilnehmer die Integration des neuen JSR in Java 8 als wichtig ein. Auch ein Backport nach Java 7 ist auf der Website des Threeten Project verfügbar.
Unterschieden wird generell zwischen Maschinen- und Menschensicht: Die eine nutzt den Starttermin 1970-01-01T00:00 in UTC. Die andere hingegen unterscheidet konzeptionell Dinge wie Kalender, Zeit, Datum, Tage und Monate in verschiedenen Kapselungen und Klassen.
Grundlegende Design-Prinzipien fĂĽr JSR 310 und seine API sind:
- Immutability und Thread-Safety: Alle Kernklassen sind unveränderlich.
- lesbare API in Form einer "Fluent API".
- Die API ist klar und wohldefiniert. Null-Werte werden so früh wie möglich abgewiesen.
- Das Basis-Kalendersystem des JSRÂ 310 beruht auf ISOÂ 8601 und somit dem gregorianischen Kalender.
Die API im Ăśberblick
Das Package java.time enthält alle Kernklassen, die der Artikel nachstehend vorstellt. Folgende Packages sind darüber hinaus ebenfalls Teil des JSR 310 (hier ohne weitere Vertiefungen):
- java.time.chrono enthält weitere Kalendersysteme neben dem Default ISO-8601,
- java.time.format umfasst Klassen zum Formatieren und Parsen,
- java.time.temporal bietet Klassen fĂĽr Feld-Zugriffe und auf zeitliche Einheiten, und
- java.time.zone unterstĂĽtzt Zeitzonen.
Die Abbildung 1 zeigt die wesentlichen Kernklassen von java.time und ihre Beziehungen als UML-Klassenmodell.
java.time.Instant und Duration
Instant ist ein Zeitstempel, repräsentiert also einen dedizierten Zeitpunkt (z. B. für eine Logging-Ausgabe). Die zeitliche Granularität ist in Nanosekunden (mit 96 Bit) festgehalten. Er kommt java.util.Date konzeptionell am nächsten (das eben kein Datum, sondern ein Zeitstempel ist).
Clock myclock = ...
Instant start = myclock.instant();
System.out.println(start); // Ausgabe: 2014-04-13T17:33:38.482Z
Mit Duration lässt sich die Zeitdauer zwischen zwei Instants bestimmen (wiederum in Nanosekunden). Die Zeitspanne kann auch negativ sein.
// ... ca. 5 Sekunden spaeter nach obiger Ausgabe
Instant end = Instant.now();
Duration elapsed = Duration.between(start, end);
System.out.println(elapsed); // Ausgabe: PT5.058S
Die Ausgabeformate folgen dem ISO-8601-Standard. Im Beispiel der Duration (mit Ausgabe PT5.058S) steht das P fĂĽr "Period" als Angabe einer Zeitspanne, T trennt Datum und Zeit, danach stehen die (etwas mehr als) fĂĽnf Sekunden.
Wer hat an der Uhr gedreht ...
Eine Clock ist eine Uhr und demnach eine Instant-Factory. Clock selbst ist abstrakt und wird intern durch FixedClock, SystemClock, TickClock und OffsetClock erweitert. Mit SystemClock hat man Zugriff auf "jetzt", also den aktuellen Instant (inkl. Datum und Zeit). Clock lässt sich also benutzen, um Aufrufe von System.currentTimeMillis() und TimeZone.getDefault() abzulösen. Alternativ kann man dazu die now-Methode der anderen Kernklassen benutzen.
Clock myclock = Clock.systemUTC();
Clock ist nützlich, um eine virtuelle Zeit zu simulieren – zum Beispiel in Tests und in Simulationen: Konsequenterweise muss man dann immer Clock nutzen und auf jegliche Aufrufe wie System.currentTimeMillis() verzichten.
Klassen fĂĽr nahezu jeden Einsatzfall
LocalDate ist ein Datum ohne Zeit und ohne Zeitzone. Es lässt sich zum Beispiel für einen Geburtstag benutzen. LocalTime ist eine Zeit ohne Datum und ohne Zeitzone, etwa für eine Laden-Schlusszeit ("bis 20 Uhr abends"). LocalDateTime kombiniert beides zu Datum mit Zeit (ohne Offset, ohne Zeitzone). Dagegen hat ZonedDateTime Datum, Zeit und Zeitzone. Es kommt daher GregorianCalendar konzeptionell am nächsten. Mit ZoneId und ZoneOffset lassen sich die IDs der Zeitzonen, beispielsweise "Europe/Berlin", und deren Offsets bezüglich "Greenwich/UTC", etwa "+02:00", bestimmen.
LocalTime ltime1 = LocalTime.of(12, 30, 00, 0); // 12:30 Uhr
LocalTime ltime2 = LocalTime.parse("12:30"); // auch 12:30 Uhr
// 18. Maerz 2014
LocalDate dateOfJava8 = LocalDate.of(2014, Month.MARCH, 18);
// der 32. Tag im Jahr ist der 1. Februar
LocalDate dateFeb1st = LocalDate.ofYearDay(2013, 32);
LocalDateTime ldatetime1 = LocalDateTime.of(dateOfJava8, ltime1);
LocalDateTime ldatetime2a = LocalDateTime.of(dateFeb1st, ltime2);
LocalDateTime ldatetime2b = LocalDateTime.of(2013, Month.FEBRUARY,↩
1, 12, 30, 00, 0);
// beides ist der 1.2.2013, 12:30 Uhr
assert(ldatetime2a.equals(ldatetime2b));
Mit Period lässt sich wie bei Duration eine Zeitspanne definieren, allerdings in Zeiteinheiten aus "Menschensicht" (also Jahren, Monaten, Tagen). Hier kann die Zeitspanne ebenfalls negativ sein.
Period between = Period.between(dateOfJava8, dateFeb1st);
System.out.println(between); // Ausgabe: P-1Y-1M-17D
Alles flieĂźt
Die Kern-API ist in Form einer "Fluent API" geschrieben, die einfach zu verketten ist und gut lesbar bleibt. Den Begriff hat unter anderem Martin Fowler geprägt. Methodennamen der neuen Java-Date/Time-API haben folgende Präfixe:
| Methodenpräfix | Bedeutung |
| of | statische Factory-Methode |
| parse | statische Factory-Methode fĂĽr Parse-Operation |
| get | gibt einen Wert zurĂĽck |
| is | Check auf einen Wert |
| with | Immutable-GegenstĂĽck zum Setter |
| plus | addiert einen Wert |
| minus | subtrahiert einen Wert |
| to | konvertiert in einen anderen Typ |
| at | kombiniert mit anderem Objekt |
Die einzelnen Aufrufe geben jeweils einen Wert zurück, so in nachstehendem Beispiel für ZonedDateTime. Damit sind die API-Methoden als Fluent API schön zu verketten.
ZonedDateTime now = ZonedDateTime.now();
int dayOfYear = now.plusHours(12).minusDays(7).
withSecond(0).withNano(0).
getDayOfYear();
Da die Klassen "immutable" sind, sind die Rückgaben immer Kopien mitsamt der angefragten Veränderung. Im Beispiel liefert plusHours(12) eine Kopie von now zurück, auf die zwölf Stunden addiert sind.
Formatierung und Ausgaben
Mit den Klassen DateTimeFormatter und DateTimeFormatterBuilder aus java.time.format lassen sich Datums- und Zeitwerte formatieren. Ein Beispiel:
LocalDate xMasDate = LocalDate.of(2013, Month.DECEMBER, 24);
LocalTime evening = LocalTime.of(20, 0);
LocalDateTime xMas = LocalDateTime.of(xMasDate, evening);
String pattern = "yyyy-MM-dd HH:mm:ss";
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(pattern);
String output = dtf.format( xMas ); // Ausgabe: 2013-12-24 20:00:00
Ein solcher, mit einem Pattern erzeugter DateTimeFormatter kann beliebig oft verwendet werden, er ist ebenfalls "immutable" und "thread-safe".