Umgang mit Sommerzeit in Java
1. Überblick
Daylight Saving Time oder DST ist eine Praxis, bei der die Uhren während der Sommermonate vorgerückt werden, um eine zusätzliche Stunde des natürlichen Lichts zu nutzen (Einsparung von Heizleistung, Beleuchtungsleistung, Verbesserung der Stimmung usw.).
Es wird vonseveral countries verwendet und muss bei der Arbeit mit Datums- und Zeitstempeln berücksichtigt werden.
In diesem Tutorial erfahren Sie, wie Sie die Sommerzeit in Java je nach Speicherort richtig handhaben.
2. JRE- und DST-Mutabilität
Erstens ist es äußerst wichtig zu verstehen, dass die weltweiten Sommerzeitzonenchange very often betragen und es keine zentrale Behörde gibt, die dies koordiniert.
Ein Land oder in einigen Fällen sogar eine Stadt kann entscheiden, ob und wie es beantragt oder widerrufen werden soll.
Jedes Mal, wenn dies geschieht, wird die Änderung inIANA Time Zone Database aufgezeichnet, und das Update wird in zukünftigenrelease of the JRE eingeführt.
In case it’s not possible to wait, we can force the modified Time Zone data containing the new DST settings into the JRE über ein offizielles Oracle-Tool namensJava Time Zone Updater Tool, das aufJava SE download page verfügbar ist.
3. Der falsche Weg: Drei-Buchstaben-Zeitzonen-ID
In den JDK 1.1-Tagen erlaubte die API Zeitzonen-IDs mit drei Buchstaben, was jedoch zu mehreren Problemen führte.
Dies lag zum einen daran, dass sich dieselbe ID mit drei Buchstaben auf mehrere Zeitzonen beziehen konnte. Zum Beispiel könntenCST US sein. "Central Standard Time", aber auch "China Standard Time". Die Java-Plattform konnte dann nur einen von ihnen erkennen.
Ein weiteres Problem war, dass Standard-Zeitzonen niemals die Sommerzeit berücksichtigen. Mehrere Gebiete / Regionen / Städte können ihre lokale Sommerzeit innerhalb derselben Standardzeitzone haben, sodass die Standardzeit sie nicht einhält.
Aufgrund der Abwärtskompatibilität beträgtit’s still possible to instantiate a java.util.Timezone with a three-letter ID. However, this method is deprecated and shouldn’t be used anymore.
4. Der richtige Weg: TZDB Timezone ID
Der richtige Weg, um die Sommerzeit in Java zu verarbeiten, besteht darin, einTimezone mit einer bestimmten TZDB-Zeitzonen-ID zu instanziieren, z. “Europe/Rome”.
Dann verwenden wir dies in Verbindung mit zeitspezifischen Klassen wiejava.util.Calendar, um eine korrekte Konfiguration des Rohversatzes vonTimeZone’s(zur GMT-Zeitzone) und automatischen DST-Verschiebungsanpassungen zu erhalten.
Mal sehen, wie die Verschiebung vonGMT+1 zuGMT+2 (die in Italien am 25. März 2018 um 02:00 Uhr stattfindet) automatisch behandelt wird, wenn die richtigenTimeZone: verwendet werden
TimeZone tz = TimeZone.getTimeZone("Europe/Rome");
TimeZone.setDefault(tz);
Calendar cal = Calendar.getInstance(tz, Locale.ITALIAN);
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.ITALIAN);
Date dateBeforeDST = df.parse("2018-03-25 01:55");
cal.setTime(dateBeforeDST);
assertThat(cal.get(Calendar.ZONE_OFFSET)).isEqualTo(3600000);
assertThat(cal.get(Calendar.DST_OFFSET)).isEqualTo(0);
Wie wir sehen können, beträgtZONE_OFFSET 60 Minuten (weil ItalienGMT+1 ist), währendDST_OFFSET zu diesem Zeitpunkt 0 ist.
Fügen wir denCalendar zehn Minuten hinzu:
cal.add(Calendar.MINUTE, 10);
Jetzt sind auchDST_OFFSET 60 Minuten geworden, und das Land hat seine Ortszeit vonCET (mitteleuropäische Zeit) aufCEST (mitteleuropäische Sommerzeit) umgestellt (GMT+2) ::
Date dateAfterDST = cal.getTime();
assertThat(cal.get(Calendar.DST_OFFSET))
.isEqualTo(3600000);
assertThat(dateAfterDST)
.isEqualTo(df.parse("2018-03-25 03:05"));
Wenn wir die beiden Daten in der Konsole anzeigen, ändert sich auch die Zeitzone:
Before DST (00:55 UTC - 01:55 GMT+1) = Sun Mar 25 01:55:00 CET 2018
After DST (01:05 UTC - 03:05 GMT+2) = Sun Mar 25 03:05:00 CEST 2018
Als letzten Test können wir den Abstand zwischen den beidenDates, 1:55 und 3:05 messen:
Long deltaBetweenDatesInMillis = dateAfterDST.getTime() - dateBeforeDST.getTime();
Long tenMinutesInMillis = (1000L * 60 * 10);
assertThat(deltaBetweenDatesInMillis)
.isEqualTo(tenMinutesInMillis);
Wie zu erwarten ist, beträgt die Entfernung 10 statt 70 Minuten.
Wir haben gesehen, wie wir vermeiden können, durch die korrekte Verwendung vonTimeZone undLocale in die üblichen Fallstricke zu geraten, die bei der Arbeit mitDate auftreten können.
5. Der beste Weg: Java 8 Date / Time API
Die Arbeit mit diesen thread-unsicheren und nicht immer benutzerfreundlichenjava.util-Klassen war immer schwierig, insbesondere aufgrund von Kompatibilitätsproblemen, die eine ordnungsgemäße Überarbeitung verhinderten.
Aus diesem Grund hat Java 8 ein brandneues Paket,java.time, und ein ganz neues API-Set,Date/Time API., eingeführt. Dies ist ISO-zentriert, vollständig threadsicher und stark von der berühmten Bibliothek Joda inspiriert. Zeit.
Schauen wir uns diese neuen Klassen genauer an, beginnend mit dem Nachfolger vonjava.util.Date,java.time.LocalDateTime:
LocalDateTime localDateTimeBeforeDST = LocalDateTime
.of(2018, 3, 25, 1, 55);
assertThat(localDateTimeBeforeDST.toString())
.isEqualTo("2018-03-25T01:55");
Wir können beobachten, wie einLocalDateTime mit demISO8601-Profil übereinstimmt, einer Standard- und weit verbreiteten Datums- / Uhrzeitnotation.
Zones undOffsets sind jedoch nicht bekannt. Deshalb müssen wir sie ina fully DST-aware java.time.ZonedDateTime konvertieren:
ZoneId italianZoneId = ZoneId.of("Europe/Rome");
ZonedDateTime zonedDateTimeBeforeDST = localDateTimeBeforeDST
.atZone(italianZoneId);
assertThat(zonedDateTimeBeforeDST.toString())
.isEqualTo("2018-03-25T01:55+01:00[Europe/Rome]");
Wie wir sehen können, enthält das Datum jetzt zwei grundlegende nachfolgende Informationen:+01:00 istZoneOffset, während[Europe/Rome]ZoneId ist.
Lassen Sie uns wie im vorherigen Beispiel die Sommerzeit durch Hinzufügen von zehn Minuten auslösen:
ZonedDateTime zonedDateTimeAfterDST = zonedDateTimeBeforeDST
.plus(10, ChronoUnit.MINUTES);
assertThat(zonedDateTimeAfterDST.toString())
.isEqualTo("2018-03-25T03:05+02:00[Europe/Rome]");
Wieder sehen wir, wie sich sowohl die Zeit als auch der Zonenversatz nach vorne verschieben und dennoch den gleichen Abstand einhalten:
Long deltaBetweenDatesInMinutes = ChronoUnit.MINUTES
.between(zonedDateTimeBeforeDST,zonedDateTimeAfterDST);
assertThat(deltaBetweenDatesInMinutes)
.isEqualTo(10);
6. Fazit
Wir haben anhand einiger praktischer Beispiele in verschiedenen Versionen der Java-Kern-API gesehen, was Sommerzeit ist und wie man damit umgeht.
Bei der Arbeit mit Java 8 und höher wird die Verwendung des neuenjava.time-Pakets aufgrund der Benutzerfreundlichkeit und seiner standardmäßigen, threadsicheren Natur empfohlen.
Wie immer ist der vollständige Quellcodeover on Github verfügbar.