Konvertieren mit Zeitzonen: Java.time und java.util.Date


Um Zeit in einem Computersystem zu repräsentieren hat sich der Unix/Epoch-Zeitstempel als Standard durchgesetzt. Dieser gibt die Zeit in Sekunden an, die seit dem ersten Januar 1970 UTC um Mitternacht vergangen sind. So einfach dieser Ansatz auch ist, so schwer ist es jedoch damit zu arbeiten. Ein Jahr besteht aus 31536000 Sekunden. Um den Unix-Zeitstempel um genau ein Jahr zu erhöhen reicht diese Information jedoch nicht aus. Man muss wissen wann ein Schaltjahr stattfindet oder wann eine Schaltsekunde. Zudem möchte man oft auch andere Zeitzonen betrachten als UTC. Höhere Programmiersprachen besitzen daher eigene Bibliotheken um mit Zeiten und Zeitzonen zu arbeiten. Die Java.time Bibliothek möchte ich hier kurz erläutern.

Die Klasse Java.util.Date speichert die Zeit intern ebenfalls als Zeitstempel ausgehend von 1970. Zur höheren Genauigkeit jedoch nicht in Sekunden sondern Millisekunden. Durch instantiieren dieser Klasse wird sie mit dem aktuellen Zeitstempel erstellt. Als Argument kann dem Konstruktor zudem der Zeitstempel mitgeteilt werden. Der fünfte August 2018 um 1 Uhr ist beispielsweise repräsentiert durch die Zahl 1533430800000.

Die Klasse Date hat zudem Methoden um den aktuellen Tag, das Jahr etc. zu lesen. Diese Methoden sind mittlerweile jedoch veraltet und sollten nicht mehr verwendet werden. Der Grund liegt in der Verzahnung mit der aktuellen Zeitzone des Systems. Befindet sich ein Rechner beispielsweise in New-York, so wird aus 1 Uhr vormittags 21 Uhr abends. Startet eine Person aus Japan das gleiche Programm, so bekommt diese 10 Uhr zu sehen. Es ist daher nicht verwunderlich, dass diese Methoden veraltet sind und nicht mehr verwendet werden sollten.

new Date(1533430800000).getHours(); // 21

Zum Glück müssen Java-Entwickler dies auch nicht mehr tun und haben mit der  Java.time eine Bibliothek zur Hand mit der Zeitzonen explizit deklariert werden müssen. Das Handling mit Zeitzonen kann am Anfang etwas lästig wirken, doch sobald man verstanden hat, dass Zeitzonen nicht vom Himmel fallen (meistens jedenfalls) ist der Umgang mit ihnen recht simpel.

Konvertierung von Date

Da die Klasse Date zu den Pionieren von Java gehört ist sie noch immer der Standard in Sachen Zeitangaben. Fast in jeder Bibliothek wird von Date Gebrauch gemacht. Es kommt daher häufig vor, dass man mit ihr arbeiten und konvertieren muss. Alle Konvertierungen geschehen über die Klasse java.time.Instant. Diese Klasse repräsentiert in Nanosekunden ebenfalls den Zeitstempel seit 1970, hat mit der Date-Klasse jedoch nichts mehr zu tun. Auch Zeitzonen etc. werden nicht berücksichtigt.

Möchte man nun an die aktuellen Stunden gelangen, muss man Instant explizit über eine Zeitzone in die Klasse java.time.ZonedDateTime konvertieren. Die Methode systemDefault() benutzt zur Konvertierung die Zeitzone des Betriebssystems.

new Date(1533430800000L).toInstant().atZone(ZoneId.of("UTC")).getHour() // 1
new Date(1533430800000L).toInstant().atZone(ZoneId.systemDefault()).getHour() // 21

Die meisten Bibliotheken lesen Zeitstempel in der Zeitzone des Betriebssystems ein. Wird ein XML geparsed, liegt das Date-Objekt in der Zeitzone des Betriebssystems vor. Dies kann häufig nicht der Realität entsprechen. In dem Hier gezeigten Beispiel soll es sich um die Zeitzone UTC handeln.

<customerTimestamp>
  <time>2018-01-01 10:00:00</time>
  <timezone>UTC</timezone>
</customerTimestamp>

Um nun eine ZonedDateTime zu bekommen, welche in der richtigen Zeitzone liegt muss das eingelesene Date-Objekt in der richtigen Zeitzone interpretiert werden. Zuerst wird das eingelesene Date in der Zeitzone des Systems zur ZonedDateTime konvertiert. Diese kann im Anschluss mit der Methode withSameLocal() in der gewünschten Zeitzone interpretiert werden.

public static void main(String[] args) throws Exception {
  final ZonedDateTime dateTime = interpret("2018-01-01 10:00:00", "UTC");
  dateTime.getHour(); // 10
  dateTime.toInstant().getEpochSecond(); // 1533430800 korrekter UTC Zeitstempel
}
	
public static ZonedDateTime interpret(final String date, final String zone) throws Exception {
  DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  return ((Date)formatter.parse("2018-01-01 10:00:00")).toInstant().atZone(ZoneId.systemDefault())
      .withZoneSameLocal(ZoneId.of(zone));
}

Konvertierung zu Date

Um aus einer ZonedDateTime oder einer DateTime ein Date zu machen muss man lediglich auf deren Instant zurückgreifen. Da die Klasse Instant keine Informationen über Zeitzonen besitzt wird das daraus erstellte Date in der Systemzeit interpretiert.

Date.from(ZonedDateTime.parse("2018-01-01T10:00:00+00:00[UTC]").toInstant()).getHours(); // 5

Um für die eingelesene Zeit eine Date-Objekt in der Systemzeit zu erzeugen muss die Zone erst an die Zeitzone des Systems angepasst werden.

Date.from(ZonedDateTime.parse("2018-01-01T10:00:00+00:00[UTC]").withZoneSameLocal(ZoneId.systemDefault()).toInstant()).getHours();  // 10