Cómo manejar zonas horarias en Next.js (Pages Router) v16
Mostrar horas en la zona horaria local del usuario
Problema
Cuando las aplicaciones muestran horarios sin tener en cuenta la ubicación del usuario, surge confusión. Una reunión programada para las "3:00 PM" significa momentos diferentes dependiendo de si el espectador está en Nueva York, Londres o Tokio. Si el servidor muestra una hora en UTC o en su propia zona horaria, los usuarios en otras regiones ven horas locales incorrectas, lo que lleva a citas perdidas y errores de programación. La misma marca de tiempo debe interpretarse de manera diferente para cada usuario según su zona horaria.
Solución
Almacena las marcas de tiempo en un formato universal como ISO 8601 o timestamps Unix, y luego formátealas para mostrarlas en la zona horaria local del usuario en el cliente. Detecta la zona horaria del usuario utilizando la API de Internacionalización del navegador y pásala a las funciones de formateo de react-intl. Esto asegura que cada usuario vea los horarios ajustados a su propia zona horaria, eliminando la ambigüedad sobre cuándo ocurren los eventos.
Pasos
1. Detectar la zona horaria del usuario en el cliente
El método Intl.DateTimeFormat().resolvedOptions().timeZone del navegador devuelve el identificador de zona horaria IANA del usuario, como America/New_York o Europe/London. Crea un hook personalizado para acceder a este valor.
import { useState, useEffect } from "react";
export function useUserTimeZone() {
const [timeZone, setTimeZone] = useState<string>("UTC");
useEffect(() => {
const detected = Intl.DateTimeFormat().resolvedOptions().timeZone;
setTimeZone(detected);
}, []);
return timeZone;
}
Este hook se ejecuta solo en el cliente, evitando desajustes de hidratación, y usa UTC por defecto hasta que se detecta la zona horaria.
2. Formatear fechas con la zona horaria del usuario usando FormattedDate
Pasa la opción timeZone a los métodos de formateo de react-intl para controlar qué zona horaria se utiliza para la visualización. Usa el componente <FormattedDate> con la zona horaria detectada.
import { FormattedDate } from "react-intl";
import { useUserTimeZone } from "../hooks/useUserTimeZone";
interface EventDateProps {
timestamp: string;
}
export function EventDate({ timestamp }: EventDateProps) {
const userTimeZone = useUserTimeZone();
return (
<FormattedDate
value={new Date(timestamp)}
timeZone={userTimeZone}
year="numeric"
month="long"
day="numeric"
hour="numeric"
minute="2-digit"
timeZoneName="short"
/>
);
}
La prop timeZone asegura que la fecha se formatee en la zona horaria local del usuario en lugar de la del servidor o UTC.
3. Formatear tiempos imperativamente con useIntl
Para escenarios donde necesitas una cadena formateada en lugar de un componente, utiliza el hook useIntl para acceder a la API de formateo imperativa.
import { useIntl } from "react-intl";
import { useUserTimeZone } from "../hooks/useUserTimeZone";
interface MeetingTimeProps {
startTime: string;
}
export function MeetingTime({ startTime }: MeetingTimeProps) {
const intl = useIntl();
const userTimeZone = useUserTimeZone();
const formattedTime = intl.formatDate(new Date(startTime), {
timeZone: userTimeZone,
hour: "numeric",
minute: "2-digit",
timeZoneName: "short",
});
return <span title={startTime}>{formattedTime}</span>;
}
El método formatDate acepta un segundo argumento con DateTimeFormatOptions, incluyendo timeZone, permitiendo un control total sobre cómo se muestra la marca de tiempo.
4. Pasar marcas de tiempo del servidor como cadenas ISO
En getServerSideProps, obtén datos en el momento de la solicitud y pásalos al componente de página como props. Serializa las fechas como cadenas ISO 8601 para preservar la información de zona horaria.
import { GetServerSideProps } from "next";
interface Event {
id: string;
title: string;
startTime: string;
}
interface EventPageProps {
event: Event;
}
export const getServerSideProps: GetServerSideProps<
EventPageProps
> = async () => {
const event = {
id: "1",
title: "Team Meeting",
startTime: new Date("2025-02-15T15:00:00Z").toISOString(),
};
return {
props: { event },
};
};
export default function EventPage({ event }: EventPageProps) {
return (
<div>
<h1>{event.title}</h1>
<EventDate timestamp={event.startTime} />
</div>
);
}
Las cadenas ISO preservan el momento exacto en el tiempo, permitiendo que el formateo del lado del cliente las convierta con precisión a cualquier zona horaria.