Как работать с часовыми поясами в TanStack Start v1
Показывайте время в часовом поясе пользователя
Проблема
Когда приложения отображают время, они показывают один и тот же момент, который выглядит по-разному в зависимости от местоположения пользователя. Если сервер выводит «20:00» в своём часовом поясе или по UTC, пользователи из других часовых поясов увидят некорректное локальное время, что приводит к путанице относительно времени событий. Пользователи ожидают, что время будет автоматически отображаться в их локальном часовом поясе, но без правильной обработки они видят время в том поясе, который использовал сервер при рендеринге. Такое несоответствие приводит к тому, что пользователи пропускают встречи, неправильно понимают расписание и теряют доверие к надёжности приложения.
Решение
Передавайте опцию timeZone в методы форматирования дат, чтобы управлять тем, как даты интерпретируются и отображаются. Функция formatDate принимает Intl.DateTimeFormatOptions, которые включают настройку часового пояса. Храните временные метки в универсальном формате, например, в виде строк ISO 8601 или Unix-меток времени, а затем форматируйте их на клиенте, где автоматически доступен часовой пояс браузера. API Intl.DateTimeFormat использует часовой пояс пользователя по умолчанию, если не указана опция timeZone, что гарантирует корректное отображение времени для каждого пользователя без ручного определения часового пояса.
Шаги
1. Создайте компонент для форматирования дат с учётом часового пояса
Используйте хук useIntl, чтобы получить доступ к объекту intl и его методу formatDate. Этот компонент принимает ISO-метку времени и форматирует её для локального часового пояса пользователя.
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>;
}
Компонент FormattedDate и метод formatDate используют API Intl.DateTimeFormat с DateTimeFormatOptions. Браузер автоматически применяет часовой пояс пользователя, если явно не указан timeZone, преобразуя UTC-метку времени в локальное время.
2. Используйте компонент для отображения времени событий
Импортируйте и используйте компонент LocalTime везде, где нужно показать временные метки. Компонент автоматически конвертирует время в часовой пояс пользователя на основе настроек его браузера.
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>
Starts: <LocalTime timestamp={event.startTime} />
</p>
<p>
Ends: <LocalTime timestamp={event.endTime} />
</p>
</div>
);
}
Каждый пользователь видит время событий, переведённое в его локальный часовой пояс. ISO-метка времени с сервера разбирается и форматируется в соответствии с локалью и часовым поясом пользователя.
3. Форматируйте время с явным указанием часового пояса
Если пользователю важно знать, в каком часовом поясе указано время, добавьте название часового пояса в параметры форматирования.
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>;
}
Указание параметра timeZone заставляет форматтер отображать время именно в этом часовом поясе. Если timeZone не задан, браузер использует локальный часовой пояс пользователя. Это удобно, если нужно показать время в определённом часовом поясе независимо от местоположения пользователя.
4. Создайте помощник для отображения относительного времени
Для недавних событий показывайте относительное время, например «2 часа назад», а абсолютное время сохраняйте во всплывающей подсказке.
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>
);
}
Относительное время отображает удобные для восприятия интервалы, а атрибут title сохраняет точное локальное время для пользователей при наведении. Оба формата автоматически учитывают часовой пояс и локаль пользователя.