Как работать с часовыми поясами в React Router v7
Отображение времени в часовом поясе пользователя
Проблема
Когда приложения отображают время без учета местоположения пользователя, это приводит к путанице и ошибкам. Сервер может сохранить время события как "2024-03-15T20:00:00Z" и отобразить "8:00 PM" напрямую из этого значения UTC. Пользователи в разных часовых поясах видят одно и то же время на часах, но интерпретируют его как местное, что приводит к пропущенным встречам и конфликтам в расписании. Основная проблема заключается в том, что один и тот же момент времени имеет разные представления в зависимости от географического местоположения, и отображение времени в неправильной зоне делает его бессмысленным или вводящим в заблуждение.
Эта проблема усугубляется, когда пользователи сотрудничают из разных регионов. Событие, запланированное на 15:00 на сервере, становится 15:00 везде в пользовательском интерфейсе, хотя оно должно отображаться как 6:00 в Токио, 14:00 в Лондоне и 9:00 в Нью-Йорке для одного и того же момента времени.
Решение
Сохраняйте все временные метки в универсальном формате, таком как строки ISO 8601 с указателями UTC или временные метки Unix. При отображении этих временных меток пользователям преобразуйте их в объекты Date в JavaScript и форматируйте их с использованием 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"
/>
{" в "}
<FormattedTime value={startTime} />
{" до "}
<FormattedTime value={endTime} />
</p>
</div>
);
}
Компоненты FormattedDate и FormattedTime автоматически используют определённый браузером часовой пояс для отображения корректного локального времени. Пользователь в Нью-Йорке увидит "15 марта 2024 года в 15:00", а пользователь в Токио — "16 марта 2024 года в 5: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} в {formattedTime}
</time>
</div>
);
}
Методы formatDate и formatTime возвращают строки, отформатированные в соответствии с локалью и часовым поясом пользователя. Атрибут dateTime сохраняет оригинальную строку ISO для машинной читаемости, в то время как отображаемый текст показывает удобное для пользователя локальное время.