Zeitzonen in Java

Martin Kompf

Bei der Online-Kommunikation mit Partnern im Ausland ist besonderes Augenmerk auf eventuelle Zeitunterschiede zu legen, um Fehlern und Verstimmungen aus dem Weg zu gehen. Ein in New York (USA) wohnender Partner ist sicher nicht begeistert, wenn man ihn um 8 Uhr Mitteleuropäischer Zeit (MEZ) anruft. Zeitzonen helfen hierbei, den Überblick zu behalten.

TimeZone

Eine Zeitzone definiert die Zeitdifferenz der Ortszeit zur koordinierten Weltzeit (UTC) sowie die Regeln zur Sommer- und Winterzeitumstellung. Traditionell seit Java 1.1 kapselt die Klasse java.util.TimeZone diese Informationen. Zur Repräsentation von Datum und Zeit inklusive der Zeitzoneninformation dient ein Objekt vom Typ Calendar. Erzeugt man ein solches ohne explizite Angabe der Zeitzone, dann übernimmt es die Standardzeitzone des Betriebssystems:

DateFormat dateTimeFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.FULL);

Calendar hereAndNow = Calendar.getInstance();
System.out.println(dateTimeFormat.format(hereAndNow.getTime()));

Der Code liefert eine Ausgabe wie 06.02.2016 15:03 Uhr MEZ, wenn er auf einem Computer mit der Zeitzoneneinstellung Europe/Berlin läuft. Will man jetzt nicht die lokale Ortszeit, sondern die aktuelle Uhrzeit in New York wissen, dann liegt es nahe, das Calendar Objekt mit Angabe der gewünschten Zeitzone zu erzeugen:

TimeZone timeZone = TimeZone.getTimeZone("America/New_York");
Calendar nowInNY = Calendar.getInstance(timeZone);
System.out.println(dateTimeFormat.format(nowInNY.getTime())); // unexpected result, prints time in MEZ

Leider liefert das nicht das gewünschte Ergebnis, sondern die Zeitausgabe erfolgt wieder in MEZ! Der Grund dafür ist das zur formatierten Ausgabe erforderliche DateFormat. Es besitzt einen eigenen Kalender mit einer eigenen Zeitzone, die bei der gezeigten Vorgehensweise ebenfalls der Standardzeitzone des Betriebssystems und damit MEZ entspricht. Um eine Ausgabe in einer anderen Zeitzone zu erhalten, muss man das DateFormat explizit mitteilen:

dateTimeFormat.setCalendar(nowInNY); // ugly
System.out.println(dateTimeFormat.format(nowInNY.getTime())); // prints time in EST

Java 8

Solche und andere Besonderheiten führten zu immer mehr Rufen nach einer komplett neugestalteten Datums- und Zeit-API. Die Umsetzung erfolgte in Java 8 mit dem Package java.time. Zur Darstellung von Zeitzonen dient jetzt java.time.ZoneId, die Klasse ZonedDateTime repräsentiert ein zeitzonenbehaftetes Datum inklusive Uhrzeit und das Pendant zu java.text.DateFormat ist java.time.format.DateTimeFormatter. Mit diesen Klassen gelingt die Beantwortung der Frage «Wie spät ist es jetzt in New York» intuitiv ohne Umwege:

DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.FULL);
    
ZonedDateTime hereAndNow = ZonedDateTime.now();
System.out.println(dateTimeFormatter.format(hereAndNow));
System.out.println(hereAndNow); // formatter is not required
    
ZoneId timeZone = ZoneId.of("America/New_York");
ZonedDateTime nowInNY = ZonedDateTime.now(timeZone);
System.out.println(dateTimeFormatter.format(nowInNY));
System.out.println(nowInNY); // formatter is not required

Die letzte Zeile zeigt, dass man sogar auf den DateTimeFormatter verzichten könnte, die Ausgabe erfolgt dann im ISO-8601 Format:

06.02.2016 16:03 Uhr MEZ
2016-02-06T16:03:50.067+01:00[Europe/Berlin]
06.02.2016 10:03 Uhr EST
2016-02-06T10:03:50.088-05:00[America/New_York]

Der Beispielcode erzeugt genaugenommen unterschiedliche Zeitstempel durch mehrmaligen Aufruf von ZonedDateTime.now(), sodass sich die zwei Zeitpunkte um einige Millisekunden unterscheiden. Das lässt sich vermeiden, indem man zuerst einen unveränderlichen Zeitstempel vom Typ Instant erzeugt und dann mittels ZonedDateTime.ofInstant(Instant, ZoneId) die unterschiedlichen ZonedDateTime Objekte konstruiert. Die folgende Funktion demonstriert dieses Vorgehen, um für eine Liste von Zeitzonen jeweils die lokale Ortszeit zu ermitteln. Die Iteration durch die Liste, das Umwandeln der Zonenbezeichnungen in ZoneId Objekte sowie die Ausgabe erfolgt dabei unter Zuhilfenahme der ebenfalls in Java 8 neu eingeführten Streams und Lambdas:

static void aroundTheWorld() {
  List<String> tzIdList = Arrays.asList(ZoneId.systemDefault().getId(), 
    "Europe/Paris", "Arctic/Longyearbyen", "America/Godthab", "America/Kentucky/Louisville", 
    "Pacific/Honolulu", "Australia/Hobart", "Asia/Pontianak", "Europe/Vatican", "Africa/Maseru");
  
  Instant now = Instant.now();
  
  tzIdList.stream()
    .map(ZoneId::of)
    .map(tz -> ZonedDateTime.ofInstant(now, tz))
    .forEach(System.out::println);
}

Zeitzonen?

Bleibt die Frage zu klären, wie man an die exakte Bezeichnung der Zeitzone des Partners kommt. Für grobe Abschätzungen reicht eine PDF Karte, wie sie die United States Central Intelligence Agency (CIA) publiziert. Zu genaueren Ergebnissen gelangt man mit der Online-Karte von GEOPostion. Kennt man die genauen geografischen Koordinaten, dann lässt sich die Zeitzone auch direkt in Java mittels der GeoTools API aus Shapefiles bestimmen.