W3docs

Analyse de dates en Java

Convertissez des chaînes en objets date-heure Java avec DateTimeFormatter et gérez les exceptions d'analyse.

L'analyse est le miroir du formatage. Même DateTimeFormatter, même alphabet de motifs, mêmes mises en garde — mais on lit une chaîne au lieu d'en écrire une. Chaque type java.time possède une fabrique parse(...) ; avec un motif par défaut (ISO-8601) elle prend un argument, et avec un motif personnalisé elle prend un formateur.

LocalDate    d  = LocalDate.parse("2025-11-04");                                       // ISO default
LocalTime    t  = LocalTime.parse("14:30:00");
LocalDateTime dt = LocalDateTime.parse("2025-11-04T14:30:00");                          // note the T
ZonedDateTime zdt = ZonedDateTime.parse("2025-11-04T14:30:00-05:00[America/New_York]");
Instant       i = Instant.parse("2025-11-04T19:30:00Z");                                // trailing Z mandatory

Chacun de ces appels utilise le formateur par défaut du type — ISO-8601 strict. Pour tout ce qui n'est pas au format ISO, il faut construire un formateur et le passer.

Analyse avec un motif personnalisé

DateTimeFormatter f = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate d = LocalDate.parse("04/11/2025", f);                                         // British DMY

L'alphabet de motifs est identique à celui utilisé pour le formatage — dd MMM yyyy, HH:mm:ss, MM/dd/yyyy h:mm a. La règle de correspondance est stricte par défaut : chaque caractère littéral du motif doit apparaître tel quel dans l'entrée, et chaque champ doit avoir le bon nombre de chiffres.

DateTimeFormatter dmy = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate.parse("4/11/2025", dmy);                                                       // FAILS: "dd" requires 2 digits
LocalDate.parse("04/11/2025", dmy);                                                      // OK

Si votre entrée a parfois un seul chiffre, utilisez d/M/yyyy (qui accepte 1 à 2 chiffres) ou construisez le formateur avec DateTimeFormatterBuilder et parseStrict(false). La forme à une seule lettre est la solution la plus simple.

La locale importe à la frontière d'analyse

La même logique de locale qu'au formatage : les noms de mois (MMMM) et les noms de jour de la semaine (EEEE) sont spécifiques à une langue, donc le formateur doit savoir dans quelle langue l'entrée est écrite.

DateTimeFormatter englishDay = DateTimeFormatter.ofPattern("EEEE, MMMM d yyyy", Locale.US);
DateTimeFormatter germanDay  = DateTimeFormatter.ofPattern("EEEE, d. MMMM yyyy",  Locale.GERMAN);

LocalDate.parse("Tuesday, November 4 2025", englishDay);   // 2025-11-04
LocalDate.parse("Dienstag, 4. November 2025", germanDay);  // 2025-11-04 (German month name)

Remarquez le yyyy dans les deux motifs. Pour produire un LocalDate, l'entrée doit fournir une année — un motif comme "EEEE, MMMM d" s'analyse, mais uniquement en TemporalAccessor sans champ d'année, donc LocalDate.parse lève une exception. Si vos chaînes n'ont vraiment pas d'année, analysez vers un TemporalAccessor et combinez-le vous-même avec une année.

Sans locale explicite, Locale.getDefault() est utilisé — et la locale par défaut d'un JVM serveur est imprévisible. Passez toujours une locale lors de l'analyse de noms de mois ou de jour à partir d'une chaîne que l'utilisateur pourrait saisir. Le miroir de « toujours formater avec une locale » s'applique.

DateTimeParseException

Un échec d'analyse lève DateTimeParseException (une sous-classe de RuntimeException, donc non déclarée sur parse). Le message indique à la fois la position et ce qui était attendu :

try {
  LocalDate.parse("2025-13-45");                              // month 13, day 45
} catch (DateTimeParseException e) {
  e.getParsedString();                                         // "2025-13-45"
  e.getErrorIndex();                                           // index where parsing gave up
  e.getMessage();                                              // human description
}

Deux types d'échecs distincts atterrissent ici :

  • Non-correspondance de format. La chaîne ne correspond pas du tout au motif — "04 nov 2025" contre "dd-MM-yyyy".
  • Valeur hors plage. La chaîne correspond au motif mais une valeur est impossible — mois 13, jour 32.

Les deux lèvent la même classe. Capturez et signalez ; ne les avalez jamais silencieusement.

Les années à deux chiffres et le piège de MAX_VALUE

Le motif yy (année à deux chiffres) a un comportement par défaut documenté mais surprenant : il analyse vers l'année la plus proche d'aujourd'hui dans une fenêtre de 100 ans. LocalDate.parse("11/04/25", DateTimeFormatter.ofPattern("MM/dd/yy")) donne 2025-11-04 en 2025 et 2125-11-04 en 2076. C'est une fonctionnalité utile pour les cas « proches d'aujourd'hui » et un piège pour les données d'archive.

La solution est d'utiliser yyyy lorsque l'entrée a quatre chiffres et d'être explicite sur la fenêtre de siècle lorsqu'elle n'en a pas :

DateTimeFormatter f = new DateTimeFormatterBuilder()
    .appendPattern("MM/dd/")
    .appendValueReduced(ChronoField.YEAR, 2, 2, 1950)         // window starts at 1950
    .toFormatter();

Si vous traitez des données héritées avec yy, documentez la fenêtre dans le code. La valeur par défaut est « pivot glissant autour de l'année en cours », ce qui n'est pas ce que vous voulez si « toutes mes données viennent des années 1980 ».

Analyser sans s'engager sur un type

DateTimeFormatter.parse(String) retourne un TemporalAccessor — le bas de la hiérarchie de types. Utile lorsque l'entrée peut être soit un LocalDate soit un LocalDateTime :

TemporalAccessor ta = DateTimeFormatter.ISO_DATE_TIME.parseBest(
    "2025-11-04T14:30:00",
    LocalDateTime::from,                                       // preferred
    LocalDate::from);                                          // fallback

parseBest(text, ...queries) essaie la méthode from de chaque type dans l'ordre et retourne le premier qui réussit. Le résultat nécessite instanceof pour être utilisé à des fins spécifiques :

if (ta instanceof LocalDateTime ldt) ...
else if (ta instanceof LocalDate ld) ...

Pour la plupart du code, appeler directement parse(...) du type spécifique est plus simple. parseBest est adapté au cas où vous acceptez plusieurs formes (une colonne CSV qui peut être une date ou une date-heure).

Analyse souple avec le constructeur

DateTimeFormatterBuilder permet d'assembler un formateur plus tolérant :

DateTimeFormatter lenient = new DateTimeFormatterBuilder()
    .appendPattern("yyyy-MM-dd[ HH:mm[:ss]]")                  // optional sections in []
    .parseLenient()                                            // accept missing leading zeros etc.
    .parseCaseInsensitive()                                    // ignore case on month/day names
    .toFormatter(Locale.US);

La syntaxe entre crochets [...] marque une section optionnelle — ce motif analyse à la fois "2025-11-04" (sans heure) et "2025-11-04 14:30" (avec heure). Combiné à parseLenient et parseCaseInsensitive, vous pouvez construire un formateur qui accepte une gamme plus large d'entrées sans écrire un analyseur personnalisé.

C'est excessif pour du code qui contrôle les deux extrémités. Utilisez le strict par défaut sauf si vous lisez des saisies utilisateur ou des données héritées.

Instant.parse est strict

Instant.parse("2025-11-04T19:30:00Z") fonctionne. Le Z final (UTC) est obligatoire ; tout autre décalage (-05:00, +09:00) nécessite d'abord OffsetDateTime.parse ou ZonedDateTime.parse, puis .toInstant() :

Instant inst = OffsetDateTime.parse("2025-11-04T14:30:00-05:00").toInstant();

C'est la conversion canonique lorsqu'une API externe vous fournit des chaînes ISO-8601 avec des décalages de fuseau horaire mais sans zone IANA.

Exemple concret : lire une config, analyser, réagir aux entrées invalides

Le programme ci-dessous analyse trois chaînes au format date à partir d'une config synthétique : une date ISO, une date avec motif personnalisé et un instant ISO. Il démontre ensuite le constructeur souple avec une section d'heure optionnelle, l'API parseBest pour « soit une date soit une date-heure », et le mode d'échec lorsque l'entrée ne correspond pas.

java— editable, runs on the server

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

  • Les trois analyseurs par défaut ISO ont chacun accepté exactement la forme standard : yyyy-MM-dd pour LocalDate, la forme séparée par T pour LocalDateTime, et la forme terminée par Z pour Instant. Aucune flexibilité, aucune supposition — c'est précisément le but. Si votre entrée correspond, aucun formateur n'est nécessaire ; si ce n'est pas le cas, en construire un ne prend qu'une ligne.
  • Le formateur souple a accepté trois formes d'entrée différentes — date seule, date avec heure, date avec heure et secondes — parce que la section entre crochets [...] est optionnelle. parseBest(text, LocalDateTime::from, LocalDate::from) a choisi le type le plus riche supporté par chaque entrée. C'est le bon patron lorsque vous acceptez des dates saisies par l'utilisateur ou issues d'une config avec une précision variable.
  • OffsetDateTime.parse(wire).toInstant() était le pont canonique entre « un horodatage ISO-8601 avec un décalage » et Instant. Instant.parse lui-même n'accepte que les chaînes suffixées par Z en UTC ; tout le reste doit passer par OffsetDateTime (ou ZonedDateTime) en premier. La conversion est une ligne dans chaque sens.
  • L'analyse avec locale allemande n'a fonctionné que parce que le formateur a été construit avec Locale.GERMAN. La locale par défaut aurait rejeté "November" si la JVM tournait en allemand (qui attend que "November" de Locale.GERMAN corresponde aux noms allemands). Fixez toujours la locale à la frontière d'analyse — le formateur seul ne suffit pas ; la locale contrôle la résolution des noms de mois et de jour.
  • Les deux blocs d'échec ont tous deux levé DateTimeParseException avec des informations de position utiles. getErrorIndex indique où l'analyseur a abandonné ; getParsedString est l'entrée telle que l'analyseur l'a vue. Exposez ces informations dans les erreurs présentées à l'utilisateur — « impossible d'analyser la date au caractère 5 » est bien plus utile que « date invalide ».

La suite

Le formatage et l'analyse ferment la frontière des chaînes. Le prochain chapitre, Java Temporal Adjusters, retourne du côté des valeurs et couvre les ajusteurs intégrés (firstDayOfMonth, nextOrSame(MONDAY), etc.) ainsi que la manière d'écrire les vôtres — utile chaque fois que la date souhaitée dépend de la date que vous avez (« premier mardi après le 15 »).

Entraînement

Pratique
Un formulaire web permet aux utilisateurs de saisir une date au format '04/11/2025' (jour/mois/année). Votre code utilise `LocalDate.parse(input, DateTimeFormatter.ofPattern('dd/MM/yyyy'))` puis vérifie si cette date est dans le futur. Un utilisateur saisit '4/11/2025' (sans zéro initial sur le jour) et l'analyse lève une exception. Quelle est la correction minimale ?
Un formulaire web permet aux utilisateurs de saisir une date au format '04/11/2025' (jour/mois/année). Votre code utilise `LocalDate.parse(input, DateTimeFormatter.ofPattern('dd/MM/yyyy'))` puis vérifie si cette date est dans le futur. Un utilisateur saisit '4/11/2025' (sans zéro initial sur le jour) et l'analyse lève une exception. Quelle est la correction minimale ?
Was this page helpful?