サンプルコードPerl入門

2009-11-05

Time::Piece - 日付と時刻を扱う

  1. Perl
  2. 日付・時刻
  3. モジュール
  4. Time::Piece

 Perlの5.10からTime::Pieceと呼ばれる時刻を便利に扱うためのモジュールが標準モジュールに加わりました。Perlの5.10以上を使用している場合はTime::Pieceモジュールを日付と時刻を扱いたい場合に利用すると便利です。

 従来のPerlでは日付や時刻を直感的に扱う手段が標準モジュールにはありませんでした。日付や時刻を扱うためには少し面倒な作業が必要でした。Time::Pieceは日付と時刻の直感的な操作を提供します。また日付・時刻の書式化の機能や解析する機能も備えます。

 Perlには実質的に日付を扱うモジュールのスタンダードといえるDateTimeというモジュールがあります。ただしこのモジュールは標準モジュールではなくCPANからインストールする必要があります。また巨大なモジュールですのでモジュールを読み込む時間が非常にかかります。mod_perlなどメモリ上にモジュールを展開しておくことのできる環境以外では実行速度に少し不満がでるかもしれません。

 Time::Peiceはある程度の機能を備えた高速・軽量なモジュールだと考えてください。

時刻・日付の情報の取得

 Time::Peiceモジュールは次のように使用します。useをするとTime::Peiceモジュールはコアのlocaltimeとgmtimeを上書きます。

use Time::Piece;

# Time::Pieceオブジェクトの取得
my $t = localtime;

# 日付や時刻の情報の取得
my $year   = $t->year;
my $month  = $t->mon;
my $mday   = $t->mday;

my $hour   = $t->hour;
my $minute = $t->minute;
my $second = $t->sec;

 localtimeを呼び出すとTime::Pieceオブジェクトを取得することができこれから日付や時刻の情報を取得することができます。

 標準関数のlocaltimeで時刻情報を取得した場合を比較のために記しておきます。

# 標準関数のlocaltimeで時刻情報を取得した場合
my ($second, $minute, $hour, $mday, $month, $year) = localtime;

# 月は0から始まるので1を加算
$month += 1;

# 年は1900を引いた値で取得されるので1900を加算
$year  += 1900;

 Time::Pieceのlocaltimeを使用したほうが直感的に日付や時刻の情報を扱うことができます。

Time::Pieceで取得できる情報一覧

 Time::Pieceで取得できる情報の一覧です。

$t->year
$t->_year 年 - 1900 (標準関数のlocaltimeと同じ) = "aaa"
$t->yy 年の下2桁
$t->mon 月(1から始まる)
$t->_mon 月(0から始まる。標準関数のlocaltimeと同じ)
$t->monname 月名の短縮名(Febなど)
$t->month $t->monnameと同じ
$t->fullmonth 月名(Februaryなど)
$t->mday
$t->day_of_month $t->mdayと同じ
$t->hour 24 hour
$t->min
$t->minute $t->minと同じ
$t->sec
$t->second $t->secと同じ
$t->wday 週番号(1が日曜日)
$t->_wday 週番号(0が日曜日)
$t->day_of_week $t->_wday
$t->wdayname 週名の短縮名(Tueなど)
$t->day $t->wdayname
$t->fullday 週名(Tuesdayなど)
$t->yday 年の中で何日目か
$t->day_of_year $t->ydayと同じ
$t->isdst 夏時間かどうか
$t->daylight_savings $t->isdstと同じ
$t->epoch エポックからの秒数

日付の判定

 Time::Pieceを使用すればうるう年かどうかの判定と月末日かどうかの判定を行うことができます。

# うるう年かどうか
$t->is_leap_year

# 月末日の取得(28-31の数値を返す)
$t->month_last_day

日付・時刻を書式化する

 日付・時刻を書式化して出力したい場合があります。Time::Pieceでは決まった書式がいくつか用意されています。またstrftimeメソッドを使用すれば自由に書式化することができます。

[A]デフォルトで用意されているフォーマット

 デフォルトで用意されているフォーマットは以下になります。

$t->hms 12:34:56
$t->hms(".") 12.34.56
$t->time $t->hmsと同じ
$t->ymd 2000-02-29
$t->date $t->ymdと同じ
$t->mdy 02-29-2000
$t->mdy("/") 02/29/2000
$t->dmy 29-02-2000
$t->dmy(".") 29.02.2000
$t->datetime 2000-02-29T12:34:56 (ISO 8601)
$t->cdate Tue Feb 29 12:34:56 2000
"$t" $t->cdateと同じ
[B]フォーマットのカスタマイズ

 フォーマットを自由にカスタマイズしたい場合はstrftimeメソッドを使用します。$formatには時刻の表現を指定します。

$t->strftime($format)

 日付と時刻をフォーマット化したサンプルです。

$t->strftime('%Y-%m-%d %H:%M:%S'); # 2009-11-34 12:14:15

 strftimeで使用できるフォーマットの一覧です。

%a : 曜日の省略名
%A : 曜日名
%b : 月の省略名
%B : 月名
%c : デフォルトのフォーマット
%C : 年の最初の2桁
%d : 日( 01 から 31 )
%D : %m/%d/%y と同じ。月日年
%e : 日( 1 から 31 )
%F : %Y-%m-%d と同じ( 2008-11-31 など )
%G : 年( %Yとの違いはよくわからない)
%g : 年の下2桁 ( 00 〜 99 )
%h : %b と同じ。月の省略名
%H : 時( 00 から 23 )
%I : 時( 01 から 12 ) 12時間表記での時刻
%j : 年の最初から初めて何日目か。( 001 から 366 )
%k : 時( 0 から 23 ) 1桁の数字( 0〜 9 )の2桁目はスペースになる。
%l : 時( 1 から 12 ) 12時間表記での時刻 1桁の数字( 0〜 9 )の2桁目はスペースになる。
%m : 月の番号( 01 から 12 )
%M : 分( 00 から 59 ) 
%n : 改行文字 
%N : ミリ秒( %3N と書くとミリ秒を3桁で、 %6Nと書くとミリ秒を6桁で表示 ) 
%p : AM か PM 
%P : am か pm 
%r : %I:%M:%S %p と同じ。( 11:55:23 PM など) 
%R : 時分( 15:16 など ) 
%s : エポックからの秒数 
%S : 秒( 00 から 61 ) 
%t : タブ文字 
%T : %H:%M:%S と同じ。時分秒( 23:14:03 など ) 
%u : 曜日の番号( 1 から 7。 月曜日が1 ) 
%U : 年の最初から数えて何週目か。( 00 から 53 ) 年の最初の日曜日を週の最初として数える。最初の日曜日の週が01。その前の週が00。 
%V : 年の最初から数えて何週目か。( 01 から 53 ) 少なくとも4日を持つ週を年の最初の週とする。月曜日を週の最初として数える。 
%w : 曜日の番号( 0 から 6。 日曜日が0 ) 
%W : 年の最初から数えて何週目か( 00 から 53 )  年の最初の月曜日を週の最初として数える。最初の月曜日の週が01。その前の週が00。 
%x : 日付のデフォルトのフォーマット 
%X : 時刻のデフォルトのフォーマット 
%y : 年の下の2桁 
%Y : 年 
%z : UTC(協定世界時)からのタイムゾーンの時刻のずれ) 
%Z : タイムゾーン名 
%% : % 
日本語を扱うときの注意

 Time::Pieceのstrftimeメソッドは、入力がバイト文字列であっても、内部文字列であっても、常にバイト文字列を返却することに注意しましょう。

# 常にバイト文字列が返却される
my $bytes = $tp->strftime("%Y年%m月");

日付・時刻を表現する文字列を解析する

 日付や時刻の表現を解析してTime::Pieceオブジェクトを作成することができます。日付や時刻の解析するにはstrptimeメソッドを使用します。

my $t = Time::Piece->strptime(日付・時刻を表現する文字列, フォーマット);

 サンプルとしてMySQLの日付の表現を解析してTime::Pieceオブジェクトを作成してみます。フォーマットには上記のstrftimeのフォーマットを使用します。

my $datetime_mysql = '2009-12-31 23:59:59';
my $t = Time::Piece->strptime($datetime_mysql, '%Y-%m-%d %H:%M:%S');

ローカライゼーション

 デフォルトの状態では月の名前や週の名前は英語です。日本語の週名を取得したい場合は週名を取得するメソッドに曜日名の配列を渡します。

my @week_names = ('日', '月', '火', '水', '木', '金', '土');
my $t = Time::Piece::localtime;
# 日本語で週名を取得
my $wday = $t->wdayname(@week_names);

日付・時刻情報の計算

Timpe::Peiceオブジェクトの引き算

 Time::Pieceでは日付や時刻の計算を行うこともできます。Time::Pieceオブジェクトどうしの引き算を行った場合は、戻り値はTime::Secondsオブジェクトになります。このオブジェクトは文字列として評価した場合は秒数になります。

my $sec = $t2 - $t1;

 またTime::Socondsオブジェクトを日や年に変換することもできます。

$sec->seconds;
$sec->minutes;
$sec->hours;
$sec->days;
$sec->weeks;
$sec->months;

# 30日
$sec->financial_months;

 日や年への変換は次の規則によってなされます。1日は24時間と換算されます。1週間は7日と換算されます。1年は365.24225日と換算されます。1年は12ヶ月と換算されます。

Time::Pieceオブジェクトの足し算

 Time::Pieceオブジェクトどうしの足し算はすることができません。Timpe::Pieceオブジェクトには秒数あるいはかTime::Secondsオブジェクトを足すことができます。

$t + 533;
$t + Time::Seconds->new(300);

 またTime::SecondsにはTime::Peiceオブジェクトに足すことのできる定数も用意されています。

ONE_DAY
ONE_WEEK
ONE_HOUR
ONE_MINUTE
ONE_MONTH
ONE_YEAR
ONE_FINANCIAL_MONTH
LEAP_YEAR
NON_LEAP_YEAR

 5日を足すには次のようにします。これらの定数を利用するにはTime::Secondsモジュールを読み込んでおく必要があります。

use Time::Seconds;
$t + ONE_DAY * 5

日付の比較

 日付の比較に比較演算子を使用することができます。"<", ">","<=", ">=", "<=>", "==" and "!="が使用できます。

$t1 < $t2

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

 任意の日付・時刻でTime::Pieceオブジェクトを生成するにはドキュメントを読む限りではstrptimeメソッドを使用するしかないようです。

my $t = Time::Piece->strptime('2009-12-31 23:59:59', '%Y-%m-%d %H:%M:%S');

 (ソースコードを読むとparseメソッドというものがあるのですが試してみたところ壊れているみたい...。ドキュメントには無いので非公開のようですし。)

とおりすがりとおりすがり 2009/12/27 21:39 細かい事ですが、Perl 5.10.1ではなく、Perl 5.10からではないですか?

perlcodesampleperlcodesample 2010/01/02 15:58 そうでした、バージョンは0から始まるのでした。修正しておきました。

不明不明 2011/11/04 10:50 私も細かい事ですが・・・

>またTime::Soceondsオブジェクトを日や年に変換することもできます。
Secondsのタイプミスだと思います。

perlcodesampleperlcodesample 2011/11/04 23:19 親切にありがとうございます。修正しました。

kitskits 2013/03/11 23:26 month_last_day は「月末日かどうかの判定」ではなく「その月の末日が何日か」(28-31の数値)を返すメソッドですよ。

perlcodesampleperlcodesample 2013/03/12 22:42 ありがとうございます。誤解がないように、コメントに追記しました。

monomono 2014/08/02 11:15 strftime が返す文字列がバイト列になっている件ですが、
それが utf8 バイト列とは限らないようです。
(例えば、 strftime('%a') )
またフォーマット文字列に、日本語を含む文字列を内部形式で渡しても、
utf8 バイト列を返すとは限らないようです。
(例えば、 strftime('%Y年%m月%d日') )

strftime が引数に求めるエンコード、イコール
吐き出す結果のエンコードを事前に知った上で、
変換が必要と思われます。

use Encode::Guess;
my $enc = guess::encoding(localtime->strftime('%a%A%b%B%p'), qw/shiftjis euc-jp/);

# $t は、Time::Piece オブジェクト
print $enc->decode($t->strftime($enc->encode('%Y年%m月%d日(%a)'))), "\n";

さらに、shiftjis だった場合に 2バイト目以降に % がくると化けますから、
\P{InBasicLatin} はエスケープしたほうがよさそうですね。

専用のサブルーチンというか、モジュールつくって
strftime をオーバーライドしたくなるレベルですね。

perlcodesampleperlcodesample 2014/08/02 14:05  単にバイト列を渡したら、バイト列が返ってくるだけなのではないですかね。日本語が入るとstrftimeは非常に遣いにくいので、ちょっと面倒ですけれど、$r->year . "年" . $r->mon . "月" みたいにすることが多いですね。

monomono 2014/08/02 15:39 バイト列を渡すとバイト列を返してくるのですけれど、
strftime は内部で c の strftime を呼び出しているため、
c の都合に左右されてしまうのですよね。
strftime('%a') はロケールによっては、
その日が月曜日なら 月 を返すわけで、
その文字列のエンコードが問題になります。
また渡したバイト列の中に、2バイト目以降が % になる文字が含まれていれば
期待しない結果(文字化け)を吐き出します。

返ってきたモノは、少なくても flagged utf8 ではないので、
use utf8; が一般的な今日、
日本語込みで strftime を使う場合は、必ず変換が必要になると言えるでしょう。

もちろん、 strftime を使わずに、
自作の sprintf で用を足してしまえば問題は起こりません。

長々と失礼しました。

monomono 2014/08/02 15:39 バイト列を渡すとバイト列を返してくるのですけれど、
strftime は内部で c の strftime を呼び出しているため、
c の都合に左右されてしまうのですよね。
strftime('%a') はロケールによっては、
その日が月曜日なら 月 を返すわけで、
その文字列のエンコードが問題になります。
また渡したバイト列の中に、2バイト目以降が % になる文字が含まれていれば
期待しない結果(文字化け)を吐き出します。

返ってきたモノは、少なくても flagged utf8 ではないので、
use utf8; が一般的な今日、
日本語込みで strftime を使う場合は、必ず変換が必要になると言えるでしょう。

もちろん、 strftime を使わずに、
自作の sprintf で用を足してしまえば問題は起こりません。

長々と失礼しました。

perlcodesampleperlcodesample 2014/08/02 15:48 そうなんですね。曜日の設定は自分でできたような気がするので、自分で設定するのがいいのかもしれませんね。

匿名匿名 2016/06/01 16:44 %m : 月の番号( 01 から 12 )
0抜きも欲しかった。。。

perlcodesampleperlcodesample 2016/06/11 22:15 匿名さん

ないようですね。残念。

投稿したコメントは管理者が承認するまで公開されません。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証