Zonas horarias en Kotlin: cómo no volverse loco con el tiempo

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Por qué el tiempo es un dolor (y cómo Kotlin lo cura)

\n\n

Si alguna vez ha escrito una aplicación que atiende a usuarios de diferentes husos horarios, conoce este dolor. La solicitud llega con una marca de tiempo, en la base de datos está UTC, el usuario en Nueva York se queja de que la notificación llegó a las 3 de la madrugada, pero en la base de datos son las 8 a.m. ¿Le suena familiar?

\n\n

Trabajar con zonas horarias es esa área de la programación donde la aparente simplicidad se convierte en decenas de errores. Pero Kotlin, que heredó las mejores prácticas de Java, nos proporciona un potente arsenal para combatir el caos. En este artículo, analizaremos cómo trabajar correctamente con zonas horarias en Kotlin, evitar errores típicos y escribir código que no se rompa durante el cambio al horario de verano.

\n\n

Breve introducción: ¿qué es una zona horaria?

\n\n

Antes de sumergirnos en el código, acordemos los términos. Una zona horaria (time zone) no es solo un desplazamiento respecto a UTC. También es un conjunto de reglas para el cambio al horario de verano/invierno (DST). Por ejemplo, Europe/Moscow no es solo +3 horas, sino también cambios históricos (en 2014, Rusia dejó de cambiar la hora).

\n\n

En el mundo Java/Kotlin, dos conceptos clave se encargan de esto:

\n\n
    \n
  • ZoneId — identificador de zona horaria (por ejemplo, "America/New_York"). Contiene todo el historial de DST.
  • \n
  • ZoneOffset — solo el desplazamiento (por ejemplo, +05:00). Sin reglas de cambio.
  • \n
\n\n

La diferencia es crítica: si usa ZoneOffset en lugar de ZoneId, su código se romperá el día del cambio de hora. Recuérdelo.

\n\n

Clases básicas de java.time en Kotlin

\n\n

Kotlin es totalmente compatible con el paquete java.time (Java 8+). Si programa para Android, no olvide agregar desugaring o use ThreeTenABP. Para Kotlin de servidor no hay problemas.

\n\n

Instant — momento en el tiempo

\n\n

Instant es un momento absoluto en la línea de tiempo. No está vinculado a un huso horario. Es precisamente lo que debería almacenar en la base de datos.

\n\n
import java.time.Instant\n\nfun main() {\n    val now = Instant.now()\n    println(now) // 2025-03-15T10:30:00.123Z\n}
\n\n

Observe la letra Z al final: es la hora Zulu, es decir, UTC. Almacene todo en UTC — esa es la regla de oro.

\n\n

ZonedDateTime — hora con zona

\n\n

Cuando necesite mostrar la hora al usuario, use ZonedDateTime. Almacena fecha, hora, desplazamiento e identificador de zona.

\n\n
import java.time.ZonedDateTime\nimport java.time.ZoneId\n\nfun main() {\n    val moscowTime = ZonedDateTime.now(ZoneId.of("Europe/Moscow"))\n    println(moscowTime) // 2025-03-15T13:30:00.123+03:00[Europe/Moscow]\n}
\n\n

¿Ve los corchetes? Eso es precisamente ZoneId, que permite al sistema manejar correctamente el DST.

\n\n

OffsetDateTime — hora con desplazamiento

\n\n

Si no necesita información de zona, solo el desplazamiento, use OffsetDateTime. Es más ligero y rápido.

\n\n
import java.time.OffsetDateTime\nimport java.time.ZoneOffset\n\nfun main() {\n    val offsetTime = OffsetDateTime.now(ZoneOffset.ofHours(5))\n    println(offsetTime) // 2025-03-15T15:30:00.123+05:00\n}
\n\n

Pero recuerde: si guarda un OffsetDateTime para Nueva York y luego EE. UU. cambia al horario de verano, su desplazamiento quedará obsoleto.

\n\n

Conversión entre zonas horarias en Kotlin

\n\n

La tarea más común es tomar la hora de la base de datos (UTC) y mostrarla en el huso horario del usuario. Analicemos cómo hacerlo correctamente.

\n\n

De UTC a zona local

\n\n
import java.time.Instant\nimport java.time.ZoneId\nimport java.time.ZonedDateTime\n\nfun convertUtcToLocal(utcInstant: Instant, targetZone: String): ZonedDateTime {\n    return utcInstant.atZone(ZoneId.of(targetZone))\n}\n\nfun main() {\n    val utcNow =                

Blogs

Book Recommendations