📜 ⬆️ ⬇️

Perl time



Perl and CPAN provide many different tools for working with time. The traditional and most well-known DateTime causes equally traditional serious complaints about the speed of work and memory consumption, so it gradually began to be ousted from our system by alternative modules. TIMTOWDI is great, but I still want to have some order in the project. Therefore, we decided to test several of the most popular modules in terms of speed, functionality and usability and choose the one that will become our main tool.

Initial conditions


Before conducting the tests, it was necessary to determine the requirements for the modules. The following conditions were set for our project.

Required functionality:

Additional conditions:

Criteria for evaluation:

Modules


There are a lot of modules for working with time, I chose the most popular of them - most often found both in code and in publications and stories of colleagues. The resulting list (with the version number used in the tests):

I will give a brief overview of the features and features that affect the test results, and give the results of the measurements. A detailed description of each module can be found in the native documentation.
')

Datetime


Probably the most popular module. More precisely, even the group of modules, since the different functional is divided into different namespaces. In general, the DateTime functionality is very wide, but the module is often criticized for poor performance.

When creating a new object, time is taken into account with an accuracy of up to a second, but it can work with an accuracy of up to nanoseconds.

Parsing lines with dates is not able by itself, but there are many ready-made parsers (formatters), DateTime::Format::* . They are also used to form a string with time in the required format if they implement the format_datetime method. For tests, I will use DateTime::Format::ISO8601 (more often used in different APIs and services) and DateTime::Format::Strptime (allows you to use your own template, similar to strptime). You can also create your own parser using DateTime::Format::Builder .

You can use DateTime::TimeZone objects to work with time zones, but this is optional. For example, you can create a time object in a specific zone simply by specifying time_zone => 'Asia/Taipei' . It is important to understand that the description of the zones is located in the module itself, and their relevance should be monitored separately. You can also send a previously prepared DateTime::TimeZone object instead of a string, which can be useful when we use a local time zone. Determining it may be long and efficient to prepare an object in advance, for example, like this:

 state $tz = DateTime::TimeZone->new( name => 'local' ); 

For work with intervals, DateTime::Duration objects are used, the same objects are returned when dates are subtracted.

Comparing dates can be performed both in terms of fractions of seconds and by integer values ​​using the appropriate methods DateTime->compare( $dt1, $dt2 ) and DateTime->compare_ignore_floating( $dt1, $dt2 ) .

In general, the interface of the module seems to me quite simple and understandable. Using multiple modules may seem inconvenient to someone, but they are well-organized and I cannot call it a disadvantage.

 #     ,  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


The main feature of this module, I would call omnivorous. He is able to do very cunning manipulations, for example, to determine the date by the line "8:00 pm December tenth", and even in different languages. But the documentation for this module is the most incomprehensible (at least to me). Like DateTime , the functionality is divided into many modules, but the logic of their separation is not obvious. In order to create a new object, you have to use the documentation for three modules at once - Date::Manip , Date::Manip::Date , Date::Manip::Obj .

He does not know how to work with fractions of a second, although this is not always necessary, but for some it may be critical.

It can parse string dates like “8:00 pm December tenth” or “4 business days later”, also in different languages. This is very cool, but I personally never encountered such a task. Probably, it makes sense when working with some weakly standardized (or intentionally very free) user input.

The intervals are represented by Date::Manip::Delta objects, they are also returned as the difference between dates.

To compare dates, a special method is used:

 $dm_date1->cmp( $dm_date2 ); 

Determining the beginning and end of the week and month is possible, but inconvenient and very slow - for this you have to perform several operations, and they are not happy with the speed.

Can not create a copy of an existing object.

In general, the interface is quite specific. Perhaps you can get used to it, and then it will seem more beautiful (as with ObjectiveC), but is it necessary to do this?

 #   .       ,   , #        . 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


Another very popular module. In addition, this is a core module included in the Perl distribution. In essence, it is an OO wrapper over standard functions. By default, overlaps localtime and gmtime . An empty object does not know how to create it; when called, new does the same as localtime , but if another Time::Piece object is passed, it creates a copy of it.

Does not know how to change the time zone of the created object, but the time in the desired zone can be obtained by the offset (in seconds).

Parsit can only by strptime pattern, which is enough in most cases.

With fractions of a second can not work.

All calculations are made with seconds. For convenience, there are preset constants from Time::Seconds ( ONE_DAY , for example). The results of the calculations are not quite obvious, so '2015-02-25 10:33:25' + ONE_YEAR = '2016-02-25 16:22:15' . The thing is that ONE_YEAR is 31556930 seconds or 365.24225 days (yes, with rounding). With the month the same: '2015-02-01 00:00:00' + ONE_MONTH = '2015-03-03 10:29:04' . Understanding this problem, the author has provided two object methods: add_months and add_years . But they also work with features: taking away the month of 2008-03-31, we get 2008-03-02. It must always be remembered and taken into account.

You can work with objects using standard arithmetic operators and comparison operators: - , += , < , >= , <=> , etc.

The object Time::Seconds returned as the date difference.

Can not change the day of the week and the month, only to determine. In the last test (determination of the beginning and end of the week and month), part of the work must be done by hand, which is not very convenient.

In general, the interface is quite simple, clear and without excesses. In many cases it will be enough.

 #   .   ->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


The module is written using XS and is positioned as very fast. It has a significant limitation - assembly requires Perl 5.18 or higher.

By default, it accepts lines like '2013-03-05 23:45:56' for parsing. But you can set another format (globally):

 Panda::Date::string_format("%Y%m%d%H%M%S"); 

When parsing, objects with a local time zone are created.

With fractions of a second can not work.

It can perform calculations without using additional objects (add dates to dates, not intervals), but Panda::Date::Int objects can also be used. It knows how to add lines like '3Y 2D' (3 years and 2 days) or objects like Panda::Date::Rel , such addition works even faster than addition with ARRAYREF . When subtracting dates, returns the object Panda::Date::Int .

For comparison, only the <=> operator is used.

Allows you to manipulate the day of the week through day_of_week , while the week starts on Sunday (value 0). Or you can use ewday , then the week starts on Monday (value 1) and ends on Sunday (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


Another popular module. A distinctive feature of it is that it uses a simple array to store information about the date and time instead of a special object. The interface is quite simple and straightforward. Many different functions for manipulating dates, a little less for manipulating time. Works up to seconds.

Unlike other modules, it does not have a built-in mechanism for generating strings with time, and only dates can parse, and then in a specific format. Therefore, in the tests for parsing and string generation is not involved.

There are no functions for comparing dates, but for this it is quite possible to use the functions of calculating the difference between dates, as recommended in the description for the module.

 #      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


A fairly new module (current version is 0.22), I only found out about it when I began to prepare this article.

Able to work with nanoseconds, but by default creates an object with time (local and UTC) with an accuracy of microseconds.

It can parse dates only in a strictly defined format - ISO 8601. That may not be very convenient. When parsing fails, throws an exception.

It can perform simple operations ± year / month / day, etc. through special methods ( plus_years for example). Unlike many other modules, when calculating 2013-01-31 + 1 month it gives the result 2013-02-28. What is right, in my opinion. Although, perhaps, someone expects a different behavior.

For comparing dates, it uses standard number comparison operators: <=> , == , >= , etc.

There are no methods for determining the interval between two dates. But you can subtract seconds from the beginning of the epoch, obtained by the epoch method. This imposes limitations and accuracy is lost, but in some cases this accuracy may be enough ( Time::Piece also works with seconds).

You can use the with_day_of_week (Monday 1, Sunday 7), with_day_of_month and math methods to determine the start / end of the month / week. To reset the time of day, you must use the with_* methods.

In general, the interface is very simple, understandable, and without noticeable specific features (as with the math of Time::Piece , for example).

 #    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 ); 

Tests and results


Test code is available on GitHub .

Test environment:
Intel Core i5-2557M CPU @ 1.70GHz, 4Gb, Mac OS X 10.10.2
Perl 5.20.1 (in Perlbrew)
Benchmark (1.18)

The test results for convenience use abbreviated names of the modules: Date::Manip = D::M , DateTime = DT , etc.

Creating objects with the current local time

  Rate D::M DT T::PD::CP::D (new time) P::D (new) T::MP::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 is expected the fastest. But Time::Moment unexpectedly almost as fast!

Creating objects with current UTC time

  Rate D::M DT T::PD::CP::DT::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% -- 

All become slower on this operation. Everything except Time::Moment . It becomes much faster and comes out on top.

Determining the time in a specific time zone

Determination of the displacement in the zone in this test is not made. The offset set in advance +10 hours. Different modules offer different ways to set the offset. Some are in seconds, others are in minutes, and the third is a '+1000' type string.

  Rate D::M DT D::CT::PP::DT::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% -- 

String parsing (date, time and zone)

Date::Calc can only parse dates (without time) and only in a certain format, so it does not participate in this test.

  Rate D::M DT (Strptime) DT (ISO8601) T::PP::DT::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% -- 

Pattern Generation

Date::Calc does not know how to format strings by itself and this test also skips.

  Rate DT D::MT::PP::DT::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% -- 

Calculating the date (addition / subtraction)

  Rate D::M DT T::PD::CT::MP::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% -- 

Date comparison

You need to understand that different modules work with different accuracy. Time::Moment and DateTime - up to nanosecond (for them the difference between dates was 1 nanosecond), the rest up to a second (difference 1 second).

  Rate D::MD::C DT T::PP::DT::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% -- 

Work with higher accuracy does not interfere with Time::Moment and then take first place.

Determining the interval between dates (subtract dates)

  Rate DT D::MD::CT::PP::DT::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::PD::CP::DT::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% -- 


, — ( , ).

findings


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

Source: https://habr.com/ru/post/254453/


All Articles