Edited at
PerlDay 16

Time::Pieceを使って日付の計算をしようとしてハマった話をします

こんばんは、Magnoliaです。

この記事はPerl Advent Calendar 2018の16日目の記事です。昨日、15日目は(予定では…)@karupaneruraさんの「ISUCON8予選問題においてPerl実装で25万点を突破する方法」でした。

さて、Qiitaに投稿するのがすっかりPerl Advent Calendarの時だけになった気がしますが…最近Perlを書く機会もすっかり無くなり、先日珍しく30分枠という長い枠で登壇しましたが、コード例はRubyで書いています。

「設計Night2018 powered by Classi」で登壇してきました - Magnolia Tech

しかし、実は最初Perlで書こうとしたのですが、ちょっと想定と違った結果になったので、結局Rubyで書き直しました、今日はその話をします。


Time::Pieceモジュール

Perlの日付処理モジュールといえばコアモジュールにTime::Pieceが用意されています。少し古い話ですが、Perl 5.10からコアモジュール入りしていて、それ以前は日付関連のモジュールはかなり混沌としていたようです。その辺りの事情は以下の記事に詳しく書かれています。

第15回 DateTime:APIの標準化をめざして:モダンPerlの世界へようこそ|gihyo.jp … 技術評論社


Time::Secondsモジュールによる日付の計算

Time::Pieceには、Time::Secondsというモジュールが同梱されていて、こちらを使うと日付の計算ができるようになります(1年後とか、1ヶ月後とか)。

use strict;

use warnings;

use Time::Piece;
use Time::Seconds;

my $t = Time::Piece->strptime('2018-01-01', '%Y-%m-%d');

my $t1 = $t + ONE_DAY;
my $t2 = $t + ONE_MONTH;
my $t3 = $t + ONE_YEAR;

print $t . "\n";
print $t1 . "\n";
print $t2 . "\n";
print $t3 . "\n";

これを実行すると…

$ perl time.pl 

Mon Jan 1 00:00:00 2018
Tue Jan 2 00:00:00 2018
Wed Jan 31 10:29:04 2018
Tue Jan 1 05:48:50 2019

年で演算すると時刻も…進んでいる??月は2月になってなくて一ヶ月進んでない??

まぁ、ドキュメントにはちゃんと書かれているんですけどね…

The methods make the assumption that there are 24 hours in a day, 7 days in a week, 365.24225 days in a year and 12 months in a year. (from The Calendar FAQ at http://www.tondering.dk/claus/calendar.html)

つまり、1年を365.24225日として計算しています…月あたりの日数は考慮していない(12等分しかしていない)ということなんですが…

4年経つと、うるう年が考慮されて丁度きれいになる…という訳だけど、ユースケース的に日付の計算をしているはずが、時刻が進むとか、月の日数に合わせた計算になっていないとか、全然直感的じゃないですよね…っていうか、普通にうるう年の日単位の計算をしてほしい…という訳で、日付の計算なのに、時刻が変わったり、月がきれいに揃わないというところにハマってしまった、という話でした。いや、ドキュメント読めばいいけど、でもユースケースと合わないと…思う。

ちなみにRubyのDateモジュールは時刻を一緒に扱わないので、そのような心配は無いです。

でも、年齢の到達日を計算する時はそれでもハマります…その辺りは冒頭で紹介したスライドをぜひ読んでみてください。

その他のハマりどころとして、3年前の「はてなデベロッパーアドンベントカレンダー 2015」にもこんな記事が有ります。

Perl の Time::Piece 利用上の注意点 - Hatena Developer Blog

というわけで、ちょっとしたTime::Pieceモジュールのちょっとしたハマりどころの紹介でした。

明日は、Morichanさんによる「Perlとクラス図の対応付け」という話です、お楽しみに!