Казалось бы, зачем нужна еще одна библиотека для работы с датами и временем когда есть всем известная библиотека Moment?! Тем интереснее, что альтернатива предложена самой командой Moment.
Библиотека Luxon заявлена как мощное, современное и удобное средство для работы с датами и временем в JavaScript. Библиотеку создал Айзек Камброн (Isaac Cambron), входящий в команду разработчиков Moment с 2013 года.
У автора было много идей по развитию Moment, которые он не мог сделать в рамках существующего кода. Вот основные моменты, которые хотелось реализовать:
- опробовать некоторые идеи как сделать API более логичным (но эти идеи были не совместимы с подходом, принятым в Moment),
- реализовать «из коробки» работу с временными зонами без дополнительных расширений,
- полностью переосмыслить работу с интернационализацией с учетом появления Intl API,
- перейти на современный набор инструментов и подходов при формирования JS кода.
Поэтому он решил написать все с нуля, что заняло около двух лет.
В результате получился как бы модернизированный вариант Moment.
Получившийся вариант показался интересным всей команде разработчиков Moment, так что было решено продвигать новую библиотеку под эгидой команды.
Принципы Luxon
- Цепочки вызовов как в Moment.
- Все типы иммутабельными.
- Более ясный и очевидный API: для разных объектов — разные методы с четко определенными параметрами.
- Intl API для обеспечения интернационализации (откат к английскому варианту, если браузер не поддерживает Intl API).
- Intl API для обеспечения работы с временными зонами.
- Более полная поддержка расчета длительности.
- Встроенная поддержка работы с интервалами.
- Инлайн документация кода.
Эти принципы привели к следующим улучшениям:
- Код Luxon намного проще в понимании и отладке.
- Использование встроенных возможностей браузера для интернационализации улучшает поведение библиотеки и, опять же, облегчает отладку.
- Поддержка временных зон реализована лучше чем в любой другой JS библиотеке.
- Luxon предоставляет одновременно простой и очень мощный инструмент для работы с длительностью.
- У библиотеки хорошая документация.
Но у Luxon есть и свои недостатки:
- Упор на использование встроенных возможностей браузера приводит к сложностям в поддержке старых браузеров.
- Некоторые возможности интернационализации, которые еще не поддерживаются браузерами не реализованы и в библиотеке (необходимо ожидать, когда в браузерах такая поддержка появится).
- Реализация Intl API в разных браузерах может различаться, соответственно, будет различаться и поведение Luxon.
Установка
Luxon предоставляет модули под все современные платформы JavaScript.
В документации есть полный список поддерживаемых браузеров с указанием ограничений применения. Для браузеров у которых отсутствует или ограничена поддержка Intl рекомендуется использовать полифил (в частности это касается IE 10 или 11).
При работе с Node.js (6+), если нужна работа с локалями, то потребуется дополнительно установить пакет full-icu и задать переменную окружения, чтобы включить использование этого пакета.
Стандартный способ установки из npm:
npm install --save luxon
У Luxon есть поддержка как TypeScript так и Flow, так же есть модуль в формате ES6.
Быстрый обзор
Библиотека Luxon состоит из пяти основных классов:
DateTime — дата и время с часовым поясом и настройками отображения, а так же сопутствующие методы.
Duration — период времени (длительность), например, «2 месяца» или «1 день, 3 часа».
Info — статические методы для получения общих данных о времени и дате.
Interval — интервал времени и методы для работы с ним.
Settings — статические методы, которые задают общее поведение Luxon.
import {DateTime, Duration, Info, Interval, Settings} from 'luxon';
Ваш первый DateTime
Самый важный класс в Luxon — DateTime. DateTime представляет дату+время вместе с часовым поясом и локалью. Вот так можно задать 15 мая 2017 года 08:30 в локальном часовом поясе:
var dt = DateTime.local(2017, 5, 15, 8, 30);
Вот вызов для определения текущего времени:
var now = DateTime.local();
Создание из объекта
DateTime.fromObject({
month:12,
day: 22,
hour: 12,
minutes: 20,
zone: 'Europe/Kaliningrad'
}); //=> 2018-12-22T12:20:00.000+02:00
Создание из строки в формате ISO 8601
DateTime.fromISO("2017-05-15"); //=> May 15, 2017 at 0:00
DateTime.fromISO("2017-05-15T08:30:00"); //=> May 15, 2017 at 8:30
При преобразовании в строку Luxon так же возвращает строку в формате ISO 8601:
DateTime.local().toString(); //=> "2018-12-18T20:58:29.995+03:00"
Получение отдельных компонентов:
var dt = DateTime.local();
dt.year; //=> 2018
dt.month; //=> 12
dt.day; //=> 18
dt.second; //=> 27
dt.weekday; //=> 2
dt.zoneName; //=> "Europe/Moscow"
dt.offset; //=> 180
dt.daysInMonth; //=> 31
Вывод в форматированном виде
Luxon имеет множество методов для преобразования DateTime в строку, два из них наиболее важны toLocaleString и toISO, первый преобразует в формат с учетом лакали браузера, а второй готовит текст для программной обработки (к примеру, для передачи на сервер):
dt.toLocaleString(); //=> "18.12.2018"
dt.toLocaleString(DateTime.DATETIME_MED); //=> "18 дек. 2018 г., 21:46"
dt.toISO(); //=> "2018-12-18T21:46:55.013+03:00"
Для форматированного вывода в Luxon есть два десятка готовых «пресетов» (таких как DATETIME_MED и TIME_WITH_LONG_OFFSET).
Так же можно сформировать собственный вариант форматирования на основе токенов:
dt.setLocale('ru').toFormat('d MMMM tt - ZZZZZ');
//=> "18 декабря 21:46:55 - Москва, стандартное время"
Преобразования DateTime
Важное замечание: объекты Luxon — иммутабельны, т.е. любые изменяющие методы примененные к ним возвращают измененную копию не изменяя исходного объекта. Поэтому все термины в этой статье (как и в документации Luxon) такие как «изменить», «установить», «переопределить» надо понимать как «создать новый экземпляр с другими свойствами».
Математические преобразования
var dt = DateTime.local(2018, 12, 18, 20, 30); //=> "18.12.2018, 20:30"
dt.plus({hours: 3, minutes: 2}); //=> "18.12.2018, 23:32"
dt.minus({days: 7}); //=> "11.12.2018, 20:30"
dt.startOf('day'); //=> "18.12.2018, 0:00"
dt.endOf('hour'); //=> "18.12.2018, 20:00"
Переопределение отдельных параметров
var dt = DateTime.local();
dt.set({hour: 3}).hour //=> 3
Преобразования Intl
Luxon поддерживает несколько разных преобразований Intl, одним из наиболее важных является форматирование под различные локали:
var dt = DateTime.local();
var f = {month: 'long', day: 'numeric'};
dt.setLocale('fr').toLocaleString(f); //=> "18 décembre"
dt.setLocale('en-GB').toLocaleString(f); //=> "18 December"
dt.setLocale('en-US').toLocaleString(f); //=> "December 18"
Класс Info может возвращать списки месяцев и дней недели в заданной локали:
Info.months('long', {locale: 'it'}); //=> ["gennaio", "febbraio", "marzo", ...]
Info.weekdays ('short', {locale: 'de'}); //=> ["Mo", "Di", "Mi", ...]
Часовые пояса
Luxon поддерживает часовые пояса. Если создавать DateTime без явного указания временной зоны, то по умолчанию будет выбрана локальная зона. Если у существующего DateTime изменить зону, то время и дата будут пересчитаны с учетом разницы между часовыми поясами.
var dt = DateTime.local(2018, 12, 18, 20, 00); //=> 2018-12-18T20:00:00.000+03:00
dt.zone.name; //=> "Europe/Moscow"
dt.setZone('Asia/Vladivostok'); //=> 2018-12-19T03:00:00.000+10:00
Luxon также поддерживает работу с датой и временем в формате UTC:
DateTime.utc(2018, 5, 15); //=> 2018-05-15T00:00:00.000Z
DateTime.utc(); //=> 2018-12-18T17:58:29.995Z
DateTime.local().toUTC(); //=> 2018-12-18T17:58:29.995Z
DateTime.utc().toLocal(); //=> 2018-12-18T20:58:29.995+03:00
Длительность
Класс Duration предоставляет возможность работать с длительностью, например, «2 часа и 7 минут». Создать длительность можно так:
var dur = Duration.fromObject({hours: 2, minutes: 7});
Длительности могут складываться и вычитаться. Длительность может иметь отрицательное значение.
dur.minus(dur).minus(dur); //=> {hours: -2, minutes: -7}
Подобным образом длительность может быть добавлена или вычтена из DateTime.
DateTime.local().plus(dur);
У длительности есть геттеры (похожие на геттеры DateTime):
dur.hours; //=> 2
dur.minutes; //=> 7
dur.seconds; //=> 0
dur.zone; //=> undefined
Так же у длительности есть и другие полезные методы:
dur.as('seconds'); //=> 7620
dur.toObject(); //=> { hours: 2, minutes: 7 }
dur.toISO(); //=> 'PT2H7M'
Интервалы
Интервалы задаются как период между двумя временными точками, для работы с ними используется класс Interval. Время начала входит в интервал, а время окончания — нет: соответственно, начало при преобразовании в строку отмечается квадратной скобкой, а конец — круглой.
var today = DateTime.local(2018, 12, 18);
var later = DateTime.local(2020, 10, 12);
var interval = Interval.fromDateTimes(today, later);
interval.toString();
//=> "[2018-12-18T00:00:00.000+03:00 – 2020-10-12T00:00:00.000+03:00)"
interval.toISO();
//=> "2018-12-18T00:00:00.000+03:00/2020-10-12T00:00:00.000+03:00"
interval.length(); //=> 57369600000
interval.length('years', true); //=> 1.8169398907103824
interval.contains(DateTime.local(2019)); //=> true
Интервалы можно сравнивать между собой и комбинировать друг с другом:
var nextYear = Interval.after(DateTime.local(), {year: 1});
var prevYear = Interval.before(DateTime.local(), {year: 1});
prevYear.overlaps(nextYear); //false
prevYear.abutsStart(nextYear); //true
nextYear.union(prevYear).length('years'); //=> 2
Luxon и Moment
Библиотека Luxon «обитает» в проекте «Moment», но не является полной заменой библиотеки Moment. Luxon не предоставляет полной функциональности Moment, к примеру, относительное форматирование дат только совсем недавно было реализовано в браузере Chrome версии 71, в других браузерах пока не работает и в Luxon поддержка для него еще не реализована (хотя и ожидается). Но даже если браузеры поддерживают требуемый функционал, то надо понимать, что он будет доступен только в этих новых средах. В устаревших браузерах Luxon будет работать с проблемами, в то время как Moment работает всегда и везде.
Кроме этого API Luxon был полностью переработан и он совершенно не совпадает с API Moment.
Отметим главные различия между Moment и Luxon.
Иммутабельность
Объекты Luxon иммутабельны, а у Moment — нет.
В приведенном ниже примере m1 и m2 — это один и тот же объект, который был изменен методом add.
var m1 = moment();
var m2 = m1.add(1, 'hours');
m1 === m2; //=> true
В случае Luxon метод plus возвращает новый объект d2 не изменяя исходный d1.
var d1 = DateTime.local();
var d2 = d1.plus({ hours: 1 });
d1 === d2; //=> false
d1.valueOf() === d2.valueOf(); //=> false
По этой причине Luxon не требует специальных конструкторов для копирования и методов для клонирования, которые используются Moment для получения копий без изменения исходного значения.
Основные функциональные различия
- Отсчет месяцев в Luxon начинается с 1, а не с нуля как в Moment (и нативноv js-объекте Date).
- Локализация и временные зоны реализованы с помощью нативного Intl API (или полифила), а не встроены в библиотеку.
- Luxon имеет встроенные типы Duration и Interval.
- Luxon пока не поддерживает относительное форматирование дат.
- В Luxon так же пока нет метода humanize для представления длительности в «очеловеченном» стиле (к примеру, «a few seconds»).
Различия в стиле API
- В методах API Luxon опционные параметры обычно располагаются последними.
- Luxon имеет множество отдельных методов для создания объектов(например, fromISO), в отличие от Moment, который имеет для этого одну функцию, а тип объекта задается параметрами.
- У Luxon очень строгие парсеры, в то время как у Moment они более либеральные, т.е. если формат входной строки будет отличаться от стандартного, то Luxon сразу выдаст ошибку, а Moment какие-то ошибки в формате попробует исправить.
- Для получения значения внутренних полей Luxon использует геттеры (dt.year, dt.isValid), а не методы как Moment (m.year(), m.isValid()).
- Luxon позволяет одним методом сразу установить все необходимые параметры dt.set({year: 2016, month: 4}), в Moment они задаются только по одному — цепочкой вызовов m.year(2016).month(4).
- Длительность в Luxon — это отдельный класс верхнего уровня Duration.
В остальном Luxon заимствовал много идей из Moment, в документации даже приводятся таблицы эквивалентности методов Moment и Luxon.
Размеры файлов библиотек
Luxon (v. 1.8.2)
luxon.min.js — 61 KB
Moment (v. 2.23.0)
moment.min.js — 51 KB
moment.min.js + locale/ru.js — 59 KB
moment-with-locales.min.js — 323 KB
Как видим, без локалей Moment по размеру на 10 KB меньше чем Luxon, но с добавлением нескольких локалей размер становится примерно равным.
Если же требуется поддержка сразу всех локалей, то тут существенный выигрыш у Luxon.
Резюме
Библиотека полностью готова к использованию и автор обещает ее поддержку. У библиотеки уже 7k+ звезд на гитхабе и популярность ее только растет. В ее код делает коммиты не только сам автор, но еще не менее 6 разработчиков.
Предположу, что библиотека Luxon это ответ на появление в браузерах поддержки Intl API. Разработчики Moment понимают, что работа с датами на вебе может существенно измениться и пытаются подготовиться к этим изменениям. Но точно предсказать развитие веба, а вместе с ним и нового проекта (который сами называют Moment labs project) они не могут. Будут ли идеи Luxon перенесены в Moment 3? Перейдет ли в какой то момент большинство пользователей с Moment на Luxon? Может быть Luxon будет переименован в Moment? Сами разработчики признаются, что не могут сейчас ответить на эти вопросы.