Как стать автором
Обновить
93.13
Рунити
Домены, хостинг, серверы, облака

Время Perl

Время на прочтение15 мин
Количество просмотров18K


Perl и CPAN предоставляют множество самых разных инструментов для работы с временем. Традиционный и наиболее известный DateTime вызывает столь же традиционные серьёзные нарекания к скорости работы и потреблению памяти, поэтому он постепенно стал вытесняться из нашей системы альтернативными модулями. TIMTOWDI — это замечательно, но в проекте всё-таки хочется иметь какой-никакой порядок. Поэтому мы решили протестировать несколько самых популярных модулей по скорости, функционалу и удобству использования и выбрать тот самый единственный, который станет нашим основным инструментом.

Начальные условия


Прежде чем проводить тесты, нужно было определиться с требованиями к модулям. Для нашего проекта были поставлены следующие условия.

Необходимый функционал:
  • работа с временными зонами (вычисления с зонами, определение локальной);
  • парсинг и форматирование строки с временем по шаблону;
  • работа с интервалами времени (прибавить день, отнять неделю, посчитать разницу между датами);
  • работа с такими понятиями как «первая неделя октября» или «двенадцатая неделя года» и т. п.

Дополнительные условия:
  • желательно один объект времени, с которым можно было бы работать;
  • лаконичный код;
  • модуль не должен быть заброшенным (давно не обновлялся, множество незакрытых багов);
  • работа в Debian со стандартным Perl (сейчас это v5.14.2).

Критерии оценки:
  • соответствие требованиям по функционалу;
  • скорость работы;
  • адекватный интерфейс;
  • субъективное удобство использования.

Модули


Модулей для работы с временем очень много, я выбрал наиболее популярные из них — чаще всего встречающиеся как в коде, так и в публикациях и рассказах коллег. Получившийся список (с номером версии, используемой в тестах):

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

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
Теги:
Хабы:
Всего голосов 4: ↑4 и ↓0+4
Комментарии11

Публикации

Информация

Сайт
runity.ru
Дата регистрации
Численность
501–1 000 человек
Местоположение
Россия
Представитель
Рунити