Конвертация между часовыми поясами

Работа с часовыми поясами в JavaScript требует строгого контроля над источником времени, его интерпретацией и преобразованием. Библиотека Luxon предоставляет единый подход к управлению датами и временем через объект DateTime, поддерживающий IANA-зоны, UTC и локальные смещения.

Базовая модель времени в Luxon

В основе Luxon лежит представление времени как неизменяемого объекта DateTime. Каждый экземпляр содержит:

  • конкретный момент времени (instant)
  • часовую зону (Zone)
  • локальное представление (форматирование зависит от зоны)

Ключевая особенность заключается в разделении:

  • момента времени (абсолютная точка на временной шкале)
  • представления времени (как этот момент отображается в конкретной зоне)
import { DateTime } from "luxon";

const dt = DateTime.local();

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


Часовые зоны и IANA-идентификаторы

Luxon использует стандарт IANA Time Zone Database:

  • Europe/Moscow
  • Asia/Almaty
  • America/New_York
  • UTC

Каждая зона учитывает переходы на летнее время и исторические изменения смещений.

const dt = DateTime.fromObject(
  { year: 2026, month: 5, day: 23, hour: 12 },
  { zone: "Asia/Almaty" }
);

Конвертация через setZone

Основной метод преобразования времени между зонами — setZone.

Он изменяет представление времени, сохраняя или пересчитывая момент в зависимости от параметра keepLocalTime.

Базовое преобразование

const dt = DateTime.fromISO("2026-05-23T12:00:00", { zone: "UTC" });

const converted = dt.setZone("Asia/Almaty");

В этом случае:

  • исходный момент остаётся тем же
  • меняется интерпретация времени под новую зону

Поведение keepLocalTime

Параметр keepLocalTime управляет тем, сохраняется ли локальное время или абсолютный момент.

keepLocalTime = false (по умолчанию)

Сохраняется момент времени:

const dt = DateTime.fromISO("2026-05-23T12:00:00", { zone: "UTC" });

const converted = dt.setZone("Asia/Almaty", { keepLocalTime: false });

Результат:

  • 12:00 UTC превращается в 18:00 в Алматы (при +6)

Это корректное преобразование момента.


keepLocalTime = true

Сохраняется локальное отображение:

const dt = DateTime.fromObject(
  { year: 2026, month: 5, day: 23, hour: 12 },
  { zone: "UTC" }
);

const shifted = dt.setZone("Asia/Almaty", { keepLocalTime: true });

Результат:

  • 12:00 остаётся 12:00
  • но момент времени сдвигается на 6 часов назад

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


Преобразование через UTC как промежуточную модель

Часто используется стратегия нормализации через UTC.

const local = DateTime.fromISO("2026-05-23T12:00:00", {
  zone: "Asia/Almaty"
});

const utc = local.toUTC();
const ny = utc.setZone("America/New_York");

Модель:

  1. локальное время → UTC
  2. UTC → целевая зона

Это снижает вероятность ошибок при цепочках преобразований.


fromObject и явное указание зоны

При создании даты важно задавать зону сразу, чтобы избежать неоднозначности.

const dt = DateTime.fromObject(
  {
    year: 2026,
    month: 5,
    day: 23,
    hour: 9
  },
  {
    zone: "America/New_York"
  }
);

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


Работа с ISO-строками и смещениями

Luxon поддерживает ISO-формат с указанием смещения:

const dt = DateTime.fromISO("2026-05-23T12:00:00+06:00");

Такой формат уже содержит информацию о смещении, но не всегда содержит IANA-зону.

Для корректной конвертации часто требуется уточнение зоны:

const dt = DateTime.fromISO("2026-05-23T12:00:00+06:00", {
  zone: "Asia/Almaty"
});

Отличие setZone от toUTC и fromUTC

Методы решают разные задачи преобразования:

setZone

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

toUTC

  • переводит момент в UTC
  • равнозначно setZone("UTC")
const utc = dt.toUTC();

fromUTC

  • интерпретирует UTC-время в локальной зоне
const local = DateTime.fromUTC(12, 30);

DST и переходы времени

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

Пример зоны с DST:

  • Europe/Berlin
  • America/Los_Angeles
const winter = DateTime.fromISO("2026-01-15T12:00:00", {
  zone: "Europe/Berlin"
});

const summer = DateTime.fromISO("2026-07-15T12:00:00", {
  zone: "Europe/Berlin"
});

Разница в смещении будет рассчитана автоматически.


Получение информации о зоне

Объект DateTime содержит свойства:

dt.zoneName; // строковое имя зоны
dt.offset;   // смещение в минутах

Пример:

const dt = DateTime.now().setZone("Asia/Almaty");

console.log(dt.zoneName);
console.log(dt.offset);

Цепочки преобразований

Luxon допускает последовательные преобразования:

const result = DateTime.fromISO("2026-05-23T12:00:00", { zone: "UTC" })
  .setZone("Asia/Almaty")
  .setZone("America/New_York")
  .toUTC();

Каждый шаг возвращает новый объект DateTime, не изменяя исходный.


Форматирование после конвертации

После преобразования важно учитывать, что форматирование зависит от текущей зоны объекта.

const dt = DateTime.fromISO("2026-05-23T12:00:00", { zone: "UTC" })
  .setZone("Asia/Almaty");

const formatted = dt.toFormat("yyyy-MM-dd HH:mm ZZZZ");

Результат будет отражать локальное время зоны Алматы.


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

Игнорирование исходной зоны

DateTime.fromISO("2026-05-23T12:00:00").setZone("Asia/Almaty");

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


Двойное применение смещения

dt.setZone("UTC").toUTC();

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


Использование keepLocalTime без понимания эффекта

dt.setZone("Asia/Almaty", { keepLocalTime: true });

Такое преобразование не сохраняет момент времени и может привести к несоответствию событий во времени.


Нормализация времени в прикладных сценариях

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

  • хранение в UTC
  • отображение в локальной зоне пользователя
const stored = DateTime.fromISO(eventTime, { zone: "UTC" });

const displayed = stored.setZone(userZone);

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