Часовые пояса

Модель времени в JavaScript и влияние на локализацию

В основе работы с датой и временем в FormatJS лежит стандартный объект Date и возможности Intl.DateTimeFormat. Важная особенность заключается в том, что Date в JavaScript не хранит часовой пояс как отдельное поле. Внутреннее представление всегда основано на UTC, а отображение зависит от параметров форматирования.

Фактически одно и то же значение времени может быть представлено по-разному в зависимости от выбранного часового пояса:

  • локальный часовой пояс окружения (браузер/сервер);
  • явно указанный timeZone;
  • настройки форматтера Intl.DateTimeFormat.

FormatJS использует эти механизмы через высокоуровневые API, обеспечивая единый подход к локализации.


Часовые пояса в ICU MessageFormat

FormatJS поддерживает ICU MessageFormat, где форматирование даты и времени может быть встроено прямо в строки сообщений.

Пример базового использования:

import {FormattedDate} from 'react-intl';

<FormattedDate value={Date.now()} />

По умолчанию используется локальный часовой пояс окружения выполнения кода.

Добавление явного формата времени:

<FormattedDate
  value={Date.now()}
  year="numeric"
  month="long"
  day="2-digit"
  hour="2-digit"
  minute="2-digit"
/>

Явное управление часовым поясом

Ключевой механизм работы с часовыми поясами — параметр timeZone, который проксируется в Intl.DateTimeFormat.

<FormattedDate
  value={Date.now()}
  timeZone="UTC"
/>

или

import {useIntl} from 'react-intl';

const intl = useIntl();

intl.formatDate(Date.now(), {
  timeZone: 'Europe/Berlin',
  year: 'numeric',
  month: '2-digit',
  day: '2-digit'
});

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


Форматы даты и времени через formatDate

FormatJS предоставляет низкоуровневый API через intl.formatDate, который оборачивает Intl.DateTimeFormat.

intl.formatDate(new Date('2026-01-10T12:00:00Z'), {
  timeZone: 'Asia/Almaty',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  hour: '2-digit',
  minute: '2-digit'
});

Параметр timeZone влияет на интерпретацию исходного UTC-времени. Важно учитывать, что исходная дата всегда рассматривается как момент времени в UTC, если явно не указано обратное.


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

При отсутствии timeZone используется системный часовой пояс:

intl.formatDate(Date.now());

При наличии timeZone результат становится детерминированным:

intl.formatDate(Date.now(), { timeZone: 'UTC' });

Это особенно важно в следующих сценариях:

  • серверный рендеринг (SSR);
  • отображение событий для пользователей в разных регионах;
  • календарные системы и расписания;
  • финансовые и логистические приложения.

Форматирование времени с учётом временных зон в React Intl

react-intl предоставляет компоненты, которые автоматически используют Intl API.

import {FormattedDate, FormattedTime} from 'react-intl';

<FormattedDate
  value={Date.now()}
  timeZone="America/New_York"
/>

<FormattedTime
  value={Date.now()}
  timeZone="Asia/Tokyo"
/>

Компоненты FormattedDate и FormattedTime позволяют задавать одинаковое время, но отображать его в разных часовых поясах без ручных преобразований.


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

Встроенные возможности Intl.DateTimeFormat позволяют выводить название временной зоны:

intl.formatDate(Date.now(), {
  timeZone: 'Europe/London',
  timeZoneName: 'long'
});

Варианты:

  • short — краткое обозначение (GMT+1, PDT);
  • long — полное название (British Summer Time);
  • shortOffset — смещение от UTC;
  • longOffset — расширенное смещение.

FormatJS передаёт эти параметры напрямую в Intl, не изменяя их поведение.


Хранение времени и стратегическая работа с UTC

В экосистеме FormatJS принято опираться на UTC как на базовый формат хранения.

Типичная схема:

  1. хранение времени в UTC (ISO 8601);
  2. передача в интерфейс без локальных преобразований;
  3. отображение через timeZone в FormatJS.

Пример:

const eventTime = '2026-05-01T18:00:00Z';

intl.formatDate(eventTime, {
  timeZone: 'Asia/Almaty',
  year: 'numeric',
  month: 'short',
  day: '2-digit',
  hour: '2-digit',
  minute: '2-digit'
});

Такой подход исключает неоднозначность при смене локали или перемещении пользователя между регионами.


DST (летнее время) и автоматическая коррекция

При использовании IANA time zone identifiers (например, Europe/Berlin, America/New_York) учитываются правила перехода на летнее время.

intl.formatDate(new Date('2026-07-01T12:00:00Z'), {
  timeZone: 'Europe/Berlin'
});

Переходы DST обрабатываются автоматически через внутренние механизмы Intl.DateTimeFormat, поэтому ручная корректировка не требуется.


Поведение на сервере и клиенте

SSR-рендеринг создаёт дополнительный слой сложности:

  • сервер может использовать один часовой пояс (например, UTC);
  • клиент — другой (локальный).

FormatJS позволяет минимизировать расхождения через явное указание timeZone.

const markup = intl.formatDate(event.date, {
  timeZone: 'UTC'
});

или через централизованную конфигурацию форматирования.


Конфигурация глобальных форматов

FormatJS позволяет задавать единые правила форматирования через IntlProvider и кастомные форматтеры.

Хотя IntlProvider не управляет часовым поясом напрямую, он служит точкой консолидации локализации, где можно централизованно контролировать использование formatDate:

<IntlProvider locale="ru">
  <App />
</IntlProvider>

Часовой пояс в таком случае обычно передаётся через параметры форматирования или через обёртки над intl.


Преобразование временных зон без внешних библиотек

FormatJS не требует дополнительных библиотек для работы с timezone-конвертацией, так как опирается на нативный Intl.

new Intl.DateTimeFormat('ru-RU', {
  timeZone: 'Asia/Almaty',
  dateStyle: 'full',
  timeStyle: 'short'
}).format(new Date());

FormatJS лишь предоставляет более структурированный доступ к этим возможностям через API локализации.


Ошибки и ограничения при работе с timeZone

Некоторые ограничения связаны не с FormatJS, а с самим Intl:

  • отсутствие поддержки кастомных временных зон вне IANA;
  • невозможность получить список всех зон через FormatJS;
  • различия между окружениями Node.js и браузера;
  • зависимость от версии ICU в Node.js.

Также важно учитывать, что строка Date без указания зоны интерпретируется по-разному в зависимости от среды выполнения.


Практика унификации отображения времени

В сложных приложениях часто применяется единый подход:

  • хранение всех дат в UTC;
  • явное указание timeZone при форматировании;
  • запрет неявного использования локального времени;
  • централизованные утилиты поверх intl.formatDate.

Пример утилиты:

const formatEventDate = (intl, date, zone) =>
  intl.formatDate(date, {
    timeZone: zone,
    year: 'numeric',
    month: 'long',
    day: 'numeric',
    hour: '2-digit',
    minute: '2-digit'
  });

Такой слой обеспечивает консистентное отображение независимо от среды выполнения и географии пользователя.