1. Concetto di fuso orario
Nel mondo esistono molti fusi orari: quando a Minsk è mezzogiorno, a New York è solo mattina e a Tokyo è già sera. Se memorizzi data e ora senza considerare il fuso orario, è facile creare confusione: ad esempio, se il tuo server è in Germania e l’utente è a Vladivostok, la visualizzazione dell’orario "2025-06-01 12:00" significherà cose del tutto diverse per ciascuno.
Fuso orario (timezone) — è l’insieme di regole che determinano quanto aggiungere o sottrarre dal tempo di Greenwich (UTC) per ottenere l’ora «locale» di una specifica regione.
In Java per lavorare con i fusi orari si usa la classe ZoneId. Ecco alcuni esempi di identificatori di zona:
- "Europe/Minsk"
- "UTC"
- "America/New_York"
- "Asia/Tokyo"
Perché è importante?
- Visualizzazione corretta dell’ora per utenti di paesi diversi.
- Registrazione corretta dell’ora degli eventi (ad esempio, logging, prenotazione dei biglietti, scadenze).
- Gestione del passaggio all’ora legale/solare (grazie, Europa!).
2. ZonedDateTime — data e ora con fuso orario
ZonedDateTime è una classe che memorizza data, ora e informazioni sul fuso orario. È come LocalDateTime, ma in più «sa» in quale regione si trova.
Creare ZonedDateTime
Data e ora correnti nel fuso orario di sistema
import java.time.ZonedDateTime;
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now); // Ad esempio: 2025-06-01T15:30:00+03:00[Europe/Minsk]
Ora in un fuso orario specifico
import java.time.ZoneId;
ZonedDateTime MinskTime = ZonedDateTime.now(ZoneId.of("Europe/Minsk"));
ZonedDateTime newYorkTime = ZonedDateTime.now(ZoneId.of("America/New_York"));
System.out.println("Minsk: " + MinskTime);
System.out.println("New York: " + newYorkTime);
Creare da LocalDateTime
import java.time.LocalDateTime;
LocalDateTime meeting = LocalDateTime.of(2025, 6, 1, 18, 0);
ZonedDateTime meetingInMinsk = meeting.atZone(ZoneId.of("Europe/Minsk"));
System.out.println(meetingInMinsk); // 2025-06-01T18:00+03:00[Europe/Minsk]
Ottenere e impostare il fuso orario
ZoneId tokyoZone = ZoneId.of("Asia/Tokyo");
ZonedDateTime tokyoTime = ZonedDateTime.now(tokyoZone);
System.out.println("Tokyo: " + tokyoTime);
Conversione tra fusi: withZoneSameInstant()
A volte serve sapere come appare lo stesso evento in un altro fuso. Per questo si usa withZoneSameInstant():
ZonedDateTime MinskMeeting = ZonedDateTime.of(2025, 6, 1, 18, 0, 0, 0, ZoneId.of("Europe/Minsk"));
ZonedDateTime newYorkMeeting = MinskMeeting.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println("Orario dell'incontro a Minsk: " + MinskMeeting);
System.out.println("Lo stesso evento a New York: " + newYorkMeeting);
Attenzione: withZoneSameInstant() converte l’ora in modo che corrisponda allo stesso istante in un altro fuso. Se usi withZoneSameLocal(), la data e l’ora rimarranno le stesse ma cambierà il fuso — questo è quasi sempre un errore!
3. Instant — punto temporale assoluto
Instant è una classe che rappresenta un momento di tempo assoluto, indipendentemente dal fuso orario. Tecnicamente è il numero di secondi e nanosecondi trascorsi dal 1º gennaio 1970 a Greenwich (UTC). Se il tempo avesse un passaporto — Instant sarebbe il suo numero.
Creare Instant
import java.time.Instant;
Instant now = Instant.now();
System.out.println(now); // Ad esempio: 2025-06-01T12:30:00.123Z
Fai attenzione alla lettera Z — significa «Zulu time», cioè UTC.
Creare a partire dai secondi dall’epoca Unix
Instant fromEpoch = Instant.ofEpochSecond(1685616000L);
System.out.println(fromEpoch); // 2023-06-01T00:00:00Z
Conversione Instant ↔ ZonedDateTime/LocalDateTime
Da ZonedDateTime a Instant
ZonedDateTime zoned = ZonedDateTime.now();
Instant instant = zoned.toInstant();
System.out.println(instant);
Da Instant a ZonedDateTime
ZoneId zone = ZoneId.of("Europe/Minsk");
ZonedDateTime fromInstant = Instant.now().atZone(zone);
System.out.println(fromInstant);
Da Instant a LocalDateTime
import java.time.LocalDateTime;
import java.time.Instant;
import java.time.ZoneId;
LocalDateTime local = LocalDateTime.ofInstant(Instant.now(), ZoneId.of("Europe/Minsk"));
System.out.println(local);
4. Pratica: ora corrente in fusi diversi, conversione tra fusi
Ottenere l’ora corrente in fusi diversi
Facciamo una mini-app che mostra l’ora corrente a Minsk, New York e Tokyo:
import java.time.ZonedDateTime;
import java.time.ZoneId;
public class TimeZonesDemo {
public static void main(String[] args) {
ZonedDateTime Minsk = ZonedDateTime.now(ZoneId.of("Europe/Minsk"));
ZonedDateTime newYork = ZonedDateTime.now(ZoneId.of("America/New_York"));
ZonedDateTime tokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
System.out.println("Minsk: " + Minsk);
System.out.println("New York: " + newYork);
System.out.println("Tokyo: " + tokyo);
}
}
Conversione dell’ora tra fusi
Supponiamo che tu abbia un evento fissato alle 18:00 a Minsk. Come sapere a che ora sarà a New York e a Tokyo?
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class MeetingTime {
public static void main(String[] args) {
LocalDateTime eventTime = LocalDateTime.of(2025, 6, 1, 18, 0);
ZonedDateTime minskEvent = eventTime.atZone(ZoneId.of("Europe/Minsk"));
ZonedDateTime newYorkEvent = minskEvent.withZoneSameInstant(ZoneId.of("America/New_York"));
ZonedDateTime tokyoEvent = minskEvent.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
System.out.println("Incontro a Minsk: " + minskEvent);
System.out.println("A New York: " + newYorkEvent);
System.out.println("A Tokyo: " + tokyoEvent);
}
}
Conversione da LocalDateTime a ZonedDateTime e ritorno
Local → Zoned:
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
LocalDateTime localTime = LocalDateTime.of(2025, 6, 1, 14, 0);
ZonedDateTime zonedTime = localTime.atZone(ZoneId.of("Europe/Minsk"));
System.out.println(zonedTime);
Zoned → Local:
LocalDateTime extracted = zonedTime.toLocalDateTime();
System.out.println(extracted);
5. Note importanti e particolarità
Perché non conviene memorizzare solo LocalDateTime
LocalDateTime è solo data e ora senza fuso orario. Per la maggior parte delle logiche di business non basta! Per esempio, se memorizzi "2025-06-01 12:00" come LocalDateTime, per un utente di Minsk e per uno di New York rappresenterà momenti reali completamente diversi.
Conserva sempre il tempo assoluto (per esempio, Instant), oppure il tempo con fuso (ZonedDateTime) se l’evento è davvero legato a una zona specifica. LocalDateTime è adatto solo quando lavori con date «fluttuanti» (per esempio un compleanno, senza considerare ora del giorno e fuso).
Problemi con il passaggio all’ora legale/solare
I fusi orari non sono solo offset rispetto a UTC, ma anche regole del passaggio all’ora legale/solare. In alcuni paesi, in un determinato giorno, si sposta l’orologio avanti o indietro di un’ora — e se memorizzi solo LocalDateTime, non saprai nemmeno se quell’ora sia mai esistita.
Esempio di «buco nel tempo»:
- Negli Stati Uniti, in marzo, alle 2:00 di notte si passa direttamente alle 3:00.
- L’orario "2025-03-10 02:30" a New York non è esistito!
Lavorando con ZonedDateTime sei protetto da simili sorprese: la libreria verificherà da sola la correttezza del tempo.
Schema: come sono correlati LocalDateTime, ZonedDateTime, Instant
graph TD
A["LocalDateTime
(data + ora, senza zona)"] -->|+ ZoneId| B["ZonedDateTime
(data + ora + zona)"]
B -->|"toInstant()"| C["Instant
(tempo assoluto, UTC)"]
C -->|"atZone(ZoneId)"| B
B -->|"toLocalDateTime()"| A
6. Errori tipici con ZonedDateTime e Instant
Errore n. 1: usare LocalDateTime per eventi globali.
Se memorizzi data e ora di un incontro tra utenti di paesi diversi come LocalDateTime, ognuno vedrà il proprio «12:00», ma si tratterà di istanti diversi. Per eventi globali usa ZonedDateTime o Instant.
Errore n. 2: ignorare il fuso orario durante il parsing di una stringa.
Se fai il parsing della stringa "2025-06-01T12:00:00" senza indicare il fuso, otterrai un LocalDateTime, non un ZonedDateTime. Per ottenere ZonedDateTime, usa stringhe con il fuso o aggiungilo esplicitamente.
Errore n. 3: conversione errata tra fusi.
Usare withZoneSameLocal() invece di withZoneSameInstant() può portare a un’ora sbagliata. Usa sempre withZoneSameInstant() se vuoi ottenere lo stesso istante in un altro fuso.
Errore n. 4: non considerare il passaggio all’ora legale/solare.
Se pianifichi eventi a ridosso del cambio d’ora, usa sempre ZonedDateTime e fidati della libreria — conosce tutti i cambi e i «buchi» nel tempo.
Errore n. 5: confrontare ZonedDateTime senza considerare il fuso.
Due ZonedDateTime con fusi diversi ma la stessa ora locale possono rappresentare istanti diversi. Per confrontarli usa toInstant().
GO TO FULL VERSION