Временные зоны

Работа со временем в пользовательских интерфейсах неизбежно связана с временными зонами. Одно и то же значение времени может означать разные моменты в реальном мире в зависимости от географического региона. Поэтому интерфейсы, отображающие или принимающие дату и время, должны учитывать локальные настройки пользователя, стандарты хранения времени и правила преобразования между зонами.

Библиотека React Aria предоставляет инструменты для создания доступных и корректно работающих компонентов ввода даты и времени. Поддержка временных зон реализуется через интеграцию с библиотекой @internationalized/date, которая содержит набор структур и функций для работы с календарями, локалями и временными зонами.

Использование специализированных объектов времени позволяет избежать распространённых ошибок JavaScript, связанных с объектом Date, например:

  • неявных преобразований между UTC и локальным временем
  • различий между браузерами
  • потери информации о временной зоне
  • некорректной работы при переходе на летнее или зимнее время

Основные типы дат и времени

Для корректной работы с временными зонами React Aria использует несколько специализированных типов.

CalendarDate

Объект даты без времени и без временной зоны.

import {CalendarDate} from '@internationalized/date';

let date = new CalendarDate(2026, 3, 16);

Характеристики:

  • содержит только год, месяц и день
  • не зависит от часового пояса
  • подходит для дней рождения, праздников, событий без конкретного времени

CalendarDateTime

Дата и время без привязки к временной зоне.

import {CalendarDateTime} from '@internationalized/date';

let dateTime = new CalendarDateTime(2026, 3, 16, 14, 30);

Особенности:

  • хранит часы и минуты
  • не содержит информации о часовом поясе
  • используется для локального ввода времени пользователем

ZonedDateTime

Дата и время с указанием временной зоны.

import {ZonedDateTime} from '@internationalized/date';

let dateTime = new ZonedDateTime(
  2026,
  3,
  16,
  "Europe/Berlin",
  0,
  14,
  30
);

Содержит:

  • дату
  • время
  • временную зону (IANA timezone)
  • смещение UTC

Этот тип является ключевым при работе с международными приложениями.


Формат временных зон

React Aria использует стандарт IANA Time Zone Database.

Примеры:

Зона Описание
Europe/Berlin Центральная Европа
America/New_York Восточное побережье США
Asia/Tokyo Япония
UTC Координированное мировое время

Использование таких идентификаторов гарантирует корректную работу переходов на летнее время.


Работа с часовым поясом пользователя

Определение временной зоны пользователя выполняется через функцию:

import {getLocalTimeZone} from '@internationalized/date';

let timezone = getLocalTimeZone();

Пример результата:

Europe/Paris
Asia/Almaty
America/Los_Angeles

Полученное значение используется при создании ZonedDateTime.


Создание текущего времени с временной зоной

Функция now() возвращает текущее время в указанной зоне.

import {now, getLocalTimeZone} from '@internationalized/date';

let dateTime = now(getLocalTimeZone());

Тип результата:

ZonedDateTime

Это позволяет точно определить момент времени с учётом локальных правил.


Преобразование между временными зонами

Частая задача — отображение одного и того же момента времени для пользователей из разных регионов.

Для этого используется метод toTimeZone.

let berlinTime = dateTime.toTimeZone("Europe/Berlin");
let tokyoTime = dateTime.toTimeZone("Asia/Tokyo");

Исходный момент времени остаётся тем же, изменяется только отображение.


Конвертация локального времени в ZonedDateTime

Когда пользователь вводит локальное время, оно не содержит информации о зоне. Поэтому необходимо преобразование.

import {toZoned} from '@internationalized/date';

let zoned = toZoned(dateTime, getLocalTimeZone());

Это добавляет:

  • часовой пояс
  • смещение UTC

Работа временных зон в компонентах React Aria

React Aria предоставляет несколько компонентов, работающих с датой и временем:

  • DateField
  • DatePicker
  • DateRangePicker
  • TimeField

Если используется ZonedDateTime, компоненты автоматически учитывают временную зону.

Пример DatePicker.

import {DatePicker} from 'react-aria-components';
import {now, getLocalTimeZone} from '@internationalized/date';

function Example() {
  let value = now(getLocalTimeZone());

  return (
    <DatePicker value={value}>
      {(state) => (
        <>
          <DateInput />
          <Calendar />
        </>
      )}
    </DatePicker>
  );
}

Компонент отображает дату в соответствии с текущей зоной.


Хранение времени на сервере

В большинстве приложений используется следующий подход:

  1. Клиент вводит время в локальной зоне
  2. Преобразование выполняется в UTC
  3. UTC сохраняется в базе данных
  4. При отображении выполняется обратное преобразование

Пример преобразования:

let utc = dateTime.toAbsolute();

Тип результата:

Instant

Этот формат независим от временных зон.


Преобразование из UTC

Когда данные получены с сервера:

import {fromAbsolute} from '@internationalized/date';

let localTime = fromAbsolute(instant, getLocalTimeZone());

Результат:

ZonedDateTime

Переход на летнее время

Одной из сложнейших проблем является Daylight Saving Time (DST).

Пример:

В Европе перевод часов происходит:

  • март — +1 час
  • октябрь — -1 час

В момент перехода некоторые значения времени могут:

  • не существовать
  • существовать дважды

Пример:

02:30

может быть:

  • до перевода часов
  • после перевода

Библиотека @internationalized/date учитывает эти ситуации автоматически.


Нормализация времени

Если пользователь вводит время, которое не существует из-за DST, система корректирует значение.

Пример:

02:30 → 03:00

Это предотвращает ошибки.


Использование parseZonedDateTime

Часто временные значения приходят из API в виде строки.

Пример ISO-формата:

2026-03-16T14:30:00+02:00[Europe/Berlin]

Парсинг выполняется следующим образом.

import {parseZonedDateTime} from '@internationalized/date';

let date = parseZonedDateTime(
  "2026-03-16T14:30:00+02:00[Europe/Berlin]"
);

Форматирование времени

Для отображения времени используется Intl.DateTimeFormat.

React Aria автоматически использует локаль пользователя.

Пример:

let formatter = new Intl.DateTimeFormat("ru-RU", {
  dateStyle: "full",
  timeStyle: "short",
  timeZone: "Europe/Moscow"
});

Работа с TimeField

Компонент TimeField позволяет вводить время.

import {TimeField} from 'react-aria-components';
import {parseTime} from '@internationalized/date';

function Example() {
  let value = parseTime("14:30");

  return (
    <TimeField value={value}>
      <DateInput />
    </TimeField>
  );
}

Если требуется поддержка временной зоны, используется ZonedDateTime.


Работа с диапазонами времени

React Aria поддерживает выбор диапазонов.

<DateRangePicker
  value={{
    start: startDate,
    end: endDate
  }}
/>

Если значения являются ZonedDateTime, обе даты будут находиться в одной временной зоне.


Сравнение дат в разных зонах

При сравнении необходимо использовать абсолютное время.

Неправильно:

date1.compare(date2)

Правильно:

date1.toAbsolute() === date2.toAbsolute()

Это гарантирует точность.


Сериализация временных значений

Для отправки на сервер обычно используется ISO-формат.

dateTime.toString()

Результат:

2026-03-16T14:30:00+02:00[Europe/Berlin]

Этот формат сохраняет:

  • дату
  • время
  • временную зону
  • смещение

Лучшие практики работы с временными зонами

Использование ZonedDateTime для событий

Все события, происходящие в определённый момент времени (например, видеоконференции), должны храниться как ZonedDateTime.


Хранение в UTC

База данных должна хранить:

UTC timestamp

Это упрощает:

  • синхронизацию
  • сравнение
  • индексацию

Конвертация только на уровне UI

Преобразование временных зон должно происходить:

UTC → Local

непосредственно перед отображением.


Избегание объекта Date

Date в Jav * aScript:

  • зависит от окружения
  • содержит скрытую локаль
  • плохо работает с международными форматами

Использование @internationalized/date устраняет эти проблемы.


Архитектура работы со временем в React Aria

Типичная схема обработки времени:

Пользователь
   ↓
DatePicker / TimeField
   ↓
CalendarDateTime
   ↓
toZoned()
   ↓
ZonedDateTime
   ↓
toAbsolute()
   ↓
UTC (сервер)

Обратный путь:

UTC
   ↓
fromAbsolute()
   ↓
ZonedDateTime
   ↓
UI

Такой подход обеспечивает:

  • корректность международных приложений
  • поддержку DST
  • точность временных расчётов
  • предсказуемое поведение интерфейсов.