
PerlとCPANは、時間を操作するためのさまざまなツールを提供します。 伝統的で最も有名な
DateTime
は、速度とメモリ消費に関して同様に伝統的な深刻な不満を引き起こしているため、徐々にシステムの代替モジュールに置き換えられ始めました。 TIMTOWDIは素晴らしいのですが、プロジェクトではまだ何らかの順序を持たせたいと思っています。 そのため、速度、機能、使いやすさについて最も人気のあるモジュールのいくつかをテストし、メインツールになるモジュールを選択することにしました。
初期条件
テストを実行する前に、モジュールの要件を決定する必要がありました。 次の条件がプロジェクトに設定されました。
必要な機能:
- タイムゾーンの操作(ゾーンを使用した計算、ローカルの決定);
- パターンに従って、文字列を時間とともに解析およびフォーマットします。
- 時間間隔の操作(1日追加、1週間かかり、日付の差を計算);
- 「10月の第1週」や「年の第12週」などの概念で作業する
追加条件:
- 作業対象のワンタイムオブジェクトが望ましい。
- 簡潔なコード。
- モジュールを放棄すべきではありません(長い間更新されていない、多くの未解決のバグ)。
- Debianで標準Perl(現在はv5.14.2)を使用して作業します。
評価基準:
- 機能要件への準拠。
- 作業速度;
- 適切なインターフェース。
- 主観的な使いやすさ。
モジュール
時間を操作するためのモジュールは多数ありますが、最も人気のあるものを選択しました。最もよく使用されるのは、コード、同僚の出版物、およびストーリーの両方です。 結果のリスト(テストで使用されたバージョン番号付き):
- DateTime (1.18);
- 日付::マニプ (6.48);
- 時間::ピース (1.29);
- パンダ::日付 (3.0.2);
- 日付:: Calc (6.4);
- 時間::モーメント (0.22)。
機能の簡単な概要と、テスト結果に影響する機能、および測定結果を示します。 各モジュールの詳細な説明は、ネイティブドキュメントに記載されています。
日時

DateTime
の機能
DateTime
非常に広範なものですが、モジュールはパフォーマンスが低いと批判されることがよくあります。
新しいオブジェクトを作成する場合、1秒の精度で時間が考慮されますが、ナノ秒の精度で動作できます。
日付を含む文字列を解析する方法はわかりませんが、多くの既製のパーサー(フォーマッター)、
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 );
日付::マニプ

DateTime
と同様に、機能は多くのモジュールに分割されますが、それらの分離のロジックは明らかではありません。 新しいオブジェクトを作成するには、3つのモジュールのドキュメントを一度に使用する必要があります
Date::Manip
、
Date::Manip::Date
、
Date::Manip::Obj
。
常に必要なわけではありませんが、彼女はほんの一瞬で作業する方法を知りませんが、誰かにとっては重要であることがわかるかもしれません。
「12月10日午後8時」や「4営業日後」などの文字列の日付も、さまざまな言語で解析できます。 これは非常にクールですが、私は個人的にそのような問題に遭遇したことはありません。 これは、ある種の弱く標準化された(または意図的に非常に自由な)ユーザー入力で作業するときにおそらく理にかなっています。
間隔は、
Date::Manip::Delta
オブジェクトで表され、
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 );
時間::ピース

localtime
と
gmtime
オーバーライドします。 空のオブジェクトを作成する方法はわかりません;
new
呼び出されると
localtime
と同じようになりますが、別の
Time::Piece
オブジェクトが渡されると、そのコピーが作成されます。
作成したオブジェクトのタイムゾーンを変更することはできませんが、目的のゾーンの時間はオフセット(秒単位)で取得できます。
strptime
テンプレートのみが
strptime
でき
strptime
。ほとんどの場合、これで十分です。
彼女はほんの一瞬で仕事をすることはできません。
すべての計算は数秒で行われます。 便宜上、
Time::Seconds
(
ONE_DAY
など)から事前定義された定数があります。 計算結果は明確ではないため、
'2015-02-25 10:33:25' + ONE_YEAR = '2016-02-25 16:22:15'
です。 問題は、
ONE_YEAR
が31556930秒または365.24225日であることです(はい、丸めあり)。 同じ月:
'2015-02-01 00:00:00' + ONE_MONTH = '2015-03-03 10:29:04'
この問題を理解した著者は、オブジェクトの2つのメソッド
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;
パンダ::日付

デフォルトでは、解析のために '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
ます。このような追加は、
ARRAYREF
た追加よりも高速に
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;
日付:: 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 );
時間::モーメント

ナノ秒で動作することができますが、デフォルトでは、マイクロ秒の精度の時間(ローカルおよびUTC)でオブジェクトを作成します。
厳密に定義された形式(ISO 8601)でのみ日付を解析できます。これはあまり便利ではないかもしれません。 解析エラーが発生すると、例外がスローされます。
特別な方法(
plus_years
、
plus_years
を使用して、±年/月/日などの簡単な操作を実行できます。 他の多くのモジュールとは異なり、2013-01-31 + 1か月を計算すると、結果は2013-02-28になります。 私の意見では、正しいことです。 しかし、おそらく誰かが他の行動を期待しています。
日付を比較するには、数値を比較するための標準演算子を使用します:
<=>
>=
、
==
、
>=
など。
2つの日付の間隔を決定する方法はありません。 ただし、
epoch
法で取得した時代の先頭から秒を引くことができます。 これにより制限が課され、精度が失われますが、場合によってはそのような精度で十分かもしれません(
Time::Piece
も秒で動作します)。
with_day_of_week
(月曜日1、日曜日7)、
with_day_of_month
およびmathメソッドを使用して、月/週の開始/終了を判別できます。 時刻をリセットするには、
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)
ベンチマーク(1.18)
便宜上、テスト結果ではモジュールの省略名が使用されています:
Date::Manip
=
D::M
、
DateTime
=
DT
など。
現在の現地時間でオブジェクトを作成する
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
-おそらく最速。 しかし、
Time::Moment
予想外にほぼ同じくらい速いです!
現在のUTC時間でオブジェクトを作成する
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% --
この操作では誰もが遅くなっています。
Time::Moment
以外のすべて。 それはずっと速くなり、最初に来ます。
特定のタイムゾーンでの時間の決定
このテストでは、ゾーン変位の決定は実行されません。 オフセットは事前に+10時間に設定されます。 異なるモジュールは異なるオフセットを提供します。 数秒で、数分で、タイプ「+1000」の3行目
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% --
文字列の解析(日付、時刻、およびゾーン)
Date::Calc
は日付(時間なし)のみを特定の形式でしか解析できないため、このテストには参加しません。
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% --
パターン生成
Date::Calc
は、文字列を個別にフォーマットする方法を知りません。このテストもスキップします。
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% --
日付の計算(加算/減算)
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% --
日付比較
異なるモジュールが異なる精度で機能することを理解する必要があります。
Time::Moment
and
DateTime
ナノ秒の精度(それらの場合、日付の差は1ナノ秒でした)、残りは最大1秒(差は1秒)です。
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% --
より高い精度で作業しても、
Time::Moment
が1位になるのを妨げません。
日付間の間隔の決定(日付の減算)
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% --
すべてのテストの結果のチャート
値が表示されない場合、それらは無視できます(モジュールがまったく関与しないテストを除く)。

結論
Time::Moment
-最速のモジュールであり、同時にナノ秒の精度で動作します。カスタムテンプレートによる解析と日付の減算を除き、必要なほぼすべての操作を実行できます。私の個人的なお気に入りは、テスト結果とインターフェースの利便性です。
DateTime
-それどころか、最も遅いモジュールの1つ。しかし、同時に最も機能的なものの1つであり、高い精度で作業することができます。
Date::Calc
-シンプルで直感的なインターフェースを備えた優れたモジュール。競合他社と比較して、利点はありません。
Panda::Date
-2番目に速いモジュール。
Time::Moment
特別な利点と比較して、そうではなく、それを制限することが重要です(Perl 5.18)。
Time::Piece
-かなり高速なコアモジュールですが、数学に独自の機能があり、考慮する必要があります。
Date::Manip
-非常に特殊なインターフェースを備えた最も遅いモジュール。その主な利点は、「12月10日午後8時」などの文字列を解析できることです。そのような機能が必要な場合は、おそらくこのモジュールを使用できますが、自分のタスクのために他のソリューションを探します。
残念ながら、私は主な目標を達成できませんでした-すべてのタスクを解決し、十分に迅速に動作できるモジュールはありません。しかし、明確なリーダーがいます-
Time::Moment
。そして、私の推奨事項は次のとおりです。機能が十分な場所で
使用し
Time::Moment
、不足している機能をモジュールで閉じます
Time::Piece
(常にコアモジュールとして使用できるため)または
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