Walrus,Digit. | 一覧 | 検索 | 更新履歴(RSS) | 新規作成
はてなブックマークに追加 はてなブックマークを表示 編集 | 編集(管理者用) | 差分

Perlメモ/Time::Pieceモジュール

編集

オブジェクト指向の時間オブジェクトTime::Pieceモジュールに関するメモ。Time::Pieceオブジェクトは、これまでの時刻値(多くのシステムでは1970/01/01 00:00:00からの秒数)と同様に数値のように扱うこともでき、一方でその年月日や時分秒、週数の取得や月末の日付などを返すメソッドも提供します。

ドキュメント

はてなブックマークを表示 はてなブックマークに追加 リンク 編集

最新版のドキュメントはCPANで読むことができます。 加藤敦さんによる、1.08版のドキュメントを、perldoc.jpで読むことができます。

Time::Piece(和訳は加藤敦さんによる)HTML形式POD形式原文(1.08版)

使い方

use Time::Piece;
my $time = localtime;
print $time->date . " " . $time->time . "\n";
$time += 3600;   # 1時間後
print $time->date . " " . $time->time . "\n";

Time::Pieceは、localtime関数とgmtime関数を、それぞれスカラコンテキストではTime::Pieceオブジェクトを返すものに変更(overload)します。ただし、リストコンテキストではTime::Pieceモジュール読込後も、通常通り秒、分、時、日、月、...の配列を返します。

通常(Time::Pieceモジュールを読み込んでいない時)は、次のようにlocaltime関数を呼ぶと $_には"Thu Nov 1 01:46:31 2007"のような文字列が入ります。しかし、Time::Pieceモジュールを読み込んだ後で同じようにlocaltimeを呼ぶと、$_にはTime::Pieceオブジェクトが入ります。これが一番簡単な、Time::Pieceオブジェクトの初期化方法です。

$_ = localtime;

Time::Pieceオブジェクトは、通常の時刻値のように、加減算などができます。上の例では、1時間後のTime::Pieceオブジェクトを得るのに、単に1時間分の秒数=3600を足しています。

一方で、各種書式での文字列を返してくれる便利なメソッドがいくつもあります。上の例では、dateメソッドとtimeメソッドを使って、簡単に'YYYY-MM-DD hh:mm:ss'形式で日時文字列を得ています。

Time::Pieceオブジェクトの生成

Time::Pieceオブジェクトの生成には、2種類の方法があります。

まず、上の例のように、localtimeまたはgmtime関数で生成することができます。引数として、時刻値を指定できます。省略時は、現在の時刻が使用されます。

use Time::Piece;
my $gm_time_piece = gmtime(0);
print $gm_time_piece->date;         # "1970-01-01"が出力される
my $local_time_piece = localtime();
print $local_time_piece->date;      # 今日の日付が出力される

もう一つは、strptimeメソッドで、日時文字列を解析する方法です。例えば、次のようにして'YYYY-MM-DD'形式の日付から、Time::Pieceオブジェクトを生成できます。

use Time::Piece;
my $time_piece = Time::Piece->strptime('2007-01-01', "%Y-%m-%d");
$time_piece += (7 * 24 * 3600); # 一週間後
print $time_piece->date;        # "2007-01-08"が出力される

次のようにすれば、'YYYY-MM-DD hh:mm:ss'形式の日時(mysqlが返す日時フォーマットもこれです)から、Time::Pieceオブジェクトを生成できます。

use Time::Piece;
my $time_piece = Time::Piece->strptime('2007-12-15 15:30:20', "%Y-%m-%d %T");
$time_piece += (3 * 3600 + 15 * 60);             # 3時間15分後
print $time_piece->date, ' ', $time_piece->time; # "2007-12-15 18:45:20"が出力される

strptimeはCライブラリのstrptime()関数にリンクしており、STRPTIMEのmanページにある書式を使うことができます。

日付の取得

Time::Pieceでは、日付の取得用にいくつかのメソッドを用意しています。

よく使うのはdateメソッド(またはこれの実体であるymdメソッド)でしょう。これは、デフォルトでは'YYYY-MM-DD'形式の日付を返してくれます。引数に、例えば'/'などの文字を与えると、これを'-'の代わりの区切り文字に使用します。空文字列を与えれば、区切り文字なしの日付も得られます。

use Time::Piece;
my $time = gmtime(0);
print $time->date;      # "1970-01-01"が出力される
print $time->date('/'); # "1970/01/01"が出力される
print $time->date('');  # "19700101"が出力される

他には、次のようなものがあります。

  • year(YYYY形式の年)、mon(月)、mday(日)、wday(1=日曜日の曜日)
  • リストコンテキストのlocatimeにあわせた_year(1900を引いた)、_mon(0..11の範囲にした月)、_wday(0=日曜日の曜日)
  • month("Feb"などの短い月名)、fullmonth("February"などの月名)、day("Tue"などの短い曜日名)、fullday("Tuesday"などの曜日名)

ちょっと便利なメソッドとして、次のようなものもあります。

  • month_last_day(28..31の範囲の、その月の最後の日付を返す)
  • is_leap_year(うるう年であれば真、それ以外の年であれば偽を返す)

詳細やこれ以外のメソッドについては、ドキュメントを確認してください。

時刻の取得

Time::Pieceでは、時刻の取得用にいくつかのメソッドを用意しています。

よく使うのはtimeメソッド(またはこれの実体であるhmsメソッド)でしょう。これは、デフォルトでは'hh:mm:ss'形式の時刻を返してくれます。引数に、例えば'-'などの文字を与えると、これを':'の代わりの区切り文字に使用します。空文字列を与えれば、区切り文字なしの日付も得られます。

use Time::Piece;
my $time = gmtime(3600 * 12);
print $time->time;      # "12:00:00"が出力される
print $time->time('-'); # "12-00-00"が出力される
print $time->time('');  # "120000"が出力される

他には、次のようなものがあります。

  • hour(時)、min(分)、sec(秒)

詳細やこれ以外のメソッドについては、ドキュメントを確認してください。

時刻値の取得

Time::Pieceでは、時刻値や関連情報の取得用にepochというメソッドを用意しています。この戻り値は、多くのシステムでは1970/01/01 00:00:00からの秒数です。

また、時刻値と現在のタイムゾーンでのローカル時刻の差(秒数)を返すtzoffsetというメソッドもあります。日本のタイムゾーン(+0900)では、tzoffsetは32400(9時間分の秒数)を返します。

これらの値は、Time::Pieceオブジェクトをgmtimeで生成したときも、localtimeで生成したときも、まったく変わりません。

use Time::Piece;
my $gmtime = gmtime(3600 * 12);
my $lctime = localtime(3600 * 12);
print $gmtime->epoch, "\n";    # "43200"が出力される
print $lctime->epoch, "\n";    # "43200"が出力される
print $gmtime->tzoffset, "\n"; # "32400"が出力される
print $lctime->tzoffset, "\n"; # "32400"が出力される
print $gmtime->time, "\n";     # "12:00:00"が出力される
print $lctime->time, "\n";     # "21:00:00"が出力される

Tips

Time::Seconds関連でしばしばトラブルになっているらしい、月またがりの日付計算など。

ローカルタイムフラグ

Time::Pieceオブジェクトは、返す日付や時刻が標準時間(グリニッジ標準時)か、ローカル時間を示すフラグを持っています。Time::Pieceオブジェクトをgmtimeで生成した場合と、localtimeで生成した場合では、このフラグの値が異なります。

use Time::Piece;
my $lt = localtime;
print "lt : " . $lt->[Time::Piece::c_islocal] . "\n"; # "1"が表示される。
my $gt = gmtime;
print "gt : " . $gt->[Time::Piece::c_islocal] . "\n"; # "0"が表示される。

Time::Pieceオブジェクトを引数として受け取るような場合には、このフラグを確認したいこともあるでしょう。この時は、上のようにして確認できます。

なお、このフラグを変更してはいけません。実際には変更できてしまいますが、変更は想定されていませんし、変更後に各メソッドが返す値は、初期のフラグに基づく値であったり、現在のフラグに基づく値であったりと、まちまちで、整合が取れなくなりますす。

localtimeとgmtimeの変換

localtimeで生成したTime::Pieceオブジェクトからgmtimeを得たいとき、gmtimeで生成したTime::Pieceオブジェクトからlocaltimeを得たいときは、Time::Pieceオブジェクトそのものではなく、Time::Pieceオブジェクトのepochメソッドの戻り値を引数に渡します。例えば、以下のようにします。

use Time::Piece;
my $gt = gmtime();
printf("(gt)  %s (is_local : %s\n", $gt->datetime, $gt->[Time::Piece::c_islocal]);
my $lt = localtime($gt->epoch);
printf("(lt)  %s (is_local : %s\n", $lt->datetime, $lt->[Time::Piece::c_islocal]);
my $gt2 = gmtime($lt->epoch);
printf("(gt2) %s (is_local : %s\n", $gt2->datetime, $gt2->[Time::Piece::c_islocal]);

「Time::Pieceオブジェクトは、通常の時刻値のように、加減算などができます」と書きましたが、Time::Pieceオブジェクトそのものを引数に渡すと、ローカルタイムフラグなどは引き継がれてしまいます。例えば、以下を確認してみてください。

use Time::Piece;
my $gt = gmtime();
my $lt = localtime($gt);
print $lt->[Time::Piece::c_islocal];

当月の初日/末日を取得する

use Time::Piece;
my $today = localtime;
my $first = Time::Piece->strptime($today->strftime('%Y-%m-01'), '%Y-%m-%d');
my $last = Time::Piece->strptime($today->strftime('%Y-%m-' . $today->month_last_day), '%Y-%m-%d');

範囲指定などでよく欲しくなる初日と末日の取得。Time::Piece::Monthのnewメソッドからほぼ丸写しです。

strftimeは「Time::Pieceオブジェクトの生成」の項で解説しているstrptimeの逆で、指定書式で出力するメソッド。これを使って初日/末日の'YYYY-MM-DD'形式の文字列を得て、さらにstrptimeで解析しなおしてTime::Pieceオブジェクトに戻しています。

なお、どちらも時刻は00:00:00です。範囲指定に日付ではなく日時(時刻も含める)を使用する時は、末日はさらに(24時間-1秒)を加算して、23:59:59にする必要があるでしょう。

$last = $last + (86400 - 1);

前月の末日/翌月の初日を取得する

use Time::Piece;
my $today = localtime;
my $prev = Time::Piece->strptime($today->strftime('%Y-%m-01'), '%Y-%m-%d') - 1;
my $next = Time::Piece->strptime($today->strftime('%Y-%m-' . $today->month_last_day), '%Y-%m-%d') + 86400;

やはり範囲指定などでよく欲しくなる前月の末日の取得。 当月初日の00:00:00にあたるTime::Pieceオブジェクトが取得できていれば、ここから1秒引くだけ。これで前月末日の23:59:59が取得できます。 同様に、翌月初日の00:00:00も、当月末日に24時間を加算すれば取得できます。

1ヶ月前/1ヵ月後を取得する。

use Time::Piece;
my $today = localtime;
my $prev = Time::Piece->strptime($today->strftime('%Y-%m-01'), '%Y-%m-%d') - 1;
$prev = $today - ($prev->month_last_day * 24 * 3600);

通常の日付計算でよくある1ヶ月前(前月同日)。前月末日を調べてから、前月の日数(=前月の末日の日付)分の秒数を減算するのが良さそうです。前月末日の取得後に再度strftime→strptimeで日付を置き換えることもできますが、これよりも単純で、時刻も保持されるのが良い点です。

use Time::Piece;
my $today = localtime;
my $next = $today + ($today->month_last_day * 24 * 3600);

同じく通常の日付計算でよくある1ヵ月後(次月同日)。こちらも同様に、当月の日数分の秒数を加算しています。

Time::Piece::MySQLを使う。

MySQLを使用する時は、Time::PieceのサブクラスであるTime::Piece::MySQLを使うこともできます。Time::Piece::MySQLオブジェクトは、Time::Pieceと同様に利用できる他、MySQLで使用されるdatetime、date、timestampと簡単に相互変換できます。

use Time::Piece::MySQL;

my $time = localtime;

print $time->mysql_datetime;
print $time->mysql_date;
print $time->mysql_time;
print $time->mysql_timestamp;

my $time = Time::Piece->from_mysql_datetime( $mysql_datetime );
my $time = Time::Piece->from_mysql_date( $mysql_date );
my $time = Time::Piece->from_mysql_timestamp( $mysql_timestamp );

localtimeやgmtimeはTime::Piece::MySQLオブジェクトを返すようになります。Time::Piece::MySQLオブジェクトから、mysql_datetimeなどのmysql_*メソッドを使って、簡単にMySQLのdatetimeフィールド、dateフィールド、timeフィールド、timestampフィールドにセットできる日時文字列が得られます。

逆にMySQLのdatetimeフィールド、dateフィールド、timestampフィールドから取得した日時文字列から、from_mysql_datetimeなどのfrom_mysql_*メソッドを使って、簡単にTime::Piece::MySQLオブジェクトが得られます。

from_mysql_*メソッドで得られるTime::Piece::MySQLオブジェクトは、日時文字列をグリニッジ標準時として解釈したもので、ローカルタイムフラグは0になっています。少なくともTime::Piece::MySQLを使う時は、datetime型のフィールドにはローカルタイムではなくグリニッジ標準時を保存した方が無難だと思います。

コメント

[[#rcomment]]