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 :
- Chaque type est immuable. Un
LocalDateune fois créé ne change jamais. Les méthodes commeplusDays(7)retournent un nouveauLocalDate. Cela rend l'API thread-safe par construction et élimine toute une catégorie de bugs. - Chaque type signifie une seule chose.
LocalDateest une date sans heure.Instantest un moment sur la ligne du temps.Durationest une longueur de temps. L'ancienDateé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 BerlinLa hiérarchie des fuseaux :
ZoneOffsetest un décalage fixe±HH:MMdepuis UTC :+05:30,-08:00. Pas de gestion de l'heure d'été.ZoneIdest 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.
Ce qu'il faut retenir de l'exécution :
- Le premier bloc a construit la famille par composition :
LocalDate+LocalTime=LocalDateTime;LocalDateTime+ZoneId=ZonedDateTime;ZonedDateTime→Instant. 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)etZonedDateTime.toLocalDateTime()ferment les boucles. - Un même
Instantaffichait 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. LeZonedDateTimeajoute l'étiquette « où je me trouve ». Confondre les deux est l'erreur duDatehérité. Durations'est affiché sous la formePT1H30MetPeriodsous la formeP3M. Le format de durée ISO-8601 estPnYnMnDTnHnMnS— tout ce qui précède leTcorrespond aux unités calendaires (Period), tout ce qui suit sont des unités de temps (Duration). La chaîne est exactement ce quetoString()retourne, et exactement ce queparse(...)accepte.today.plusDays(7)a produit unLocalDatedifférent. Affichertodayà nouveau juste après a montré que l'original était inchangé — c'est la garantie d'immuabilité. Chaqueplus/minus/withretourne 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 unlong, pas unPeriod, 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 utiliseChronoUnitquelque 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.