Как работать с часовыми поясами в Next.js (Pages Router) v16
Показывайте время в часовом поясе пользователя
Проблема
Когда приложения показывают время без учёта местоположения пользователя, возникает путаница. Встреча, назначенная на «15:00», будет происходить в разное время для людей в Нью-Йорке, Лондоне или Токио. Если сервер отображает время в UTC или своём часовом поясе, пользователи из других регионов видят неправильное локальное время, что приводит к пропущенным встречам и ошибкам в расписании. Один и тот же таймстамп должен интерпретироваться по-разному для каждого пользователя в зависимости от его часового пояса.
Решение
Храните таймстампы в универсальном формате, например ISO 8601 или Unix timestamp, а для отображения форматируйте их в часовом поясе пользователя на клиенте. Определяйте часовой пояс пользователя через Internationalization API браузера и передавайте его в функции форматирования react-intl. Так каждый пользователь будет видеть время, скорректированное под свой часовой пояс, и не будет путаницы, когда именно происходит событие.
Шаги
1. Определите часовой пояс пользователя на клиенте
Браузерный Intl.DateTimeFormat().resolvedOptions().timeZone возвращает идентификатор часового пояса IANA пользователя, например America/New_York или Europe/London. Создайте кастомный хук для получения этого значения.
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;
}
Этот хук работает только на клиенте, чтобы избежать проблем с гидратацией, и по умолчанию использует UTC, пока не определён часовой пояс.
2. Форматируйте даты с учётом часового пояса пользователя через FormattedDate
Передавайте опцию timeZone в методы форматирования react-intl, чтобы управлять часовым поясом для отображения. Используйте компонент <FormattedDate> с определённым часовым поясом.
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"
/>
);
}
Свойство timeZone гарантирует, что дата будет отформатирована в локальном часовом поясе пользователя, а не сервера или UTC.
3. Императивное форматирование времени с помощью useIntl
Если вам нужен отформатированный текст, а не компонент, используйте хук useIntl, чтобы получить доступ к императивному API форматирования.
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>;
}
Метод formatDate принимает вторым аргументом DateTimeFormatOptions, включая timeZone, что позволяет полностью контролировать отображение временной метки.
4. Передавайте серверные временные метки в виде ISO-строк
В getServerSideProps получайте данные во время запроса и передавайте их в компонент страницы через props. Сериализуйте даты в строки формата ISO 8601, чтобы сохранить информацию о часовом поясе.
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>
);
}
ISO-строки сохраняют точный момент времени, что позволяет корректно конвертировать их в любой часовой пояс на клиенте.