W3docs

Parsing delle date in Java

Analizza stringhe in oggetti data-ora Java con DateTimeFormatter e gestisci le eccezioni di parsing.

Il parsing è il contrario della formattazione. Stesso DateTimeFormatter, stesso alfabeto di pattern, stesse avvertenze — ma si legge una stringa invece di scriverne una. Ogni tipo java.time ha un factory method parse(...); con un pattern predefinito (ISO-8601) accetta un argomento, e con un pattern personalizzato accetta un formatter.

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

Ciascuno usa il formatter predefinito del tipo — ISO-8601 strict. Per qualsiasi cosa che non abbia forma ISO, costruisci un formatter e passalo.

Parsing con pattern personalizzato

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

L'alfabeto dei pattern è lo stesso della formattazione — dd MMM yyyy, HH:mm:ss, MM/dd/yyyy h:mm a. La regola di corrispondenza è strict per impostazione predefinita: ogni carattere letterale nel pattern deve comparire nell'input verbatim, e ogni campo deve avere il numero corretto di cifre.

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

Se l'input a volte ha una sola cifra, usa d/M/yyyy (che accetta 1-2 cifre) oppure costruisci il formatter con DateTimeFormatterBuilder e parseStrict(false). La forma a lettera singola è la soluzione più semplice.

Il locale conta al confine del parsing

La stessa questione del locale della formattazione: i nomi dei mesi (MMMM) e i nomi dei giorni della settimana (EEEE) sono specifici della lingua, quindi il formatter deve sapere in quale lingua è scritto l'input.

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)

Nota il yyyy in entrambi i pattern. Per produrre un LocalDate l'input deve fornire un anno — un pattern come "EEEE, MMMM d" esegue il parsing, ma solo in un TemporalAccessor senza campo anno, quindi LocalDate.parse su di esso lancia un'eccezione. Se le tue stringhe non hanno genuinamente un anno, esegui il parsing su un TemporalAccessor e combinalo con un anno tu stesso.

Senza un locale esplicito, viene usato Locale.getDefault() — e il locale predefinito di una JVM server è imprevedibile. Passa sempre un locale quando si effettua il parsing di nomi di mesi o giorni da una stringa che l'utente potrebbe digitare. Vale lo specchio del principio "formatta sempre con un locale".

DateTimeParseException

Un parsing fallito lancia DateTimeParseException (una sottoclasse di RuntimeException, quindi non dichiarata su parse). Il messaggio indica sia la posizione che cosa era atteso:

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
}

Due tipi distinti di errore finiscono qui:

  • Formato non corrispondente. La stringa non corrisponde affatto al pattern — "04 nov 2025" contro "dd-MM-yyyy".
  • Valore fuori range. La stringa corrisponde al pattern ma un valore è impossibile — mese 13, giorno 32.

Entrambi lanciano la stessa classe. Cattura e segnala; non ingoiare mai silenziosamente.

Anni a due cifre e la trappola del MAX_VALUE

Il pattern yy (anno a due cifre) ha un comportamento predefinito documentato ma sorprendente: esegue il parsing all'anno più vicino a oggi all'interno di una finestra di 100 anni. LocalDate.parse("11/04/25", DateTimeFormatter.ofPattern("MM/dd/yy")) è 2025-11-04 nel 2025 e 2125-11-04 nel 2076. È una funzionalità per i casi "vicino a oggi" e un trabocchetto per i dati d'archivio.

La soluzione è usare yyyy quando l'input ha quattro cifre ed essere espliciti sulla finestra del secolo quando non le ha:

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

Se stai gestendo dati legacy con yy, documenta la finestra nel codice. Il default è "pivot mobile intorno all'anno corrente", che non è ciò che vuoi per "tutti i miei dati sono degli anni '80".

Parsing senza impegnarsi su un tipo

DateTimeFormatter.parse(String) restituisce un TemporalAccessor — il fondo della gerarchia dei tipi. Utile quando l'input potrebbe essere un LocalDate o un LocalDateTime:

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

parseBest(text, ...queries) prova il metodo from di ogni tipo in ordine e restituisce il primo che ha successo. Il risultato richiede instanceof per essere usato in modo specifico:

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

Per la maggior parte del codice, chiamare direttamente parse(...) del tipo specifico è più semplice. parseBest è per il caso in cui si accettano più forme (una colonna CSV che potrebbe essere una data o una data-ora).

Parsing tollerante con il builder

DateTimeFormatterBuilder consente di assemblare un formatter più permissivo:

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 sintassi con parentesi quadre [...] contrassegna una sezione opzionale — quel pattern esegue il parsing sia di "2025-11-04" (senza ora) che di "2025-11-04 14:30" (con ora). Combinato con parseLenient e parseCaseInsensitive, puoi costruire un formatter che accetta una gamma più ampia di input senza scrivere un parser personalizzato.

Questo è eccessivo per codice che controlla entrambi i lati. Usa il default strict a meno che non stia leggendo input utente o dati legacy.

Instant.parse è strict

Instant.parse("2025-11-04T19:30:00Z") funziona. La Z finale (UTC) è obbligatoria; qualsiasi altro offset (-05:00, +09:00) richiede prima OffsetDateTime.parse o ZonedDateTime.parse, poi .toInstant():

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

Questa è la conversione canonica quando un'API esterna ti fornisce stringhe ISO-8601 con offset di fuso orario ma senza zona IANA.

Un esempio pratico: leggi una config, esegui il parsing, reagisci agli input errati

Il programma seguente effettua il parsing di tre stringhe a forma di data da una config sintetica: una data ISO, una data con pattern personalizzato e un istante ISO. Dimostra poi il builder tollerante con una sezione temporale opzionale, l'API parseBest per "sia una data che una data-ora", e la modalità di fallimento quando l'input non corrisponde.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • I tre parser ISO predefiniti hanno accettato esattamente la forma standard: yyyy-MM-dd per LocalDate, la forma separata da T per LocalDateTime, e la forma terminata con Z per Instant. Nessuna flessibilità, nessuna supposizione — ed è questo il punto. Se il tuo input si adatta, non serve alcun formatter; se non si adatta, costruirne uno è questione di una riga.
  • Il formatter tollerante ha accettato tre forme di input diverse — solo data, data con ora, data con ora e secondi — perché la sezione tra parentesi [...] è opzionale. parseBest(text, LocalDateTime::from, LocalDate::from) ha scelto il tipo più ricco supportato da ciascun input. Questo è il pattern corretto quando si accettano date inserite dall'utente o da config con precisione variabile.
  • OffsetDateTime.parse(wire).toInstant() è stato il bridge canonico da "un timestamp ISO-8601 con offset" a Instant. Instant.parse stesso accetta solo UTC con suffisso Z; tutto il resto deve passare prima per OffsetDateTime (o ZonedDateTime). La conversione è di una riga per ogni direzione.
  • Il parsing in locale tedesco ha funzionato solo perché il formatter era costruito con Locale.GERMAN. Il locale predefinito avrebbe rifiutato "November" se la JVM fosse in esecuzione in tedesco (che si aspetta che "November" di Locale.GERMAN venga confrontato con i nomi tedeschi). Fissa sempre il locale al confine del parsing — il formatter da solo non basta; il locale controlla la risoluzione dei nomi di mesi e giorni.
  • I due blocchi di fallimento hanno entrambi lanciato DateTimeParseException con informazioni utili sulla posizione. getErrorIndex indica dove il parser si è fermato; getParsedString è l'input come lo ha visto il parser. Visualizza questi dati negli errori per l'utente — "impossibile analizzare la data al carattere 5" è enormemente più utile di "data non valida".

Cosa c'è dopo

La formattazione e il parsing chiudono il confine con le stringhe. Il prossimo capitolo, Java Temporal Adjusters, torna al lato dei valori e copre gli adjuster predefiniti (firstDayOfMonth, nextOrSame(MONDAY), ecc.) e come scriverne di propri — utili ogni volta che la data desiderata dipende dalla data che si ha ("il primo martedì dopo il 15").

Esercizio

Pratica
Un modulo web consente agli utenti di digitare una data nel formato '04/11/2025' (giorno/mese/anno). Il tuo codice usa `LocalDate.parse(input, DateTimeFormatter.ofPattern('dd/MM/yyyy'))` e poi chiede 'questa data è nel futuro?'. Un utente digita '4/11/2025' (senza zero iniziale sul giorno) e il parsing lancia un'eccezione. Qual è la correzione più piccola?
Un modulo web consente agli utenti di digitare una data nel formato '04/11/2025' (giorno/mese/anno). Il tuo codice usa `LocalDate.parse(input, DateTimeFormatter.ofPattern('dd/MM/yyyy'))` e poi chiede 'questa data è nel futuro?'. Un utente digita '4/11/2025' (senza zero iniziale sul giorno) e il parsing lancia un'eccezione. Qual è la correzione più piccola?
Was this page helpful?