モダンPerlの世界へようこそ

第15回 DateTime:APIの標準化をめざして

この記事を読むのに必要な時間:およそ 4 分

Cから継承したAPI

プログラマにとって,ログの解析や作成などに含まれる日付や時刻の操作は切っても切り離せない分野のひとつです。もちろんPerlにも日付や時刻を操作するための関数は組み込まれています。

ただし,Cから継承してきたlocaltime()やgmtime()の返り値は,お世辞にもわかりやすいとはいえません。リストコンテキストで呼び出せば年月日,時分秒などの値を取り出せるとはいえ,単一の配列で受け取ると,個々の要素を使うときに直感的ではなくなりますし,明示的な名前をつけたスカラー変数を並べて受け取るのはいかにも冗長です。

use strict;
use warnings;

# 短いけれど非直感的
my @tm = localtime(); $tm[5] += 1900; $tm[4]++;
printf "%04d-%02d-%02d %02d:%02d:%02d\n", reverse(@tm[0..5]);

# わかりやすいけれど冗長
my ($sec, $min, $hour, $day, $mon, $year) = localtime();
$year += 1900; $mon++;
printf "%04d-%02d-%02d %02d:%02d:%02d\n",
    $year, $mon, $day, $hour, $min, $sec;

配列から構造体へ

そのため,Perl 5でオブジェクト指向的な考え方が導入されたのを受けて,1996年11月には,Class::Struct(当時はClass::Templateと呼ばれていました)をベースにしたTime::tmと,それを利用したTime::localtime,Time::gmtimeがそれぞれPerlのコアに導入されました(Perl 5.003_11以降)。これを使うと,先の例はもう少し簡潔ないし直感的に書けるようになります。

use strict;
use warnings;
use Time::localtime;
my $tm = localtime();
printf "%04d-%02d-%02d %02d:%02d:%02d",
    $tm->year + 1900, $tm->mon + 1, $tm->mday,
    $tm->hour, $tm->min, $tm->sec;

もっとも,これは配列をオブジェクト化したに過ぎません。$tm->yearは相変わらず1900年からの経過年ですし,$tm->monは0~11の整数です。なまじオブジェクト化した分,演算子を直接適用しづらくなっている面もあり,少なくともPerlの父,ラリー・ウォール氏は満足できなかったようです。

ラリーの仕様から生まれたTime::Piece

氏は,2000年1月のメールで,現行のlocaltime()のインタフェースを廃止できればと述べつつ,ひとつの理想的なインタフェースの案を提示します※1)。

そのメールに反応して生まれたのが(2000年3月のリリース当初はTime::Objectと呼ばれていた)現在のTime::Pieceでした。

Time::Pieceを使うと,先の例はたとえばこのように書き直せます。yearやmonの値にはもう手を加える必要はありません。

use strict;
use warnings;
use Time::Piece;

my $tm = localtime();
printf "%04d-%02d-%02d %02d:%02d:%02d",
    $tm->year, $tm->mon, $tm->mday, $tm->hour, $tm->min, $tm->sec;

また,このようにも書けますし,POSIX的なstrftimeやstrptimeにも対応しています。あまり表示にこだわりがないなら,$tm->datetimeとして(YYYY-MM-DDThh:mm:ssのような)ISO 8601風の出力にしてもよいでしょう。

print $tm->ymd, ' ', $tm->hms, "\n";
print $tm->strftime('%Y-%m-%d %H:%M:%S'), "\n";

また,同梱されているTime::Secondsの助けを借りれば日時の加減算もできます。

use strict;
use warnings;
use Time::Piece;
use Time::Seconds;

my $tm = localtime() + ONE_DAY;
print $tm->datetime, "\n";

YAPCまでの日数を求めたければこのように書けます。

use strict;
use warnings;
use Time::Piece;

my $yapc  = Time::Piece->strptime('2009-09-10', '%Y-%m-%d');
my $delta = $yapc - localtime();
print int($delta->days), "\n";
※1

http://www.nntp.perl.org/group/perl.perl5.porters/2000/01/msg5283.html

日時関連モジュールの混沌

このTime::Pieceは,ラリーの仕様を実装したという事情もあり,2001年4月に一度はPerl 5.8系列(正確にはその開発版である5.7系列)のコアに入ります(2001年7月に開発版としてリリースされたPerl 5.7.2を見ると,その様子が確認できます)。

ところが,それと相前後するように第二の日時モジュールブームが起こり,2001年の4月から7月にかけて,Date::Handler, Date::ICal, Class::Date, Date::Simpleといったモジュールが立て続けにリリースされたため,5.7.2がリリースされた直後の2001年8月には,Perlの日時モジュールを専門に扱うためにできたばかりのメーリングリストで,「最近また日時関連モジュールが乱立しているが,もう少し整理してはどうか」という議論が起こります。

このような議論はDate::CalcDate::ManipDate::Parseといったモジュールが次々にリリースされた1995/1996年の時点ですでにあったのですが,たしかに当時からDate::で始まるモジュールとTime::で始まるモジュールの棲み分けにはあいまいな部分がありました(どちらの名前空間に入っていようと,多くのモジュールは日付も時刻も扱えました)。それぞれ独自のAPIを持ち,相互にオブジェクトのやりとりができるような状態でもなかったため,複数の日時関連モジュールを使いたい場合は,いちいちOSのエポック時などに変換しなければならないという制約もありました。そのため,共通のAPIを求める声は少なくなかったのですが,TMTOWTDIをモットーとしてきたPerl界だけに結局調整はつかず,血統的には最も標準に近い位置にあったTime::Pieceでさえ「論争中の名前空間における一実装に過ぎない」という理由でコアから削除されてしまいます※2)。

※2

コアに取り込んでしまうとPerlをサポートするすべてのプラットフォームで正しく動作するようフォローする作業が発生するため,不要なモジュールはなるべくコアには入れないことになっています。その(当時の)判断基準については,このメールなどに紹介されています。

著者プロフィール

石垣憲一(いしがきけんいち)

あるときは翻訳家。あるときはPerlプログラマ。先日『カクテルホントのうんちく話』(柴田書店)を上梓。最新刊は『ガリア戦記』(平凡社ライブラリー)。

URLhttp://d.hatena.ne.jp/charsbar/

コメント

コメントの記入