Zonas horarias en Go: cómo no perder una hora, un día o datos

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

Introducción: por qué el tiempo no es solo tic-tac

\n\n

Si alguna vez has escrito una aplicación en Go que trabaja con fechas y usuarios de diferentes países, seguramente te has enfrentado al dolor de cabeza llamado «zonas horarias». Parece sencillo, ¿verdad? Tomas time.Now() y listo. Pero solo hasta que tu servidor está en UTC y el cliente en MSK+3, y tu «hoy» se convierte mágicamente en «ayer».

\n\n

En este artículo analizaremos cómo trabajar correctamente con zonas horarias en Go, con ejemplos reales, sin rodeos y con fragmentos de código listos para usar. Aprenderás cómo no perder datos, no volverte loco durante la depuración y hacer que tu aplicación sea resistente a los husos horarios. ¡Allá vamos!

\n\n

Conceptos básicos: qué se esconde detrás de time.Time

\n\n

En Go, el tipo time.Time no es solo un número de milisegundos. Es una estructura que almacena dos componentes clave:

\n\n
    \n
  • Tiempo absoluto — la cantidad de nanosegundos desde el 1 de enero de 1970 (timestamp Unix).
  • \n
  • Location — un puntero a la zona horaria (huso horario).
  • \n
\n\n

Es precisamente Location lo que determina cómo se mostrará la hora: como 14:00 MSK o como 11:00 UTC. El error más peligroso de los principiantes es pensar que time.Now() devuelve la «hora local». En realidad, devuelve la hora actual con la zona local del servidor. Traslada el código a otro servidor y todo «se desvía».

\n\n

Cómo crear tiempo con una zona específica

\n\n

La forma más sencilla es usar time.LoadLocation. Carga la zona desde la base de datos del sistema IANA (generalmente en /usr/share/zoneinfo).

\n\n
package main\n\nimport (\n    "fmt"\n    "time"\n)\n\nfunc main() {\n    loc, err := time.LoadLocation("Europe/Moscow")\n    if err != nil {\n        panic(err)\n    }\n\n    // Creamos tiempo en una zona específica\n    t := time.Date(2025, 3, 15, 14, 30, 0, 0, loc)\n    fmt.Println(t) // 2025-03-15 14:30:00 +0300 MSK\n}\n
\n\n

Ten en cuenta: time.Date recibe Location como último argumento. Si pasas time.UTC o time.Local, el comportamiento será diferente.

\n\n

¿Y si la zona no está en el sistema?

\n\n

A veces (por ejemplo, en imágenes Docker mínimas) la base de datos zoneinfo no está presente. La solución es usar time.FixedZone para crear una zona con un desplazamiento fijo:

\n\n
loc := time.FixedZone("MSK", 3*60*60) // +3 horas desde UTC\nt := time.Now().In(loc)\nfmt.Println(t)\n
\n\n

Inconveniente: FixedZone no conoce el horario de verano ni los cambios históricos. Para la mayoría de las aplicaciones modernas esto es aceptable, pero si necesitas precisión histórica (por ejemplo, para operaciones financieras del año 2014), usa LoadLocation.

\n\n

Regla principal del desarrollo backend: almacenar en UTC

\n\n

La regla de oro para trabajar con zonas horarias en Go (y en cualquier otro lenguaje) es almacenar siempre el tiempo en UTC y convertir solo a nivel de visualización. ¿Por qué?

\n\n
    \n
  • El servidor puede mudarse a otro huso horario.
  • \n
  • Los usuarios pueden estar en diferentes zonas.
  • \n
  • La comparación y ordenación del tiempo UTC es trivial.
  • \n
  • Los registros (logs) son más fáciles de analizar.
  • \n
\n\n

Así es como se ve en la práctica:

\n\n
// Obtenemos la hora del usuario (por ejemplo, desde un formulario)\nuserTime := "2025-03-15 14:30"\nuserLoc, _ := time.LoadLocation("Asia/Tokyo")\n\n// Parseamos como hora local del usuario\nt, _ := time.ParseInLocation("2006-01-02 15:04", userTime, userLoc)\n\n// Convertimos a UTC para almacenar\nutcTime := t.UTC()\nfmt.Println(utcTime) // 2025-03-15 05:30:00 +0000 UTC\n
\n\n

Error típico: pérdida de Location al serializar

\n\n

Cuando guardas time.Time en una base de datos (por ejemplo, PostgreSQL a través de database/sql), el controlador puede «perder» el Location y dejarlo en UTC o Local. Para evitar sorpresas, siempre convierte

Blogs

Book Recommendations