Cómo mostrar horas a los usuarios en su zona horaria local
Tu servidor guarda las horas en UTC. Tus usuarios están en todas partes. El trabajo de tu frontend es convertir UTC a la hora local de cada usuario y mostrarlo de forma legible. Es más sencillo de lo que solía ser, pero hay varias formas de equivocarse.
Paso 1: Obtener la zona horaria del usuario
const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
// → "America/New_York" o "Europe/London", etc.
Esto devuelve un nombre de zona IANA desde la configuración de locale del navegador. Fiable en navegadores modernos. Guárdalo en el perfil del usuario para que las páginas renderizadas en el servidor también puedan usarlo — no te fíes únicamente de la conversión en el cliente para nada que esté indexado o en caché.
Deja que los usuarios lo cambien. Algunos viajan; algunos sistemas reportan la zona incorrecta.
Paso 2: Convertir y formatear en el navegador
La API Intl.DateTimeFormat es la herramienta correcta para esto. Gestiona el horario de verano automáticamente y respeta las convenciones de formato del locale (12h vs 24h, orden de fecha, etc.):
const utcTimestamp = "2026-06-20T19:00:00Z"; // de tu API
const date = new Date(utcTimestamp);
const formatter = new Intl.DateTimeFormat("en-US", {
timeZone: "America/New_York",
dateStyle: "full",
timeStyle: "short",
});
formatter.format(date);
// → "Saturday, June 20, 2026 at 3:00 PM"
Para un usuario en Tokio:
new Intl.DateTimeFormat("ja-JP", {
timeZone: "Asia/Tokyo",
dateStyle: "full",
timeStyle: "short",
}).format(date);
// → "2026年6月21日日曜日 4:00" (día siguiente en Tokio)
Paso 3: Incluir el nombre de zona en la visualización
Mostrar “3:00 PM” sin una etiqueta de zona deja a los usuarios inseguros sobre si la conversión ocurrió. Incluye la abreviación de la zona o la ciudad:
new Intl.DateTimeFormat("en-US", {
timeZone: "America/New_York",
timeStyle: "long", // incluye la abreviación de la zona
}).format(date);
// → "3:00:00 PM EDT"
Para herramientas de agenda, considera mostrar tanto la hora local del usuario como UTC:
3:00 PM EDT (19:00 UTC)
Esto permite a los usuarios verificar si la conversión es correcta cuando no están seguros.
Errores comunes
Restar horas manualmente:
// Mal: hardcodea UTC-4, se rompe cuando cambia el horario de verano
const localTime = new Date(utcTimestamp.getTime() - 4 * 60 * 60 * 1000);
No hagas esto. Usa Intl.DateTimeFormat con un nombre de zona IANA — aplica el offset correcto para cada fecha específica de forma automática.
toLocaleString() sin opción timeZone:
// Poco fiable: usa el locale del sistema, que varía entre servidor y navegador
date.toLocaleString();
Siempre pasa timeZone explícitamente.
“Hoy” y “mañana” basados en la fecha UTC:
2026-06-20T23:00:00Z es el 20 de junio en Nueva York y el 21 de junio en Tokio. Si etiquetas eventos como “hoy” o “mañana” basándote en la fecha UTC, los usuarios en algunas zonas ven la etiqueta incorrecta.
function isToday(utcTimestamp, userTimeZone) {
const now = new Date();
const formatter = new Intl.DateTimeFormat("en-CA", {
// en-CA da YYYY-MM-DD
timeZone: userTimeZone,
dateStyle: "short",
});
return formatter.format(new Date(utcTimestamp)) === formatter.format(now);
}
Ordenar por hora de visualización: Siempre ordena los eventos por su timestamp UTC. Ordenar por cadenas de hora local se rompe durante los cambios de horario y en comparaciones entre zonas.
Renderizado en el servidor
Si renderizas horas en el servidor, necesitas la zona IANA del usuario en el momento de renderizado. Guárdala en la sesión o el perfil del usuario:
// Node.js — Intl está disponible en versiones modernas de Node
new Intl.DateTimeFormat("en-US", {
timeZone: user.timezone, // 'America/New_York' del perfil
dateStyle: "full",
timeStyle: "short",
}).format(new Date(utcTimestamp));
Cuidado con los desajustes de hidratación: si el servidor renderiza una hora en UTC y el cliente la vuelve a renderizar en hora local, verás un parpadeo de contenido incorrecto. O renderiza las horas solo en el cliente, o asegúrate de que el servidor tiene la zona correcta en el momento de renderizado.