Java : le fuseau horaire CET prend-il en compte l'heure d'été ?

Réponse courte : cela dépend de comment vous utilisez CET. Le comportement par défaut de Java avec le code CET peut surprendre, et la solution propre est d'éviter complètement cette abréviation au profit d'un identifiant IANA comme Europe/Paris.

Le piège des abréviations de fuseau

"CET" signifie Central European Time, soit UTC+1. Mais la majorité des pays qui suivent CET passent à CEST (Central European Summer Time, UTC+2) pendant l'été. L'abréviation "CET" est donc ambiguë : elle peut désigner :

  • un fuseau qui observe l'heure d'été (comportement réel de l'Europe centrale),
  • un décalage fixe UTC+1 sans heure d'été.

Ce que fait Java

Testons avec java.time, l'API moderne (Java 8+) :

import java.time.*;

public class CetDemo {
    public static void main(String[] args) {
        ZoneId cet = ZoneId.of("CET");
        System.out.println("ZoneId       : " + cet);
        System.out.println("Hiver (1 jan): " + ZonedDateTime.of(2025, 1, 15, 12, 0, 0, 0, cet));
        System.out.println("Été  (15 juil): " + ZonedDateTime.of(2025, 7, 15, 12, 0, 0, 0, cet));
    }
}

Résultat :

ZoneId       : CET
Hiver (1 jan): 2025-01-15T12:00+01:00[CET]
Été  (15 juil): 2025-07-15T12:00+02:00[CET]

Le décalage change entre +01:00 en hiver et +02:00 en été : ZoneId.of("CET") respecte donc bien l'heure d'été. En pratique, Java lie CET à l'identifiant IANA CET qui, dans la base tzdata, inclut les règles DST européennes.

Mais avec TimeZone, c'est différent

Avec l'ancienne API java.util.TimeZone, le comportement peut diverger si votre locale ou votre JVM interprète CET comme un simple décalage :

import java.util.TimeZone;

TimeZone tz = TimeZone.getTimeZone("CET");
System.out.println(tz.useDaylightTime()); // true — mais peut être false selon la JVM
System.out.println(tz.inDaylightTime(new java.util.Date()));

Si vous obtenez false alors que vous êtes en été, la JVM a interprété CET comme un décalage fixe. C'est précisément le piège.

La solution : utiliser des identifiants IANA

Les identifiants comme Europe/Paris, Europe/Berlin, Europe/Madrid sont sans ambiguïté. Chacun pointe vers une règle précise qui inclut les transitions historiques d'heure d'été et les changements législatifs (Turquie, Russie, etc.).

ZoneId paris = ZoneId.of("Europe/Paris");

ZonedDateTime hiver = ZonedDateTime.of(2025, 1, 15, 12, 0, 0, 0, paris);
ZonedDateTime ete   = ZonedDateTime.of(2025, 7, 15, 12, 0, 0, 0, paris);

System.out.println(hiver); // 2025-01-15T12:00+01:00[Europe/Paris]
System.out.println(ete);   // 2025-07-15T12:00+02:00[Europe/Paris]

Et si je veux un UTC+1 fixe, sans DST ?

Dans ce cas, utilisez un ZoneOffset explicite, qui ne change jamais :

ZoneOffset fixe = ZoneOffset.ofHours(1);
ZonedDateTime t = ZonedDateTime.of(2025, 7, 15, 12, 0, 0, 0, fixe);
System.out.println(t); // 2025-07-15T12:00+01:00 — pas de DST

Gérer la transition DST

La bascule hiver → été saute une heure (2h → 3h du matin), et la bascule été → hiver répète une heure (3h → 2h). ZonedDateTime gère ces cas proprement :

ZoneId paris = ZoneId.of("Europe/Paris");

// 30 mars 2025 : transition printanière à 2h → 3h
ZonedDateTime gap = ZonedDateTime.of(2025, 3, 30, 2, 30, 0, 0, paris);
System.out.println(gap); // 2025-03-30T03:30+02:00 — Java saute à 3h30

// 26 octobre 2025 : transition automnale à 3h → 2h (heure ambiguë)
ZonedDateTime overlap = ZonedDateTime.of(2025, 10, 26, 2, 30, 0, 0, paris);
System.out.println(overlap); // Choix du premier décalage par défaut

Bonnes pratiques

  • Stockez toujours en UTC dans votre base de données ; convertissez dans le fuseau de l'utilisateur à l'affichage.
  • Préférez java.time (ZonedDateTime, Instant, ZoneId) à l'ancien java.util.Date.
  • N'utilisez jamais les abréviations (CET, PST, EST) dans du code de production — toujours des identifiants IANA.
  • Tenez votre tzdata à jour : les règles évoluent (abolition du changement d'heure dans l'UE envisagée, modifications en Turquie, etc.).

En résumé : ZoneId.of("CET") en Java moderne observe bien l'heure d'été, mais préférez systématiquement ZoneId.of("Europe/Paris") pour éviter toute ambiguïté entre développeurs et entre JVM.