Миграция на js-joda требует предварительной
инвентаризации текущего использования временных API. Основная цель —
выявить все точки, где используются встроенный Date,
сторонние библиотеки (Moment.js, Day.js, Luxon) и самописные утилиты для
работы с датой и временем.
Ключевой этап подготовки — определение границ временной логики:
Установка базового пакета:
npm install @js-joda/core
Для работы с часовыми поясами:
npm install @js-joda/timezone
js-joda реализует иммутабельную модель времени, в
которой каждая операция возвращает новый объект. Это принципиально
отличается от Date, который изменяем и содержит скрытые
особенности работы с UTC.
Основные типы:
LocalDate — дата без времени и зоныLocalTime — время без датыLocalDateTime — дата и время без зоныZonedDateTime — дата, время и часовой поясInstant — момент времени в UTCНа этапе анализа выявляются типичные паттерны:
const now = new Date();
const tomorrow = new Date();
tomorrow.setDate(now.getDate() + 1);
Проблемные места:
DateЭквивалент в js-joda:
import { LocalDate } from '@js-joda/core';
const today = LocalDate.now();
const tomorrow = today.plusDays(1);
Полная замена временной модели редко выполняется одномоментно. Используется промежуточный слой адаптации.
Создание утилит-обёрток:
import { LocalDate, Instant } from '@js-joda/core';
export const fromDate = (date) =>
Instant.ofEpochMilli(date.getTime());
export const toDate = (instant) =>
new Date(instant.toEpochMilli());
Такой слой позволяет постепенно переводить модули без нарушения интерфейсов.
// Было
const d = new Date(2025, 0, 15);
// Стало
import { LocalDate } from '@js-joda/core';
const d = LocalDate.of(2025, 1, 15);
Важно учитывать, что месяцы в Date начинаются с 0, тогда
как js-joda использует естественную нумерацию.
// Было
const d = new Date();
d.setDate(d.getDate() + 7);
import { LocalDate } from '@js-joda/core';
const d = LocalDate.now().plusDays(7);
Все операции становятся функциональными и не изменяют исходное значение.
// Date
if (a > b) { ... }
import { LocalDate } from '@js-joda/core';
if (a.isAfter(b)) { ... }
Moment.js часто используется как обёртка над изменяемыми объектами времени. Основная сложность миграции — переход от цепочек мутаций к иммутабельной модели.
// Moment.js
moment('2025-01-15')
import { LocalDate } from '@js-joda/core';
LocalDate.parse('2025-01-15');
// Moment.js
moment().add(7, 'days')
LocalDate.now().plusDays(7);
// Moment.js
moment().format('YYYY-MM-DD')
В js-joda форматирование вынесено в отдельный API:
import { DateTimeFormatter } from '@js-joda/core';
LocalDate.now().format(DateTimeFormatter.ISO_DATE);
Luxon ближе по философии к js-joda, но отличается
моделью типов и зависимостью от DateTime.
// Luxon
DateTime.local(2025, 1, 15)
import { LocalDate } from '@js-joda/core';
LocalDate.of(2025, 1, 15);
Luxon:
DateTime.now().setZone('Europe/Berlin')
js-joda:
import { ZonedDateTime, ZoneId } from '@js-joda/core';
ZonedDateTime.now(ZoneId.of('Europe/Berlin'));
Основная сложность миграции связана с переходом от локального времени к явной модели зоны.
Типичный анти-паттерн:
const date = new Date();
Такой код не содержит информации о контексте времени.
Эквивалент:
import { ZonedDateTime, ZoneId } from '@js-joda/core';
const date = ZonedDateTime.now(ZoneId.systemDefault());
Работа с зонами становится явной частью модели данных.
JSON-обмен требует преобразования объектов js-joda в
строки или timestamps.
const json = {
date: LocalDate.now().toString()
};
const date = LocalDate.parse(json.date);
import { Instant } from '@js-joda/core';
const instant = Instant.ofEpochMilli(Date.now());
const millis = instant.toEpochMilli();
LocalDateTime.now() // отсутствие timezone может быть критичным
Правильный выбор:
ZonedDateTime.now()
LocalDate + LocalTime // некорректная логика без явного объединения
Корректно:
localDate.atTime(localTime);
if (localDate == anotherDate)
Корректно:
localDate.isEqual(anotherDate);
При интеграции в большие системы используется слой адаптеров между старым и новым представлением времени.
Пример универсального конвертера:
import { LocalDate } from '@js-joda/core';
export const legacyToLocalDate = (date) =>
LocalDate.of(
date.getFullYear(),
date.getMonth() + 1,
date.getDate()
);
Основное внимание уделяется регрессионным сценариям:
Пример сравнения:
expect(LocalDate.now().plusDays(1).isAfter(LocalDate.now())).toBe(true);
js-joda снижает количество ошибок, связанных с мутацией
объектов времени и неявной работой UTC. Однако увеличивается
необходимость явного управления типами и зонами.
Ключевые последствия миграции: