Потеря часового пояса

Часовой пояс в JavaScript не является частью базового объекта Date, поэтому любые операции, которые приводят к преобразованию между Luxon и нативным Date, потенциально ведут к его потере или неявному изменению интерпретации времени. В библиотеке Luxon это одна из ключевых проблемных зон, поскольку модель данных Luxon принципиально отличается от встроенной в язык.

Нативный объект Date хранит момент времени в виде количества миллисекунд с эпохи Unix (UTC). Часовой пояс как сущность в нём отсутствует. Любая информация о зоне существует только на уровне отображения (локальная зона среды выполнения) и не сохраняется внутри объекта.

Это приводит к фундаментальному эффекту:

  • Date хранит момент времени
  • Luxon хранит момент времени + контекст часового пояса

При переходах между этими моделями происходит потеря части контекста.

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

В Luxon дата-время представлено объектом DateTime, который включает:

  • абсолютный момент времени (epoch)
  • IANA-зону (Europe/Paris, Asia/Almaty, UTC)
  • локальные правила отображения

Пример:

import { DateTime } from "luxon";

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

Здесь сохраняется именно зона, а не только смещение.

Потеря зоны при преобразовании в JS Date

Ключевая точка деградации происходит при вызове:

const jsDate = dt.toJSDate();

jsDate содержит только timestamp. Информация о "Asia/Almaty" исчезает полностью.

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

const dt2 = DateTime.fromJSDate(jsDate);

Luxon создаёт объект в локальной системной зоне окружения, а не восстанавливает исходную.

Это означает:

  • исходная зона теряется
  • интерпретация смещается к System Zone
  • сохраняется только абсолютный момент времени

Различие между моментом и отображением

Один и тот же timestamp может быть интерпретирован по-разному:

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

const jsDate = dt.toJSDate();

const dt2 = DateTime.fromJSDate(jsDate);

console.log(dt.zoneName);  // Asia/Almaty
console.log(dt2.zoneName); // system zone

Несмотря на идентичный момент времени, контекст отображения утрачен.

Ошибки при сериализации в JSON

Типичный сценарий потери зоны:

const payload = {
  time: DateTime.now().setZone("Asia/Almaty")
};

const json = JSON.stringify(payload);

После этого:

const parsed = JSON.parse(json);

Получается строка без информации о зоне. Если далее восстановить:

DateTime.fromISO(parsed.time)

Luxon применит системную зону или UTC (в зависимости от формата строки), но исходная зона не восстановится.

fromISO и скрытая потеря контекста

ISO-строки могут содержать или не содержать зону:

С зоной:

DateTime.fromISO("2026-05-24T12:00:00+06:00");

или

DateTime.fromISO("2026-05-24T12:00:00Z");

В этих случаях Luxon восстанавливает момент времени, но не оригинальную IANA-зону, только фиксированное смещение.

Без зоны:

DateTime.fromISO("2026-05-24T12:00:00");

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

setZone и поведение keepLocalTime

Метод setZone управляет тем, как интерпретируется время при смене зоны:

dt.setZone("UTC");

По умолчанию:

  • сохраняется абсолютный момент времени
  • меняется отображение

Но при:

dt.setZone("UTC", { keepLocalTime: true });

происходит обратное:

  • сохраняются локальные компоненты времени
  • меняется абсолютный момент

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

System Zone как источник скрытых ошибок

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

DateTime.local();

или

DateTime.now();

В распределённых системах это создаёт проблему:

  • сервер в UTC
  • клиент в локальной зоне
  • база данных хранит UTC

В результате один и тот же код даёт разные zoneName.

Потеря зоны при round-trip преобразованиях

Наиболее частый паттерн деградации:

  1. Luxon DateTime с IANA-зоной
  2. toJSDate()
  3. передача в API
  4. восстановление через fromJSDate()

В итоге:

  • IANA-зона исчезает
  • остаётся только timestamp
  • интерпретация зависит от окружения

Корректное сохранение зоны

Luxon не сохраняет IANA-зону в Date и требует явного сериализационного слоя.

Практический подход:

const serialized = {
  iso: dt.toISO(),
  zone: dt.zoneName
};

Восстановление:

const restored = DateTime.fromISO(serialized.iso, {
  zone: serialized.zone
});

Это единственный способ сохранить семантику времени полностью.

Потеря зоны при арифметике дат

При операциях:

dt.plus({ hours: 5 });

зона сохраняется, но при преобразовании в JS Date или строку без зоны возможна утрата контекста.

Особенно опасны цепочки:

DateTime.now()
  .setZone("Asia/Almaty")
  .toJSDate()
  .toISOString();

На выходе остаётся только UTC-строка без информации о исходной зоне.

DST и скрытые сдвиги

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

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

Luxon корректно учитывает DST только пока сохраняется IANA-зона. После преобразования в Date эта информация исчезает.

Основные сценарии деградации данных о времени

  • преобразование DateTime → Date → DateTime
  • JSON-сериализация без зоны
  • использование fromISO без указания зоны
  • системная зона вместо явной
  • API, принимающее только timestamp

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

Разделение ответственности между слоями

В архитектуре приложений с Luxon обычно выделяются уровни:

  • доменный уровень: DateTime с зоной
  • транспортный уровень: ISO-строки или timestamp
  • хранилище: UTC timestamp

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

Поведение Luxon при отсутствии зоны

Если зона не задана явно:

DateTime.fromMillis(0);

или

DateTime.local();

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

  • операционной системы
  • настроек сервера
  • Docker-контейнера
  • браузера клиента

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