W3docs

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:

  1. Ogni tipo è immutabile. Un LocalDate una volta creato non cambia mai. Metodi come plusDays(7) restituiscono un nuovo LocalDate. Questo rende l'API thread-safe per costruzione ed elimina un'intera categoria di bug.
  2. Ogni tipo rappresenta una cosa sola. LocalDate è una data senza orario. Instant è un momento sulla linea temporale. Duration è una lunghezza di tempo. Il Date legacy 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 Berlin

La gerarchia dei fusi orari:

  • ZoneOffset è un offset fisso ±HH:MM rispetto 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.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • Il primo blocco ha costruito la famiglia per composizione: LocalDate + LocalTime = LocalDateTime; LocalDateTime + ZoneId = ZonedDateTime; ZonedDateTimeInstant. 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) e ZonedDateTime.toLocalDateTime() chiudono i cicli.
  • Un singolo Instant ha stampato tre orari dall'"aspetto" diverso quando visti da New York, Berlino e Tokyo. Questo è il punto di Instant: è il momento, indipendentemente da dove ti trovi. Il ZonedDateTime aggiunge l'etichetta "dove mi trovo". Confondere i due è l'errore del Date legacy.
  • Duration ha stampato PT1H30M e Period ha stampato P3M. Il formato di durata ISO-8601 è PnYnMnDTnHnMnS — tutto prima della T sono unità di calendario (Period), tutto dopo sono unità di tempo (Duration). La stringa è esattamente ciò che toString() restituisce, ed esattamente ciò che parse(...) accetta.
  • today.plusDays(7) ha prodotto un LocalDate diverso. Stampare today di nuovo subito dopo ha mostrato che l'originale era invariato — questa è la garanzia di immutabilità. Ogni plus/minus/with restituisce 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 un long, non un Period, perché la risposta in giorni non ha ambiguità di calendario (a differenza dei mesi, che variano in lunghezza). Ogni capitolo di questa parte usa ChronoUnit da 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.

Esercitati

Pratica
Devi memorizzare il momento in cui un server ha ricevuto una richiesta HTTP, in modo che il log possa essere ordinato globalmente tra server in fusi orari diversi. Quale tipo `java.time` è adatto?
Devi memorizzare il momento in cui un server ha ricevuto una richiesta HTTP, in modo che il log possa essere ordinato globalmente tra server in fusi orari diversi. Quale tipo `java.time` è adatto?
Was this page helpful?