W3docs

Introduction à l'API Java Date et Heure

Introduction à l'API moderne Java date et heure dans java.time, remplaçant les anciennes classes Date et Calendar.

Java 8 a introduit java.time, un nouveau package pour représenter les dates, les heures, les durées, les fuseaux horaires et les opérations arithmétiques entre eux. Il a remplacé deux API précédentes — java.util.Date et java.util.Calendar — qui avaient la réputation méritée d'être le coin le plus mal conçu du JDK. La nouvelle API a été inspirée par la bibliothèque Joda-Time de Stephen Colebourne ; si vous avez utilisé Joda, java.time vous semblera familier.

Les deux points importants concernant la refonte :

  1. Chaque type est immuable. Un LocalDate une fois créé ne change jamais. Les méthodes comme plusDays(7) retournent un nouveau LocalDate. Cela rend l'API thread-safe par construction et élimine toute une catégorie de bugs.
  2. Chaque type signifie une seule chose. LocalDate est une date sans heure. Instant est un moment sur la ligne du temps. Duration est une longueur de temps. L'ancien Date était d'une certaine façon tout cela à la fois, selon le constructeur utilisé ; la nouvelle API les sépare afin que le type vous indique quel genre de valeur vous avez.

Ce chapitre est la carte. Les dix chapitres suivants approfondissent chaque classe.

Les types fondamentaux

"A date"           LocalDate            2025-11-04
"A time of day"    LocalTime            14:30:00
"Both, no zone"    LocalDateTime        2025-11-04T14:30:00
"Both, with zone"  ZonedDateTime        2025-11-04T14:30:00-05:00 [America/New_York]
"A moment"         Instant              2025-11-04T19:30:00Z      (UTC, seconds-since-epoch)
"A length of time" Duration             PT1H30M                   (1 hour 30 minutes)
"A length of date" Period               P1Y2M3D                   (1 year 2 months 3 days)

La distinction horizontale — Local* vs Zoned/Instant — est la plus importante. Les types Local ne portent aucun fuseau horaire. Un LocalDate de 2025-11-04 correspond au « quatre novembre » ; il ne précise pas si c'est le quatre à Tokyo ou à Honolulu. C'est le bon type pour un anniversaire, une date de contrat ou un sélecteur de date dans une interface utilisateur.

Les types Zoned portent leur fuseau. ZonedDateTime représente « cet instant calendaire à cet endroit », ce dont vous avez besoin pour « réunion prévue à 9 h à New York. » Instant est un moment sur la ligne du temps mondiale — secondes UTC depuis l'époque — ce dont vous avez besoin pour la journalisation, les horodatages de messages, tout ce qui doit être ordonné globalement sans nécessiter d'étiquettes locales.

La distinction horizontale entre Duration et Period compte aussi. Duration est une longueur de temps comparable en secondes — PT24H correspond exactement à 24 × 3600 secondes. Period est une longueur exprimée en termes calendaires — P1M (un mois) représente 30 jours certains mois et 31 dans d'autres. Pour les mesures de temps, utilisez Duration. Pour « ajouter un mois à une date de facturation », utilisez Period.

La forme fluide

Chaque type est construit et modifié via une API fluide cohérente :

LocalDate today    = LocalDate.now();
LocalDate stardate = LocalDate.of(2025, 11, 4);
LocalDate parsed   = LocalDate.parse("2025-11-04");

LocalDate nextWeek = today.plusDays(7);                     // immutable: returns a NEW LocalDate
LocalDate lastYear = today.minusYears(1);
LocalDate firstOfMonth = today.withDayOfMonth(1);            // with* returns a copy with one field changed

boolean before = today.isBefore(stardate);
int year = today.getYear();

Trois formes que vous verrez partout :

  • now() — valeur courante depuis l'horloge système.
  • of(...) — composants explicites.
  • parse(...) — à partir d'une chaîne (ISO-8601 par défaut).

Et pour les transformations :

  • plusX(n) / minusX(n) — arithmétique.
  • withX(value) — remplacer un seul champ.
  • isBefore(other) / isAfter(other) — comparaison.

Cette forme se répète dans LocalDate, LocalTime, LocalDateTime, ZonedDateTime et Instant. Une fois que vous connaissez le schéma, chaque classe vous parle dans le même dialecte avec un vocabulaire légèrement différent.

Les fuseaux horaires sont complexes, et l'API le reconnaît

La principale raison pour laquelle java.util.Date était pénible est qu'il essayait de rendre les fuseaux horaires invisibles. Le résultat était la fameuse classe de bugs « stocker une Date, la récupérer sur un serveur dans un fuseau horaire différent, obtenir une mauvaise date calendaire. » java.time résout ce problème en rendant le fuseau explicite dans le type.

Si vous acceptez une date d'un utilisateur et ne savez pas dans quel fuseau il se trouve, stockez-la comme un LocalDate. S'il vous dit que c'est « 9 h de son côté » et que vous connaissez son fuseau, stockez-la comme un ZonedDateTime avec le fuseau. Si vous enregistrez un événement serveur, stockez-le comme un Instant. Ne stockez pas un LocalDateTime en espérant que le fuseau horaire passera implicitement ; le fuseau manquant est tout le problème.

Instant now = Instant.now();                                 // unambiguous: a moment in UTC
ZonedDateTime localized = now.atZone(ZoneId.of("Europe/Berlin"));  // a label for that moment in Berlin

La hiérarchie des fuseaux :

  • ZoneOffset est un décalage fixe ±HH:MM depuis UTC : +05:30, -08:00. Pas de gestion de l'heure d'été.
  • ZoneId est un fuseau nommé : Europe/Berlin, America/New_York. Porte l'enregistrement de la base IANA du décalage que ce fuseau a à n'importe quelle date donnée, y compris les transitions d'heure d'été et les changements historiques.

Préférez toujours ZoneId à ZoneOffset quand vous avez le choix. « America/New_York » est correct à travers l'heure d'été ; « −05:00 » n'est correct qu'en dehors de l'heure d'été.

Les types hérités ne sont pas disparus

java.util.Date, java.util.Calendar et java.text.SimpleDateFormat existent toujours. Le nouveau code ne devrait pas les utiliser — mais beaucoup d'ancien code le fait, et vous devrez interopérer. Les méthodes de conversion sont directes :

// java.util.Date <-> java.time.Instant
Instant inst = legacyDate.toInstant();
Date    back = Date.from(inst);

// java.util.Calendar -> java.time.ZonedDateTime
ZonedDateTime zdt = ZonedDateTime.ofInstant(
    cal.toInstant(), cal.getTimeZone().toZoneId());

Le schéma est unidirectionnel : l'héritage → java.time est simple ; pour tout ce qui est nouveau, restez dans java.time et convertissez uniquement à la frontière de l'API où vit l'ancien code. Les chapitres Legacy Date et Calendar à la fin de cette partie couvrent le pont en détail.

Un exemple complet : la famille de types dans un programme

Le programme ci-dessous utilise chaque type introduit dans la carte — LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant, Duration, Period — et montre comment ils se convertissent entre eux. C'est la version « tour d'ensemble » ; chaque type individuel aura son propre chapitre à partir d'ici.

java— editable, runs on the server

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

  • Le premier bloc a construit la famille par composition : LocalDate + LocalTime = LocalDateTime ; LocalDateTime + ZoneId = ZonedDateTime ; ZonedDateTimeInstant. C'est le réseau de conversions, et vous les effectuerez chaque fois que vous traverserez une frontière d'API. Les flèches vont dans les deux sens pour la plupart des paires — Instant.atZone(zone) et ZonedDateTime.toLocalDateTime() ferment les boucles.
  • Un même Instant affichait trois heures d'apparence différente vues depuis New York, Berlin et Tokyo. C'est là l'intérêt d'Instant : c'est le moment, indépendamment d'où vous vous trouvez. Le ZonedDateTime ajoute l'étiquette « où je me trouve ». Confondre les deux est l'erreur du Date hérité.
  • Duration s'est affiché sous la forme PT1H30M et Period sous la forme P3M. Le format de durée ISO-8601 est PnYnMnDTnHnMnS — tout ce qui précède le T correspond aux unités calendaires (Period), tout ce qui suit sont des unités de temps (Duration). La chaîne est exactement ce que toString() retourne, et exactement ce que parse(...) accepte.
  • today.plusDays(7) a produit un LocalDate différent. Afficher today à nouveau juste après a montré que l'original était inchangé — c'est la garantie d'immuabilité. Chaque plus/minus/with retourne un nouvel objet ; le récepteur n'est jamais modifié. Aucune copie défensive, aucun souci de thread-safety, jamais.
  • ChronoUnit.DAYS.between(today, launch) était l'opération de « distance ». Elle retourne un long, pas un Period, parce que la réponse en jours n'a aucune ambiguïté calendaire (contrairement aux mois, qui varient en longueur). Chaque chapitre de cette partie utilise ChronoUnit quelque part — c'est le catalogue des unités de temps dont parle l'API.

Et ensuite

Le chapitre suivant, Java LocalDate, commence la visite en profondeur. LocalDate est le plus simple des cinq types « point dans le temps » et le bon endroit pour apprendre la forme fluide que tous les autres partagent.

Pratique

Pratique
Vous devez stocker le moment où un serveur a reçu une requête HTTP, afin que le journal puisse être trié globalement sur des serveurs dans différents fuseaux horaires. Quel type `java.time` convient ?
Vous devez stocker le moment où un serveur a reçu une requête HTTP, afin que le journal puisse être trié globalement sur des serveurs dans différents fuseaux horaires. Quel type `java.time` convient ?
Was this page helpful?