Библиотеки работы с датами исторически решали проблему отсутствия удобного API в JavaScript. Однако архитектура современного ECMAScript и развитие стандартного Intl API сделали значительную часть их функциональности избыточной.
Основные проблемы, которые приводят к отказу от
moment.js и частично от date-fns:
Современный подход заключается в использовании встроенных международных API, предоставляемых движком Jav * aScript: Intl.DateTimeFormat, Intl.RelativeTimeFormat, Intl.NumberFormat, Intl.Collator, Intl.PluralRules.
Ключевой компонент замены moment.js — Intl.DateTimeFormat. Он позволяет форматировать даты в зависимости от локали без внешних зависимостей.
const formatter = new Intl.DateTimeFormat('ru-RU', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
formatter.format(new Date());
Вместо:
moment().format('LL');
Разница принципиальная: формат определяется не строковым шаблоном, а набором опций, интерпретируемых ICU.
const timeFormatter = new Intl.DateTimeFormat('ru-RU', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
timeFormatter.format(new Date());
Эквивалент moment().format('HH:mm:ss') без необходимости
подключения библиотеки.
moment.js требует подключения локалей:
import 'moment/locale/ru';
moment.locale('ru');
Intl API использует данные окружения:
new Intl.DateTimeFormat('ru-RU', { dateStyle: 'full' }).format(new Date());
Одно из наиболее частых применений moment.js — относительное время.
const rtf = new Intl.RelativeTimeFormat('ru', { numeric: 'auto' });
rtf.format(-1, 'day'); // "вчера"
rtf.format(3, 'day'); // "через 3 дня"
// moment
moment().subtract(5, 'minutes').fromNow();
// Intl
const rtf = new Intl.RelativeTimeFormat('ru', { numeric: 'auto' });
rtf.format(-5, 'minute');
function formatRelative(date) {
const diff = (date - new Date()) / 1000;
const units = [
['year', 31536000],
['month', 2592000],
['day', 86400],
['hour', 3600],
['minute', 60],
['second', 1]
];
const rtf = new Intl.RelativeTimeFormat('ru', { numeric: 'auto' });
for (const [unit, seconds] of units) {
const delta = diff / seconds;
if (Math.abs(delta) >= 1) {
return rtf.format(Math.round(delta), unit);
}
}
return rtf.format(0, 'second');
}
date-fns не покрывает валюты, moment вообще не предназначен для этого, но в реальных приложениях это часто соседняя задача.
const currency = new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: 'RUB'
});
currency.format(1500);
new Intl.NumberFormat('ru-RU', {
style: 'percent'
}).format(0.25);
new Intl.NumberFormat('ru-RU', {
style: 'unit',
unit: 'kilometer'
}).format(10);
Сортировка строк с учётом языка — одна из причин появления вспомогательных функций в date-fns-подобных экосистемах.
const collator = new Intl.Collator('ru', { sensitivity: 'base' });
['яблоко', 'Ёж', 'арбуз'].sort(collator.compare);
moment.js и date-fns не решают задачу склонений.
const pr = new Intl.PluralRules('ru');
pr.select(1); // one
pr.select(2); // few
pr.select(5); // many
Использование:
const forms = {
one: 'день',
few: 'дня',
many: 'дней',
other: 'дня'
};
function formatDays(n) {
const rule = new Intl.PluralRules('ru').select(n);
return `${n} ${forms[rule]}`;
}
date-fns строится как набор функций:
format()
addDays()
subMonths()
differenceInDays()
Intl API не работает как библиотека манипуляции датами, но закрывает слой представления.
Date или Temporal (в
будущем)moment(date)
.add(3, 'days')
.locale('ru')
.format('D MMMM YYYY');
const date = new Date();
date.setDate(date.getDate() + 3);
const formatter = new Intl.DateTimeFormat('ru-RU', {
day: 'numeric',
month: 'long',
year: 'numeric'
});
formatter.format(date);
Intl-объекты дорогие в создании, поэтому их переиспользование критично.
const formatters = {
ruLongDate: new Intl.DateTimeFormat('ru-RU', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
};
formatters.ruLongDate.format(new Date());
В высоконагруженных системах создание форматтера внутри функции становится узким местом.
Intl API напрямую зависит от ICU-данных, встроенных в runtime:
Это делает Intl более системным API, чем библиотечным.
| moment.js / date-fns | Intl API |
|---|---|
| format() | Intl.DateTimeFormat |
| fromNow() | Intl.RelativeTimeFormat |
| formatCurrency() | Intl.NumberFormat (currency) |
| sort strings | Intl.Collator |
| plural rules | Intl.PluralRules |
Современный стек работы с датами в JavaScript фактически разделяется на три уровня:
Дата как значение
Логика
Представление
Разделение этих слоёв устраняет необходимость в универсальной библиотеке уровня moment.js и снижает связность кода с внешними зависимостями.