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



    Казалось бы, зачем нужна еще одна библиотека для работы с датами и временем когда есть всем известная библиотека 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? Сами разработчики признаются, что не могут сейчас ответить на эти вопросы.
    Поделиться публикацией

    Похожие публикации

    Комментарии 13

      +5
      Ну и как тут не вспомнить что есть еще и вторая по популярности библиотека – date-fns.org

      Отличается от moment и luxon тем, что в ней используются не объекты-обертки, а чистые функции, работающие с нативным Date.
        –6
        О! Название функции «format». Ещё одно название в мой список идиотских очень понятных, правильных и емких названий, которыми так популярны модные и молодежные библиотеки.
          +5

          Не вижу ничего непонятного в этом имени. И в Moment, и в нативном Intl.DateTimeFormat тоже есть метод format.


          Более того, с date-fns вы получаете просто набор функций, которые можно переименовать при импорте по своему желанию.


          import {format as formatDateByPattern} from 'date-fns';
          
          formatDateByPattern(new Date(2014, 1, 11), 'MM/DD/YYYY');
          +3
          А главное — не настолько жирная, как momentjs.
          +2
          Авторы молодцы. Не стали дожидаться пока люди сделают библиотеку-конкурента и взяли быка за рога.
            –1

            Да 4 года назад уже сделали.


            • Имеет всё те же преимущества, что и Luxon. Даже больше: offset является объектом duration, а не просто числом; интервалы могут задаваться не только двумя датами, но и одной из дат + продолжительность, и всё это правильно сериализуется в ISO, а не в какой-то свой формат с открытыми интервалами; везде, где ожидается момент/интервал/продолжительность может передаваться как объект, так и json с компонентами, так и iso8601 представление.
            • Разработана за два месяца одним разработчиком, а не два года шестью.
            • Весит 20кб в неминифицированном виде, а не 60 в минифицированном.
            • По скорости на порядок быстрее Moment, сомневаюсь, что Luxon сильно быстрее.

            Но кому это всё надо? Никому, там же всего полторы сотни звёздочек.

              0
              API Luxon намного больше той части, что приведена в статье.

              • Для создания интервалов есть несколько разных методов в том числе Interval.after и Interval.before, которые принимают два параметра: дату и продолжительность
              • Сериализовать интервал в ISO можно выполнив один метод interval.toISO()
              • У Luxon есть методы, которые так же могут принимать разные типы данных — вот, к примеру, описание метода Interval.after:
                Interval.after(start: DateTime | Date | Object, duration: Duration | Object | number): Interval

              В документации много еще всего есть, если, конечно, интересно.
                0
                API Luxon намного больше

                А API $mol_time намного меньше, при той же выразительности.


                Для создания интервалов есть несколько разных методов

                Которые сразу же забывают про переданную продолжительность.


                интервал в ISO можно

                Только вот это уже будет не тот интервал, что мы создавали. В ISO их 3 вида. И это именно разные сущности с разной семантикой, а не просто 3 способа задать одну сущность. Разница ощущается, когда начинается арифметика, где внезапно оказывается, что месяц — это не всегда 30 дней и тп.


                start: DateTime | Date | Object

                Как обычно статическая типизация сделана для галочки. Вот так это должно выглядеть:


                start : number | Date | string | {
                    year? : number
                    month? : number
                    day? : number
                    hour? : number
                    minute? : number
                    second? : number
                    offset? : $mol_time_duration_config
                }
            +1
            Интересно было бы увидеть сравнение luxon и js-joda.
              0

              Размер luxon стоит сравнивать не только с moment+locales, но с moment+locales+moment-timezone
              И вот там выигрыши получается настолько большой, что выбор очевиден. (В основном потому что в moment-timezone необходимо грузить БД с таймзонами, а в luxon используется intl API)

                0

                В moment.js до сих пор не запилили tree shaking? Как с этим дела у Luxon и date-fns?

                  0
                  Как я понимаю, какие то доработки по tree shaking стоят в планах для версии Luxon 2.0.
                    +2

                    В Luxon получше чем у moment, потому что теперь есть 3 отдельные импорты DateTime, Duration, Interval, вместо одного супер-объекта moment.


                    У date-fns c этим дела обстоят лучше всех, потому что там экспортируются отдельные функции, как в lodash

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое