Поддержка часовых поясон

Модель часовых поясов в Luxon

Luxon опирается на стандарт IANA Time Zone Database, где часовые пояса представлены строковыми идентификаторами вида Europe/Paris, Asia/Almaty, America/New_York. Это принципиально отличает библиотеку от работы с фиксированными смещениями, поскольку часовой пояс в таком подходе учитывает переходы на летнее и зимнее время, исторические изменения и локальные правила региона.

Ключевая особенность:

  • часовой пояс в Luxon — это не просто смещение, а полноценное правило трансформации времени;
  • одна и та же временная метка может иметь разные отображаемые значения в зависимости от зоны.

Внутри используется API Intl, предоставляемый средой выполнения (Node.js или браузер), поэтому корректная работа зон зависит от поддержки окружения.


Базовый часовой пояс и системная зона

При создании объекта DateTime без явного указания зоны используется локальная зона окружения:

import { DateTime } from "luxon";

const dt = DateTime.now();

Такой объект наследует системный часовой пояс, который определяется операционной системой или настройками среды выполнения.

Получение информации о текущей зоне:

DateTime.local().zoneName;

Результат обычно соответствует IANA-идентификатору или локальному псевдониму, преобразованному через Intl.


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

Для явного задания зоны используется поле zone при создании:

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

При таком подходе значение времени интерпретируется сразу в контексте указанного пояса.

Также поддерживается создание из ISO-строки:

const dt = DateTime.fromISO("2026-05-23T10:00:00", {
  zone: "Europe/Paris"
});

Важно, что ISO-строка сама по себе не всегда содержит информацию о зоне, поэтому параметр zone определяет интерпретацию.


Переключение часового пояса

Смена зоны без изменения абсолютного момента времени выполняется через setZone:

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

const converted = dt.setZone("Europe/London");

Семантика операции:

  • временная точка остаётся той же (timestamp не меняется);
  • изменяется только представление (час, дата, смещение).

Это ключевое отличие от пересоздания объекта.


Сохранение локального времени при смене зоны

setZone поддерживает режим сохранения локального времени:

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

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

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

  • часы и минуты остаются прежними;
  • изменяется фактический момент времени (timestamp сдвигается).

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


Работа с UTC

UTC рассматривается как базовая координата времени и активно используется для нормализации:

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

const utc = dt.toUTC();

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

const back = utc.toLocal();

Также возможно явное задание зоны UTC при создании:

const dt = DateTime.utc(2026, 5, 23, 10);

Смещения и отображение информации о зоне

У каждого объекта DateTime доступны параметры, описывающие текущую временную зону:

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

Пример:

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

console.log(dt.offset); // например, 540

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


Переходы на летнее время (DST)

Luxon учитывает DST автоматически, если зона это поддерживает.

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

const dt1 = DateTime.fromISO("2026-03-28T12:00:00", {
  zone: "Europe/Berlin"
});

const dt2 = dt1.plus({ days: 7 });

При переходе через границу летнего времени:

  • смещение может измениться;
  • разница в 24 часа по календарю не всегда равна 24 часам по абсолютному времени.

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


Арифметика времени и зоны

При операциях сложения и вычитания Luxon оперирует абсолютным временем, а не календарным представлением:

const dt = DateTime.fromISO("2026-10-25T01:30:00", {
  zone: "Europe/London"
});

const result = dt.plus({ hours: 2 });

Если в промежутке происходит смена часового пояса из-за DST, результат может «перепрыгнуть» через несуществующее локальное время.


Перевод между зонами с форматированием

Часто требуется не просто смена зоны, а вывод одного и того же момента в разных регионах:

const dt = DateTime.utc(2026, 5, 23, 10);

const ny = dt.setZone("America/New_York");
const tokyo = dt.setZone("Asia/Tokyo");

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

ny.toFormat("yyyy-MM-dd HH:mm");
tokyo.toFormat("yyyy-MM-dd HH:mm");

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


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

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

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

Такое поведение может приводить к неоднозначности. Для устранения используется явная нормализация:

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

или

const dt = DateTime.fromISO("2026-05-23T10:00:00Z");

Суффикс Z фиксирует UTC и исключает двусмысленность.


Проверка валидности часового пояса

Luxon не выбрасывает ошибку при неизвестной зоне автоматически, но объект становится невалидным:

const dt = DateTime.now().setZone("Invalid/Zone");

dt.isValid; // false
dt.invalidReason;
dt.invalidExplanation;

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


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

Все сравнения выполняются по абсолютному времени:

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

const b = DateTime.fromISO("2026-05-23T05:00:00", {
  zone: "Europe/London"
});

a.equals(b); // true или false в зависимости от совпадения timestamp

Сравнение игнорирует локальные представления и опирается на UTC-представление.


Конвертация и сериализация

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

dt.toISO({ includeOffset: true });

или исключена:

dt.toISO({ includeOffset: false });

Также возможно получение ISO в UTC:

dt.toUTC().toISO();

Особенности окружений Node.js и браузера

Поддержка часовых поясов зависит от реализации Intl:

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

Проверка доступности:

Intl.DateTimeFormat().resolvedOptions().timeZone;

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


Рекомендации по архитектуре работы с зонами

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

  • хранение времени в UTC как базовой модели;
  • преобразование в локальные зоны только на уровне представления;
  • использование IANA-идентификаторов вместо фиксированных offset;
  • избегание хранения локального времени без контекста зоны;
  • явная нормализация входных данных.

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


Поведение при сериализации и хранении

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

dt.toUTC().toISO();

или timestamp:

dt.toMillis();

Хранение с зоной допускается только при сохранении бизнес-смысла локального времени (например, расписания мероприятий), где важно именно региональное представление.


Обработка временных аномалий

При переходах DST могут возникать:

  • несуществующие локальные времена (gap);
  • дублирующиеся часы (overlap).

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

Пример:

DateTime.fromISO("2026-03-29T02:30:00", {
  zone: "Europe/Berlin"
});

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