CodeGym /Cursos /JAVA 25 SELF /ZonedDateTime, Instant, trabajo con zonas horarias

ZonedDateTime, Instant, trabajo con zonas horarias

JAVA 25 SELF
Nivel 13 , Lección 3
Disponible

1. Concepto de zona horaria

En el mundo existen muchas zonas horarias: cuando en Minsk es mediodía, en Nueva York apenas es por la mañana y en Tokio ya es por la tarde. Si almacenas fecha y hora sin tener en cuenta la zona horaria, es fácil generar confusión: por ejemplo, si tu servidor está en Alemania y el usuario en Vladivostok, la visualización de "2025-06-01 12:00" significará cosas muy distintas para cada uno.

Zona horaria (timezone) — es el conjunto de reglas que determina cuánto tiempo hay que sumar o restar respecto a la hora de Greenwich (UTC) para obtener la hora «local» de una región concreta.

En Java se utiliza la clase ZoneId para trabajar con zonas horarias. He aquí algunos ejemplos de identificadores de zona:

  • "Europe/Minsk"
  • "UTC"
  • "America/New_York"
  • "Asia/Tokyo"

¿Por qué es importante?

  • Mostrar la hora correctamente a usuarios de distintos países.
  • Registrar correctamente la hora de los eventos (por ejemplo, logging, reservas de billetes, plazos).
  • Tener en cuenta el cambio al horario de verano/invierno (¡gracias, Europa!).

2. ZonedDateTime — fecha y hora con zona horaria

ZonedDateTime es una clase que almacena fecha, hora y la información de la zona horaria. Es como LocalDateTime, pero además «sabe» en qué región se encuentra.

Creación de ZonedDateTime

Fecha y hora actuales en la zona horaria del sistema

import java.time.ZonedDateTime;

ZonedDateTime now = ZonedDateTime.now();
System.out.println(now); // Por ejemplo: 2025-06-01T15:30:00+03:00[Europe/Minsk]

Hora en una zona horaria concreta

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("Nueva York: " + newYorkTime);

Creación a partir de 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]

Obtención y establecimiento de la zona horaria

ZoneId tokyoZone = ZoneId.of("Asia/Tokyo");
ZonedDateTime tokyoTime = ZonedDateTime.now(tokyoZone);
System.out.println("Tokio: " + tokyoTime);

Conversión entre zonas: withZoneSameInstant()

A veces necesitas saber cómo se ve el mismo evento en otra zona. Para ello usamos 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("Hora de la reunión en Minsk: " + MinskMeeting);
System.out.println("El mismo evento en Nueva York: " + newYorkMeeting);

Atención: withZoneSameInstant() convierte la hora de forma que corresponda al mismo momento en otra zona. Si usas withZoneSameLocal(), la fecha y la hora quedarán iguales y solo cambiará la zona — ¡esto casi siempre es un error!

3. Instant — punto absoluto en el tiempo

Instant es una clase que representa un momento absoluto en el tiempo, independientemente de la zona horaria. Técnicamente, es la cantidad de segundos y nanosegundos transcurridos desde el 1 de enero de 1970 en Greenwich (UTC). Si el tiempo tuviera un pasaporte, Instant sería su número.

Creación de Instant

import java.time.Instant;

Instant now = Instant.now();
System.out.println(now); // Por ejemplo: 2025-06-01T12:30:00.123Z

Fíjate en la letra Z: significa «Zulu time», es decir, UTC.

Creación a partir de segundos desde la época Unix

Instant fromEpoch = Instant.ofEpochSecond(1685616000L);
System.out.println(fromEpoch); // 2023-06-01T00:00:00Z

Conversión InstantZonedDateTime/LocalDateTime

De ZonedDateTime a Instant

ZonedDateTime zoned = ZonedDateTime.now();
Instant instant = zoned.toInstant();
System.out.println(instant);

De Instant a ZonedDateTime

ZoneId zone = ZoneId.of("Europe/Minsk");
ZonedDateTime fromInstant = Instant.now().atZone(zone);
System.out.println(fromInstant);

De 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. Práctica: hora actual en distintas zonas y conversión entre zonas

Obtener la hora actual en distintas zonas horarias

Hagamos una miniaplicación que muestra la hora actual en Minsk, Nueva York y Tokio:

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("Nueva York:  " + newYork);
        System.out.println("Tokio:     " + tokyo);
    }
}

Conversión de la hora entre zonas

Supongamos que tienes un evento programado a las 18:00 en Minsk. ¿A qué hora será eso en Nueva York y Tokio?

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("Reunión en Minsk:   " + minskEvent);
        System.out.println("En Nueva York:        " + newYorkEvent);
        System.out.println("En Tokio:            " + tokyoEvent);
    }
}

Conversión de LocalDateTime a ZonedDateTime y viceversa

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. Observaciones importantes y matices

Por qué no debes almacenar solo LocalDateTime

LocalDateTime es solo fecha y hora sin zona horaria. Para la mayoría de las lógicas de negocio esto no basta. Por ejemplo, si almacenas "2025-06-01 12:00" como LocalDateTime, para un usuario de Minsk y otro de Nueva York serán momentos distintos en el tiempo real.

Siempre almacena el tiempo absoluto (por ejemplo, Instant), o la hora con zona (ZonedDateTime) si el evento está realmente ligado a una zona concreta. LocalDateTime es adecuado solo si trabajas con fechas «flotantes» (por ejemplo, cumpleaños sin tener en cuenta la hora del día ni la zona).

Problemas con el cambio al horario de verano/invierno

Las zonas horarias no son solo el desplazamiento respecto a UTC, sino también las reglas de cambio al horario de verano/invierno. Por ejemplo, en algunos países en un día concreto se adelanta o se atrasa una hora — y si solo almacenas LocalDateTime, no sabrás si esa hora existió realmente.

Ejemplo de «agujero en el tiempo»:

  • En Estados Unidos, en marzo, a las 2:00 se adelanta a las 3:00.
  • ¡La hora "2025-03-10 02:30" en Nueva York no existió!

Trabajando con ZonedDateTime estás protegido frente a estas sorpresas: la biblioteca comprueba por sí misma la validez de la hora.

Esquema: cómo se relacionan LocalDateTime, ZonedDateTime, Instant

graph TD
    A["LocalDateTime 
(fecha + hora, sin zona)"] -->|+ ZoneId| B["ZonedDateTime
(fecha + hora + zona)"] B -->|"toInstant()"| C["Instant
(tiempo absoluto, UTC)"] C -->|"atZone(ZoneId)"| B B -->|"toLocalDateTime()"| A

6. Errores típicos al trabajar con ZonedDateTime e Instant

Error n.º 1: usar LocalDateTime para eventos globales.
Si almacenas la fecha y hora de una reunión de usuarios de distintos países como LocalDateTime, cada uno verá su «12:00», aunque se trate de momentos distintos. Para eventos globales usa ZonedDateTime o Instant.

Error n.º 2: ignorar la zona horaria al parsear una cadena.
Si parseas la cadena "2025-06-01T12:00:00" sin indicar la zona, obtendrás un LocalDateTime, no un ZonedDateTime. Para obtener un ZonedDateTime, usa cadenas con zona o añádela explícitamente.

Error n.º 3: conversión incorrecta entre zonas.
Usar withZoneSameLocal() en lugar de withZoneSameInstant() puede llevar a horas incorrectas. Usa siempre withZoneSameInstant() si quieres obtener el mismo momento en otra zona.

Error n.º 4: no tener en cuenta el cambio al horario de verano/invierno.
Si planeas eventos en el límite del cambio, usa obligatoriamente ZonedDateTime y confía en la biblioteca: conoce todos los cambios y «agujeros» en el tiempo.

Error n.º 5: comparar ZonedDateTime sin tener en cuenta la zona.
Dos ZonedDateTime con zonas diferentes, pero la misma hora local, pueden representar momentos distintos. Para comparar, usa toInstant().

1
Tarea
JAVA 25 SELF, nivel 13, lección 3
Bloqueada
Centro de mando global: hora en todo el mundo 🌍
Centro de mando global: hora en todo el mundo 🌍
1
Tarea
JAVA 25 SELF, nivel 13, lección 3
Bloqueada
Conferencia internacional: fijación del evento en la zona horaria 🌐
Conferencia internacional: fijación del evento en la zona horaria 🌐
1
Tarea
JAVA 25 SELF, nivel 13, lección 3
Bloqueada
Teletransporte programado: un mismo evento en diferentes zonas horarias 🚀
Teletransporte programado: un mismo evento en diferentes zonas horarias 🚀
1
Tarea
JAVA 25 SELF, nivel 13, lección 3
Bloqueada
Crónicas del tiempo: viaje a través de Instant 🌌
Crónicas del tiempo: viaje a través de Instant 🌌
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION