Переход с Moment.js

Библиотека Moment.js долгое время использовалась как стандарт де-факто для работы с датами и временем в JavaScript. Однако её архитектура основана на изменяемых объектах и «монолитном» подходе к API, что приводит к проблемам масштабируемости, предсказуемости и поддержки в современных приложениях.

js-joda представляет собой реализацию подхода Java Time API (JSR-310) в JavaScript, ориентированную на строгую типизацию концепций времени, неизменяемость и композиционность. Переход между этими библиотеками требует пересмотра привычных паттернов работы с датами.

Ключевые отличия:

  • неизменяемые объекты вместо мутаций
  • разделение понятий даты, времени и зоны
  • отсутствие «универсального Date объекта»
  • строгая работа с форматированием и парсингом
  • явное управление часовыми поясами

Архитектурная разница моделей данных

Moment.js использует единую сущность moment, которая объединяет дату, время, таймзону и операции над ними. В js-joda каждое понятие выделено отдельно:

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

Такой подход исключает неоднозначности, возникающие при смешивании контекстов.

Пример различия:

// Moment.js
const m = moment("2025-01-01T10:00:00");

// js-joda
const dateTime = LocalDateTime.parse("2025-01-01T10:00:00");

Неизменяемость вместо мутаций

Moment.js изменяет существующий объект:

const m = moment();
m.add(1, 'day');

В js-joda каждое изменение возвращает новый объект:

const today = LocalDate.now();
const tomorrow = today.plusDays(1);

Следствие архитектуры:

  • отсутствуют побочные эффекты
  • безопасное использование в многопоточном и асинхронном коде
  • предсказуемое состояние объектов

Сопоставление базовых операций

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

Moment.js:

moment("2024-06-01");

js-joda:

LocalDate.parse("2024-06-01");

Текущая дата

Moment.js:

moment();

js-joda:

LocalDate.now();

Работа с компонентами времени

Moment.js:

moment().year();
moment().month();
moment().date();

js-joda:

const date = LocalDate.now();
date.year();
date.monthValue();
date.dayOfMonth();

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

Добавление и вычитание

Moment.js:

moment().add(10, 'days');
moment().subtract(2, 'months');

js-joda:

const d = LocalDate.now();
d.plusDays(10);
d.minusMonths(2);

Важное различие поведения

Moment.js изменяет объект, js-joda возвращает новый экземпляр. Это критично при переносе бизнес-логики, завязанной на цепочечные вызовы.


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

Moment.js активно использует шаблоны:

moment().format("YYYY-MM-DD HH:mm");

js-joda требует явного форматтера:

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

const formatter = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm');
LocalDateTime.now().format(formatter);

Особенности перехода:

  • шаблоны Moment.js не совместимы напрямую
  • регистр символов отличается (YYYYyyyy)
  • форматтеры создаются отдельно и переиспользуются

Часовые пояса

Moment.js часто использует plugin moment-timezone:

moment.tz("2024-01-01", "Europe/Moscow");

js-joda выделяет зоны в отдельный тип:

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

const zone = ZoneId.of("Europe/Moscow");
const zdt = ZonedDateTime.now(zone);

Ключевое отличие:

  • зона является обязательной частью модели, а не расширением

Instant и работа с UTC

Moment.js:

moment.utc();

js-joda:

Instant.now();

Для преобразования:

const zdt = ZonedDateTime.now();
const instant = zdt.toInstant();

Разница в работе с интервалами времени

Moment.js использует duration и moment.duration:

moment.duration(5, 'days');

js-joda:

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

Duration.ofDays(5);

Для дат используется Period:

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

Period.ofMonths(2);

Разделение важно:

  • Duration — точное время (секунды, наносекунды)
  • Period — календарные единицы (дни, месяцы, годы)

Сравнение дат

Moment.js:

momentA.isBefore(momentB);
momentA.isAfter(momentB);

js-joda:

dateA.isBefore(dateB);
dateA.isAfter(dateB);

Дополнительно:

dateA.equals(dateB);

Парсинг нестандартных форматов

Moment.js допускает гибкий парсинг:

moment("01/02/2024", "DD/MM/YYYY");

js-joda требует явного форматтера:

const formatter = DateTimeFormatter.ofPattern('dd/MM/yyyy');
LocalDate.parse("01/02/2024", formatter);

Следствие:

  • меньше «магии» при разборе строк
  • выше предсказуемость входных данных
  • явное управление ошибками парсинга

Типичные ошибки при миграции

1. Ожидание мутаций

const d = LocalDate.now();
d.plusDays(1); // результат не сохраняется

Корректно:

const d2 = d.plusDays(1);

2. Использование Moment-форматов

YYYY-MM-DD // неверно для js-joda

Корректно:

yyyy-MM-dd

3. Попытка прямой замены API

Moment.js часто позволяет неявные преобразования типов, js-joda требует явных конверсий между сущностями:

LocalDateTime.now().toLocalDate();
LocalDate.now().atStartOfDay();

Стратегия поэтапной миграции

Изоляция слоя дат

Логика работы со временем выносится в отдельный модуль, исключая прямое использование Moment.js в бизнес-слое.


Параллельное использование библиотек

На переходном этапе возможно сосуществование:

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

const legacy = moment();
const modern = LocalDate.now();

Постепенная замена моделей

  • сначала заменяются операции форматирования
  • затем арифметика дат
  • затем работа с зонами
  • в последнюю очередь — парсинг входных данных

Сопоставление API Moment.js и js-joda

Moment.js js-joda
moment() LocalDateTime.now()
moment().add() plusDays / plusMonths
moment().format() format(formatter)
moment.utc() Instant.now()
moment.tz() ZonedDateTime + ZoneId
moment.duration() Duration / Period

Изменение подхода к проектированию времени

Переход приводит к изменению логики:

  • вместо универсального объекта времени используются специализированные типы
  • вместо мутаций применяется функциональный стиль
  • вместо неявных преобразований используются строгие операции конвертации
  • вместо глобальных таймзон применяется явное указание зоны

Такой подход уменьшает количество скрытых ошибок, связанных с датами, особенно в распределённых системах и приложениях с несколькими часовыми поясами