W3docs

Java LocalDate

Représentez des dates sans heure ni fuseau horaire en Java avec LocalDate — création, manipulation et interrogations.

LocalDate est une date calendaire — année, mois, jour — sans heure du jour et sans fuseau horaire. Elle représente la même date sur toutes les horloges partout dans le monde : quand vous écrivez LocalDate.of(2025, 11, 4), c'est le quatre novembre dans le calendrier ISO, point final. Pas de 14:30 attaché, pas de décalage UTC, pas d'ambiguïté Tokyo-vs-Honolulu.

Cela en fait le bon type pour de nombreuses choses que le legacy java.util.Date gérait mal : anniversaires, dates de contrat, dates de facture, date sélectionnée par un sélecteur de date dans une interface utilisateur. Partout où un jour calendaire est l'unité, LocalDate est la classe.

Création

Les trois fabriques standard :

LocalDate today  = LocalDate.now();                          // system default zone
LocalDate stardate = LocalDate.of(2025, 11, 4);              // year, month (1-12), day (1-31)
LocalDate parsed = LocalDate.parse("2025-11-04");            // ISO-8601 yyyy-MM-dd

now() lit la date courante dans le fuseau horaire par défaut de la JVM. C'est presque toujours ce que vous voulez ; pour les tests c'est un problème, et les formes surchargées avec Clock (LocalDate.now(clock)) vous permettent d'injecter une horloge fixe. Le chapitre sur le parsing couvre parse avec des formats personnalisés ; la valeur par défaut n'accepte que les dates ISO-8601.

Vous pouvez également utiliser l'enum Month au lieu d'un entier 1..12 :

LocalDate.of(2025, Month.NOVEMBER, 4);                       // type-safe; no risk of using 0 for January

Si vous avez déjà écrit new GregorianCalendar(2025, 11, 4) et obtenu décembre (parce que l'API legacy utilise des mois à base zéro), la forme enum est l'amélioration que vous recherchez.

Inspection

Le catalogue des accesseurs :

int year      = date.getYear();
Month month   = date.getMonth();                              // enum
int monthVal  = date.getMonthValue();                         // 1-12
int day       = date.getDayOfMonth();
DayOfWeek dow = date.getDayOfWeek();                          // enum: MONDAY, TUESDAY, ...
int dayOfYear = date.getDayOfYear();                          // 1-366
boolean leap  = date.isLeapYear();
int monthLen  = date.lengthOfMonth();                         // 28-31
int yearLen   = date.lengthOfYear();                          // 365 or 366

Month et DayOfWeek sont des enums. Utilisez-les ; ils rendent le code qui compare à un jour ou un mois spécifique beaucoup plus lisible :

if (date.getDayOfWeek() == DayOfWeek.MONDAY) ...              // type-safe
if (date.getMonth() == Month.NOVEMBER) ...                    // no off-by-one risk

Chaque enum possède ses propres méthodes helper — Month.length(boolean leap), DayOfWeek.getValue() retournant 1-7 avec Lundi = 1, et DayOfWeek.plus(7) pour « le même jour, n jours à partir de maintenant. »

Modification — chaque méthode retourne une nouvelle instance

Les méthodes arithmétiques :

date.plusDays(7);                                              // a week later
date.plusWeeks(2);
date.plusMonths(1);                                            // careful: month length varies
date.plusYears(1);

date.minusDays(30);
date.minusYears(5);

Et les formes « remplacer un champ » :

date.withYear(2026);
date.withMonth(1);
date.withDayOfMonth(1);
date.withDayOfYear(1);                                         // first day of the year

Chacune de ces méthodes retourne un nouveau LocalDate. L'original reste inchangé. date.plusDays(7) sans capturer le résultat est une no-op — et un bug que nous avons tous écrit au moins une fois.

La mise en garde « la longueur du mois varie » pour plusMonths : quand l'ajout d'un mois atterrirait sur un jour qui n'existe pas dans le mois cible, java.time clamp au dernier jour. LocalDate.of(2025, 1, 31).plusMonths(1) donne 2025-02-28 (ou 02-29 une année bissextile), pas 2025-03-03. Le comportement est documenté et cohérent, mais cela signifie que plusMonths(1) et minusMonths(1) ne sont pas toujours des inverses l'un de l'autre.

Comparaison

date.isBefore(other);
date.isAfter(other);
date.isEqual(other);                                           // same as equals here; useful on ZonedDateTime
date.compareTo(other);                                         // -1 / 0 / +1

LocalDate implémente Comparable<LocalDate>, donc elle se trie naturellement dans n'importe quelle collection. Pour « est-ce que cette date est dans [début, fin] ? » la forme typique est !date.isBefore(start) && !date.isAfter(end).

Distance : until et ChronoUnit.between

Combien de jours entre deux dates ?

long days = ChronoUnit.DAYS.between(start, end);               // a long; signed
long weeks = ChronoUnit.WEEKS.between(start, end);
long months = ChronoUnit.MONTHS.between(start, end);

Period diff = start.until(end);                                // a Period (years/months/days)

ChronoUnit.X.between est le bon appel pour « combien de X entiers y a-t-il entre ces dates ? » until retourne un Period, qui est la décomposition en forme calendaire — utile pour « vous êtes membre depuis 2 ans, 3 mois, 14 jours. »

Notez la convention de signe : between(start, end) est positif quand end est après start, négatif sinon.

Le raccourci « quel jour de la semaine est... »

Le package des ajusteurs temporels vous donne les prédicats que vous calculeriez autrement à la main :

import static java.time.temporal.TemporalAdjusters.*;

date.with(firstDayOfMonth());
date.with(lastDayOfMonth());
date.with(firstDayOfNextMonth());
date.with(next(DayOfWeek.MONDAY));                             // next Monday strictly after `date`
date.with(nextOrSame(DayOfWeek.MONDAY));                       // today if today is Monday, else next
date.with(previousOrSame(DayOfWeek.SUNDAY));
date.with(lastInMonth(DayOfWeek.FRIDAY));                      // last Friday of the month

Le chapitre Temporal Adjusters couvre ces éléments en profondeur. Pour l'instant, à retenir : n'écrivez pas « le prochain lundi après cette date » à la main ; les ajusteurs l'ont déjà.

Mise en garde sur les fuseaux horaires

LocalDate n'a pas de fuseau horaire, donc LocalDate.now() doit en choisir un pour savoir quel jour calendaire est « maintenant ». Le défaut est le fuseau horaire par défaut de la JVM (ZoneId.systemDefault()). Si vous tournez sur un serveur configuré en UTC à 23:30 heure locale à New York, LocalDate.now() retourne la date de demain du point de vue de New York — parce que le fuseau horaire de la JVM dit qu'il est déjà passé minuit UTC.

Pour une date locale à un fuseau connu, passez le fuseau explicitement :

LocalDate tokyoToday = LocalDate.now(ZoneId.of("Asia/Tokyo"));

Cela mord en production exactement quand le laptop du développeur est dans un fuseau différent du serveur déployé. Soyez explicite quand le fuseau horaire compte.

Un exemple concret : arithmétique sur les dates de facture

Le programme ci-dessous utilise LocalDate pour le genre de travail qu'un petit système de facturation ferait — générer une date de facture, calculer des dates d'échéance, compter les jours en retard, déterminer la fin de mois, et trouver le prochain jour ouvrable. C'est la forme réaliste du code LocalDate.

java— editable, runs on the server

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

  • LocalDate.of(2025, Month.NOVEMBER, 4) était la forme sûre. La surcharge entière (2025, 11, 4) fonctionne, mais l'enum Month rend impossible l'utilisation de 0 pour janvier — l'erreur historique de GregorianCalendar. Quand le deuxième argument peut être l'un ou l'autre, utilisez l'enum.
  • plusDays(30) a retourné un nouveau LocalDate ; l'affichage de l'original à la fin du programme l'a montré intact. Chaque méthode arithmétique et with* suit cette règle, ce qui rend le type thread-safe par construction. Pas de copie défensive nécessaire ; passer un LocalDate à une méthode est toujours sûr.
  • La démo plusMonths(1) a montré le comportement de clamp : 31 janvier + 1 mois = 28 février (ou 29 en année bissextile). Le comportement est documenté et cohérent, mais jan31.plusMonths(1).minusMonths(1) retourne January 28, pas January 31. L'aller-retour avec récupération de l'original fonctionne pour plusDays/minusDays, pas pour plusMonths/minusMonths.
  • Les ajusteurs temporels (lastDayOfMonth, firstDayOfNextMonth, nextOrSame(MONDAY)) ont remplacé des parcours calendaires manuels de plusieurs lignes. Chaînés, ils expriment « le premier lundi le ou après le premier du mois prochain » en deux ajusteurs. Le chapitre suivant sur LocalTime et le chapitre dédié Temporal Adjusters vont plus loin.
  • ChronoUnit.DAYS.between(invoice, today) a retourné un long signé. L'équivalent invoiceDate.until(today) a retourné un Period — en forme calendaire, avec des champs séparés année/mois/jour. Les deux répondent à des questions différentes : ChronoUnit.X pour « combien de X entiers », Period pour « sous forme conviviale au calendrier ». Choisissez celui dont la forme correspond à la sortie que vous voulez.

Ce qui suit

LocalDate couvrait le côté date. Le chapitre suivant, Java LocalTime, en est le miroir — l'heure du jour, sans date attachée et sans fuseau horaire. Même API fluente, classe plus petite, mêmes garanties d'immuabilité.

Pratique

Pratique
`LocalDate.of(2025, 1, 31).plusMonths(1)` retourne quelle valeur, et pourquoi ?
`LocalDate.of(2025, 1, 31).plusMonths(1)` retourne quelle valeur, et pourquoi ?
Was this page helpful?