Gestion de l’heure d’été en Java

Gestion de l'heure d'été en Java

1. Vue d'ensemble

Daylight Saving Time, ou DST, est une pratique consistant à faire avancer les horloges pendant les mois d'été afin de profiter d'une heure supplémentaire de lumière naturelle (économie de puissance de chauffage, de puissance d'éclairage, amélioration de l'ambiance, etc.).

Il est utilisé parseveral countries et doit être pris en compte lorsque vous travaillez avec des dates et des horodatages.

Dans ce didacticiel, nous allons voir comment gérer correctement l'heure d'été en Java en fonction de différents emplacements.

2. Mutabilité JRE et DST

Premièrement, il est extrêmement important de comprendre que les zones DST mondialeschange very often et qu’il n’ya pas d’autorité centrale qui les coordonne.

Un pays, ou dans certains cas même une ville, peut décider si et comment l'appliquer ou la révoquer.

Chaque fois que cela se produit, le changement est enregistré dans leIANA Time Zone Database, et la mise à jour sera déployée dans un futurrelease of the JRE.

In case it’s not possible to wait, we can force the modified Time Zone data containing the new DST settings into the JRE via un outil Oracle officiel appeléJava Time Zone Updater Tool, disponible sur lesJava SE download page.

3. La mauvaise façon: ID de fuseau horaire à trois lettres

De retour dans les jours JDK 1.1, l’API permettait des identifiants de fuseau horaire de trois lettres, mais cela posait plusieurs problèmes.

Premièrement, cela était dû au fait que la même identification à trois lettres pouvait faire référence à plusieurs fuseaux horaires. Par exemple,CST pourrait être U.S. «Heure normale du Centre», mais aussi «heure normale de la Chine». La plate-forme Java ne pourrait alors reconnaître que l'un d'entre eux.

Un autre problème est que les fuseaux horaires standard ne prennent jamais l'heure d'été dans un compte. Plusieurs zones / régions / villes peuvent avoir leur heure d'été locale dans le même fuseau horaire standard, de sorte que l'heure standard ne l'observe pas.

En raison de la compatibilité descendante,it’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. La bonne façon: ID de fuseau horaire TZDB

La bonne façon de gérer DST en Java est d'instancier unTimezone avec un ID de fuseau horaire TZDB spécifique, par exemple. “Europe/Rome”.

Ensuite, nous l'utiliserons en conjonction avec des classes spécifiques au temps telles quejava.util.Calendar pour obtenir une configuration correcte du décalage brut deTimeZone’s (vers le fuseau horaire GMT) et des ajustements automatiques de décalage d'heure d'été.

Voyons comment le passage deGMT+1 àGMT+2 (qui se produit en Italie le 25 mars 2018 à 02h00) est automatiquement géré lorsque vous utilisez les bonsTimeZone:

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);

Comme nous pouvons le voir,ZONE_OFFSET est de 60 minutes (car l'Italie estGMT+1) tandis queDST_OFFSET est 0 à ce moment-là.

Ajoutons dix minutes auxCalendar:

cal.add(Calendar.MINUTE, 10);

Maintenant,DST_OFFSET est devenu 60 minutes aussi, et le pays a fait la transition de son heure locale deCET (heure d'Europe centrale) àCEST (heure d'été d'Europe centrale) qui estGMT+2 :

Date dateAfterDST = cal.getTime();

assertThat(cal.get(Calendar.DST_OFFSET))
  .isEqualTo(3600000);
assertThat(dateAfterDST)
  .isEqualTo(df.parse("2018-03-25 03:05"));

Si nous affichons les deux dates dans la console, nous verrons également le fuseau horaire changer:

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

Comme test final, nous pouvons mesurer la distance entre les deuxDates, 1:55 et 3:05:

Long deltaBetweenDatesInMillis = dateAfterDST.getTime() - dateBeforeDST.getTime();
Long tenMinutesInMillis = (1000L * 60 * 10);

assertThat(deltaBetweenDatesInMillis)
  .isEqualTo(tenMinutesInMillis);

Comme on pouvait s'y attendre, la distance est de 10 minutes au lieu de 70.

Nous avons vu comment éviter de tomber dans les pièges courants que nous pouvons rencontrer lorsque nous travaillons avecDate en utilisant correctementTimeZone etLocale.

5. La meilleure façon: API de date / heure Java 8

Travailler avec ces classesjava.util peu sûres et pas toujours conviviales a toujours été difficile, en particulier en raison de problèmes de compatibilité qui les empêchaient d'être correctement refactorisées.

Pour cette raison, Java 8 a introduit un tout nouveau package,java.time, et un tout nouvel ensemble d'API, lesDate/Time API. C'est ISO-centric, entièrement thread-safe et fortement inspiré de la célèbre bibliothèque Joda- Temps.

Examinons de plus près ces nouvelles classes, à partir du successeur dejava.util.Date,java.time.LocalDateTime:

LocalDateTime localDateTimeBeforeDST = LocalDateTime
  .of(2018, 3, 25, 1, 55);

assertThat(localDateTimeBeforeDST.toString())
  .isEqualTo("2018-03-25T01:55");

Nous pouvons observer comment unLocalDateTime est conforme au profilISO8601, une notation date-heure standard et largement adoptée.

Cependant, il ignore complètementZones etOffsets, c'est pourquoi nous devons le convertir ena fully DST-aware java.time.ZonedDateTime:

ZoneId italianZoneId = ZoneId.of("Europe/Rome");
ZonedDateTime zonedDateTimeBeforeDST = localDateTimeBeforeDST
  .atZone(italianZoneId);

assertThat(zonedDateTimeBeforeDST.toString())
  .isEqualTo("2018-03-25T01:55+01:00[Europe/Rome]");

Comme nous pouvons le voir, maintenant la date incorpore deux informations de fin fondamentales:+01:00 est leZoneOffset, tandis que[Europe/Rome] est leZoneId.

Comme dans l'exemple précédent, déclenchons l'heure d'été en ajoutant dix minutes:

ZonedDateTime zonedDateTimeAfterDST = zonedDateTimeBeforeDST
  .plus(10, ChronoUnit.MINUTES);

assertThat(zonedDateTimeAfterDST.toString())
  .isEqualTo("2018-03-25T03:05+02:00[Europe/Rome]");

Encore une fois, nous voyons comment le décalage de l'heure et de la zone se décalent vers l'avant tout en maintenant la même distance:

Long deltaBetweenDatesInMinutes = ChronoUnit.MINUTES
  .between(zonedDateTimeBeforeDST,zonedDateTimeAfterDST);
assertThat(deltaBetweenDatesInMinutes)
  .isEqualTo(10);

6. Conclusion

Nous avons vu ce qu'est l'heure d'été et comment la gérer grâce à des exemples pratiques dans différentes versions de l'API Java Core.

Lorsque vous travaillez avec Java 8 et supérieur, l'utilisation du nouveau packagejava.time est encouragée grâce à sa facilité d'utilisation et à sa nature standard, thread-safe.

Comme toujours, le code source complet est disponibleover on Github.