Как работать с часовыми поясами в React Router v7
Показывайте время в часовом поясе пользователя
Проблема
Когда приложения показывают время без учёта местоположения пользователя, это приводит к путанице и ошибкам. Например, сервер может сохранить время события как "2024-03-15T20:00:00Z" и отобразить "20:00" напрямую из этого значения в UTC. Пользователи в разных часовых поясах видят одно и то же время на часах, но воспринимают его как локальное, что приводит к пропущенным встречам и накладкам в расписании. Главная проблема в том, что один и тот же момент времени отображается по-разному в зависимости от географического положения, и если показывать время в неправильном поясе, оно теряет смысл или вводит в заблуждение.
Эта проблема усугубляется, когда пользователи работают вместе из разных регионов. Событие, запланированное на 15:00 по серверу, становится 15:00 для всех в интерфейсе, хотя на самом деле это должно быть 6:00 в Токио, 14:00 в Лондоне и 9:00 в Нью-Йорке для одного и того же момента.
Решение
Храните все временные метки в универсальном формате, например, в виде строк ISO 8601 с указанием UTC или в виде Unix-меток времени. При отображении этих значений пользователям преобразуйте их в объекты JavaScript Date и форматируйте с помощью API интернационализации, которые учитывают часовой пояс браузера. Современные браузеры автоматически определяют локальный часовой пояс пользователя, а библиотеки форматирования используют это, чтобы преобразовать UTC-метки во время, корректное для местоположения пользователя, без ручных вычислений смещений.
Такой подход разделяет хранение и отображение данных. Сервер отправляет единую эталонную временную метку, а каждый клиент отображает её с учётом своего часового пояса, чтобы все видели правильное локальное время для одного и того же глобального события.
Шаги
1. Передавайте временные метки в формате ISO 8601 из вашего источника данных
Убедитесь, что ваш API или загрузчик данных возвращает временные метки в виде строк ISO 8601 с указанием UTC или как Unix-метки времени в миллисекундах.
export async function loader() {
const event = await fetchEvent();
return {
title: event.title,
startTime: "2024-03-15T20:00:00Z",
endTime: "2024-03-15T22:00:00Z",
};
}
Суффикс Z указывает на время по UTC. Когда эта строка преобразуется в объект Date в JavaScript, браузер будет хранить её как временную метку в UTC.
2. Создайте компонент для форматирования времени с помощью react-intl
Импортируйте компоненты форматирования из react-intl и передавайте объекты Date, созданные из ваших ISO-строк.
import { FormattedDate, FormattedTime } from "react-intl";
import type { Route } from "./+types/event";
export default function EventDetails({ loaderData }: Route.ComponentProps) {
const startTime = new Date(loaderData.startTime);
const endTime = new Date(loaderData.endTime);
return (
<div>
<h1>{loaderData.title}</h1>
<p>
<FormattedDate
value={startTime}
year="numeric"
month="long"
day="numeric"
/>
{" at "}
<FormattedTime value={startTime} />
{" to "}
<FormattedTime value={endTime} />
</p>
</div>
);
}
Компоненты FormattedDate и FormattedTime автоматически используют определённый браузером часовой пояс для отображения корректного локального времени. Пользователь в Нью-Йорке увидит «15 марта 2024 г., 15:00», а пользователь в Токио — «16 марта 2024 г., 05:00» для одной и той же UTC-метки времени.
3. Используйте императивный API для динамического форматирования
Если нужно отформатировать время в атрибутах, вычисляемых значениях или вне JSX, используйте хук useIntl.
import { useIntl } from "react-intl";
import type { Route } from "./+types/event";
export default function EventCard({ loaderData }: Route.ComponentProps) {
const intl = useIntl();
const startTime = new Date(loaderData.startTime);
const formattedDate = intl.formatDate(startTime, {
year: "numeric",
month: "short",
day: "numeric",
});
const formattedTime = intl.formatTime(startTime, {
hour: "numeric",
minute: "2-digit",
});
return (
<div>
<h2>{loaderData.title}</h2>
<time dateTime={loaderData.startTime}>
{formattedDate} at {formattedTime}
</time>
</div>
);
}
Методы formatDate и formatTime возвращают строки, отформатированные в соответствии с локалью и часовым поясом пользователя. Атрибут dateTime сохраняет исходную ISO-строку для машинного чтения, а отображаемый текст показывает удобное локальное время.