Introduzione all'API Java per Data e Ora
Introduzione alla moderna API java.time di Java 8, che sostituisce le classi legacy Date e Calendar con tipi immutabili e precisi.
Java 8 ha introdotto java.time, un nuovo package per rappresentare date, orari, durate, fusi orari e le operazioni aritmetiche tra di essi. Ha sostituito due API precedenti — java.util.Date e java.util.Calendar — che avevano la meritata reputazione di essere l'angolo peggio progettato del JDK. La nuova API è stata ispirata dalla libreria Joda-Time di Stephen Colebourne; se hai già usato Joda, java.time ti risulterà familiare.
I due aspetti importanti della riprogettazione:
- Ogni tipo è immutabile. Un
LocalDateuna volta creato non cambia mai. Metodi comeplusDays(7)restituiscono un nuovoLocalDate. Questo rende l'API thread-safe per costruzione ed elimina un'intera categoria di bug. - Ogni tipo rappresenta una cosa sola.
LocalDateè una data senza orario.Instantè un momento sulla linea temporale.Durationè una lunghezza di tempo. IlDatelegacy era in qualche modo tutto questo allo stesso tempo, a seconda del costruttore usato; la nuova API li separa così che il tipo ti dice che tipo di valore hai.
Questo capitolo è la mappa. I successivi dieci capitoli approfondiscono ogni classe.
I tipi fondamentali
"A date" LocalDate 2025-11-04
"A time of day" LocalTime 14:30:00
"Both, no zone" LocalDateTime 2025-11-04T14:30:00
"Both, with zone" ZonedDateTime 2025-11-04T14:30:00-05:00 [America/New_York]
"A moment" Instant 2025-11-04T19:30:00Z (UTC, seconds-since-epoch)
"A length of time" Duration PT1H30M (1 hour 30 minutes)
"A length of date" Period P1Y2M3D (1 year 2 months 3 days)La divisione orizzontale — Local* vs Zoned/Instant — è la più importante. I tipi Local non portano alcun fuso orario. Un LocalDate di 2025-11-04 è "il quattro novembre"; non dice se è il quattro a Tokyo o a Honolulu. È il tipo giusto per una data di nascita, una data contrattuale o un selettore di date nell'interfaccia utente.
I tipi Zoned portano il loro fuso orario. ZonedDateTime è "questo istante del calendario in questo posto," che è ciò che vuoi per "riunione fissata per le 9 di mattina a New York." Instant è un momento sulla linea temporale globale — secondi UTC dall'epoch — che è ciò che vuoi per i log, i timestamp dei messaggi, qualsiasi cosa che debba essere ordinata globalmente senza bisogno di etichette locali.
La divisione orizzontale tra Duration e Period è anch'essa importante. Duration è una lunghezza di tempo misurabile in secondi — PT24H è esattamente 24 × 3600 secondi. Period è una lunghezza espressa in termini di calendario — P1M (un mese) è 30 giorni in alcuni mesi e 31 in altri. Per misurare il tempo, vuoi Duration. Per "aggiungi un mese a una data di fatturazione," vuoi Period.
La forma fluente
Ogni tipo viene costruito e modificato tramite un'API fluente coerente:
LocalDate today = LocalDate.now();
LocalDate stardate = LocalDate.of(2025, 11, 4);
LocalDate parsed = LocalDate.parse("2025-11-04");
LocalDate nextWeek = today.plusDays(7); // immutable: returns a NEW LocalDate
LocalDate lastYear = today.minusYears(1);
LocalDate firstOfMonth = today.withDayOfMonth(1); // with* returns a copy with one field changed
boolean before = today.isBefore(stardate);
int year = today.getYear();Tre forme che vedrai ovunque:
now()— valore corrente dall'orologio di sistema.of(...)— componenti espliciti.parse(...)— da una stringa (ISO-8601 per impostazione predefinita).
E per le trasformazioni:
plusX(n)/minusX(n)— aritmetica.withX(value)— sostituisce un singolo campo.isBefore(other)/isAfter(other)— confronto.
Questa forma si ripete in LocalDate, LocalTime, LocalDateTime, ZonedDateTime e Instant. Una volta capito il pattern, ogni classe ti parla nello stesso dialetto con un vocabolario leggermente diverso.
I fusi orari sono complessi, e l'API lo ammette
Il motivo principale per cui java.util.Date era problematico è che cercava di rendere i fusi orari invisibili. Il risultato era la famosa categoria di bug "memorizza un Date, recuperalo su un server in un fuso orario diverso, ottieni la data del calendario sbagliata." java.time risolve questo rendendo il fuso orario esplicito nel tipo.
Se accetti una data da un utente e non sai in quale fuso orario si trova, memorizzala come LocalDate. Se ti dice che è "le 9 di mattina nel suo fuso orario" e conosci il suo fuso, memorizzala come ZonedDateTime con il fuso. Se registri un evento del server, memorizzalo come Instant. Non memorizzare un LocalDateTime sperando che il fuso orario passi attraverso; il fuso mancante è l'intero bug.
Instant now = Instant.now(); // unambiguous: a moment in UTC
ZonedDateTime localized = now.atZone(ZoneId.of("Europe/Berlin")); // a label for that moment in BerlinLa gerarchia dei fusi orari:
ZoneOffsetè un offset fisso±HH:MMrispetto a UTC:+05:30,-08:00. Nessuna gestione dell'ora legale.ZoneIdè un fuso orario nominato:Europe/Berlin,America/New_York. Porta il record del database IANA di quale offset ha quel fuso in qualsiasi giorno, incluse le transizioni dell'ora legale e i cambiamenti storici.
Preferisci sempre ZoneId rispetto a ZoneOffset quando hai una scelta. "America/New_York" è corretto anche con l'ora legale; "−05:00" è corretto solo fuori dall'ora legale.
I tipi legacy non sono scomparsi
java.util.Date, java.util.Calendar e java.text.SimpleDateFormat esistono ancora. Il nuovo codice non dovrebbe usarli — ma molto codice vecchio lo fa, e avrai bisogno di interoperare. I metodi di conversione sono diretti:
// java.util.Date <-> java.time.Instant
Instant inst = legacyDate.toInstant();
Date back = Date.from(inst);
// java.util.Calendar -> java.time.ZonedDateTime
ZonedDateTime zdt = ZonedDateTime.ofInstant(
cal.toInstant(), cal.getTimeZone().toZoneId());Il pattern è unidirezionale: legacy → java.time è diretto; per qualsiasi cosa nuova, rimani in java.time e converti solo al confine dell'API dove vive il vecchio codice. I capitoli Legacy Date e Calendar alla fine di questa parte trattano il ponte in dettaglio.
Un esempio pratico: la famiglia di tipi in un unico programma
Il programma seguente usa ogni tipo introdotto nella mappa sopra — LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant, Duration, Period — e mostra come si convertono l'uno nell'altro. È la versione "tour"; ogni singolo tipo avrà il proprio capitolo da qui in avanti.
Cosa ricavare dall'esecuzione:
- Il primo blocco ha costruito la famiglia per composizione:
LocalDate+LocalTime=LocalDateTime;LocalDateTime+ZoneId=ZonedDateTime;ZonedDateTime→Instant. Questo è il reticolo delle conversioni, e lo farai ogni volta che attraversi un confine API. Le frecce vanno in entrambe le direzioni per la maggior parte delle coppie —Instant.atZone(zone)eZonedDateTime.toLocalDateTime()chiudono i cicli. - Un singolo
Instantha stampato tre orari dall'"aspetto" diverso quando visti da New York, Berlino e Tokyo. Questo è il punto diInstant: è il momento, indipendentemente da dove ti trovi. IlZonedDateTimeaggiunge l'etichetta "dove mi trovo". Confondere i due è l'errore delDatelegacy. Durationha stampatoPT1H30MePeriodha stampatoP3M. Il formato di durata ISO-8601 èPnYnMnDTnHnMnS— tutto prima dellaTsono unità di calendario (Period), tutto dopo sono unità di tempo (Duration). La stringa è esattamente ciò chetoString()restituisce, ed esattamente ciò cheparse(...)accetta.today.plusDays(7)ha prodotto unLocalDatediverso. Stamparetodaydi nuovo subito dopo ha mostrato che l'originale era invariato — questa è la garanzia di immutabilità. Ogniplus/minus/withrestituisce un nuovo oggetto; il ricevitore non viene mai modificato. Nessuna copia difensiva, nessuna preoccupazione per la thread-safety, mai.ChronoUnit.DAYS.between(today, launch)era l'operazione di "distanza". Restituisce unlong, non unPeriod, perché la risposta in giorni non ha ambiguità di calendario (a differenza dei mesi, che variano in lunghezza). Ogni capitolo di questa parte usaChronoUnitda qualche parte — è il catalogo delle unità di tempo di cui parla l'API.
Cosa c'è dopo
Il prossimo capitolo, Java LocalDate, inizia il tour in profondità. LocalDate è il più semplice dei cinque tipi "punto nel tempo" ed è il posto giusto per imparare la forma fluente che tutti gli altri condividono.