W3docs

Formattazione delle date in Java

Formatta date e orari Java in stringhe con DateTimeFormatter e pattern standard o personalizzati.

La formattazione delle date consiste nel trasformare un valore data/ora in una stringa leggibile dall'utente. Questo capitolo tratta DateTimeFormatter — come costruirne uno (predefinito, localizzato o basato su pattern), l'alfabeto completo dei pattern, la gestione delle locale e dei fusi orari, e gli errori comuni che producono output errati. Funziona con tutti i tipi java.time: LocalDate, LocalTime, LocalDateTime, ZonedDateTime e Instant.

Ogni tipo java.time ha un metodo toString() che produce la rappresentazione ISO-8601: 2025-11-04, 14:30:00, 2025-11-04T14:30:00Z. Questo va bene per i log e per il traffico tra macchine. Per la visualizzazione rivolta all'utente ("4 novembre 2025" o "4 nov, 14:30") è necessario un formatter.

La classe è java.time.format.DateTimeFormatter. È il sostituto moderno, thread-safe e immutabile del vecchio java.text.SimpleDateFormat (che non era thread-safe e causava bug sottili in produzione quando condiviso). Salva un'istanza come static final e riutilizzala tra thread per sempre — senza sincronizzazione, senza copie difensive.

Tre modi per costruire un formatter

// 1. Built-in ISO formatters
DateTimeFormatter.ISO_LOCAL_DATE;                  // 2025-11-04
DateTimeFormatter.ISO_LOCAL_DATE_TIME;             // 2025-11-04T14:30:00
DateTimeFormatter.ISO_OFFSET_DATE_TIME;            // 2025-11-04T14:30:00-05:00
DateTimeFormatter.ISO_ZONED_DATE_TIME;             // 2025-11-04T14:30:00-05:00[America/New_York]
DateTimeFormatter.ISO_INSTANT;                     // 2025-11-04T19:30:00Z
DateTimeFormatter.BASIC_ISO_DATE;                  // 20251104

// 2. Localised formatters
DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);          // November 4, 2025 (en-US)
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);    // Nov 4, 2025, 2:30:00 PM

// 3. Pattern-based formatters
DateTimeFormatter.ofPattern("dd MMM yyyy");                   // 04 Nov 2025
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm zzz");          // 2025-11-04 14:30 EST

L'API dei pattern è quella che userai di più. Quella localizzata è adatta quando hai bisogno di un formato appropriato per la cultura e vuoi che il JDK scelga il layout per te.

Formattazione

La firma del metodo è simmetrica su entrambi i lati:

String s = formatter.format(temporal);
String s2 = temporal.format(formatter);                       // same thing, fluent style

Entrambe funzionano. La maggior parte del codice usa la forma fluent.

LocalDate today = LocalDate.now();
String us = today.format(DateTimeFormatter.ofPattern("MM/dd/yyyy"));         // 11/04/2025
String iso = today.format(DateTimeFormatter.ISO_LOCAL_DATE);                 // 2025-11-04
String eu = today.format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));          // 04.11.2025

L'alfabeto dei pattern

La tabella completa — quella a cui tornerai. Le lettere sono case-sensitive e il numero di ripetizioni è importante.

LetteraSignificatoEsempio
yannoy2025, yy25, yyyy2025
MmeseM11, MM11, MMMNov, MMMMNovember
dgiorno del mesed4, dd04
Egiorno della settimanaETue, EEEETuesday
Hora 0-23H14, HH14
hora 1-12h2, hh02 (usare con a)
aAM/PMaPM
mminutom5, mm05
ssecondos9, ss09
Sfrazione di secondoSSS123 (millis)
nnanosecondonnnnnnnnn123456789
znome del fuso orariozEST, zzzzEastern Standard Time
Zoffset del fuso orarioZ-0500, ZZ-0500, ZZZZGMT-05:00
Xoffset ISOX-05, XX-0500, XXX-05:00
VID del fuso orarioVVAmerica/New_York

Il testo letterale si racchiude tra virgolette singole:

DateTimeFormatter.ofPattern("EEEE, MMMM d 'at' h:mm a");      // Tuesday, November 4 at 2:30 PM

Per una virgoletta singola letterale, usa due virgolette: ''.

La coppia più confusa è m vs M (minuscolo = minuto, maiuscolo = mese) e H vs h (maiuscolo = 0-23, minuscolo = 1-12). La maggior parte dei bug del tipo "l'orario è sbagliato di qualcosa di strano" deriva da uno di questi errori di battitura.

Localizzazione: Locale e withLocale

Un formatter usa la locale predefinita della JVM se non viene specificata diversamente. Per output "sempre in inglese" o "sempre in tedesco", fissa la locale:

DateTimeFormatter english = DateTimeFormatter.ofPattern("EEEE, MMMM d", Locale.US);
DateTimeFormatter german  = DateTimeFormatter.ofPattern("EEEE, d. MMMM", Locale.GERMAN);
DateTimeFormatter french  = DateTimeFormatter.ofPattern("EEEE d MMMM", Locale.FRENCH);

today.format(english);   // Tuesday, November 4
today.format(german);    // Dienstag, 4. November
today.format(french);    // mardi 4 novembre

Per i contenuti renderizzati lato server, passa sempre una locale. La "locale predefinita della JVM" è imprevedibile sui server di produzione ed è la causa di bug del tipo "funziona sul mio portatile, sbagliato sul server".

Visualizzazione del fuso orario

ZonedDateTime e Instant sono gli unici tipi che hanno informazioni sul fuso orario. Formattare un LocalDateTime con un pattern che include z o Z lancia un'eccezione — non c'è nessun fuso da stampare. Prima converti:

ZonedDateTime zdt = ldt.atZone(ZoneId.of("America/New_York"));
zdt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm z"));   // 2025-11-04 14:30 EST

Per Instant, il formatter ha bisogno anch'esso di un fuso — Instant non ne ha, quindi i formatter di visualizzazione che includono qualsiasi campo dipendente dal fuso hanno bisogno di withZone:

DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
    .withZone(ZoneId.of("America/New_York"));
f.format(Instant.now());                                      // formatter supplies the zone for display

Senza withZone, formattare un Instant con un pattern a forma di calendario lancia un'eccezione.

Formatter stilizzati con FormatStyle

Le factory localizzate offrono quattro dimensioni canoniche:

DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);   // 11/4/25 (en-US), 04.11.25 (de-DE)
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);  // Nov 4, 2025
DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);    // November 4, 2025
DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);    // Tuesday, November 4, 2025

Le stesse quattro dimensioni esistono per ofLocalizedTime e ofLocalizedDateTime. Usale quando vuoi che il layout segua la locale dell'utente invece di imporre una forma fissa. Combinale con .withLocale(...) per fissare la locale.

Un esempio pratico: una data, sei varianti di visualizzazione

Il programma seguente formatta un unico ZonedDateTime in sei modi comuni: ISO per i log delle macchine, formato US a 12 ore per gli utenti inglesi, formato europeo a 24 ore per gli utenti tedeschi, una forma localizzata lunga, un pattern personalizzato con testo letterale incorporato e un formatter Instant-via-withZone per i timestamp grezzi.

java— editable, runs on the server

Cosa trarre dall'esecuzione:

  • I campi static final DateTimeFormatter nella cache hanno la forma giusta. DateTimeFormatter è immutabile e thread-safe; crearne uno è economico ma non gratuito, e riutilizzare la stessa istanza ovunque è il pattern raccomandato dal JDK. Non costruirne uno nuovo all'interno di un ciclo caldo.
  • Lo stesso ZonedDateTime ha prodotto sei stringhe diverse a seconda del formatter. L'oggetto valore non è mai cambiato; il formatter è l'unica cosa che controlla il layout. Questa è la separazione per cui esiste DateTimeFormatter — mantieni il tipo valore pulito, sposta la presentazione al formatter.
  • Il blocco "errori comuni" ha stampato 14:11 per HH:MM perché M è il mese, non il minuto. I due sono la coppia più confusa nell'alfabeto dei pattern. Se l'orario visualizzato sembra sospettosamente simile a un componente della data, controlla il case nel pattern.
  • La scala FormatStyle ha prodotto quattro stringhe progressivamente più lunghe. Usa FormatStyle.MEDIUM come valore predefinito sensato per "mostrare una data a un utente senza pensarci troppo"; LONG e FULL per contesti dove l'anno e il giorno della settimana devono essere inequivocabili; SHORT per spazi UI limitati.
  • LocalDateTime con un pattern che include il fuso orario ha lanciato un'eccezione — il formatter ha bisogno dei dati del fuso, e LocalDateTime non li ha. La soluzione è convertire (ldt.atZone(zone)) oppure rimuovere il campo del fuso dal pattern. In entrambi i casi, il modo in cui fallisce è chiaro a runtime.

Cosa viene dopo

La formattazione è la direzione valore → stringa. Il capitolo successivo, Parsing delle date in Java, è l'inverso — stringa → valore — usando gli stessi pattern DateTimeFormatter e lo stesso insieme di avvertenze. I due insieme sono il confine I/O per qualsiasi codice che scambia date con utenti, configurazioni, log o API remote.

Esercitazione

Pratica
Un server web registra i timestamp con `ZonedDateTime.now().format(DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm'))`. Su una JVM con locale tedesca il mese viene stampato come 'Nov' invece di '11'. Qual è la causa più probabile?
Un server web registra i timestamp con `ZonedDateTime.now().format(DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm'))`. Su una JVM con locale tedesca il mese viene stampato come 'Nov' invece di '11'. Qual è la causa più probabile?
Was this page helpful?