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

Работа с часовыми поясами в JavaScript строится вокруг стандарта IANA Time Zone Database и механизмов, предоставляемых объектом Intl. Основной инструмент — Intl.DateTimeFormat, который позволяет форматировать даты с учётом локали и конкретного часового пояса без ручной коррекции смещений и без прямой работы с UTC-часами на уровне бизнес-логики.

Базовая модель часовых поясов

В международной системе времени часовой пояс задаётся строковым идентификатором из базы IANA, например:

  • "UTC"
  • "Europe/London"
  • "Asia/Almaty"
  • "America/New_York"

Эти идентификаторы описывают правила смещения относительно UTC и учитывают переходы на летнее/зимнее время.

Ключевой момент: JavaScript не оперирует числовыми смещениями как основным способом задания зоны — используется именованная зона IANA.


Параметр timeZone в Intl.DateTimeFormat

Основной способ задания часового пояса — опция timeZone:

const formatter = new Intl.DateTimeFormat('ru-RU', {
  timeZone: 'Asia/Almaty',
  year: 'numeric',
  month: '2-digit',
  day: '2-digit',
  hour: '2-digit',
  minute: '2-digit'
});

formatter.format(new Date());

Если timeZone не указан, используется локальная зона окружения выполнения (браузер или сервер).


Поведение форматирования

Intl.DateTimeFormat не изменяет объект Date. Он лишь интерпретирует его значение в контексте указанной зоны.

const date = new Date('2026-01-01T12:00:00Z');

const f1 = new Intl.DateTimeFormat('en-US', {
  timeZone: 'UTC',
  timeStyle: 'short'
});

const f2 = new Intl.DateTimeFormat('en-US', {
  timeZone: 'America/New_York',
  timeStyle: 'short'
});

f1.format(date); // 12:00 PM
f2.format(date); // 7:00 AM (зависит от смещения)

Важно: одна и та же дата интерпретируется по-разному в разных зонах, но сама точка времени остаётся неизменной.


Получение доступных часовых поясов

Современные реализации ECMAScript предоставляют метод:

Intl.supportedValuesOf('timeZone');

Он возвращает список поддерживаемых IANA-зон:

[
  "Africa/Abidjan",
  "Africa/Accra",
  "Africa/Addis_Ababa",
  ...
]

Использование этого метода важно для:

  • построения UI выбора зоны
  • валидации пользовательского ввода
  • кросс-платформенной совместимости

Смещение и переходы на летнее время

Часовые пояса IANA включают исторические и политические изменения.

Например, зона может:

  • менять смещение в разные годы
  • отменять или вводить DST (daylight saving time)
  • иметь разные правила в прошлом и будущем

Это означает, что результат форматирования зависит не только от даты, но и от временного контекста.

new Intl.DateTimeFormat('en-US', {
  timeZone: 'Europe/Berlin',
  timeStyle: 'short',
  dateStyle: 'short'
}).format(new Date('2024-07-01T12:00:00Z'));

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

Опция timeZoneName позволяет вывести человекочитаемое имя зоны:

new Intl.DateTimeFormat('ru-RU', {
  timeZone: 'Asia/Almaty',
  timeZoneName: 'long'
}).format(new Date());

Возможные значения:

  • "short" — краткое обозначение (например, GMT+6)
  • "long" — полное описание (например, «Астана, Алматы время» в зависимости от локали)
  • "shortOffset" / "longOffset" — смещение относительно UTC

Разбор результата через formatToParts

Для точного контроля форматирования используется:

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'Asia/Tokyo',
  year: 'numeric',
  month: '2-digit',
  day: '2-digit',
  hour: '2-digit',
  minute: '2-digit'
});

formatter.formatToParts(new Date());

Результат:

[
  { type: 'month', value: '01' },
  { type: 'literal', value: '/' },
  { type: 'day', value: '24' },
  ...
]

Это позволяет:

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

resolvedOptions() и фактическая зона

Метод resolvedOptions() показывает, какая зона реально используется:

const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'Asia/Almaty'
});

formatter.resolvedOptions();

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

{
  locale: 'en-US',
  timeZone: 'Asia/Almaty',
  calendar: 'gregory',
  numberingSystem: 'latn'
}

Этот метод полезен для:

  • отладки
  • проверки fallback-логики
  • диагностики окружения

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

Любая работа Intl с датами опирается на UTC-представление Date.

new Date().toISOString();

Intl.DateTimeFormat выполняет преобразование:

UTC → IANA time zone → локальные правила → форматирование

Никаких ручных пересчётов смещения в прикладном коде не требуется.


Границы и типичные ошибки

1. Использование числовых смещений вместо IANA

timeZone: '+06:00' // некорректно в большинстве окружений

Правильно:

timeZone: 'Asia/Almaty'

2. Попытка «изменить» Date

const d = new Date();
d.setHours(d.getHours() + 6);

Такой подход ломает DST-логику и приводит к ошибкам при переходах времени.


3. Предположение о стабильности смещения

Часовой пояс — не фиксированное число часов. Он может меняться исторически, и Intl учитывает это автоматически.


Производительность и кеширование форматтеров

Создание Intl.DateTimeFormat — относительно дорогая операция.

Оптимальная практика:

const formatter = new Intl.DateTimeFormat('ru-RU', {
  timeZone: 'Asia/Almaty',
  dateStyle: 'medium',
  timeStyle: 'short'
});

// переиспользование
formatter.format(new Date());

Создание одного форматтера и его переиспользование значительно эффективнее, чем пересоздание на каждый вызов.


Локализация отображения времени

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

new Intl.DateTimeFormat('en-US', { timeZone: 'Asia/Tokyo' })
new Intl.DateTimeFormat('ja-JP', { timeZone: 'Asia/Tokyo' })

Различия:

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

Совместное использование с другими Intl API

Часовые пояса часто комбинируются с:

  • Intl.RelativeTimeFormat — относительное время
  • Intl.NumberFormat — числовые единицы времени
  • Intl.Locale — анализ локали

Пример связки:

const dateFormatter = new Intl.DateTimeFormat('ru-RU', {
  timeZone: 'Europe/Moscow',
  dateStyle: 'full',
  timeStyle: 'short'
});

Поведение в серверной среде

В Node.js часовой пояс по умолчанию может отличаться от браузера.

process.env.TZ = 'UTC';

или через timeZone в Intl:

new Intl.DateTimeFormat('en-US', {
  timeZone: 'UTC'
});

Это устраняет зависимость от системных настроек сервера.


Практическая модель работы

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

  1. Хранение времени в UTC
  2. Передача времени как Date или ISO-строки
  3. Форматирование через Intl.DateTimeFormat
  4. Отображение в IANA-зоне пользователя

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