W3docs

Java LocalDateTime

Combinez date et heure sans information de fuseau horaire en Java avec LocalDateTime.

LocalDateTime est LocalDate et LocalTime assemblés — une date calendaire et une heure du jour, mais toujours sans fuseau horaire. C'est la valeur naturelle pour « cet événement s'est produit à 14h30 le 4 novembre », et c'est le type java.time le plus utilisé dans les bases de code réelles qui ne gèrent pas des utilisateurs à l'échelle mondiale.

L'API fluide a la même forme que les deux moitiés — mêmes fabriques now/of/parse, mêmes modificateurs plusX/minusX/withX, mêmes comparaisons isBefore/isAfter. Les parties intéressantes sont les nouvelles : la combinaison avec une date ou une heure, la décomposition inverse, et la conversion explicite vers ZonedDateTime quand le fuseau horaire devient enfin nécessaire.

Création

LocalDateTime now    = LocalDateTime.now();                  // JVM default zone
LocalDateTime made   = LocalDateTime.of(2025, 11, 4, 14, 30);
LocalDateTime made2  = LocalDateTime.of(2025, Month.NOVEMBER, 4, 14, 30, 15);
LocalDateTime made3  = LocalDateTime.of(LocalDate.of(2025, 11, 4), LocalTime.of(14, 30));
LocalDateTime parsed = LocalDateTime.parse("2025-11-04T14:30:00");   // ISO-8601

La forme ISO-8601 comporte un T littéral entre la date et l'heure — c'est le séparateur standard. LocalDateTime.parse("2025-11-04 14:30:00") (espace au lieu du T) ne se parse pas avec le comportement par défaut ; il faudrait un DateTimeFormatter personnalisé pour cela.

Décomposition

LocalDate date = dt.toLocalDate();
LocalTime time = dt.toLocalTime();

Ce sont les inverses de LocalDate.atTime(time) / LocalTime.atDate(date). Les deux sont de pures projections — aucune perte d'information, aucun fuseau horaire introduit.

Tous les accesseurs en une seule vue

LocalDateTime hérite du menu d'accesseurs des deux moitiés :

dt.getYear();          dt.getMonth();        dt.getMonthValue();
dt.getDayOfMonth();    dt.getDayOfWeek();    dt.getDayOfYear();
dt.getHour();          dt.getMinute();       dt.getSecond();          dt.getNano();

Mêmes noms, mêmes sémantiques. Vous n'avez pas besoin d'appeler toLocalDate() ou toLocalTime() pour accéder aux éléments individuels — ils sont tous disponibles directement.

Arithmétique à travers la frontière

La différence cruciale avec LocalTime : LocalDateTime.plusHours(3) à 23:00 ne boucle pas silencieusement. Il passe au jour suivant :

LocalDateTime late = LocalDateTime.of(2025, 11, 4, 23, 0);
late.plusHours(3);   // 2025-11-05T02:00 — date advanced as expected

C'est la raison même d'utiliser LocalDateTime plutôt que LocalTime pour tout calcul susceptible de franchir minuit. Le calcul est cohérent avec ce qu'on attendrait d'une horloge réelle qui connaît le jour en cours.

dt.plusDays(7);          dt.plusHours(36);        dt.plusMinutes(150);
dt.minusYears(1);        dt.minusSeconds(45);

dt.withYear(2026);       dt.withHour(0);          dt.withMinute(0);

La règle de troncature de plusMonths de LocalDate s'applique ici aussi : LocalDateTime.of(2025, 1, 31, 12, 0).plusMonths(1) donne 2025-02-28T12:00, et non 2025-03-03T12:00. La troncature porte uniquement sur la composante date ; l'heure reste inchangée.

Le fuseau horaire est absent intentionnellement

Un LocalDateTime n'est pas un instant sur la ligne de temps globale. LocalDateTime.of(2025, 11, 4, 9, 0) pourrait être 9h du matin à New York, 9h du matin à Berlin, ou 9h du matin à Tokyo — trois Instants bien distincts, et LocalDateTime ne vous dira pas lequel. Si deux LocalDateTimes sont égaux, cela signifie que les chaînes de date et d'heure sont identiques ; cela ne signifie pas que les instants sous-jacents sont identiques.

C'est une fonctionnalité, pas un bug. Pour « le contrat est signé à 14h00 heure locale où que se trouve le signataire », LocalDateTime est exactement la bonne forme. Pour « le serveur a reçu la requête à... », c'est la mauvaise forme — utilisez Instant. Pour « la réunion commence à 14h00 heure de New York », c'est encore la mauvaise forme — utilisez ZonedDateTime.

Pour convertir vers un instant zoné, vous devez ajouter le fuseau horaire explicitement :

ZonedDateTime ny = ldt.atZone(ZoneId.of("America/New_York"));
Instant       inst = ldt.atZone(ZoneId.systemDefault()).toInstant();

L'appel atZone(...) est l'appel déterminant — c'est le moment où le système de types vous oblige à choisir le fuseau horaire voulu. Une fois la décision prise, la conversion vers Instant est mécanique. Les deux chapitres suivants (ZonedDateTime, Instant) couvrent en détail les formes zonée et globale.

Comparaison

dt.isBefore(other);
dt.isAfter(other);
dt.isEqual(other);
dt.compareTo(other);

L'ordre est lexicographique par (date, heure). Même avertissement qu'auparavant : deux LocalDateTimes se comparent selon leurs représentations textuelles de date et d'heure, et non selon les instants sous-jacents — parce qu'il n'y a pas d'instants sous-jacents sans fuseau horaire.

Distance

ChronoUnit.X.between fonctionne directement :

long minutes = ChronoUnit.MINUTES.between(start, end);
long days = ChronoUnit.DAYS.between(start, end);
Duration d = Duration.between(start, end);

Duration.between fonctionne avec LocalDateTime (il fonctionne avec tout Temporal). Pour une arithmétique purement calendaire — « combien de mois entre ces deux LocalDateTimes » — utilisez ChronoUnit.MONTHS.between, qui renvoie un long, ou Period.between(start.toLocalDate(), end.toLocalDate()) pour la décomposition sous forme calendaire.

Un exemple concret : planification à travers minuit

Le programme ci-dessous utilise LocalDateTime pour un petit bout de code de planification : un poste de nuit qui commence à 22h00 et se termine à 06h00, en calculant correctement la durée à travers minuit ; en arrondissant « maintenant » au prochain quart d'heure ; en trouvant la prochaine occurrence d'une réunion récurrente à 09h30 ; et en illustrant la règle selon laquelle le fuseau horaire doit être ajouté explicitement lors de la conversion vers un instant dans le temps.

java— editable, runs on the server

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

  • Duration.between(startShift, endShift) a produit PT8H. Le poste a franchi minuit, et les composantes de date ont absorbé le report — sans aucune ambiguïté. Le même calcul sur de simples LocalTimes aurait renvoyé PT-16H (l'écueil de LocalTime du chapitre précédent). Pour une arithmétique susceptible de franchir minuit, LocalDateTime est le type approprié.
  • En partant de 20:00, plusHours(3) est resté sur 11-04 (23:00, pas de franchissement de minuit) ; plusHours(5) a avancé jusqu'à 11-05T01:00. La famille plus/minus sur LocalDateTime propage correctement les reports à travers toute la chaîne A/M/J/h/m/s/ns. Aucun cas particulier requis dans votre code.
  • Le bloc « prochaine réunion à 09h30 » a construit le 09:30 du jour avec trois appels withX, puis a choisi entre aujourd'hui et demain en fonction de isBefore. C'est la forme habituelle pour « le prochain événement récurrent à cette heure » — assez court pour être inlinéé, assez fréquent pour être factorisé dans un helper si vous en avez plusieurs.
  • Le bloc « même LocalDateTime, fuseaux horaires différents » a produit deux Instants différents distants de six heures. C'est la raison centrale pour laquelle LocalDateTime ne prétend pas être un instant. La classe refuse de faire semblant qu'une date et une heure seules sont suffisantes ; c'est une étiquette sur une horloge murale quelque part, et laquelle dépend du fuseau horaire que vous fournissez.
  • Le contrôle d'immuabilité final a montré que now était inchangé après plusDays(7).withHour(0).withMinute(0). Cette garantie s'applique à toutes les opérations, à toutes les chaînes, à tous les helpers — il n'existe aucun moyen de muter un LocalDateTime. Passez-le librement, partagez-le entre threads, stockez-le dans une Map.

La suite

LocalDateTime est le dernier des trois types « Local » — pas de fuseau horaire, aucune prétention d'être un instant. Le chapitre suivant, Java ZonedDateTime, ajoute le fuseau horaire explicitement : un LocalDateTime plus un ZoneId plus le décalage résolu pour cette heure locale dans ce fuseau, qui ensemble fixent un instant réel sur la ligne de temps globale.

Pratique

Pratique
Deux développeurs — l'un à New York, l'autre à Berlin — construisent tous deux la valeur `LocalDateTime.of(2025, 11, 4, 14, 0)`. S'agit-il du même instant dans le temps ?
Deux développeurs — l'un à New York, l'autre à Berlin — construisent tous deux la valeur `LocalDateTime.of(2025, 11, 4, 14, 0)`. S'agit-il du même instant dans le temps ?
Was this page helpful?