Как работать с часовыми поясами в TanStack Start v1

Отображение времени в часовом поясе пользователя

Проблема

Когда приложения отображают время, они представляют один и тот же момент, который выглядит по-разному в зависимости от местоположения зрителя. Если сервер отображает "8:00 PM" в своем часовом поясе или 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>
        Начало: <LocalTime timestamp={event.startTime} />
      </p>
      <p>
        Конец: <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 сохраняет точное локальное время для пользователей, которые наводят курсор. Оба формата автоматически учитывают часовой пояс и локаль пользователя.