Сохранение момента времени при смене зоны

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

  1. Сохранение абсолютного момента времени
  2. Сохранение локальных календарных значений

Luxon позволяет явно управлять обоими вариантами через метод setZone().

Абсолютный момент времени — это конкретная точка на временной шкале UTC. Например:

  • 2025-03-10T12:00:00Z
  • Unix timestamp
  • количество миллисекунд от эпохи Unix

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

Пример:

import { DateTime } from 'luxon'

const dt = DateTime.fromISO(
  '2025-03-10T15:00:00',
  { zone: 'UTC' }
)

const moscow = dt.setZone('Europe/Moscow')

console.log(dt.toISO())
// 2025-03-10T15:00:00.000Z

console.log(moscow.toISO())
// 2025-03-10T18:00:00.000+03:00

В этом примере:

  • 15:00 UTC
  • 18:00 Europe/Moscow

— это один и тот же момент времени.


Поведение setZone() по умолчанию

Метод setZone() сохраняет момент времени автоматически.

const dt = DateTime.now()

const tokyo = dt.setZone('Asia/Tokyo')

Luxon:

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

Это особенно важно при:

  • хранении событий;
  • работе с API;
  • синхронизации серверов;
  • логировании;
  • обработке расписаний.

Проверка сохранения timestamp

Timestamp остаётся одинаковым независимо от зоны.

const utc = DateTime.fromISO(
  '2025-05-01T12:00:00',
  { zone: 'UTC' }
)

const berlin = utc.setZone('Europe/Berlin')

console.log(utc.toMillis())
console.log(berlin.toMillis())

Результат:

1746100800000
1746100800000

Миллисекунды совпадают полностью.


Визуальное изменение времени

При смене зоны локальное время изменяется автоматически.

const dt = DateTime.fromISO(
  '2025-06-01T10:00:00',
  { zone: 'UTC' }
)

console.log(dt.toString())
// 2025-06-01T10:00:00.000Z

console.log(
  dt.setZone('America/New_York').toString()
)
// 2025-06-01T06:00:00.000-04:00

console.log(
  dt.setZone('Asia/Tokyo').toString()
)
// 2025-06-01T19:00:00.000+09:00

Изменяются:

  • часы;
  • смещение;
  • иногда календарная дата.

Но момент времени остаётся прежним.


Когда необходимо сохранять момент времени

Хранение событий

Событие должно происходить одновременно для всех пользователей мира.

Примеры:

  • запуск трансляции;
  • публикация релиза;
  • начало вебинара;
  • время платежа;
  • отправка сообщения.
const release = DateTime.fromISO(
  '2025-07-01T15:00:00Z'
)

const paris = release.setZone('Europe/Paris')
const seoul = release.setZone('Asia/Seoul')

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


Логирование

Логи всегда должны ссылаться на точный момент времени.

const logTime = DateTime.utc()

const localView = logTime.setZone(
  'America/Los_Angeles'
)

Смена зоны нужна только для отображения.


Работа с базой данных

Обычно время хранится:

  • в UTC;
  • либо как Unix timestamp.

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

const dbDate = DateTime.fromISO(
  '2025-04-01T08:00:00Z'
)

const userDate = dbDate.setZone(
  'Asia/Almaty'
)

Параметр keepLocalTime

По умолчанию Luxon сохраняет момент времени.

Однако иногда требуется сохранить локальные часы, а не timestamp.

Для этого используется:

keepLocalTime: true

Пример:

const dt = DateTime.fromObject(
  {
    year: 2025,
    month: 5,
    day: 1,
    hour: 10
  },
  { zone: 'UTC' }
)

const changed = dt.setZone(
  'Europe/Berlin',
  { keepLocalTime: true }
)

console.log(dt.toISO())
// 2025-05-01T10:00:00.000Z

console.log(changed.toISO())
// 2025-05-01T10:00:00.000+02:00

Теперь:

  • локальное время осталось 10:00;
  • timestamp изменился.

Разница между двумя режимами

Сохранение момента времени

dt.setZone('Asia/Tokyo')

Меняется:

  • отображение времени.

Не меняется:

  • timestamp.

Сохранение локального времени

dt.setZone(
  'Asia/Tokyo',
  { keepLocalTime: true }
)

Меняется:

  • timestamp.

Не меняется:

  • локальное время часов.

Практическое сравнение

Исходное время:

const dt = DateTime.fromISO(
  '2025-01-01T12:00:00',
  { zone: 'UTC' }
)

Вариант 1 — сохранить момент

const result = dt.setZone('Asia/Tokyo')

console.log(result.toISO())
// 2025-01-01T21:00:00.000+09:00

Timestamp одинаковый.


Вариант 2 — сохранить локальные часы

const result = dt.setZone(
  'Asia/Tokyo',
  { keepLocalTime: true }
)

console.log(result.toISO())
// 2025-01-01T12:00:00.000+09:00

Теперь это уже другой момент времени.


Ошибки при неправильном понимании зон

Ошибка календарного события

Частая проблема — перенос локального времени вместо абсолютного момента.

Неправильно:

const meeting = DateTime.fromISO(
  '2025-08-01T09:00:00',
  { zone: 'Europe/Berlin' }
)

const ny = meeting.setZone(
  'America/New_York',
  { keepLocalTime: true }
)

В результате создаётся другое событие.


Потеря синхронности

Если международная конференция начинается:

2025-09-01 12:00 UTC

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

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


Работа с UTC

Наиболее безопасная практика:

  1. хранить время в UTC;
  2. преобразовывать зоны только для отображения;
  3. сохранять абсолютный timestamp.
const utcTime = DateTime.utc()

const local = utcTime.setZone(
  'Asia/Dubai'
)

Использование toUTC()

Метод toUTC() — специализированный вариант setZone().

const local = DateTime.now()

const utc = local.toUTC()

Luxon:

  • сохраняет момент времени;
  • меняет только представление.

Использование toLocal()

Метод toLocal() переводит объект в системную зону.

const utc = DateTime.utc()

const local = utc.toLocal()

Timestamp также сохраняется.


Влияние перехода на летнее время

Luxon автоматически учитывает DST.

const dt = DateTime.fromISO(
  '2025-03-30T01:30:00',
  { zone: 'UTC' }
)

const berlin = dt.setZone('Europe/Berlin')

console.log(berlin.toString())

Смещение может изменяться автоматически:

  • +01:00
  • +02:00

в зависимости от даты.


Смена зоны и изменение даты

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

const utc = DateTime.fromISO(
  '2025-01-01T23:30:00',
  { zone: 'UTC' }
)

const tokyo = utc.setZone('Asia/Tokyo')

console.log(tokyo.toISO())
// 2025-01-02T08:30:00.000+09:00

Дата изменилась:

  • было 1 января;
  • стало 2 января.

Но момент времени остался тем же.


Проверка идентичности момента времени

Для проверки можно использовать:

toMillis()

или:

valueOf()

Пример:

const a = DateTime.utc()

const b = a.setZone('Asia/Tokyo')

console.log(a.valueOf() === b.valueOf())
// true

Создание времени сразу в нужной зоне

Иногда перевод зоны не нужен.

const dt = DateTime.fromObject(
  {
    year: 2025,
    month: 10,
    day: 5,
    hour: 14
  },
  {
    zone: 'Europe/London'
  }
)

В этом случае объект сразу создаётся в нужной зоне.


Сериализация и зоны

ISO-строка содержит информацию о смещении.

const dt = DateTime.now()

console.log(dt.toISO())

Пример:

2025-05-01T14:30:00.000+03:00

При восстановлении Luxon корректно восстанавливает момент времени.

const restored = DateTime.fromISO(isoString)

Использование setZone() в цепочках

const formatted = DateTime
  .utc()
  .setZone('Asia/Singapore')
  .toFormat('dd.MM.yyyy HH:mm')

Luxon сохраняет timestamp на всех этапах, пока не используется keepLocalTime.


Внутреннее устройство

Luxon хранит:

  • timestamp;
  • объект зоны;
  • локальные вычисленные компоненты.

При обычном setZone():

  1. timestamp остаётся прежним;

  2. пересчитываются:

    • часы;
    • дата;
    • offset.

При keepLocalTime: true:

  1. локальные компоненты сохраняются;
  2. пересчитывается timestamp.

Сравнение с JavaScript Date

Объект Date всегда хранит UTC timestamp, но:

  • плохо работает с зонами;
  • не хранит выбранную таймзону явно.

Luxon предоставляет:

  • полноценную модель зон;
  • безопасное преобразование;
  • контроль над сохранением момента времени.

Пример:

const native = new Date()

const luxon = DateTime.fromJSDate(native)

Рекомендации по архитектуре

Для серверов

Использовать UTC:

DateTime.utc()

Для пользовательского интерфейса

Преобразовывать время под пользователя:

serverDate.setZone(userZone)

Для календарных событий

Хранить:

  • UTC timestamp;
  • исходную зону пользователя при необходимости.

Для ежедневных расписаний

Если событие должно всегда происходить в локальное время пользователя, использовать:

keepLocalTime: true

Например:

  • будильники;
  • локальные напоминания;
  • рабочие часы.

Частые ошибки

Использование keepLocalTime без необходимости

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


Хранение локального времени без зоны

Опасный вариант:

2025-05-01 12:00

Без зоны невозможно определить абсолютный момент времени.


Смешивание UTC и локального времени

Неправильно:

DateTime.now()
DateTime.utc()

без понимания различий между ними.


Ключевые правила

setZone() по умолчанию

Сохраняет:

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

Меняет:

  • локальное отображение.

keepLocalTime: true

Сохраняет:

  • локальные часы и дату.

Меняет:

  • абсолютный timestamp.

Безопасная стратегия

Для большинства приложений:

  • хранение в UTC;
  • отображение в локальной зоне;
  • отказ от keepLocalTime без реальной необходимости.