js-joda-timezone

Библиотека js-joda предоставляет современную модель работы с датой и временем, вдохновлённую API java.time из Java 8. Базовый пакет @js-joda/core содержит классы для локальных дат, времени и временных отметок, однако не включает полноценную поддержку часовых поясов IANA.

Пакет @js-joda/timezone добавляет:

  • поддержку базы часовых поясов IANA;
  • работу с регионами (Europe/Moscow, Asia/Almaty);
  • автоматический учёт переходов на летнее и зимнее время;
  • корректные вычисления смещений UTC;
  • преобразования между часовыми поясами.

Без js-joda-timezone объект ZonedDateTime способен работать только с фиксированными смещениями (+03:00, UTC), но не с реальными мировыми часовыми зонами.


Установка

npm install @js-joda/core @js-joda/timezone

Подключение:

const {
    ZonedDateTime,
    LocalDateTime,
    ZoneId,
    Instant
} = require('@js-joda/core')

require('@js-joda/timezone')

Импорт timezone-пакета обязателен. Он расширяет функциональность ZoneRulesProvider и подключает базу временных зон.


Что такое часовой пояс в js-joda

В библиотеке существуют два разных понятия:

Тип Пример Назначение
ZoneOffset +05:00 фиксированное смещение
ZoneId Asia/Almaty реальная временная зона

Фиксированное смещение

const { ZoneOffset } = require('@js-joda/core')

const offset = ZoneOffset.of('+05:00')

console.log(offset.toString())

Результат:

+05:00

Такое смещение не учитывает летнее время и изменения правил.


Региональная зона

const zone = ZoneId.of('Asia/Almaty')

console.log(zone.id())

Результат:

Asia/Almaty

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


База часовых поясов IANA

js-joda-timezone использует базу:

tz database (Olson database)

Она содержит:

  • мировые часовые зоны;
  • исторические изменения;
  • переходы на DST;
  • отмены и изменения правил.

Примеры зон:

Europe/Berlin
America/New_York
Asia/Tokyo
Asia/Almaty
UTC

Получение объекта ZoneId

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

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

Использование UTC

const utc = ZoneId.of('UTC')

Проверка идентификатора

console.log(zone.id())

Создание ZonedDateTime

Из LocalDateTime

const dateTime = LocalDateTime.of(
    2025,
    3,
    10,
    14,
    30
)

const zone = ZoneId.of('Asia/Almaty')

const zoned = dateTime.atZone(zone)

console.log(zoned.toString())

Результат:

2025-03-10T14:30+05:00[Asia/Almaty]

Структура ZonedDateTime

Объект содержит:

  1. локальную дату;
  2. локальное время;
  3. смещение UTC;
  4. идентификатор зоны.

Пример:

2025-03-10T14:30+05:00[Asia/Almaty]

Разбор:

Часть Значение
2025-03-10 дата
14:30 время
+05:00 смещение
Asia/Almaty зона

Получение текущего времени в зоне

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

console.log(now.toString())

Преобразование между часовыми поясами

withZoneSameInstant

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

const tokyo = ZonedDateTime.now(
    ZoneId.of('Asia/Tokyo')
)

const london = tokyo.withZoneSameInstant(
    ZoneId.of('Europe/London')
)

console.log(tokyo.toString())
console.log(london.toString())

Пример результата:

2025-03-10T21:00+09:00[Asia/Tokyo]
2025-03-10T12:00Z[Europe/London]

Момент времени одинаковый, отображение разное.


withZoneSameLocal

Метод сохраняет локальное время.

const localChanged = tokyo.withZoneSameLocal(
    ZoneId.of('Europe/London')
)

Теперь:

21:00 в Токио

станет:

21:00 в Лондоне

Абсолютный момент времени изменится.


Работа с UTC

UTC играет ключевую роль при хранении времени в распределённых системах.

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

const utcTime = ZonedDateTime.now(
    ZoneId.of('UTC')
)

Перевод в локальную зону

const local = utcTime.withZoneSameInstant(
    ZoneId.of('Asia/Almaty')
)

Instant и часовые пояса

Instant не содержит информации о часовом поясе.

Он представляет:

точку времени на временной шкале UTC

Создание ZonedDateTime из Instant

const instant = Instant.now()

const zoned = instant.atZone(
    ZoneId.of('Europe/Paris')
)

console.log(zoned.toString())

Смещение UTC

Получение смещения

const zoned = ZonedDateTime.now(
    ZoneId.of('America/New_York')
)

console.log(zoned.offset().toString())

Пример:

-04:00

DST — летнее время

Одно из главных преимуществ js-joda-timezone — автоматическая работа с переходами DST.


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

В некоторых странах время перескакивает:

02:00 -> 03:00

Попытка создать несуществующее время:

const dateTime = LocalDateTime.of(
    2025,
    3,
    30,
    2,
    30
)

const zone = ZoneId.of('Europe/Berlin')

const zoned = dateTime.atZone(zone)

console.log(zoned.toString())

Библиотека автоматически скорректирует значение согласно правилам зоны.


Дублирующееся время

При переводе часов назад время может повторяться дважды.

Например:

02:30

может существовать:

  • до перевода;
  • после перевода.

js-joda-timezone использует правила зоны для выбора корректного смещения.


Получение правил зоны

ZoneRules

const zone = ZoneId.of('Europe/Berlin')

const rules = zone.rules()

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

const instant = Instant.now()

const offset = rules.offset(instant)

console.log(offset.toString())

Проверка DST

const isDst = rules.isDaylightSavings(
    Instant.now()
)

console.log(isDst)

Получение длительности DST

const duration = rules.daylightSavings(
    Instant.now()
)

console.log(duration.toString())

Список доступных зон

const zones = ZoneId.getAvailableZoneIds()

console.log(zones)

Результат:

Set(596) { ... }

Проверка существования зоны

function zoneExists(id) {
    return ZoneId
        .getAvailableZoneIds()
        .has(id)
}

console.log(
    zoneExists('Asia/Almaty')
)

Парсинг ZonedDateTime

ISO-формат

const zoned = ZonedDateTime.parse(
    '2025-03-10T14:30:00+05:00[Asia/Almaty]'
)

console.log(zoned.toString())

Форматирование

Стандартный вывод

console.log(zoned.toString())

Использование DateTimeFormatter

const {
    DateTimeFormatter
} = require('@js-joda/core')

const formatter =
    DateTimeFormatter.ofPattern(
        'yyyy-MM-dd HH:mm z'
    )

console.log(
    zoned.format(formatter)
)

Пример:

2025-03-10 14:30 ALMT

Краткие и полные названия зон

Символы форматирования

Символ Значение
z короткое имя
zzzz полное имя
X ISO offset
VV идентификатор зоны

Пример

const formatter =
    DateTimeFormatter.ofPattern(
        'yyyy-MM-dd HH:mm:ss VV'
    )

console.log(
    zoned.format(formatter)
)

Конвертация пользовательского времени

Частая задача:

пользователь вводит локальное время

Необходимо:

  1. определить зону пользователя;
  2. преобразовать в UTC;
  3. сохранить в БД.

Пример

const localDateTime =
    LocalDateTime.parse(
        '2025-05-01T15:00'
    )

const userZone =
    ZoneId.of('Asia/Almaty')

const utc = localDateTime
    .atZone(userZone)
    .withZoneSameInstant(
        ZoneId.of('UTC')
    )

console.log(utc.toString())

Работа с серверным временем

Распространённая архитектура:

Данные Формат
хранение UTC
API ISO-8601
отображение локальная зона

Конвертация времени сервера

const serverTime =
    Instant.parse(
        '2025-05-01T10:00:00Z'
    )

const clientTime =
    serverTime.atZone(
        ZoneId.of('Asia/Almaty')
    )

console.log(clientTime.toString())

Исторические изменения времени

Преимущество js-joda-timezone перед обычным Date заключается в поддержке исторических правил.

Например:

  • изменение DST;
  • изменение UTC offset;
  • отмена летнего времени;
  • смена политических правил.

Сериализация

JSON

const json = JSON.stringify({
    meeting: zoned.toString()
})

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

const parsed = JSON.parse(json)

const meeting =
    ZonedDateTime.parse(
        parsed.meeting
    )

Отличие от встроенного Date

Возможность Date js-joda-timezone
неизменяемость нет да
IANA зоны ограниченно полноценно
DST нестабильно корректно
API устаревший современный
предсказуемость низкая высокая

Иммутабельность

Все объекты js-joda неизменяемы.

const original =
    ZonedDateTime.now(
        ZoneId.of('UTC')
    )

const changed =
    original.plusHours(5)

console.log(original.toString())
console.log(changed.toString())

Исходный объект не изменится.


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

Добавление часов

const result = zoned.plusHours(5)

Добавление дней

const result = zoned.plusDays(3)

Вычитание месяцев

const result = zoned.minusMonths(1)

Влияние DST на арифметику

Добавление суток и добавление 24 часов — разные операции.

plusDays

zoned.plusDays(1)

Сохраняет локальное время.


plusHours

zoned.plusHours(24)

Добавляет фиксированное количество часов.

При переходах DST результаты могут отличаться.


Сравнение ZonedDateTime

equals

a.equals(b)

Сравниваются:

  • дата;
  • время;
  • зона;
  • смещение.

isEqual

a.isEqual(b)

Сравнивается только абсолютный момент времени.


Пример различий

const tokyo = ZonedDateTime.parse(
    '2025-03-10T21:00+09:00[Asia/Tokyo]'
)

const london = ZonedDateTime.parse(
    '2025-03-10T12:00Z[Europe/London]'
)

console.log(
    tokyo.equals(london)
)

console.log(
    tokyo.isEqual(london)
)

Результат:

false
true

Использование в Node.js

js-joda-timezone особенно полезен:

  • в backend-сервисах;
  • системах бронирования;
  • финансовых системах;
  • календарях;
  • международных приложениях;
  • системах уведомлений.

Использование в браузере

Библиотека работает и в браузере.

Пример:

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

import '@js-joda/timezone'

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

const zone =
    Intl.DateTimeFormat()
        .resolvedOptions()
        .timeZone

console.log(zone)

Интеграция:

const userZone = ZoneId.of(zone)

Типичные ошибки

Отсутствие подключения timezone

Ошибка:

unsupported ZoneId

Причина:

require('@js-joda/timezone')

не был импортирован.


Использование Date вместо Instant

Нежелательно смешивать:

  • Date;
  • timestamp;
  • Instant;
  • LocalDateTime.

Лучше хранить время в Instant или ZonedDateTime.


Использование LocalDateTime без зоны

LocalDateTime не содержит:

  • UTC offset;
  • часовой пояс;
  • информацию DST.

Для глобальных приложений этого недостаточно.


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

Хранение

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

Instant или UTC

Отображение

Конвертировать в:

ZonedDateTime пользователя

API

Передавать:

ISO-8601

Пример:

2025-05-01T10:00:00Z

Практический пример: система встреч

Создание встречи

const meetingLocal =
    LocalDateTime.of(
        2025,
        6,
        1,
        15,
        0
    )

const organizerZone =
    ZoneId.of('Europe/Berlin')

const utcMeeting =
    meetingLocal
        .atZone(organizerZone)
        .withZoneSameInstant(
            ZoneId.of('UTC')
        )

console.log(utcMeeting.toString())

Отображение участнику

const participantZone =
    ZoneId.of('Asia/Tokyo')

const participantTime =
    utcMeeting.withZoneSameInstant(
        participantZone
    )

console.log(
    participantTime.toString()
)

Производительность

js-joda-timezone тяжелее обычного Date, поскольку:

  • использует immutable-модель;
  • хранит правила зон;
  • выполняет точные вычисления DST.

Однако преимущества:

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

Поддержка TypeScript

Библиотека содержит типы.

Пример:

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

import '@js-joda/timezone'

const zoned: ZonedDateTime =
    ZonedDateTime.now(
        ZoneId.of('UTC')
    )

Совместимость с ISO-8601

js-joda-timezone полностью ориентирован на ISO-8601.

Примеры:

2025-05-01T10:00:00Z
2025-05-01T15:00:00+05:00
2025-05-01T15:00:00+05:00[Asia/Almaty]

Когда использовать js-joda-timezone

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

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

Когда достаточно @js-joda/core

Если приложение:

  • не использует мировые часовые зоны;
  • работает только в UTC;
  • не требует DST;
  • не использует региональные зоны;

то может быть достаточно только @js-joda/core.


Ключевые классы timezone-пакета

Класс Назначение
ZoneId идентификатор зоны
ZoneRules правила зоны
ZoneOffset фиксированное смещение
ZonedDateTime дата и время с зоной
Instant момент времени UTC

Общая схема работы

Instant
   ↓
ZoneId
   ↓
ZonedDateTime
   ↓
Форматирование и отображение

Основные преимущества js-joda-timezone

  • immutable API;
  • предсказуемая арифметика;
  • полноценная поддержка IANA;
  • корректный DST;
  • отсутствие проблем встроенного Date;
  • удобная модель времени;
  • совместимость с Java java.time;
  • высокая читаемость кода;
  • безопасные преобразования между зонами.