Работа с часовыми поясами

Работа с часовыми поясами в JavaScript требует явного разделения между локальным временем среды выполнения, универсальным временем (UTC) и региональными временными зонами, определяемыми через IANA Time Zone Database. Day.js реализует этот слой через плагины, не включая поддержку таймзон в ядро библиотеки, что делает поведение предсказуемым и минималистичным.

Ключевая особенность подхода Day.js — разделение ответственности:

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

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


Базовая модель времени: локальное и UTC

Day.js всегда хранит внутреннее представление даты как timestamp (количество миллисекунд с Unix epoch). Разница проявляется только на этапе интерпретации и форматирования.

import dayjs from 'dayjs';

const a = dayjs();        // локальное время окружения
const b = dayjs.utc();    // UTC-время

UTC-режим фиксирует точку отсчёта без смещения относительно локальной зоны. Это фундамент для любых операций с часовыми поясами, поскольку перевод между зонами всегда сводится к преобразованию через UTC.


Подключение поддержки UTC и timezone

Работа с часовыми поясами в Day.js требует двух ключевых расширений:

  • utc — обеспечивает базовую работу с UTC
  • timezone — добавляет поддержку IANA-зон
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';

dayjs.extend(utc);
dayjs.extend(timezone);

Плагин timezone зависит от utc, поскольку все преобразования проходят через универсальное время.


Принцип IANA Time Zone Database

Часовые пояса в Day.js задаются строками вида:

  • Europe/Berlin
  • Asia/Almaty
  • America/New_York

Эти идентификаторы описывают не фиксированное смещение, а набор правил:

  • базовое смещение от UTC
  • правила перехода на летнее время (DST)
  • исторические изменения смещений

По этой причине прямое использование числовых оффсетов (например, +5) не эквивалентно IANA-зоне.


Создание даты в конкретной временной зоне

Метод tz позволяет интерпретировать дату в контексте указанной зоны:

const d = dayjs.tz('2026-01-01 12:00', 'Asia/Almaty');

Здесь строка времени рассматривается как локальное время для зоны Asia/Almaty, а не как локальное время системы.

Если зона не указана, используется системная:

const d = dayjs.tz('2026-01-01 12:00');

Преобразование между часовыми поясами

Любая дата в Day.js может быть переведена в другую зону через повторное применение tz:

const base = dayjs.tz('2026-01-01 12:00', 'Europe/London');

const ny = base.tz('America/New_York');
const tokyo = base.tz('Asia/Tokyo');

При этом происходит не изменение времени как такового, а его переинтерпретация относительно новой зоны с учётом UTC-основы.


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

Форматирование даты может учитывать активную временную зону:

dayjs.tz('2026-01-01 12:00', 'Asia/Almaty')
  .format('YYYY-MM-DD HH:mm z');

Маркер z выводит краткое обозначение зоны (если доступно), а сам результат зависит от текущего контекста таймзоны объекта.

Важно различать:

  • format() — просто вывод значения
  • tz() — изменение контекста зоны

Получение текущего времени в заданной зоне

Day.js позволяет получать текущее время не только системы, но и конкретной зоны:

const nowAlmaty = dayjs().tz('Asia/Almaty');
const nowTokyo = dayjs().tz('Asia/Tokyo');

Каждый вызов возвращает момент времени, синхронизированный с UTC, но отображаемый в разных локальных правилах.


Работа с оффсетами и их ограничения

Оффсет — это смещение от UTC в минутах:

dayjs().utcOffset();

Однако использование только оффсета приводит к потере информации о:

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

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


Перевод локального времени в UTC

Один из ключевых сценариев — нормализация входных данных:

const local = dayjs('2026-01-01 12:00');
const utcTime = local.utc();

Этот шаг критичен при сохранении дат в базе данных. UTC становится единым стандартом хранения независимо от пользовательской зоны.


Обратное преобразование из UTC

После хранения UTC-значений часто требуется восстановление локального представления:

const fromDb = dayjs.utc('2026-01-01T09:00:00Z');
const inAlmaty = fromDb.tz('Asia/Almaty');

Здесь происходит наложение региональных правил отображения поверх универсального времени.


Особенности летнего времени (DST)

Day.js через IANA-зоны учитывает переходы на летнее время автоматически.

Пример поведения:

  • дата в марте и октябре может иметь разное смещение
  • один и тот же час может существовать или отсутствовать
dayjs.tz('2026-03-29 02:30', 'Europe/Berlin');

Некоторые локальные времена могут быть невалидными из-за перехода DST, и библиотека корректирует их согласно правилам зоны.


Сравнение дат в разных часовых поясах

Сравнение выполняется через UTC, а не через отображаемое локальное время:

const a = dayjs.tz('2026-01-01 10:00', 'Asia/Tokyo');
const b = dayjs.tz('2026-01-01 03:00', 'Europe/Moscow');

a.valueOf() === b.valueOf();

Несмотря на разное локальное отображение, сравнение основано на абсолютном времени.


Парсинг ISO и временные зоны

ISO-строки с указанием зоны обрабатываются напрямую:

dayjs('2026-01-01T12:00:00+05:00');

В таких случаях Day.js извлекает смещение и переводит значение в UTC.

Если же зона задаётся отдельно:

dayjs.tz('2026-01-01T12:00:00', 'Asia/Almaty');

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


Изменение временной зоны без изменения момента времени

Метод tz может использоваться в режиме “переключения представления”:

const base = dayjs.utc('2026-01-01T12:00:00Z');

const almatyView = base.tz('Asia/Almaty');
const berlinView = base.tz('Europe/Berlin');

Здесь сохраняется один и тот же момент времени, но меняется способ его отображения.


Типовые ошибки при работе с часовыми поясами

На практике часто встречаются следующие проблемы:

  • смешивание локального времени и UTC без явного преобразования
  • хранение дат без нормализации
  • использование числовых оффсетов вместо IANA-зон
  • повторное применение tz() без понимания текущего контекста

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


Хранение и передача дат в распределённых системах

Стандартный подход при использовании Day.js:

  • хранение: только UTC
  • передача: ISO-строка в UTC
  • отображение: преобразование через tz() на стороне клиента
const payload = dayjs().utc().toISOString();

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


Работа с временными зонами в серверном окружении

На сервере Node.js отсутствует пользовательская локальная зона, но системная всё равно существует. Это создаёт риск неоднородного поведения между средами.

Использование явного UTC и IANA-зон устраняет эту неоднозначность:

const serverTime = dayjs.utc();
const userTime = serverTime.tz('Asia/Almaty');

Итоговая модель преобразований

Внутренняя логика Day.js при работе с часовыми поясами сводится к следующей цепочке:

  • входная строка или дата
  • нормализация в UTC
  • применение IANA-правил зоны
  • форматирование или обратное преобразование

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