Perl и CPAN предоставляют множество самых разных инструментов для работы с временем. Традиционный и наиболее известный
DateTime
вызывает столь же традиционные серьёзные нарекания к скорости работы и потреблению памяти, поэтому он постепенно стал вытесняться из нашей системы альтернативными модулями. TIMTOWDI — это замечательно, но в проекте всё-таки хочется иметь какой-никакой порядок. Поэтому мы решили протестировать несколько самых популярных модулей по скорости, функционалу и удобству использования и выбрать тот самый единственный, который станет нашим основным инструментом.Начальные условия
Прежде чем проводить тесты, нужно было определиться с требованиями к модулям. Для нашего проекта были поставлены следующие условия.
Необходимый функционал:
- работа с временными зонами (вычисления с зонами, определение локальной);
- парсинг и форматирование строки с временем по шаблону;
- работа с интервалами времени (прибавить день, отнять неделю, посчитать разницу между датами);
- работа с такими понятиями как «первая неделя октября» или «двенадцатая неделя года» и т. п.
Дополнительные условия:
- желательно один объект времени, с которым можно было бы работать;
- лаконичный код;
- модуль не должен быть заброшенным (давно не обновлялся, множество незакрытых багов);
- работа в Debian со стандартным Perl (сейчас это v5.14.2).
Критерии оценки:
- соответствие требованиям по функционалу;
- скорость работы;
- адекватный интерфейс;
- субъективное удобство использования.
Модули
Модулей для работы с временем очень много, я выбрал наиболее популярные из них — чаще всего встречающиеся как в коде, так и в публикациях и рассказах коллег. Получившийся список (с номером версии, используемой в тестах):
- DateTime (1.18);
- Date::Manip (6.48);
- Time::Piece (1.29);
- Panda::Date (3.0.2);
- Date::Calc (6.4);
- Time::Moment (0.22).
Я сделаю краткий обзор возможностей и особенностей, влияющих на результаты тестов, и приведу результаты замеров. Подробное описание каждого модуля можно найти в родной документации.
DateTime
Наверное, наиболее популярный модуль. Точнее, даже группа модулей, т. к. разный функционал разделён по разным пространствам имён. В целом функционал
DateTime
очень широк, но модуль часто подвергается критике за низкую производительность.При создании нового объекта учитывается время с точностью до секунды, но работать может с точностью до наносекунд.
Парсить строки с датами сам по себе не умеет, но есть множество готовых парсеров (форматтеров),
DateTime::Format::*
. Они же используются и для формирования строки с временем в необходимом формате, если реализуют метод format_datetime
. Для тестов я буду использовать DateTime::Format::ISO8601
(чаще используется в разных API и сервисах) и DateTime::Format::Strptime
(позволяет использовать свой шаблон, аналогичный strptime). Также можно создать свой собственный парсер с помощью DateTime::Format::Builder
.Для работы с временными зонами можно использовать объекты
DateTime::TimeZone
, но это необязательно. Например, создать объект времени в определённой зоне можно просто указав time_zone => 'Asia/Taipei'
. Важно понимать, что описание зон находится в самом модуле и за их актуальностью нужно следить отдельно. Также можно вместо строки передать заранее подготовленный объект DateTime::TimeZone
, что может быть полезно, когда мы используем локальную временную зону. Определение её может быть долгим и эффективнее заранее подготовить объект, например, так:state $tz = DateTime::TimeZone->new( name => 'local' );
Для работы с интервалами используются объекты
DateTime::Duration
, эти же объекты возвращаются при вычитании дат.Сравнение дат можно выполнять как с учетом долей секунд, так и по целым значениям, используя соответствующие методы
DateTime->compare( $dt1, $dt2 )
и DateTime->compare_ignore_floating( $dt1, $dt2 )
.В целом интерфейс модуля мне кажется достаточно простым и понятным. Использование множества модулей может кому-то показаться неудобным, но организованы они грамотно и я не могу назвать это недостатком.
# Пустой объект создавать не умеет, для new всегда нужны параметры.
my $dt = DateTime->new(
year => 1964,
month => 10,
day => 16,
hour => 16,
minute => 12,
second => 47,
nanosecond => 500_000_000,
time_zone => 'Asia/Taipei',
);
# Объект с текущим локальным временем
$dt = DateTime->now();
# Объект с текущим временем UTC
$dt = DateTime->now( time_zone => 'UTC' );
# Объект с текущим временем в заданной временной зоне
$dt = DateTime->now( time_zone => '+1000' );
# Парсинг строки (ISO8601)
$dt = DateTime::Format::ISO8601->parse_datetime('2015-02-18T10:50:31.521345123+10:00');
# Парсинг строки по шаблону (медленнее, чем ISO8601)
my $dt_format = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%dT%H:%M:%S.%9N%z',
on_error => 'croak',
);
$dt = $dt_format->parse_datetime('2015-02-18T10:50:31.521345123+1000');
# Генерация строки по шаблону
my $str = $dt_format->format_datetime($dt);
# Прибавить 1 год 2 месяца 3 дня 4 часа 5 минут и 6 секунд
my $dt_duration = DateTime::Duration->new(
years => 1,
months => 2,
days => 3,
hours => 4,
minutes => 5,
seconds => 6,
);
my $dt2 = $dt + $dt_duration;
# Сравнить даты
my $result = DateTime->compare( $dt, $dt2 ); # результат: -1 т. к. $dt < $dt2
# Интервал между датами
my $interval = $dt2->subtract_datetime( $dt1 );
# Определение начала / конца недели и месяца
my $week_begin = $dt->clone->truncate( to => 'week' );
my $week_end = $week_begin->clone->add( days => 6 );
my $month_begin = $dt->clone->truncate( to => 'month' );
my $month_end = $month_begin->clone->add( months => 1 )->subtract( days => 1 );
Date::Manip
Главной особенностью этого модуля я бы назвал всеядность. Он умеет делать весьма хитрые манипуляции, например, определять дату по строке «8:00pm December tenth», и даже на разных языках. Но документация к этому модулю — самая непонятная (по крайней мере мне). Подобно
DateTime
, функционал разделён по множеству модулей, но логика их разделения не очевидна. Для того, чтобы создать новый объект, приходится использовать документацию сразу к трём модулям — Date::Manip
, Date::Manip::Date
, Date::Manip::Obj
.С долями секунды работать не умеет, хотя это и не всегда необходимо, но для кого-то может оказаться критичным.
Умеет парсить строковые даты вроде «8:00pm December tenth» или «4 business days later», ещё и на разных языках. Это очень круто, но я лично с такой задачей никогда не сталкивался. Наверное, это имеет смысл при работе с каким-то слабостандартизированным (или намеренно очень свободным) пользовательским вводом.
Интервалы представлены объектами
Date::Manip::Delta
, они же возвращаются как разность между датами.Для сравнения дат используется специальный метод:
$dm_date1->cmp( $dm_date2 );
Определение начала и конца недели и месяца возможно, но неудобно и очень медленно — для этого приходится выполнять несколько операций, и все они не радуют скоростью.
Не умеет создавать копию существующего объекта.
В целом интерфейс довольно специфичный. Наверное, к нему можно привыкнуть, и тогда он будет казаться более красивым (как с ObjectiveC), но нужно ли это делать?
# Новые пустые объекты. Отдельно для модуля и отдельно для даты, рекомендуется именно так,
# потому что внутри создаются и переиспользуются базовые объекты.
my $dm = Date::Manip::Date->new;
my $dt = $dm->new_date;
# Текущее локальное время. Только через парсинг.
$dt->parse('now');
# Текущее время UTC
$dt->parse('now gmt');
# Время в заданной зоне
$date->parse('now gtm+10');
# Парсинг строки (ISO8601)
$date->parse('2015-02-18T10:50:31.521345123+10:00');
# Генерация строки по шаблону
my $str = $dm_date->printf("%Y.%m.%d %H-%M-%S %z");
# Прибавить 1 год 2 месяца 3 дня 4 часа 5 минут и 6 секунд
my $dm_delta = Date::Manip::Delta->new;
$dm_delta->parse('1:2:0:3:4:5:6'); # 0 — недели
my $dm_date2 = $dm_date->calc( $dm_delta );
# Сравнить даты
my $result = $dm_date1->cmp( $dm_date2 ); # -1
# Интервал между датами
my $interval = $dm_date2->calc( $dm_date1 );
# Определение начала и конца текущих недели и месяца
# (каждый раз использую parse('now') т. к. не знаю, как копировать готовый объект)
my $week_begin = $dm_date->new_date;
$week_begin->parse('now');
$week_begin->prev(1,1,[0,0,0]); # Перевести дату на понедельник 00:00:00
# первый аргумент значит, что ищем понедельник (1-й день недели)
# второй значит, что текущий день тоже считается
# третий - время дня (ч,м,с)
my $week_end = $dm_date->new_date;
$week_end->parse('now');
$week_end->prev(7,1,[0,0,0]);
my $month_begin = $dm_date->new_date;
$month_begin->parse('now');
$month_begin->set('time',[0,0,0]);
$month_begin->set('d',1);
my $delta = Date::Manip::Delta->new;
$delta->parse('0:1:0:-1:0:0:0');
my $month_end = $dm_m1->calc( $delta );
Time::Piece
Ещё один очень популярный модуль. К тому же, это core-модуль, входящий в поставку Perl. По сути своей является ОО-оберткой над стандартными функциями. По умолчанию перекрывает
localtime
и gmtime
. Пустой объект создавать не умеет, при вызове new
делает то же самое, что и localtime
, но если передан другой объект Time::Piece
, то создаёт его копию.Не умеет менять временную зону созданного объекта, но время в нужной зоне можно получить по смещению (в секундах).
Парсить умеет только по шаблону
strptime
, что достаточно в большинстве случаев.С долями секунды работать не умеет.
Все вычисления производятся с секундами. Для удобства есть предустановленные константы из
Time::Seconds
(ONE_DAY
, например). Результаты вычислений получаются не совсем очевидные, так '2015-02-25 10:33:25' + ONE_YEAR = '2016-02-25 16:22:15'
. Все дело в том, что ONE_YEAR
это 31556930 секунд или 365.24225 дня (да, с округлением). C месяцем то же самое: '2015-02-01 00:00:00' + ONE_MONTH = '2015-03-03 10:29:04'
. Автор, понимая эту проблему, предусмотрел два метода объекта: add_months
и add_years
. Но работают они тоже с особенностями: отняв месяц от 2008-03-31, мы получим 2008-03-02. Это нужно всегда помнить и учитывать.Можно работать с объектами, используя стандартные арифметические операторы и операторы сравнения:
-
, +=
, <
, >=
, <=>
и т. п.В качестве разности дат возвращается объект
Time::Seconds
.Не умеет менять день недели и месяца, только определять. В последнем тесте (определение начала и конца недели и месяца) часть работы приходится делать руками, что не очень удобно.
В целом интерфейс достаточно простой, понятный и без излишеств. Во многих случаях его будет достаточно.
# Текущее локальное время. Можно через ->new или через localtime
my $tp = Time::Piece->new;
$tp = localtime;
# Текущее время UTC
$tp = gmtime;
# Смещение в заданную зону делается простым сложением.
$tp += 60*60*10; # тут смещение в секундах
# Парсинг строки (по заданному шаблону)
$tp = Time::Piece->strptime('2015-02-18T10:50:31+1000', '%Y-%m-%dT%H:%M:%S%z');
# Генерация строки по шаблону
my $str = $tp->strftime("%Y.%m.%d %H-%M-%S %z");
# Прибавить 1 год 2 месяца 3 дня 4 часа 5 минут и 6 секунд
my $tp2 = $tp1 + 3 * ONE_DAY + 4 * ONE_HOUR + 5 * ONE_MINUTE + 6;
$tp2->add_years(1);
$tp2->add_months(2);
# Сравнить даты
my $result = $tp1 <=> $tp2; # -1
# Вычитание
my $interval_in_seconds = $tp1 - $tp2;
Panda::Date
Модуль написан с использованием XS и позиционируется как очень быстрый. Имеет существенное ограничение — для сборки требуется Perl 5.18 или выше.
По умолчанию для парсинга принимает строки вида '2013-03-05 23:45:56'. Но можно задать и другой формат (глобально):
Panda::Date::string_format("%Y%m%d%H%M%S");
При парсинге создаются объекты с локальной временной зоной.
С долями секунды работать не умеет.
Может производить вычисления без использования дополнительных объектов (складывать даты с датам, а не с интервалами), но можно использовать и объекты
Panda::Date::Int
. Умеет прибавлять строки вида '3Y 2D' (3 года и 2 дня) или объекты типа Panda::Date::Rel
, такое сложение работает даже быстрее, чем сложение с ARRAYREF
. При вычитании дат возвращает объект Panda::Date::Int
.Для сравнения используется только оператор
<=>
.Позволяет манипулировать днём недели через
day_of_week
, при этом неделя начинается с воскресенья (значение 0). Или можно использовать ewday
, тогда неделя начинается с понедельника (значение 1) и заканчивается воскресеньем (7).# Пустой объект создать нельзя
# Можно создать объект с текущим временем тремя разными способами
my $pd = Panda::Date->new;
my $pd = Panda::Date->new( time );
my $pd = now; # импортировано по умолчанию
# При этом скорость у всех разная. Результат теста
# Rate Panda::Date new(time) Panda::Date new Panda::Date now
# Panda::Date new(time) 742853/s -- -9% -23%
# Panda::Date new 813840/s 10% -- -16%
# Panda::Date now 967947/s 30% 19% --
# Текущее локальное время (рекомендуется использовать now).
$pd = now;
# Текущее время UTC
$pd = Panda::Date->new( time, 'UTC' );
# Время в заданной зоне
$pd->to_tz('UTC-10');
# Парсинг строки (только в заданном глобально формате)
$pd = Panda::Date->new('2015-02-18 10:50:31');
# Генерация строки по шаблону
my $str = $pd->strftime("%Y.%m.%d %H-%M-%S %z");
# Прибавить 1 год 2 месяца 3 дня 4 часа 5 минут и 6 секунд
my $pd1 = Panda::Date::now;
my $pd2 = $pd1 + [1,2,3,4,5,6];
my $pd2 = $pd1 + '1Y 2M 3D 4h 5m 6s'; # работает быстрее
# Сравнить даты
my $result = $pd1 <=> $pd2;
# Вычитание дат
my $interval = $pd1 - $pd2;
Date::Calc
Ещё один популярный модуль. Отличительной особенностью его является то, что он использует простой массив для хранения информации о дате и времени вместо специального объекта. Интерфейс достаточно прост и понятен. Много разных функций для манипуляций с датами, чуть меньше для манипуляций с временем. Работает с точностью до секунд.
В отличие от других модулей, не имеет встроенного механизма для генерации строк с временем, а парсить умеет только даты, и то в специфичном формате. Поэтому в тестах на парсинг и генерацию строк не участвует.
Функции для сравнения дат нет, но для этого вполне можно использовать функции вычисления разницы между датами, как рекомендуется в описании к модулю.
# Получение локальных даты и времени
my @dc = Today_and_Now();
# Получение времени UTC осуществляется передачей дополнительного параметра.
@dc = Today_and_Now(1);
# Для определения времени в том или ином часовом поясе можно использовать время UTC и смещение
# на необходимое количество часов
my @delta_tz = (0, 10, 0, 0); # дни, часы, минуты, секунды
my @dc_tz =Add_Delta_DHMS( Today_and_Now(1), @delta_tz );
# Прибавить 1 год 2 месяца 3 дня 4 часа 5 минут и 6 секунд
@dc = Add_Delta_YMDHMS( @dc, (1,2,3,4,5,6) );
# Сравнить даты
my @dc1 = (2015,2,18,10,50,31);
my @dc2 = (2015,2,18,10,50,32);
my $result = 0+Delta_DHMS( @dc1, @dc2 ); # положительный результат означает,
# что дата 2 больше даты 1
# Вычитание
my @interval = Delta_YMDHMS( @dc1, @dc2 );
# Определение начала / конца недели и месяца. Многое нужно делать вручную.
@dc =Today(); # получаем сразу без времени
my $dow =Day_of_Week( @dc );
my @week_begin =Add_Delta_Days( @dc, (1 - $dow) );
my @week_end =Add_Delta_Days( @week_begin, 6 );
my @month_begin = @dc;
$month_begin[2] = 1; # просто меняем день месяца
my @month_end =Add_Delta_Days( Add_Delta_YMD( @month_begin, (0,1,0) ), -1 );
Time::Moment
Достаточно новый модуль (текущая версия — 0.22), я узнал о нём только когда начал готовить эту статью.
Умеет работать с наносекундами, но по умолчанию создаёт объект с временем (локальное и UTC) с точностью до микросекунд.
Может парсить даты только в строго определённом формате — ISO 8601. Что может быть не очень удобно. При ошибке парсинга выкидывает исключение.
Может выполнять простые операции ± год / месяц / день и т. д. через специальные методы (
plus_years
например). В отличие от многих других модулей, при вычислении 2013-01-31 + 1 месяц даёт результат 2013-02-28. Что правильно, на мой взгляд. Хотя, возможно, кто-то ожидает и другого поведения.Для сравнения дат использует стандартные операторы сравнения чисел:
<=>
, ==
, >=
и т. д.Нет методов для определения интервала между двумя датами. Но можно выполнить вычитание секунд с начала эпохи, полученных методом
epoch
. Это накладывает ограничения и теряется точность, но в каких-то случаях такой точности может быть достаточно (Time::Piece
тоже ведь с секундами работает).Для определения начала / конца месяца / недели можно использовать методы
with_day_of_week
(понедельник 1, воскресенье 7), with_day_of_month
и математику. Для обнуления времени суток приходится использовать методы with_*
.В целом интерфейс очень прост, понятен и без заметных специфических особенностей (как с математикой у
Time::Piece
, например).# Создать пустой объект
my $tm = Time::Moment->new;
# Создать объект с текущим локальным временем
$tm = Time::Moment->now;
# Создать объект с текущим временем UTC
$tm = Time::Moment->now_utc;
# Время в определённой зоне (заданной смещением в минутах)
my $tm_with_offset = $tm->with_offset_same_instant(600); # тут смещение в минутах
# Парсинг строки (в формате ISO8601)
$tm = Time::Moment->from_string('2015-02-18T10:50:31.521345123+10:00');
# Генерация строки по шаблону
my $str = $tm->strftime("%Y.%m.%d %H-%M-%S (%f) %z");
# Прибавить 1 год 2 месяца 3 дня 4 часа 5 минут и 6 секунд
my $tm2 = $tm1->plus_years(1)->plus_months(2)->plus_days(3)
->plus_hours(4)->plus_minutes(5)->plus_seconds(6);
# Сравнить даты
my $result = $tm1 <=> $tm2; # результат: -1
# Вычитание
my $interval_in_seconds = $tm1->epoch - $tm2->epoch;
# Определение начала / конца недели и месяца
$tm = $tm->with_hour(0)
->with_minute(0)
->with_second(0)
->with_nanosecond(0)
my $week_begin = $tm->with_day_of_week(1);
my $week_end = $tm->with_day_of_week(7)
my $month_begin = $tm->with_day_of_month(1);
my $month_end = $tm->with_day_of_month( $tm->length_of_month );
Тесты и результаты
Код тестов доступен на GitHub.
Тестовая среда:
Intel Core i5-2557M CPU @ 1.70GHz, 4Gb, Mac OS X 10.10.2
Perl 5.20.1 (в Perlbrew)
Benchmark (1.18)
В результатах теста для удобства используются сокращённые названия модулей:
Date::Manip
= D::M
, DateTime
= DT
и т. д.Создание объектов с текущим локальным временем
Rate D::M DT T::P D::C P::D (new time) P::D (new) T::M P::D (now)
D::M 3373/s -- -73% -97% -98% -99% -100% -100% -100%
DT 12582/s 273% -- -89% -92% -98% -98% -98% -99%
T::P 119244/s 3435% 848% -- -20% -81% -82% -86% -86%
D::C 149116/s 4321% 1085% 25% -- -77% -78% -82% -82%
P::D (new time) 644519/s 19009% 5022% 441% 332% -- -5% -22% -23%
P::D (new) 677138/s 19976% 5282% 468% 354% 5% -- -18% -19%
T::M 830755/s 24531% 6503% 597% 457% 29% 23% -- -1%
P::D (now) 839971/s 24804% 6576% 604% 463% 30% 24% 1% --
Panda::Date
— ожидаемо самый быстрый. Но Time::Moment
неожиданно почти так же быстр!Создание объектов с текущим временем UTC
Rate D::M DT T::P D::C P::D T::M
D::M 1999/s -- -83% -98% -99% -100% -100%
DT 11498/s 475% -- -90% -93% -98% -99%
T::P 120130/s 5909% 945% -- -26% -82% -92%
D::C 161964/s 8001% 1309% 35% -- -76% -89%
P::D 671656/s 33495% 5741% 459% 315% -- -55%
T::M 1476686/s 73761% 12743% 1129% 812% 120% --
Все становятся медленнее на этой операции. Все, кроме
Time::Moment
. Он становится значительно быстрее и выходит на первое место.Определение времени в конкретной временной зоне
Определение смещения по зоне в этом тесте не производим. Смещение задаём заранее +10 часов. Разные модули по-разному предлагают задавать смещение. Одни в секундах, другие в минутах, третьи строкой типа '+1000'.
Rate D::M DT D::C T::P P::D T::M
D::M 1725/s -- -61% -95% -97% -100% -100%
DT 4439/s 157% -- -87% -92% -99% -99%
D::C 33939/s 1868% 665% -- -39% -92% -95%
T::P 55584/s 3122% 1152% 64% -- -87% -92%
P::D 438601/s 25327% 9782% 1192% 689% -- -40%
T::M 735173/s 42520% 16463% 2066% 1223% 68% --
Парсинг строки (дата, время и зона)
Date::Calc
умеет парсить только даты (без времени) и только в определённом формате, поэтому в данном тесте он не участвует. Rate D::M DT (Strptime) DT (ISO8601) T::P P::D T::M
D::M 1138/s -- -43% -63% -99% -100% -100%
DT (Strptime) 1993/s 75% -- -36% -98% -100% -100%
DT (ISO8601) 3090/s 171% 55% -- -98% -100% -100%
T::P 127471/s 11097% 6297% 4025% -- -84% -90%
P::D 792571/s 69519% 39675% 25547% 522% -- -37%
T::M 1266979/s 111190% 63482% 40899% 894% 60% --
Генерация по шаблону
Date::Calc
не умеет самостоятельно форматировать строки и данный тест тоже пропускает. Rate DT D::M T::P P::D T::M
DT 10895/s -- -55% -95% -98% -98%
D::M 24273/s 123% -- -88% -95% -95%
T::P 202159/s 1756% 733% -- -57% -59%
P::D 473339/s 4245% 1850% 134% -- -3%
T::M 488258/s 4382% 1912% 142% 3% --
Вычисление даты (сложение / вычитание)
Rate D::M DT T::P D::C T::M P::D (array) P::D (string)
D::M 3493/s -- -21% -71% -88% -99% -99% -100%
DT 4403/s 26% -- -64% -85% -99% -99% -100%
T::P 12092/s 246% 175% -- -58% -98% -98% -99%
D::C 29019/s 731% 559% 140% -- -94% -95% -97%
T::M 487483/s 13854% 10972% 3932% 1580% -- -16% -48%
P::D (array) 579109/s 16477% 13053% 4689% 1896% 19% -- -38%
P::D (string) 934644/s 26655% 21129% 7630% 3121% 92% 61% --
Сравнение дат
Нужно понимать, что разные модули работают с разной точностью.
Time::Moment
и DateTime
— с точностью до наносекунды (для них разница между датами была 1 наносекунда), остальные до секунды (разница 1 секунда). Rate D::M D::C DT T::P P::D T::M
D::M 27427/s -- -34% -64% -92% -99% -99%
D::C 41837/s 53% -- -46% -88% -99% -99%
DT 77067/s 181% 84% -- -79% -98% -98%
T::P 363376/s 1225% 769% 372% -- -88% -89%
P::D 3145500/s 11369% 7418% 3981% 766% -- -7%
T::M 3399073/s 12293% 8025% 4311% 835% 8% --
Работа с более высокой точностью не мешает
Time::Moment
и тут занять первое место.Определение интервала между датами (вычитание дат)
Rate DT D::M D::C T::P P::D T::M
DT 6892/s -- -42% -88% -97% -99% -100%
D::M 11964/s 74% -- -78% -95% -98% -99%
D::C 55448/s 704% 363% -- -75% -93% -97%
T::P 219763/s 3089% 1737% 296% -- -71% -89%
P::D 767510/s 11036% 6315% 1284% 249% -- -63%
T::M 2085234/s 30155% 17329% 3661% 849% 172% --
Time::Moment
хоть и работает быстрее всех, но делает наиболее ограниченную операцию — вычитание epoch
(впрочем, достаточную во многих случаях). DateTime
работает медленнее всех, но единственный, кто работает с точностью до наносекунд (что может быть лишним, но всё же).Определение начала и конца недели и месяца от текущей даты
Комплексный тест, выполняющий сразу несколько операций. В качестве окончания недели / месяца использую начало последнего дня (время 00:00:00).
Rate D::M DT T::P D::C P::D T::M
D::M 93.9/s -- -88% -99% -99% -100% -100%
DT 790/s 741% -- -92% -96% -99% -100%
T::P 10060/s 10608% 1173% -- -45% -93% -94%
D::C 18309/s 19388% 2217% 82% -- -87% -90%
P::D 138748/s 147586% 17458% 1279% 658% -- -22%
T::M 177777/s 189129% 22397% 1667% 871% 28% --
Диаграмма по результатам всех тестов
Там, где значений не видно — они ничтожны (за исключением тех тестов, где модуль совсем не участвует).
Выводы
Time::Moment
— самый быстрый модуль, и при этом работает с точностью до наносекунд. Позволяет выполнять почти все необходимые операции, кроме парсинга по кастомному шаблону и вычитания дат. Мой личный фаворит по результатам тестов и по удобству интерфейса.DateTime
— наоборот, один из самых медленных модулей. Но при этом один из самых функциональных и способен работать с высокой точностью.Date::Calc
— неплохой модуль с простым и понятным интерфейсом. По сравнению с конкурентами никаких преимуществ не имеет.Panda::Date
— второй по скорости модуль. По сравнению с Time::Moment
особыми преимуществами не обладает, а ограничение его (Perl 5.18) может быть критичным.Time::Piece
— достаточно быстрый core-модуль, но со своими особенностями в математике, которые нужно учитывать.Date::Manip
— самый медленный модуль с очень специфичным интерфейсом. Главное его преимущество — возможность парсить строки типа «8:00pm December tenth». Если есть необходимость в таком функционале, то, наверное, можно использовать этот модуль, но я бы поискал другие решения под свои задачи.К сожалению, главной цели мне добиться не удалось — нет модулей, способных решить все поставленные задачи и работающих достаточно быстро. Но есть явный лидер —
Time::Moment
. И моя рекомендация будет такая:использовать
Time::Moment
везде, где его функционала достаточно, а недостающий функционал закрывать модулями Time::Piece
(благо доступен всегда как core-модуль) или DateTime
(в самом крайнем случае).Статьи на тему:
www.perl.com/pub/2003/03/13/datetime.html
blogs.perl.org/users/chansen/2014/08/timemoment-vs-datetime.html
perltricks.com/article/148/2015/2/2/Time--Moment-can-save-time