- 2006-06-04 (日)
- Perl
Perlには、日時の加・減算を扱うモジュールが標準でついてきません。僕の仕事場ではずっと、同僚が作ったオリジナルモジュールを皆で使いまわしていたのですが、今になって、世間的 (CPAN) にはどんなものがあるのか気になって調べてみました。※参考になったのは miyagawa 氏のメールマガジンの過去記事と、perl.com の The Many Dates and Times of Perl なるエントリでした。
今回は数ある日付関連モジュールの中から、Dave Rolsky氏の DateTime モジュールについて、その基本的な使い方について簡単にまとめたので共有してみます。 同氏は上記 perl.com 記事の執筆者であり、この前の YAPC::Asia で DateTime project について講演してくれていた人です。気合の入ったモジュールを作ってくれた事に感謝。
目次
- 基本
- 日時の加算・減算
- 2つの日時の差分を調べる
- 日時っぽい文字列を DateTime オブジェクトに変換する
- 様々なフォーマットの整形文字列にする
- タイムゾーンの概念
- ロケールの概念
- 難点について
- 関連情報
基本
現在日時のインスタンスを作る
use DateTime; my $dt = DateTime->now( time_zone=>'local' );
任意の日時のインスタンスを作る
my $dt = DateTime->new( time_zone => 'local', year => 2006, month => 6, day => 2, hour => 16, minute => 30, second => 50 );
文字列に変換する
print $dt->ymd('/'); # 2006/06/02 print $dt->hms(':'); # 16:30:50 print "$dt"; # 2006-06-02T16:30:50
任意のフォーマットに整形された文字列を作る
# 2006/06/02 16:35:33 print $dt->strftime('%Y/%m/%d %H:%M:%S'); # 2006年06月02日 16時35分33秒 print $dt->strftime('%Y年%m月%d日 %H時%M分%S秒');
※フォーマット指定記号については perldoc DateTime > USAGE > strftime Specifiersを参照
月の日数を調べる
my $dt = DateTime->last_day_of_month( year=>2005, month=>2 ); print $dt->day; # 28
曜日を調べる
$dt->wday; # 1から7の数字で、1が月曜
my @wd = qw(月 火 水 木 金 土 日); print $wd[ $dt->wday ]; # 金
日時の加算・減算
4日足す (4日後)
my $dt = DateTime->new( year=>2006, month=>5, day=>30 ); $dt->add( days => 4 ); print $dt->ymd; # 2006-06-03
3年引く (3年前)
my $dt = DateTime->new( year=>2006, month=>5, day=>30 ); $dt->subtract( years => 3 ); print $dt->ymd; # 2003-05-30
うるう年も考慮したい場合は年や月を日数で計算すべし
# 2004年はうるう年 my $dt = DateTime->new( year=>2005, month=>1, day=>10 ); $dt->subtract( days => 365 ); # 1年前 print $dt->ymd; # 2004-01-11
2つの日時の差分を調べる
普通に引き算
DateTimeインスタンス同士を引き算すると DateTime::Duration オブジェクトが作成されます
my $d1 = DateTime->new( year=>2006, month=>10, day=>5 );
my $d2 = DateTime->new( year=>2005, month=>12, day=>1 ); my $dur = $d1 - $d2; print $dur->months; # 10 print $dur->days; # 4
差分日数が知りたい
上記コードにて $dur 取得後は、 10ヶ月と4日 という差分結果を、308日 というような日数に変換することはできません。DateTime::Duration オブジェクトは変換元の日時情報を覚えていない為です (詳しくは perldoc DateTime > METHODS > in_units(..) の項目 を参照のこと)。なので、差分日数が欲しい場合は、普通に引き算する代わりに、以下のようなメソッドを使います:
my $dur = $d1->delta_days($d2); print $dur->in_units('days'); # 308
同様に、差分の分数・秒数が知りたい場合は以下のメソッドを使います:
my $dur = $d1->delta_ms($d2); # minutes & seconds の略 print $dur->in_units('minutes'); # 444420
※この辺りはちょっとわかり難い感じ。うーん。
日時っぽい文字列を DateTime オブジェクトに変換する
文字列パターンを指定した上で変換
use DateTime::Format::Strptime;
my $strp = DateTime::Format::Strptime->new( pattern => '%Y年%m月%d日 %H:%M:%S' # 文字列のパターンを指定 );
$dt = $strp->parse_datetime('2003年04月18日 15:30:22'); print $dt->day; # 18
※パターン記号については perldoc DateTime::Format::Strptime > STRPTIME PATTER TOKENS を参照
WEBで使われているフォーマットを変換
ウェブで使われている様々な形式の日時文字列を、DateTimeオブジェクトに変換してくれます。おりこうさん。
use DateTime::Format::HTTP; my $dt = DateTime::Format::HTTP->parse_datetime( '09 Feb 1994 22:23:32 GMT' # 対象文字列 ); print $dt->year; # 1994
※変換可能な文字列の一覧は perldoc DateTime::Format::HTTP > METHODS > parse_datetime() を参照
様々なフォーマットの文字列に変換する
MySQL の日付型カラム用
my $dt = DateTime->now( time_zone=>'local' ); use DateTime::Format::MySQL; my $str = DateTime::Format::MySQL->format_datetime($dt); print $str; # 2006-06-02 19:52:41
メールヘッダー用
use DateTime::Format::Mail; my $str = DateTime::Format::Mail->format_datetime($dt); print $str; # Fri, 02 Jun 2006 19:52:41 +0900
HTTP Protocol 用 (RFC 1123)
use DateTime::Format::HTTP; my $str = DateTime::Format::HTTP->format_datetime($dt); print $str; # Fri, 02 Jun 2006 10:52:41 GMT
日本語 with 和暦年号
use DateTime::Format::Japanese;
my $fmt = DateTime::Format::Japanese->new(
number_format => 2,
);
my $str = $fmt->format_datetime($dt); print $str; # 平成18年6月2日19時33分31秒 my $wareki = $fmt->format_year($dt); print $wareki; # 平成18年
$str や $wareki には、perl internal string 形式の文字列 (utf8 エンコード + utf8 flag on) が入ります。別の文字コードに変換する場合は、さらに以下のように書くと良いかと思われます。
use Encode;
$str = encode('euc-jp',$str); print $str; # 平成18年
※単純に '平成' とか '昭和' とかの年号がほしいだけなら、代わりに Date::Japanese::Era を使うのも良いっすね。
タイムゾーンの概念
サーバOSのタイムゾーン設定に従う
new や now 等のコンストラクタに time_zone => 'local' と指定する事で、サーバと同じタイムゾーンに設定されます。指定しなかった場合、タイムゾーンは 'UTC' (GMTとも言う) になるので注意。
my $dt = DateTime->now( time_zone=>'local' ); # 現在地域(日本)の日時 my $dt = DateTime->now(); # UTC日時
※サーバによって 'local' が通用しないケースがあるみたい。うまくいかない場合は perldoc DateTime::TimeZone > USAGE > the "local" timezone を参照のこと。
現在のタイムゾーン名を取得
print $dt->time_zone_long_name; # Asia/Tokyo
タイムゾーンを変更する
my $dt = DateTime->now( time_zone=>'local' ); print $dt->hms; # 19:30:58 - 東京の現在時刻 $dt->set_time_zone('America/Chicago'); print $dt->hms; # 05:30:58 - シカゴの現在時刻
※指定可能なタイムゾーン名の一覧は こちら を参照、または perldoc DateTime::TimeZoneCatalog を見るべし
2つのタイムゾーン間の時差を調べる
my $d1 = DateTime->now(time_zone=>'Asia/Tokyo');
my $d2 = DateTime->now(time_zone=>'America/Chicago'); my $seconds = $d1->offset - $d2->offset; print $seconds / 60 / 60; # 14時間
ロケールの概念
デフォルトは English
my $dt = DateTime->now( time_zone => 'local' ); print $dt->month_name; # June print $dt->day_name; # Friday
print $dt->strftime( $dt->locale->long_date_format ); # June 2, 2006
ロケールを設定すると...
my $dt = DateTime->now( time_zone => 'local', locale => 'ja' ); print $dt->month_name; # 6月 print $dt->day_name; # 金曜日
print $dt->strftime( $dt->locale->long_date_format ); # 2006年6月2日
※これらの文字列は perl internal string 形式 (utf8 エンコード + utf8 flag on) なので、必要に応じて Encode::encode() 等で変換してあげましょう。
※指定可能なロケールの一覧は perldoc > DateTime::LocaleCatalog を参照のこと
難点について
他の日付関連モジュールを熟知しているわけでは無いので実行速度を含め、比較検討的な指摘は今のところできません。で、強いて難点挙げるならば
- インストールするのに必要な依存モジュールが多いこと
- 展開&作成されるモジュールファイル数・サイズが膨大 (合計5.0MB前後)
なのかなぁ、と。これだけ何でもできてしまうモジュールだけに「まあ、いっか」となりますが。
shell + make が使える環境ならば $ perl -MCPAN -e 'install CPAN' にて自動インストールできるので問題ないのですが、ライトユーザの人たちや、あまり自由の利かないサーバ環境での使用となると、敷居が高くなってしまうのが惜しい。
で、その辺についての解決策を次のエントリ「いつでもどこでも、すぐに DateTime モジュールを使いたい」で書いてみようと思います。To Be Continued...
関連情報
- perl DateTime project - 公式のサポートサイト。FAQが充実してます。
- YAPC::Asia での DateTime Project 講演資料 と 動画
- [ruby]Rubyで日付・時間を操作 I sort my thought... さん。感謝!