ZonedDateTime для работы с зонированным временем

ZonedDateTime представляет собой один из ключевых типов в js-joda, предназначенный для работы с моментами времени с учётом часового пояса и правил смещения (UTC offset). В отличие от локальных представлений даты и времени, данный тип фиксирует не только календарно-временную структуру, но и контекст зоны, что делает его основным инструментом для серверных приложений, распределённых систем и любых сценариев, где критична точность временных меток.


ZonedDateTime объединяет три сущности:

  • календарную дату (год, месяц, день)
  • локальное время (часы, минуты, секунды, наносекунды)
  • часовую зону (ZoneId + правила смещения)

Такое объединение позволяет однозначно определить момент времени на временной шкале UTC и одновременно сохранить его локальное представление.

Ключевая особенность: ZonedDateTime всегда привязан к конкретной зоне, а не просто к фиксированному смещению. Это означает, что он учитывает исторические изменения правил часовых поясов, включая переходы на летнее/зимнее время.


Создание ZonedDateTime

Из текущего времени

import { ZonedDateTime, ZoneId } from '@js-joda/core';

const now = ZonedDateTime.now(ZoneId.of('Europe/Berlin'));

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

const systemNow = ZonedDateTime.now();

Из Instant (момента времени)

Instant — это абсолютная точка на временной оси UTC.

import { Instant, ZonedDateTime, ZoneId } from '@js-joda/core';

const instant = Instant.now();
const zdt = ZonedDateTime.ofInstant(instant, ZoneId.of('Asia/Almaty'));

Этот способ используется при конвертации серверных timestamp в локальные представления.


Из локальной даты и времени

import { LocalDateTime, ZonedDateTime, ZoneId } from '@js-joda/core';

const ldt = LocalDateTime.of(2026, 5, 24, 10, 30);
const zdt = ZonedDateTime.of(ldt, ZoneId.of('America/New_York'));

Здесь важно понимать: один и тот же LocalDateTime может соответствовать разным моментам времени в зависимости от зоны.


Структура объекта ZonedDateTime

ZonedDateTime можно рассматривать как комбинацию:

  • LocalDateTime → календарное представление
  • ZoneId → идентификатор зоны
  • ZoneOffset → текущее смещение
const zone = zdt.zone();
const offset = zdt.offset();
const local = zdt.toLocalDateTime();

Работа с часовыми поясами

Получение доступных зон

import { ZoneId } from '@js-joda/core';

const zone = ZoneId.of('Europe/Moscow');

Список зон основан на базе IANA (tz database), что обеспечивает корректную работу исторических переходов.


Изменение зоны без изменения момента времени

const original = ZonedDateTime.now(ZoneId.of('Asia/Tokyo'));
const converted = original.withZoneSameInstant(ZoneId.of('Europe/Paris'));

Метод withZoneSameInstant сохраняет абсолютный момент времени, меняя только представление.


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

const shifted = original.withZoneSameLocal(ZoneId.of('Europe/Paris'));

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


DST (летнее время) и неоднозначные моменты

Одна из сложностей ZonedDateTime — переходы между зимним и летним временем.

При создании времени в период перехода возможны:

  • несуществующие локальные времена
  • дублирующиеся часы

Пример:

const zdt = ZonedDateTime.of(
  LocalDateTime.of(2026, 3, 29, 2, 30),
  ZoneId.of('Europe/Berlin')
);

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


Арифметика дат и времени

ZonedDateTime поддерживает операции смещения:

Добавление времени

const future = zdt.plusHours(5);
const nextDay = zdt.plusDays(1);

Вычитание

const past = zdt.minusMinutes(90);

Важно: операции учитывают переходы часовых поясов. Добавление 24 часов не всегда эквивалентно «следующему календарному дню» в локальной зоне.


Сравнение ZonedDateTime

Проверка порядка

const a = ZonedDateTime.now(ZoneId.of('UTC'));
const b = ZonedDateTime.now(ZoneId.of('Europe/London'));

const result = a.isBefore(b);

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


Разница между моментами

const duration = a.until(b);
const seconds = duration.seconds();

Результат всегда выражается относительно UTC-оси.


Форматирование и парсинг

Строковое представление

const str = zdt.toString();

Формат ISO-8601 с зоной:

2026-05-24T10:30+03:00[Asia/Almaty]

Парсинг строки

import { ZonedDateTime } from '@js-joda/core';

const parsed = ZonedDateTime.parse(
  '2026-05-24T10:30+03:00[Asia/Almaty]'
);

Парсер требует корректного указания зоны.


Преобразование в Instant

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

const instant = zdt.toInstant();

Instant используется для хранения в базе данных, логирования и передачи по API.


Преобразование из Instant обратно

const restored = ZonedDateTime.ofInstant(
  instant,
  ZoneId.of('Europe/Paris')
);

Один и тот же Instant может выглядеть по-разному в разных зонах.


Практические сценарии использования

Хранение событий пользователя

ZonedDateTime позволяет фиксировать событие с учётом локальной зоны пользователя:

const eventTime = ZonedDateTime.now(userZone);

Планирование задач

const scheduled = ZonedDateTime.of(
  2026, 6, 1, 9, 0, 0, 0,
  ZoneId.of('Asia/Almaty')
);

Логирование распределённых систем

В логах предпочтительно использовать Instant, но ZonedDateTime помогает восстановить контекст:

const logTime = ZonedDateTime.now(ZoneId.of('UTC'));

Особенности неизменяемости

ZonedDateTime является immutable:

  • любые операции создают новый объект
  • исходное значение не изменяется
const updated = zdt.plusDays(1);
// zdt остаётся прежним

Это свойство упрощает работу в многопоточных и асинхронных средах.


Взаимодействие с базами данных

При сохранении рекомендуется:

  • хранить Instant
  • либо сохранять ZonedDateTime в ISO-формате
const dbValue = zdt.toInstant().toString();

При восстановлении — явное указание зоны:

const zdtFromDb = ZonedDateTime.ofInstant(
  Instant.parse(dbValue),
  ZoneId.of('Asia/Almaty')
);

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

Игнорирование зоны

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

Смешивание offset и zone

ZoneId содержит правила, OffsetDateTime — только фиксированное смещение. ZonedDateTime учитывает оба уровня.

Неправильные преобразования

Частая ошибка — использовать withZoneSameLocal вместо withZoneSameInstant, что приводит к смещению момента времени.


Производительность и архитектурные аспекты

ZonedDateTime более тяжёлый по сравнению с LocalDateTime и Instant из-за:

  • расчёта правил зоны
  • обработки DST
  • хранения дополнительного контекста

Поэтому в высоконагруженных системах:

  • ZonedDateTime используется на границе системы
  • внутри — Instant или epoch time

Совместимость с другими типами js-joda

  • LocalDateTime → локальное время без зоны
  • OffsetDateTime → фиксированное смещение
  • Instant → абсолютный момент
  • ZonedDateTime → полное представление с правилами зоны

ZonedDateTime можно рассматривать как наиболее полную форму представления времени в экосистеме даты и времени библиотеки.