CodeGym /Corsi /JAVA 25 SELF /ZonedDateTime, Instant, gestione dei fusi orari

ZonedDateTime, Instant, gestione dei fusi orari

JAVA 25 SELF
Livello 13 , Lezione 3
Disponibile

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 InstantZonedDateTime/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().

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION