Переход с Moment.js

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

Luxon создавалась как современная альтернатива с акцентом на:

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

Миграция с Moment.js на Luxon требует не только замены методов, но и изменения подхода к работе с датой как с объектом.


Концептуальные различия моделей Moment и Luxon

Изменяемость против неизменяемости

Moment.js использует мутабельную модель:

const m = moment();
m.add(1, 'day'); // изменяет исходный объект

Luxon строится на неизменяемости:

const { DateTime } = require('luxon');

const dt = DateTime.now();
const updated = dt.plus({ days: 1 });

Ключевая особенность: любой метод возвращает новый объект, исходный не меняется.

Это влияет на всю архитектуру кода:

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

Разделение сущностей

Moment объединяет дату, время, длительности и форматирование в одном объекте.

Luxon разделяет сущности:

  • DateTime — конкретный момент во времени;
  • Duration — промежуток времени;
  • Interval — интервал между двумя моментами;
  • Zone — часовой пояс.

Такое разделение устраняет неоднозначность:

const { DateTime, Duration } = require('luxon');

const dt = DateTime.local();
const dur = Duration.fromObject({ hours: 2 });
const later = dt.plus(dur);

Базовая замена Moment API

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

Moment:

moment();
moment("2024-01-01");
moment.utc();

Luxon:

DateTime.now();
DateTime.fromISO("2024-01-01");
DateTime.utc();

Основное различие — явное указание формата входных данных.


Форматы строк

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

moment("01-02-2024"); // зависит от локали

Luxon требует явного формата:

DateTime.fromFormat("01-02-2024", "dd-MM-yyyy");

Или стандарт ISO:

DateTime.fromISO("2024-02-01");

Это снижает неоднозначность и ошибки интерпретации дат.


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

Сравнение API

Moment:

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

Luxon:

DateTime.now().toFormat("yyyy-MM-dd HH:mm");

Отличия:

  • токены отличаются (YYYYyyyy);
  • метод format заменён на toFormat.

Работа с локалями

Moment:

moment().locale("ru").format("LL");

Luxon:

DateTime.now().setLocale("ru").toLocaleString(DateTime.DATE_FULL);

Luxon использует предустановленные форматы:

  • DateTime.DATE_SHORT
  • DateTime.DATE_MED
  • DateTime.DATETIME_FULL

и опирается на Intl API.


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

Основная разница подхода

Moment требует дополнительного пакета moment-timezone.

Luxon имеет встроенную поддержку через Intl.


Moment:

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

Luxon:

DateTime.fromISO("2024-01-01T10:00", { zone: "Europe/Moscow" });

или изменение зоны:

DateTime.now().setZone("Europe/Moscow");

Перевод между зонами

Moment:

moment().tz("Asia/Tokyo");

Luxon:

DateTime.now().setZone("Asia/Tokyo");

Важно: Luxon не мутирует объект, а создаёт новый с другой зоной.


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

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

Moment:

moment().add(2, "days").subtract(3, "hours");

Luxon:

DateTime.now()
  .plus({ days: 2 })
  .minus({ hours: 3 });

Различия:

  • объектный формат вместо двух аргументов;
  • единообразие plus/minus.

Работа с Duration

Moment:

moment.duration(2, "hours");

Luxon:

const { Duration } = require("luxon");

Duration.fromObject({ hours: 2 });

Duration в Luxon — полноценная сущность с методами:

const d = Duration.fromObject({ hours: 2, minutes: 30 });
d.as("minutes"); // 150

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

Moment:

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

Luxon:

dtA < dtB;
dtA > dtB;
dtA.hasSame(dtB, "day");

Или более явно:

dtA.startOf("day").equals(dtB.startOf("day"));

Нормализация и начало/конец периода

Moment:

moment().startOf("month");
moment().endOf("day");

Luxon:

DateTime.now().startOf("month");
DateTime.now().endOf("day");

Поведение схожее, но результат всегда новый объект.


Интервалы

Moment не имеет встроенной сущности Interval.

Luxon:

const { DateTime, Interval } = require("luxon");

const start = DateTime.now();
const end = start.plus({ hours: 5 });

const interval = Interval.fromDateTimes(start, end);
interval.length("hours"); // 5
interval.contains(DateTime.now());

Interval становится важной частью миграции, так как часто заменяет кастомную логику.


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

1. Неявный парсинг дат

Moment:

moment("2024-01-02");

Luxon:

DateTime.fromISO("2024-01-02");

Необходимо явно указывать формат, иначе результат будет invalid.


2. Мутабельный код

Было:

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

Становится:

const dt = DateTime.now();
return dt.plus({ days: 1 });

Игнорирование возврата нового объекта приводит к ошибкам.


3. Различие токенов форматирования

Moment:

  • YYYY — год
  • DD — день

Luxon:

  • yyyy
  • dd

Ошибки в токенах приводят к некорректному выводу без исключений.


4. UTC и локальное время

Moment:

moment.utc();

Luxon:

DateTime.utc();

Но важнее различие в поведении .toLocal():

DateTime.utc().toLocal();

Эквивалентные паттерны миграции

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

Moment:

moment();

Luxon:

DateTime.now();

ISO строка

Moment:

moment().toISOString();

Luxon:

DateTime.now().toISO();

Unix timestamp

Moment:

moment().valueOf();

Luxon:

DateTime.now().toMillis();

или:

DateTime.now().toSeconds();

Клонирование даты

Moment:

moment(m);

Luxon:

dt;

Клонирование обычно не требуется из-за неизменяемости.


Структурный подход к переписыванию кода

Миграция с Moment на Luxon обычно сводится к трём слоям преобразований:

  1. Парсинг

    • moment()DateTime.from*
  2. Манипуляции

    • add/subtractplus/minus
  3. Вывод

    • formattoFormat или toLocaleString

Рекомендации по постепенной миграции

Кодовые базы часто содержат смешанное использование Moment и Luxon. В таких случаях применяется промежуточная стратегия:

  • изолирование работы с датами в отдельный модуль;
  • создание адаптеров между типами;
  • постепенная замена точек использования Moment API;
  • исключение прямого использования moment() вне слоя абстракции.

Поведенческие изменения, влияющие на архитектуру

Переход на Luxon меняет не только синтаксис, но и стиль проектирования:

  • исчезает необходимость защищаться от мутаций;
  • упрощается поток данных через функции;
  • возрастает роль явных типов времени (DateTime, Duration, Interval);
  • уменьшается количество неявных преобразований.

Эти изменения приводят к более детерминированной работе с временными данными и снижают вероятность скрытых ошибок в логике приложения.