Временные зоны в js-joda реализованы через строгую модель разделения
абсолютного времени (Instant) и локального
календарного времени (LocalDateTime). Такой подход исключает
неоднозначности, характерные для встроенного Date, и
позволяет корректно работать с переходами на летнее и зимнее время (DST
— Daylight Saving Time).
Ключевой принцип: время хранится как момент на временной оси, а отображается через правила конкретной временной зоны
В js-joda временная зона описывается через идентификатор
ZoneId. Он соответствует стандарту IANA Time Zone
Database:
Europe/BerlinAsia/AlmatyAmerica/New_YorkZoneId не содержит смещения сам по себе, а представляет
набор правил, которые определяют:
import { ZoneId } from '@js-joda/core';
const zone = ZoneId.of('Europe/Berlin');
Разделение между Instant и ZonedDateTime
является фундаментальным:
Представляет момент на временной шкале в UTC:
import { Instant } from '@js-joda/core';
const instant = Instant.now();
Комбинирует:
import { ZonedDateTime, ZoneId } from '@js-joda/core';
const zdt = ZonedDateTime.now(ZoneId.of('Asia/Almaty'));
Преобразование Instant в локальное время зоны:
import { Instant, ZoneId, ZonedDateTime } from '@js-joda/core';
const instant = Instant.parse('2026-01-01T12:00:00Z');
const zoned = ZonedDateTime.ofInstant(
instant,
ZoneId.of('Europe/Berlin')
);
Результат зависит от текущего смещения зоны, включая DST, если он активен.
Каждая временная зона в конкретный момент времени предоставляет
ZoneOffset:
const offset = zoned.offset();
console.log(offset.toString()); // например +01:00 или +02:00
DST представляет собой временное изменение стандартного смещения зоны.
При переходе вперёд часть локального времени не существует:
Пример: 02:00 → 03:00 (час «пропадает»)
import { LocalDateTime, ZoneId, ZonedDateTime } from '@js-joda/core';
const ldt = LocalDateTime.of(2026, 3, 29, 2, 30);
const zone = ZoneId.of('Europe/Berlin');
const zdt = ZonedDateTime.of(ldt, zone);
В таких случаях js-joda применяет правила смещения зоны и корректирует время.
При переходе назад один и тот же локальный час встречается дважды.
Пример: 02:30 может существовать дважды с разными смещениями.
const ldt = LocalDateTime.of(2026, 10, 25, 2, 30);
const zone = ZoneId.of('Europe/Berlin');
const zdt = ZonedDateTime.of(ldt, zone);
При DST-коллизиях применяются стратегии:
В js-joda поведение определяется внутренними правилами
ZoneRules.
Каждая зона содержит набор правил:
const rules = ZoneId.of('Europe/Berlin').rules();
ZoneRules позволяют:
const isDst = zoned.offset().totalSeconds() !== ZoneOffset.ofHours(1).totalSeconds();
Более корректный способ:
const rules = ZoneId.of('Europe/Berlin').rules();
const isDst = rules.isDaylightSavings(zoned.toInstant());
Перенос времени между зонами выполняется через
withZoneSameInstant:
const berlin = ZonedDateTime.now(ZoneId.of('Europe/Berlin'));
const tokyo = berlin.withZoneSameInstant(
ZoneId.of('Asia/Tokyo')
);
Смысл операции:
Сохраняет абсолютный момент времени
Сохраняет локальное время, изменяя момент
const shifted = berlin.withZoneSameLocal(ZoneId.of('Asia/Tokyo'));
Это приводит к изменению Instant.
IANA база включает изменения правил:
Пример: одна и та же зона может иметь разные offset в разные эпохи.
const past = ZonedDateTime.of(
1980,
6,
1,
12,
0,
0,
0,
ZoneId.of('Europe/Berlin')
);
LocalDateTime не содержит информации о временной
зоне:
const ldt = LocalDateTime.of(2026, 5, 25, 10, 0);
Он не может однозначно быть преобразован в Instant без
ZoneId.
const zdt = ldt.atZone(ZoneId.of('Asia/Almaty'));
При этом учитываются DST и правила зоны.
const instant = zdt.toInstant();
Результат всегда однозначен и не зависит от зоны.
Внутренние операции js-joda следуют цепочке:
Эта модель гарантирует отсутствие неоднозначностей при арифметике времени.
При добавлении часов учитываются правила DST:
const shifted = zdt.plusHours(5);
Если операция пересекает DST-переход:
Сравнение всегда происходит через Instant:
const a = ZonedDateTime.now(ZoneId.of('Europe/Berlin'));
const b = ZonedDateTime.now(ZoneId.of('Asia/Tokyo'));
const result = a.toInstant().isBefore(b.toInstant());
Нормализация применяется при создании ZonedDateTime из LocalDateTime:
Использование ZoneId позволяет:
DST рассматривается не как форматирование, а как часть правил зоны: