Миграция существующего кода

Миграция на js-joda требует предварительной инвентаризации текущего использования временных API. Основная цель — выявить все точки, где используются встроенный Date, сторонние библиотеки (Moment.js, Day.js, Luxon) и самописные утилиты для работы с датой и временем.

Ключевой этап подготовки — определение границ временной логики:

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

Установка базового пакета:

npm install @js-joda/core

Для работы с часовыми поясами:

npm install @js-joda/timezone

Базовая модель перехода

js-joda реализует иммутабельную модель времени, в которой каждая операция возвращает новый объект. Это принципиально отличается от Date, который изменяем и содержит скрытые особенности работы с UTC.

Основные типы:

  • LocalDate — дата без времени и зоны
  • LocalTime — время без даты
  • LocalDateTime — дата и время без зоны
  • ZonedDateTime — дата, время и часовой пояс
  • Instant — момент времени в UTC

Анализ использования Date

На этапе анализа выявляются типичные паттерны:

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());

Такой слой позволяет постепенно переводить модули без нарушения интерфейсов.


Переход с нативного Date

Замена создания даты

// Было
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.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

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());

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


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

JSON-обмен требует преобразования объектов js-joda в строки или timestamps.

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

const json = {
  date: LocalDate.now().toString()
};

Десериализация

const date = LocalDate.parse(json.date);

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

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()
  );

Тестирование после миграции

Основное внимание уделяется регрессионным сценариям:

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

Пример сравнения:

expect(LocalDate.now().plusDays(1).isAfter(LocalDate.now())).toBe(true);

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

js-joda снижает количество ошибок, связанных с мутацией объектов времени и неявной работой UTC. Однако увеличивается необходимость явного управления типами и зонами.

Ключевые последствия миграции:

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