Разница между setZone и toUTC/toLocal

В библиотеке Luxon объект DateTime всегда содержит:

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

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

import { DateTime } from 'luxon';

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

console.log(dt.toString());
// 2025-03-10T12:00:00.000Z

После смены зоны тот же момент времени может выглядеть иначе:

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

console.log(moscow.toString());
// 2025-03-10T15:00:00.000+03:00

Время изменилось с 12:00 на 15:00, но сама временная точка осталась прежней.

Именно вокруг этого поведения строится различие между setZone(), toUTC() и toLocal().


Метод setZone()

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

dt.setZone('Europe/Berlin')

Главная особенность

setZone() сохраняет исходный момент времени.

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


Пример сохранения временной точки

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

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

console.log(utc.toISO());
// 2025-06-01T12:00:00.000Z

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

Что произошло

Исходный момент:

12:00 UTC

После перевода в Токио:

21:00 UTC+9

Это одна и та же временная точка.

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


Внутренняя логика setZone()

setZone() работает по следующему принципу:

  1. Берётся Unix timestamp.
  2. Timestamp остаётся неизменным.
  3. Luxon пересчитывает локальные часы для новой зоны.

Условно:

timestamp = const
zone = newZone
localTime = recalculated

Проверка timestamp

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

const b = a.setZone('Europe/Moscow');

console.log(a.toMillis());
console.log(b.toMillis());

Результат:

1735732800000
1735732800000

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


toUTC()

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

Фактически:

dt.toUTC()

эквивалентно:

dt.setZone('UTC')

Пример toUTC()

const local = DateTime.fromISO(
  '2025-08-20T18:00:00',
  { zone: 'Europe/Berlin' }
);

const utc = local.toUTC();

console.log(local.toString());
// 2025-08-20T18:00:00.000+02:00

console.log(utc.toString());
// 2025-08-20T16:00:00.000Z

Анализ

Берлин летом использует UTC+2.

Следовательно:

18:00 Berlin
=
16:00 UTC

Timestamp не изменился.


toLocal()

Метод toLocal() переводит время в локальную системную зону.

Фактически:

dt.toLocal()

аналогичен:

dt.setZone(systemZone)

где systemZone — часовой пояс операционной системы.


Пример toLocal()

const utc = DateTime.utc(2025, 5, 1, 12);

const local = utc.toLocal();

console.log(local.toString());

Если системная зона:

Asia/Almaty

результат может быть:

2025-05-01T17:00:00.000+05:00

Главное различие

setZone()

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

dt.setZone('America/New_York')

toUTC()

Быстрый способ перевести время в UTC.

dt.toUTC()

toLocal()

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

dt.toLocal()

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

Метод Назначение Меняется timestamp
setZone() Перевод в любую зону Нет
toUTC() Перевод в UTC Нет
toLocal() Перевод в локальную зону Нет

Критически важный параметр keepLocalTime

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

Но поведение можно изменить:

setZone(zone, { keepLocalTime: true })

Это один из самых сложных и важных моментов в Luxon.


Поведение keepLocalTime: false

Стандартное поведение:

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

const result = dt.setZone('Europe/Moscow');

console.log(result.toString());
// 2025-06-01T13:00:00.000+03:00

Что произошло

Сохранился timestamp.

Изменилось отображение часов.


Поведение keepLocalTime: true

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

const result = dt.setZone(
  'Europe/Moscow',
  { keepLocalTime: true }
);

console.log(result.toString());
// 2025-06-01T10:00:00.000+03:00

Теперь локальное время осталось 10:00.

Но timestamp изменился.


Что реально делает keepLocalTime

Без опции:

10:00 UTC
=
13:00 Moscow

С опцией:

10:00 Moscow

То есть Luxon создаёт совершенно другую временную точку.


Сравнение timestamp

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

const b = a.setZone(
  'Europe/Moscow',
  { keepLocalTime: true }
);

console.log(a.toMillis());
console.log(b.toMillis());

Значения будут разными.


Когда нужен keepLocalTime

Сценарий: пользователь меняет часовой пояс события

Например:

Встреча начинается в 10:00

Пользователь хочет:

10:00 по Нью-Йорку
вместо
10:00 по Лондону

В этом случае требуется сохранить локальные часы, а не timestamp.


Когда keepLocalTime использовать нельзя

Для:

  • логов;
  • серверного времени;
  • API;
  • хранения дат в БД;
  • синхронизации;
  • таймеров;
  • аналитики.

В таких задачах необходимо сохранять абсолютный момент времени.


Типичная ошибка

Неверное ожидание:

dt.setZone('UTC')

Многие предполагают, что время на часах не изменится.

Но Luxon сохраняет timestamp.

Поэтому часы пересчитываются.


Неправильное понимание UTC

const dt = DateTime.fromISO(
  '2025-06-01T12:00:00',
  { zone: 'Europe/Moscow' }
);

console.log(dt.toUTC().toString());

Результат:

2025-06-01T09:00:00.000Z

Это корректно.

Потому что:

12:00 Moscow
=
09:00 UTC

Разница между сменой зоны и созданием новой даты

Смена зоны

dt.setZone('UTC')

Меняется отображение.


Создание нового времени

DateTime.fromObject(
  {
    year: 2025,
    month: 6,
    day: 1,
    hour: 12
  },
  {
    zone: 'UTC'
  }
)

Создаётся новый timestamp.


Использование fixed-offset зон

setZone() поддерживает фиксированные смещения:

dt.setZone('UTC+3')

или:

dt.setZone('UTC-5')

Пример

const dt = DateTime.utc();

const zone = dt.setZone('UTC+6');

console.log(zone.toString());

Работа с DST

DST (Daylight Saving Time) особенно важен при переводе зон.


Пример летнего времени

const winter = DateTime.fromISO(
  '2025-01-10',
  { zone: 'Europe/Berlin' }
);

const summer = DateTime.fromISO(
  '2025-07-10',
  { zone: 'Europe/Berlin' }
);

console.log(winter.offset);
// 60

console.log(summer.offset);
// 120

Влияние на setZone()

При переводе Luxon автоматически учитывает DST.

const utc = DateTime.utc(2025, 7, 1, 12);

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

console.log(berlin.toString());

Результат будет учитывать летнее время.


Проверка текущей зоны

console.log(dt.zoneName);

Проверка смещения

console.log(dt.offset);

Результат в минутах:

180

означает:

UTC+3

Определение локальной зоны

console.log(DateTime.local().zoneName);

Практический сценарий: сервер и клиент

Сервер хранит UTC

const createdAt = DateTime.utc();

Клиент отображает локальное время

const local = createdAt.toLocal();

Практический сценарий: международный календарь

Сохранение события

const event = DateTime.fromISO(
  '2025-12-01T15:00:00',
  { zone: 'UTC' }
);

Отображение для пользователя из Токио

event.setZone('Asia/Tokyo')

Отображение для пользователя из Нью-Йорка

event.setZone('America/New_York')

Событие остаётся тем же самым.


Практический сценарий: перенос расписания

Иногда требуется не перевод времени, а именно перенос.

Например:

09:00 London
→
09:00 Tokyo

Тогда используется:

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

Сравнение поведения

Обычный перевод

const dt = DateTime.fromISO(
  '2025-01-01T09:00:00',
  { zone: 'Europe/London' }
);

console.log(
  dt.setZone('Asia/Tokyo').toString()
);

Результат:

18:00 Tokyo

С keepLocalTime

console.log(
  dt.setZone(
    'Asia/Tokyo',
    { keepLocalTime: true }
  ).toString()
);

Результат:

09:00 Tokyo

Это уже другой timestamp.


Что выбрать на практике

Использовать setZone()

Когда требуется:

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

Использовать toUTC()

Когда требуется:

  • хранение дат;
  • работа с сервером;
  • сериализация;
  • API;
  • база данных;
  • единый формат времени.

Использовать toLocal()

Когда требуется:

  • отображение локального времени;
  • интерфейс пользователя;
  • браузерные приложения;
  • локализация времени.

Краткая схема поведения

setZone()

timestamp сохраняется
часы пересчитываются

setZone(…, { keepLocalTime: true })

часы сохраняются
timestamp изменяется

toUTC()

setZone('UTC')

toLocal()

setZone(systemZone)