Perl DateTime モジュール

Perlで日時を扱うデファクトになるかもしれない DateTime モジュールをお勉強.


DateTime オブジェクトを作る

DateTime オブジェクトが一つの日時情報を表す. DateTime オブジェクト作成時には日時以外に,ロケールタイムゾーンを指定する.

my $dt = DateTime->new( year   => 1066,   # year のみ必須,他は全てオプショナル
                        month  => 10,
                        day    => 25,
                        hour   => 7,
                        minute => 15,
                        second => 47,
                        nanosecond => 500000000,
                        locale => 'Japanese',
                        time_zone  => 'America/Chicago', # default: floating
                      );

オブジェクト作成時には指定パラメータの検証が行われ,ありえない値 (15月とか28時とか) が指定された場合は例外が発生する. locale の妥当な値はDateTime::Localeオブジェクト or ロケール名, time_zoneの妥当な値はDateTime::TimeZone がとれるタイムゾーン名となっている.

タイムゾーンには大きく分けて 'floating', 'UTC', それ以外,の三種類ある. 大雑把に説明すると,floatingタイムゾーンタイムゾーンが特に設定されていない状態,UTCは世界協定時刻という地域によらない世界的な時間の流れ,それ以外のタイムゾーンが各地域の時間の流れ,である. DateTimeオブジェクトは内部的にはUTCタイムゾーンを使って時間を管理している.

日時指定に関しては,上記の指定方法以外にも以下のような指定が可能.

# epoch time (1970/01/01 00:00:00 を起点とした経過秒数) から作成
my $dt1 = DateTime->from_epoch( epoch => 1.234567, local => 'Japanese'  );
    
# 現在の時刻/今日の日付から作成
my $dt2 = DateTime->now();
my $dt3 = DateTime->today();
    
# 月末指定
my $dt4 = DateTime->last_day_of_month( year   => 1066, month  => 10 );
# ある年の何日目,という指定
my $dt5 = DateTime->from_day_of_month( year   => 1066, day => 150 );

DateTimeオブジェクトを作成するもう一つの方法として,既存オブジェクトの複製という方法もある.

my $dt6 = $dt1->clone();

日時情報/その他の情報を取得する

まずは基本情報を得てみる.

# 年情報を取得する.
# 他に,month, day, hour, min, sec などが利用可能.
$y = $dt->year;
    
# 正確な秒情報を取得する. (0.0 〜 61.999999999)
# 他にも小数点以下のみ表示する nanosecond (0〜999,999,999) などが利用可能.
$s = $dt->fractional_second
    
# 日付情報を yyyy-mm-dd 表示 (第一引数で結合記号 '-' は変更可能)
print $dt->ymd( '/' );
# 時間情報を hh:mm:ss 表示   (第一引数で結合記号 ':' は変更可能)
print $dt->hms();
# 日時情報を ISO 時刻フォーマット 'yyyy-mm-ddThh::mm::ss' で表示
print $dt->datetime;
    
# epoch time を取得する
$ep = $dt->epoch;
    
# ロケール (DateTime::Locale オブジェクト) を取得する
$loc = $dt->locale;
# タイムゾーン (DateTime::TimeZone オブジェクト)
$tz = $dt->time_zone;
# タイムゾーン名
$tz_name = $dt->time_zone_long_name;

他にも,次のようなちょっと高度な情報も取得できる.

  • month_name (月名, locale によって定義), month_abbr (月名の略称)
  • day_name (曜日名, localeによって定義), day_abbr (曜日名の略称)
  • day_of_week (1:月曜 〜 7:日曜), day_of_year (1 〜 366)
  • christian_era ("BC" or "AD")
  • weekday_of_month (その日が月の第何〜曜日か)
  • is_leap_year (うるう年か否か), is_dst (サマータイム中か否か)
  • week (「何年」の「第何週」か: ($w_year, $w_num) = $dt->week), week_of_month (今月の「第何週」か)

week 系の情報の取り扱いについては注意が必要. ISOは「ある年の第一週 = 01/04 を含む週」と定義しているため,「2005年01月01日」が「2004年の第53週」だったりしてしまう.また「ある月の第一週 = 最初の木曜日を含む週」と定義しており,よって月初めの日が「第0週」な場合もある.

日時情報/その他の情報を設定する

基本的には set メソッドで変更可能だが, time_zoneパラメータだけは set_time_zone メソッドを使う必要がある.

$dt->set( year => 2006 );
$dt->set_time_zone( 'America/New_York' );

set_time_zone メソッドを使うと,オブジェクトの持つ 現在時刻もタイムゾーンにあわせて適切に変化する.

# floating               : 時刻は 2005/04/01 01:00:00
my $dt = DateTime->new(year => 2005, month => 4, day => 1, hour =>1, minute => 0);
    
# floating → Asia/Tokyo : 時刻は 2005/04/01 01:00:00 のまま
$dt->set_time_zone( "Asia/Tokyo" );  
    
# Asia/Tokyo → UTC      : 時刻は 2005/03/31 16:00:00 に変更される
$dt->set_time_zone( "UTC" );

日時情報の変更には以下のような賢いメソッドも利用できる.

# to に 'year' を指定すると,日時を年の初め (January 1, 00:00:00) に設定する
# to には 'month', 'week' なども指定可能
$dt->truncate( to => 'year' );

日時情報に対する加算/減算

日時情報に対する加算/減算には,日時の差分を表すDateTime::Durationクラスが利用される. 例えば,日時情報に対する加算は以下のように書ける.

# DateTime::Duration オブジェクトを作成し,それを現在の日時情報に加算
my $dur = DateTime::Duration->new( years => 3, months => 5, days => 1, );
$dt->add_duration( $dur );
    
# あるいは,次のように書くことも出来る
$dt->add( years => 3, months => 5, days => 1 );

同様に,減算についてはsubtract_duration( $duration )subtract( ... )を使えば良い.

二つの DateTime オブジェクトの差分を計算することもできる. 差分情報は DateTime::Duration オブジェクトで表される.

$duration1 = $dt1->subtract_datetime( $dt2 );           # 通常の日付差分情報を計算
$duration2 = $dt1->subtract_datetime_absolute( $dt2 );  # 秒で表された日付差分情報を計算

subtract_datetime_absoluteは秒単位で表現されたDateTime::Durationオブジェクトを返すのだが,これは分以上の時間単位で表された日付差分情報は本来的に曖昧だからだ. 例えば一口に一分差と言っても,閏秒の関係でそれは60秒だったり61秒だったりする.

素晴らしいことにDateTimeオブジェクトの加減算は普通に数学オペレータを使って行なうこともできる.

my $new_dt = $dt + $duration_obj;
my $new_dt = $dt - $duration_obj;
my $duration_obj = $dt - $new_dt;

日時情報の比較

日時情報の比較もオーバーロードされた数学的関数を使ってやれば簡単.

if ($dt1 > $dt2) { ... }
if ($dt1 == $dt2) { ... }

ちなみに,DateTime->compare($a, $b) というメソッドがあって,これは $a > $b のときは 1 を,$a == $b のときは 0 を,$a < $b のときは -1 を返す. これを使えば,次のようなことが出来る.

# 日付情報を古いものから順に処理する
foreach my $dt ( sort { DateTime->compare($a, $b) } @dates ) {
    ....
}

フォーマッタなど

DateTime::Format::*は,DateTimeオブジェクトと文字列の相互変換を実現する. 例として,MySQL の日時フォーマットと相互変換するフォーマッタ DateTime::Format::MySQL を紹介する.

# mysql の DATETIME 型文字列から Datetime オブジェクトを作る
# 他に parse_date, parse_time がある
my $dt = DateTime::Format::MySQL->parse_datetime( '2006-08-15 01:51:30' );
    
# DateTime オブジェクトから MySQL の DATETIME 型文字列を作成する
# 他に format_date, format_time がある
print DateTime::Format::MySQL->format_datetime( $dt )

この他にも,PostgreSQL 用や HTTP 用,メールヘッダ用などの多くの便利なフォーマッタモジュールが存在する. さらに,DateTime::Format::Strptimeを使うとより汎用的にパース/整形できる. モジュールの一覧と簡単な説明

サマータイム

サマータイムが出てくると話がややこしくなるので,無視する.