Cómo manejar zonas horarias en TanStack Start v1
Mostrar horarios en la zona horaria local del usuario
Problema
Cuando las aplicaciones muestran horas, representan un momento único que aparece de manera diferente según la ubicación del usuario. Si un servidor muestra "8:00 PM" en su propia zona horaria o UTC, los usuarios en diferentes zonas horarias verán una hora local incorrecta, lo que genera confusión sobre cuándo ocurren realmente los eventos. Los usuarios esperan que las horas reflejen automáticamente su zona horaria local, pero sin un manejo adecuado, ven las horas en cualquier zona horaria que el servidor utilizó durante la renderización. Este desajuste hace que los usuarios pierdan citas, malinterpreten horarios y pierdan confianza en la fiabilidad de la aplicación.
Solución
Pasa una opción timeZone a los métodos de formateo de fechas para controlar cómo se interpretan y muestran las fechas. La función formatDate acepta Intl.DateTimeFormatOptions que incluyen configuración de zona horaria. Almacena las marcas de tiempo en un formato universal como cadenas ISO 8601 o marcas de tiempo Unix, luego formátealas en el cliente donde la zona horaria del navegador está automáticamente disponible. La API Intl.DateTimeFormat utiliza la zona horaria predeterminada del usuario cuando no se especifica la opción timeZone, asegurando que las horas se muestren correctamente para la ubicación de cada usuario sin detección manual de zona horaria.
Pasos
1. Crear un componente de formateo de fechas con conciencia de zona horaria
Utiliza el hook useIntl para acceder al objeto intl y su método formatDate. Este componente acepta una marca de tiempo ISO y la formatea para la zona horaria local del usuario.
import { useIntl } from "react-intl";
interface LocalTimeProps {
timestamp: string;
showDate?: boolean;
showTime?: boolean;
}
export function LocalTime({
timestamp,
showDate = true,
showTime = true,
}: LocalTimeProps) {
const intl = useIntl();
const date = new Date(timestamp);
const formatted = intl.formatDate(date, {
...(showDate && {
year: "numeric",
month: "short",
day: "numeric",
}),
...(showTime && {
hour: "numeric",
minute: "2-digit",
timeZoneName: "short",
}),
});
return <time dateTime={timestamp}>{formatted}</time>;
}
El componente FormattedDate y el método formatDate utilizan las APIs de Intl.DateTimeFormat con DateTimeFormatOptions. El navegador aplica automáticamente la zona horaria del usuario cuando no se proporciona una timeZone explícita, convirtiendo la marca de tiempo UTC a hora local.
2. Usa el componente para mostrar horarios de eventos
Importa y utiliza el componente LocalTime donde necesites mostrar marcas de tiempo. El componente maneja la conversión de zona horaria automáticamente basándose en la configuración del navegador de cada usuario.
import { createFileRoute } from "@tanstack/react-router";
import { LocalTime } from "~/components/LocalTime";
export const Route = createFileRoute("/events/$eventId")({
component: EventDetail,
});
function EventDetail() {
const event = Route.useLoaderData();
return (
<div>
<h1>{event.title}</h1>
<p>
Comienza: <LocalTime timestamp={event.startTime} />
</p>
<p>
Finaliza: <LocalTime timestamp={event.endTime} />
</p>
</div>
);
}
Cada usuario ve los horarios del evento convertidos a su zona horaria local. La marca de tiempo ISO del servidor se analiza y formatea según las preferencias de configuración regional y zona horaria del usuario.
3. Formatea horarios con visualización explícita de zona horaria
Cuando los usuarios necesiten saber qué zona horaria representa un horario, incluye el nombre de la zona horaria en las opciones de formato.
import { useIntl } from "react-intl";
interface ExplicitTimezoneProps {
timestamp: string;
timezone?: string;
}
export function ExplicitTimezone({
timestamp,
timezone,
}: ExplicitTimezoneProps) {
const intl = useIntl();
const date = new Date(timestamp);
const formatted = intl.formatDate(date, {
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
timeZone: timezone,
timeZoneName: "long",
});
return <time dateTime={timestamp}>{formatted}</time>;
}
Especificar una opción timeZone obliga al formateador a mostrar la hora en esa zona horaria específica. Cuando timezone es undefined, el navegador utiliza la zona horaria local del usuario. Esto es útil para mostrar horarios en una zona horaria específica independientemente de dónde se encuentre el usuario.
4. Crea un helper para mostrar tiempo relativo
Para eventos recientes, muestra tiempos relativos como "hace 2 horas" mientras preservas el tiempo absoluto en un tooltip.
import { useIntl } from "react-intl";
interface RelativeTimeProps {
timestamp: string;
}
export function RelativeTime({ timestamp }: RelativeTimeProps) {
const intl = useIntl();
const date = new Date(timestamp);
const now = Date.now();
const diffInSeconds = Math.floor((now - date.getTime()) / 1000);
const absoluteTime = intl.formatDate(date, {
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
});
let value: number;
let unit: Intl.RelativeTimeFormatUnit;
if (diffInSeconds < 60) {
value = -diffInSeconds;
unit = "second";
} else if (diffInSeconds < 3600) {
value = -Math.floor(diffInSeconds / 60);
unit = "minute";
} else if (diffInSeconds < 86400) {
value = -Math.floor(diffInSeconds / 3600);
unit = "hour";
} else {
value = -Math.floor(diffInSeconds / 86400);
unit = "day";
}
const relativeTime = intl.formatRelativeTime(value, unit);
return (
<time dateTime={timestamp} title={absoluteTime}>
{relativeTime}
</time>
);
}
El tiempo relativo actualiza la visualización para mostrar duraciones amigables mientras que el atributo title preserva la hora local exacta para los usuarios que pasan el cursor por encima. Ambos formatos respetan automáticamente la zona horaria y la configuración regional del usuario.