TempusJS — работаем с датой в javascript

в 10:34, , рубрики: javascript, работа с датами, метки: ,

Всем привет!
Мне часто приходится работать со статистическими данными и там очень много завязано на датах. Притом, одна и та же дата может быть использована на странице в разных форматах (например, в удобном для машины и в удобном для человека). Я думаю, что большинство из вас отлично представляют весь этот ужасающий по размерам код, который получается при использовании объекта Date.
К примеру, чтобы получить текущую дату в формате ДД.ММ.ГГГГ нам потребуется сделать следующее:

var d = new Date(),
       fd = d.getDate() + '.' + (d.getMonth()+1) + '.' + d.getFullYear();

А когда таких строк становится много? Легко ли упомнить, что в javascript месяц начинается с нуля, когда разрабатываешь не только на нем? Или то, что тут миллисекунды, а не секунды, как почти везде на бэкенде? Можно решить часть задач популярной библиотекой Moment.js, но она работает весьма медленно.
Рассматриваемая библиотека решает эти проблемы.
Если интересно, предлагаю вам прочитать этот небольшой обзор.

TempusJS во многом состоит из синтаксического сахара над объектом Date, поэтому работает очень быстро. Синтаксис же самой библиотеки весьма прост. Например, записать предыдущий пример, можно так:

var fd = tempus().format('%d.%m.%Y');

Теперь о скорости. В спойлере вы можете увидеть сравнение Tempus с Moment и нативным способом форматирования даты (см. выше):

Сравнение нативного JS, MomentJS и TempusJS

Получаем текущую дату

Native JS x 2,175,575 ops/sec ±0.75% (96 runs sampled) 
Moment x 284,864 ops/sec ±0.85% (96 runs sampled) 
Tempus x 2,086,081 ops/sec ±0.73% (97 runs sampled)
Форматирование

Native JS x 1,637,517 ops/sec ±0.61% (100 runs sampled) 
Moment x 8,808 ops/sec ±1.07% (100 runs sampled) 
Tempus x 942,815 ops/sec ±0.68% (94 runs sampled) 
Автоопределение даты и парсинг

Native JS x 11,204,316 ops/sec ±0.81% (88 runs sampled) 
Moment x 38,511 ops/sec ±1.41% (95 runs sampled) 
Tempus x 93,973 ops/sec ±1.06% (85 runs sampled) 

Парсинг даты по формату

Moment x 46,293 ops/sec ±0.63% (100 runs sampled) 
Tempus x 109,947 ops/sec ±0.93% (99 runs sampled) 

Парсинг и валидация

Moment x 44,588 ops/sec ±1.09% (90 runs sampled) 
Tempus x 103,439 ops/sec ±0.90% (94 runs sampled)

Результаты получены на моем ноутбуке в Google Chrome 30.0.1599.114. В других браузерах результаты отличаются, но соотношение остается примерно тем же.
Для тестов использовалась библиотека benchmark.js
Бенчмарки по другим функциям, вы можете увидеть тут.

Итак, в преимущества данной библиотеки можно записать следующее:

  • Поддерживает IE6+, Chrome, Firefox, Opera;
  • Поддерживает цепочки вызовов;
  • Месяцы могут начинаться с 1 (по умолчанию), а не нуля;
  • Миллисекунды могут быть отключены (по умолчанию) или включены;
  • Быстрая работа (Так как, во многих случаях, используется родной для браузера объект Date, реализация которого написана на более быстрых языках);
  • Поддерживает кастомные форматы и плагины
  • Валидация даты очень быстра и зависит только от устанавливающих дату функций (т.к. валидация проходит уже при занесении значений, а не высчитывается отдельно);
  • Есть генератор массива дат;
  • Мультиязычность и автоопределение языка пользователя.

Все функции этой библиотеки описаны в документации. Также, на сайте имеются тесты (если вы обнаружите какую-либо ошибку, то сообщите о ней, пожалуйста, сюда.

Здесь речь пойдет только о некоторых функциях.

Форматирование и парсинг

Итак, для начала еще один пример форматирования даты. Здесь мы также используем цепочку вызовов. В конце каждой установки значения, мы получаем обратно объект TempusDate, который можем использовать дальше в цепочке. Пример:

tempus(). // получаем новую дату
    calc({month: -1}). // уменьшаем ее на один месяц
    format('%d.%m.%Y'); // Выводим в виде строки

Таким образом, мы получим этот же день, час и секунду, но месяц назад. Это бывает полезно для получения отчетов за последний месяц.

Следующий пример — парсинг даты.

// Вернет объект TempusDateс датой "2013-11-18"
tempus('18.11.2013');
// Вернет объект TempusDateс датой "2013-12-12"
tempus('2013-12-12', '%Y-%m-%d'));

Tempus может автоматически определять некоторые известные форматы. Также, можно указывать определенный формат, тогда парсинг будет происходить быстрее. Плюс к этому, можно задавать дату, которая будет возвращена, если парсинг завершится неудачей:

// Т.к. "123" не подходит к формату '%d.%m.%Y', то 
// будет возвращен объект, содержащий дату 2013-01-01
tempus('123', '%d.%m.%Y', tempus([2013, 1, 1]));

Список форматов по умолчанию можно посмотреть здесь

А теперь поменяем формат уже отформатированной даты

// '2013-11-05'
tempus('05.11.2013').format('%Y-%m-%d');
	
// Или так
// 'October, 12'
tempus('2013-10-12 12:31:01', '%Y-%m-%d %H:%M:%S').format('%B, %d');

Также, можно использовать локализацию для форматирования. По умолчанию, будет выбран язык пользователя (берем из браузера) или язык по умолчанию, если язык пользователя не обнаружен среди доступных языков Tempus.

// Устанавливаем язык
tempus.lang('ru');

// Стандартно используем format
// 'Ноябрь, 05'
tempus(1383609600).format('%B, %d');

На данный момент языков всего два — русский и английский, так что буду рад помощи.

Валидация

Валидация даты происходит следующим образом:

// Вернет false
tempus('32.08.2013', '%d.%m.%Y').valid();

// Вернет true
tempus('00:00 01.01.2012', '%H:%M %d.%m.%Y').valid();

В случае ошибки, можно посмотреть поля, в которых она возникла — везде, где значение не false:

// Вернет {"year":-5,"month":false,"day":false,"hours":false,
//     "minutes":false,"seconds":false,"milliseconds":false}
tempus().
    year(-5). // задаем год=-5, т.е. невалидный
    errors(); // получаем объект с ошибками

Диапазоны дат

Иногда нам требуется получить количество лет (например, возраст), месяцев, дней и т.д. между двумя датами. Для этого мы можем использовать метод between, который находит разницу между двумя датами и возвращает в нужном формате ('year', 'month', 'day', 'hours', 'minutes', 'seconds', 'milliseconds').
Вот простой пример получения количества месяцев между 1 ноября 2013 года и 5 мая 2014 года:

// Вернет 6
tempus([2013, 11, 1]).between(tempus([2014, 5, 5]), 'month');

Или сколько часов осталось до нового года

tempus().between(tempus([2014]), 'hours');

В последнем примере можно заметить, что я указал только год. При установке значения массивом или объектом, нехватающие значения будут
заполнены минимальными. Список констант с минимальными значениями, вы можете увидеть в документации.

Также, мы можем изменить какую-либо дату, используя функцию calc:

// Вернет TempusDate с датой 2012-01-01
tempus([2011, 5, 2]).calc({year: 1, month: -4, day: -1});

Свои форматы

Применяем свой формат для месяца, который может принимать значения от 1 до 12 (а не 01 до 12):

// Регистрируем новый формат
tempus.registerFormat('%q', // директива - %q
    function(date) { // Здесь указываем функцию форматирования, т.е. что будет подставлено вместо %q
        return date.month();
    },
    function(value) { // А здесь функцию парсинга
        var v = Number(value);
        return {month: (isNaN(v) ? undefined : v) };
    },
    1, // Минимальная длина, которую может принимать значение
    2, // Максимальная длина
    'number' // Тип
);
 
// Тестируем
// Вернет "01.1.2013";
tempus({year: 2013, month: 1, day: 1}).format('%d.%q.%Y');
 
// Вернет {"year":2013,"month":2,"day":10,"hours":0,"minutes":0,"seconds":0};
tempus('10.2.2013', '%d.%q.%Y').get();

При регистрации можно заметить, что некоторые параметры задаются отдельно, в то время, как можно было бы использовать регулярное выражение. На самом деле, там изначально оно и было, но после отказа от него, скорость выросла в несколько десятков раз.
Если вам потребуется удалить какой то формат, то используйте unregisterFormat:

tempus.unregisterFormat('%d');
// Вернет "%d.01.2013", т.к. директивы %d уже не существует.
tempus.format({year: 2013, month: 1, day: 1}, '%d.%m.%Y');

Геттеры/сеттеры

Можно получать/устанавливать некоторые значения, используя функции year(), month(), day(), hours(), minutes(), seconds(), milliseconds(), dayOfWeek(), utc(), timestamp() или set(). Например:

tempus(). // Получаем текущую дату
    year(1900). // Оставляем все как есть, но устанавливаем 1900 год
    leapYear(); // Проверяем, високосный ли это год, в данном случае false
    
tempus().year(); // А так получаем текущий год в числовом виде

Генерация дат

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

// returns ["29.03.2013", "30.03.2013", "31.03.2013", "01.04.2013", "02.04.2013"];
tempus.generate({
    dateFrom: '20130329',
    formatFrom: '%Y.%m.%d',
    dateTo: '20130402',
    period: {day: 1},
    format: '%d.%m.%Y'
});

Это может быть полезно для отображения графиков по датам и смена формата отображения прямо на клиенте, без запросов к бэкенду. Дата может быть сгенерирована как массив, так и как объекты, где в качестве ключей будут сами даты (это полезно, когда нам требуется к какой-либо дате привязать какое-либо событие, например, когда делаем свой календарь). Также, даты могут быть сгруппированы по дням, неделям, месяцам, часам, годам — по чему угодно. Это также может быть применено к календарю.

Плагины

Ну и последнее — плагины. Здесь мы расширяем фабрику, для генерации случайной даты. Также, нам потребуется класс TempusDate, его можно найти в tempus.classes(). Вот пример плагина:

(function (tempus) {
    var TempusDate = tempus.classes('TempusDate');
    tempus.randomDate = function() {
        var date = new TempusDate();
        date.year(Math.floor((Math.random()*(tempus.MAX_YEAR - tempus.MIN_YEAR)) + tempus.MIN_YEAR)).
             month(Math.floor((Math.random()*(tempus.MAX_MONTH - tempus.MIN_MONTH)) + tempus.MIN_MONTH)).
             day(Math.floor((Math.random()*(date.dayCount() - tempus.MIN_DAY)) + tempus.MIN_DAY)).
             hours(Math.floor((Math.random()*(tempus.MAX_HOURS - tempus.MIN_HOURS)) + tempus.MIN_HOURS)).
             minutes(Math.floor((Math.random()*(tempus.MAX_MINUTES - tempus.MIN_MINUTES)) + tempus.MIN_MINUTES)).
             seconds(Math.floor((Math.random()*(tempus.MAX_SECONDS - tempus.MIN_SECONDS)) + tempus.MIN_SECONDS));
        return date;
    };
})(tempus);

// Теперь мы можем создавать даты следующим образом
var someRandomDate = tempus.randomDate();

Думаю, что таким способом можно будет удобно писать виджеты, используя связку jQuery+Tempus, Angular+Tempus и т.п.

Исходники

Установить можно, скачав исходники с гитхаба:
https://github.com/crusat/tempus-js/releases
Или через bower:

$ bower install tempus

Вам потребуется только один файл — tempus.js или tempus.min.js.

Надеюсь, что данная библиотека окажется полезной, а также интересно было бы узнать, чего в ней не хватает, чтобы дальше развивать библиотеку в правильном направлении. Спасибо за внимание!
P.S. Спасибо за инвайт!

Автор: crusat

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js