21 December 2018

Luxon — новая библиотека для работы с датами от команды Moment.js

Website developmentJavaScript


Казалось бы, зачем нужна еще одна библиотека для работы с датами и временем когда есть всем известная библиотека Moment?! Тем интереснее, что альтернатива предложена самой командой Moment.

Библиотека Luxon заявлена как мощное, современное и удобное средство для работы с датами и временем в JavaScript. Библиотеку создал Айзек Камброн (Isaac Cambron), входящий в команду разработчиков Moment с 2013 года.

У автора было много идей по развитию Moment, которые он не мог сделать в рамках существующего кода. Вот основные моменты, которые хотелось реализовать:

  • опробовать некоторые идеи как сделать API более логичным (но эти идеи были не совместимы с подходом, принятым в Moment),
  • реализовать «из коробки» работу с временными зонами без дополнительных расширений,
  • полностью переосмыслить работу с интернационализацией с учетом появления Intl API,
  • перейти на современный набор инструментов и подходов при формирования JS кода.

Поэтому он решил написать все с нуля, что заняло около двух лет.
В результате получился как бы модернизированный вариант Moment.
Получившийся вариант показался интересным всей команде разработчиков Moment, так что было решено продвигать новую библиотеку под эгидой команды.

Принципы Luxon


  1. Цепочки вызовов как в Moment.
  2. Все типы иммутабельными.
  3. Более ясный и очевидный API: для разных объектов — разные методы с четко определенными параметрами.
  4. Intl API для обеспечения интернационализации (откат к английскому варианту, если браузер не поддерживает Intl API).
  5. Intl API для обеспечения работы с временными зонами.
  6. Более полная поддержка расчета длительности.
  7. Встроенная поддержка работы с интервалами.
  8. Инлайн документация кода.

Эти принципы привели к следующим улучшениям:

  • Код 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 для получения копий без изменения исходного значения.

Основные функциональные различия


  1. Отсчет месяцев в Luxon начинается с 1, а не с нуля как в Moment (и нативноv js-объекте Date).
  2. Локализация и временные зоны реализованы с помощью нативного Intl API (или полифила), а не встроены в библиотеку.
  3. Luxon имеет встроенные типы Duration и Interval.
  4. Luxon пока не поддерживает относительное форматирование дат.
  5. В 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? Сами разработчики признаются, что не могут сейчас ответить на эти вопросы.
Tags: moment.js luxon javascript javascript library intl работа со временем
Hubs: Website development JavaScript
+16
14.4k 82
Comments 18
Ads
Top of the day