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 ESTL'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 styleEntrambe 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.2025L'alfabeto dei pattern
La tabella completa — quella a cui tornerai. Le lettere sono case-sensitive e il numero di ripetizioni è importante.
| Lettera | Significato | Esempio |
|---|---|---|
y | anno | y → 2025, yy → 25, yyyy → 2025 |
M | mese | M → 11, MM → 11, MMM → Nov, MMMM → November |
d | giorno del mese | d → 4, dd → 04 |
E | giorno della settimana | E → Tue, EEEE → Tuesday |
H | ora 0-23 | H → 14, HH → 14 |
h | ora 1-12 | h → 2, hh → 02 (usare con a) |
a | AM/PM | a → PM |
m | minuto | m → 5, mm → 05 |
s | secondo | s → 9, ss → 09 |
S | frazione di secondo | SSS → 123 (millis) |
n | nanosecondo | nnnnnnnnn → 123456789 |
z | nome del fuso orario | z → EST, zzzz → Eastern Standard Time |
Z | offset del fuso orario | Z → -0500, ZZ → -0500, ZZZZ → GMT-05:00 |
X | offset ISO | X → -05, XX → -0500, XXX → -05:00 |
V | ID del fuso orario | VV → America/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 PMPer 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 novembrePer 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 ESTPer 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 displaySenza 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, 2025Le 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.
Cosa trarre dall'esecuzione:
- I campi
static final DateTimeFormatternella 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
ZonedDateTimeha 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 esisteDateTimeFormatter— mantieni il tipo valore pulito, sposta la presentazione al formatter. - Il blocco "errori comuni" ha stampato
14:11perHH:MMperché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
FormatStyleha prodotto quattro stringhe progressivamente più lunghe. UsaFormatStyle.MEDIUMcome valore predefinito sensato per "mostrare una data a un utente senza pensarci troppo";LONGeFULLper contesti dove l'anno e il giorno della settimana devono essere inequivocabili;SHORTper spazi UI limitati. LocalDateTimecon un pattern che include il fuso orario ha lanciato un'eccezione — il formatter ha bisogno dei dati del fuso, eLocalDateTimenon 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.