Cómo convertir la hora local de un usuario a UTC antes de guardarla

Desarrollador
scheduling

Un usuario reserva una reunión para “las 3pm del viernes”. Lo que se escribe en tu base de datos depende de una sola pregunta: ¿convertiste su hora local a UTC antes de guardarlo?

Si no, has almacenado 2026-06-20 15:00:00 sin ningún contexto de zona horaria. Cuando lo recuperes y se lo muestres a otro usuario — o al mismo usuario después de un cambio de horario — la hora estará mal.

La regla: siempre guarda UTC. Siempre convierte desde la zona IANA del usuario en el punto de entrada. Nunca guardes un offset fijo.

El flujo correcto de entrada

El usuario selecciona: viernes 20 de junio, 3:00pm
Zona horaria del usuario: America/New_York (desde el navegador o perfil)

Convertir en la entrada:
  2026-06-20T15:00:00 America/New_York
  → 2026-06-20T19:00:00Z  (UTC, porque EDT = UTC−4 en junio)

Guardar: 2026-06-20T19:00:00Z

Al recuperarlo, convierte de vuelta a la hora local del usuario:

2026-06-20T19:00:00Z → 3:00pm America/New_York ✓
2026-06-20T19:00:00Z → 8:00pm Europe/London ✓  (para otro asistente)

Obtener la zona horaria del usuario

En el navegador:

const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
// → "America/New_York"

Envía esto a tu backend junto con la cadena de fecha y hora. No lo tomes ciegamente como válido — deja que los usuarios lo confirmen o lo cambien en su perfil. Algunos dispositivos reportan zonas incorrectas.

Node.js con date-fns-tz:

import { fromZonedTime } from "date-fns-tz";

const localDateString = "2026-06-20T15:00:00"; // de la entrada del usuario
const timeZone = "America/New_York"; // del navegador/perfil

const utcDate = fromZonedTime(localDateString, timeZone);
// utcDate es un objeto Date de JS que representa 2026-06-20T19:00:00Z

Python (con zoneinfo, Python 3.9+):

from datetime import datetime
from zoneinfo import ZoneInfo

local_dt = datetime(2026, 6, 20, 15, 0, 0, tzinfo=ZoneInfo("America/New_York"))
utc_dt = local_dt.astimezone(ZoneInfo("UTC"))
# utc_dt → 2026-06-20 19:00:00+00:00

La trampa de la ambigüedad del cambio de horario

La noche en que los relojes se retrasan, la 1:30am America/New_York ocurre dos veces — una antes de la transición y otra después. Si un usuario elige una hora en esa franja repetida, no puedes saber cuál de las dos ocurrencias quiere decir.

La mayoría de las librerías usan por defecto la ocurrencia anterior (pre-transición). Haz esto explícito en tu código en lugar de depender del comportamiento predeterminado. Para herramientas de agenda, la solución más limpia es no ofrecer franjas en la hora ambigua de las noches de cambio de horario, o marcarlas y pedir al usuario que confirme.

Qué guardar

  • Un timestamp UTC (TIMESTAMP WITH TIME ZONE en PostgreSQL, UTC ISO 8601, o epoch Unix)
  • La cadena de zona IANA del usuario (America/New_York) junto al evento, para mostrarlo
  • No: una fecha y hora local sin zona, ni un offset numérico como −04:00

La zona IANA es para mostrar la hora. El timestamp UTC es para comparaciones, ordenación y cálculos de calendario.

Buy me a coffe