W3docs

Classe Date héritée de Java

La classe java.util.Date héritée — pourquoi java.time la remplace et comment convertir entre les deux.

java.util.Date était la classe date-heure originale de Java, présente depuis Java 1.0 en 1995. Elle est toujours dans le JDK ; le nouveau code ne devrait pas l'utiliser, mais vous la rencontrerez dans des bibliothèques, des bases de données (java.sql.Date l'étend) et dans tout code antérieur à environ 2014. Ce chapitre existe pour faire le pont avec java.time.

En résumé : Date est en interne un wrapper autour d'un long représentant des millisecondes depuis l'époque, tout comme Instant est en interne une paire (seconds, nanos). La conversion naturelle est donc Date ↔ Instant. Pour toute autre opération (année, mois, jour, arithmétique calendaire), convertissez d'abord en java.time.

Ce qu'est réellement Date

public class Date implements Cloneable, Comparable<Date>, Serializable {
  private long fastTime;        // milliseconds since 1970-01-01T00:00:00Z
}

C'est tout l'état réel. Un Date est un point dans le temps, mesuré en millisecondes depuis l'époque Unix, en UTC. Malgré son nom, il ne contient pas de date calendaire — getYear() et ses semblables calculent à partir de la valeur en millisecondes dans le fuseau horaire par défaut de la JVM, ce qui est à l'origine des problèmes notoires de cette API.

Date now = new Date();                                       // current moment
Date epoch = new Date(0);                                    // 1970-01-01T00:00:00Z
Date fromMs = new Date(1_700_000_000_000L);

long ms = now.getTime();                                     // milliseconds since epoch

new Date() et Date.getTime() sont les deux méthodes qui ont bien vieilli. Tout le reste a soit été déprécié, soit comporte un piège.

Les accesseurs calendaires dépréciés

Ces méthodes ont été dépréciées dans Java 1.1 (1997) lorsque Calendar a été ajouté :

date.getYear();                                              // year - 1900   (deprecated)
date.getMonth();                                             // 0-11          (deprecated)
date.getDate();                                              // 1-31, day of month (deprecated)
date.getDay();                                               // 0-6, day of week (deprecated)
date.getHours(); date.getMinutes(); date.getSeconds();       // local-zone reads (deprecated)

Ces dépréciations existent depuis 28 ans. Elles fonctionnent encore. Les pièges :

  • getYear() renvoie year - 1900. Pour 2025, il renvoie 125. C'est sans conteste un candidat à la décision d'API la plus étrange du JDK.
  • getMonth() renvoie de 0 à 11. Janvier vaut 0, décembre vaut 11. Les bugs de décalage de un sont garantis si vous écrivez getMonth() + 1 et l'oubliez une fois.
  • Chaque accesseur lit dans le fuseau horaire par défaut de la JVM. Le même Date sur deux machines dans des fuseaux différents donne des résultats getDate() différents.

N'appelez pas ces méthodes. Dès que vous vous surprenez à vouloir utiliser date.getYear(), convertissez en Instant/ZonedDateTime et utilisez les accesseurs modernes.

Le pont Date ↔ Instant

Date legacy = new Date();
Instant inst = legacy.toInstant();                            // since Java 8

Instant other = Instant.parse("2025-11-04T19:30:00Z");
Date back = Date.from(other);                                 // since Java 8

toInstant() et Date.from(...) sont les méthodes de conversion modernes, ajoutées avec java.time. Ce sont les deux seuls appels à java.util.Date que vous devriez écrire dans du nouveau code.

La conversion est avec perte dans un sens : Date est en précision milliseconde, Instant en précision nanoseconde. Un aller-retour Instant → Date → Instant tronque les nanosecondes sous la milliseconde :

Instant high = Instant.parse("2025-11-04T19:30:00.123456789Z");
Instant low = Date.from(high).toInstant();
// low = 2025-11-04T19:30:00.123Z — the 456789 nanos are gone

Pour les horodatages serveur, c'est acceptable ; pour la capture d'événements à haute résolution, restez dans Instant de bout en bout.

java.sql.Date et java.sql.Timestamp

Les types JDBC sont des sous-classes de java.util.Date :

  • java.sql.Date — une date sans heure (le composant heure est forcé à 00:00:00 dans un certain fuseau). Nom trompeur ; c'est toujours un wrapper en millisecondes depuis l'époque en dessous.
  • java.sql.Time — une heure sans date.
  • java.sql.Timestamp — comme Date mais avec une précision nanoseconde.

Ils ont tous des méthodes de conversion JDK 8 :

java.sql.Date    sqlDate  = java.sql.Date.valueOf(LocalDate.of(2025, 11, 4));
LocalDate localDate = sqlDate.toLocalDate();

java.sql.Timestamp ts = java.sql.Timestamp.from(Instant.now());
Instant inst = ts.toInstant();

Les pilotes JDBC modernes acceptent également les types java.time directement via setObject/getObject — pour le nouveau code, ignorez les types java.sql.* et utilisez LocalDate/Instant. Les conversions sont là pour le code qui doit interopérer avec un pilote ou un framework qui n'a pas encore migré.

Comparaison et tri

Date implémente Comparable<Date>. L'ordre est par millisecondes depuis l'époque — identique à Instant. Ainsi, trier une List<Date> fonctionne de la même manière que trier une List<Instant>.

equals compare le long sous-jacent. Deux valeurs Date sont égales si et seulement si elles ont la même valeur en millisecondes. Le hachage fonctionne (c'est (int)(time ^ (time >>> 32))), donc Date convient comme clé de HashMap — bien qu'encore une fois, Instant soit le choix moderne.

Mutabilité

Le piège caché le plus important : Date est mutable.

Date d = new Date();
d.setTime(0);                                                // mutates d in place

Cela signifie que toute méthode qui accepte ou retourne un Date est dangereuse — l'appelant peut modifier la valeur après l'avoir passée ; l'appelé peut modifier la valeur que l'appelant détient. Le code de bibliothèque copie défensivement (new Date(d.getTime())) chaque Date qu'il reçoit. C'est le genre de bookkeeping que java.time a totalement éliminé en rendant chaque type immuable.

Dans le code hérité, traitez tout champ Date comme s'il pouvait changer sous vos pieds. Convertissez en Instant à la frontière de l'API si vous avez besoin d'un instantané stable.

SimpleDateFormat : l'autre chose dépréciée

Associé à Date dans l'ancien code, on trouve java.text.SimpleDateFormat :

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String s = sdf.format(new Date());
Date d = sdf.parse("2025-11-04");

SimpleDateFormat n'est pas thread-safe. Le partager entre threads produira des sorties incorrectes, des exceptions, ou les deux, de manière intermittente. La solution de contournement standard dans le code hérité est ThreadLocal<SimpleDateFormat> ; le remplacement moderne est DateTimeFormatter, qui est thread-safe et peut être mis en cache.

Si vous maintenez du code avec un static SimpleDateFormat, traitez-le comme un bug connu, que quelqu'un ait signalé une défaillance ou non.

Exemple pratique : interopérabilité avec le code hérité

Le programme ci-dessous utilise Date comme vous le trouveriez dans une API héritée et montre le chemin de conversion vers java.time pour chaque opération. Lisez-le comme « voici la recette de migration » : chaque appel hérité a un équivalent en une ligne avec Instant ou ZonedDateTime.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • Date.toString() s'affiche dans le fuseau horaire par défaut de la JVM. La même valeur Date s'affiche différemment sur un serveur UTC et sur un laptop America/New_York. C'est le défaut de conception central qui a motivé la refonte avec java.time — la valeur est en UTC, l'affichage est local, et l'API ne vous donne aucun moyen simple de savoir quelle vue vous regardez. Si vous tenez à préciser le fuseau, convertissez en ZonedDateTime et fournissez le fuseau explicitement.
  • now.getYear() a renvoyé 125 pour 2025. La convention year - 1900 était une erreur de Java 1.0 jamais corrigée ; la méthode a été dépréciée en 1.1 et est toujours là. Tout appel à getYear() sur un Date renvoyant « 125 » ou « 85 » est un bug qui attend de s'afficher à un utilisateur.
  • Instant.parse("...nanosecondes") a affiché neuf chiffres de précision ; la même valeur aller-retour via Date.from et retour a perdu les six derniers. Pour les logs serveur (la précision milliseconde suffit), la troncature n'a pas d'importance. Pour « j'ai capturé cet événement avec un timing haute précision », ne faites pas d'aller-retour via Date.
  • Le +1 jour en héritage était now.getTime() + 24L * 60 * 60 * 1000 — arithmétique manuelle, facile à mal taper (oublier le L et causer un dépassement). Le moderne inst.plus(Duration.ofDays(1)) est sûr du point de vue des types et se lit à voix haute. Lors d'une migration, remplacer chaque calcul time + N * ms par Duration est le fruit le plus facile à cueillir.
  • La démonstration de mutabilité à la fin a montré que shared.setTime(0) changeait la valeur vue à travers les deux références. Dans une base de code multi-thread, c'est une condition de course ; en mono-thread, c'est quand même un rituel de copie défensive que le JDK imposait à chaque bibliothèque. L'API moderne ne demande jamais ce rituel.

Prochaine étape

java.util.Date est la moitié de l'API héritée. L'autre moitié est java.util.Calendar — la classe ajoutée dans Java 1.1 pour donner à Date les accesseurs calendaires que Date lui-même n'aurait pas dû avoir. Le prochain chapitre, Java Calendar Class, est le dernier de cette partie et le couvre avec la même forme de recette de migration : chaque appel hérité a un équivalent dans java.time.

Exercices pratiques

Pratique
Une ancienne bibliothèque renvoie un `java.util.Date` depuis `getCreatedAt()`. Vous voulez savoir si c'est dans le même jour calendaire qu'aujourd'hui à New York. Quelle est la bonne approche ?
Une ancienne bibliothèque renvoie un `java.util.Date` depuis `getCreatedAt()`. Vous voulez savoir si c'est dans le même jour calendaire qu'aujourd'hui à New York. Quelle est la bonne approche ?
Was this page helpful?