仕事で久々にTime::Pieceを扱っていて、localtimeの取り方がちょっと良くわからなかったので調べてみました。
事の発端はデータの時刻が一部ずれるという問題で、どうもlocaltimeへ変換するところに問題が有りそうです。実際にきているデータ例は以下のような感じ。
ちなみに、こっちだと意図した結果が帰ってきます。
なおDateTime::Format::Strptimeでも同じような事ができますが、同じく正しいのは一番下のみ下の2つになります。7/24追記※GMTのケースは%Zの前にスペースが無いだけでした
2011-07-12 18:00:00+09:00のような日付が渡ってきた場合、パースはできるけど、Time Zoneを考慮しないという点でしょう。ちょっとはまりました。
事の発端はデータの時刻が一部ずれるという問題で、どうもlocaltimeへ変換するところに問題が有りそうです。実際にきているデータ例は以下のような感じ。
%Y-%m-%d %T+09:00%Y-%m-%dT%T JST%Y-%m-%dT%T+09:00%Y-%m-%dT%T+0900%Y-%m-%dT%T+9:00%Y-%m-%dT%T-07:00%Y-%m-%dT%T-08:00%a %B %d %T %Y +0900%a %b %d %T JST %Y%a,%d %b %Y %T +0900%a, %d %B %Y %T +0900%a, %d %B %Y %T GMT%a, %d %b %Y %T +09:00%a, %d %b %Y %T +0300
※一部割愛
まぁいろんなケースがあります。でこの形式の時間をこんな感じで処理してました。
my $t = eval { Time::Piece->strptime($str, '%Y-%m-%d %T+09:00') } ||
eval { Time::Piece->strptime($str, '%Y-%m-%dT%T JST') } ||
eval { Time::Piece->strptime($str, '%Y-%m-%dT%T+09:00') } ||
eval { Time::Piece->strptime($str, '%Y-%m-%dT%T+0900') } ||
eval { Time::Piece->strptime($str, '%Y-%m-%dT%T+9:00') } ||
eval { Time::Piece->strptime($str, '%Y-%m-%dT%TZ') } ||
eval { Time::Piece->strptime($str, '%a %B %d %T %Y -0800') } ||
eval { Time::Piece->strptime($str, '%a %b %d %T JST %Y') } ||
eval { Time::Piece->strptime($str, '%a %d %b %Y %T -0800') } ||
eval { Time::Piece->strptime($str, '%a,%d %b %Y %T +0900') } ||
eval { Time::Piece->strptime($str, '%a, %d %B %Y %T +0900') } ||
eval { Time::Piece->strptime($str, '%a, %d %B %Y %T GMT') } ||
eval { Time::Piece->strptime($str, '%a, %d %b %Y %T +09:00') } ||
eval { Time::Piece->strptime($str, '%a, %d %b %Y %T +0300') };
#一部割愛
$tにはTime Zoneまで考慮された時刻が渡っているという想定です。が、実際は違ってました。use strict;
use warnings;
use Time::Piece;
sub parse_time {
my ($time, $format) = @_;
my $t = Time::Piece->strptime($time, $format);
warn $t->strftime('%Y-%m-%d %T');
}
parse_time("2012-07-12 18:00:00+09:00", '%Y-%m-%d %T+09:00');
parse_time("2012-07-12 18:00:00+03:00", '%Y-%m-%d %T+03:00');
parse_time("2012-07-12 18:00:00-07:00", '%Y-%m-%d %T-07:00');
上記コードはタイムゾーン指定が±hh:mmな日付をstrptimeでパースして、グリニッジ標準時の時刻を返します。実際実行してみると・・ローカルタイムゾーンが全く無視された、GMTとしての2012-07-12 18:00:00が帰ってきます。2012-07-12 18:00:00 at time-piece-test.pl line 8.2012-07-12 18:00:00 at time-piece-test.pl line 8.2012-07-12 18:00:00 at time-piece-test.pl line 8.
ちなみに、こっちだと意図した結果が帰ってきます。
parse_time("2012-07-12 18:00:00+0900", '%Y-%m-%d %T%z'); # print 09:00:00
parse_time("2012-07-12 18:00:00+0300", '%Y-%m-%d %T%z'); # print 15:00:00
日付フォーマットを規定したISO8601では、タイムゾーンの定義はこんな感じISO8601perldocにも書いてありますが、Time::Pieceの時刻フォーマットはFreeBSDコマンドのstrftimeに準拠していて、ISO8601ではなくて、RFC822に依存します。
TZD = time zone designator (Z or +hh:mm or -hh:mm)
RFC822ということで、HH:MMな表記はHHMMに直さねばなりません。そして%zで受ける必要があります。ちょと不思議なのは、Time::Pieceでこれが出来ないことです。※7/24追記 下記のDateTime::Format::Strptimeはできるzone = "UT" / "GMT" ; Universal Time; North American : UT/ "EST" / "EDT" ; Eastern: - 5/ - 4/ "CST" / "CDT" ; Central: - 6/ - 5/ "MST" / "MDT" ; Mountain: - 7/ - 6/ "PST" / "PDT" ; Pacific: - 8/ - 7/ 1ALPHA ; Military: Z = UT;; A:-1; (J not used); M:-12; N:+1; Y:+12/ ( ("+" / "-") 4DIGIT ) ; Local differential; hours+min. (HHMM)
parse_time("2012-07-12 18:00:00 JST", '%Y-%m-%d %T %Z'); # error!これは自前で±HHMMに置き換えるしか無いのかなぁ。なおDateTime::Format::Strptimeでも同じような事ができますが、同じく正しいのは
use strict;
use warnings;
use DateTime::Format::Strptime;
sub parse_time {
my ($time, $format) = @_;
my $d = DateTime::Format::Strptime->new(pattern => $format)->parse_datetime($time);
$d->set_time_zone("UTC");
warn $d->strftime('%Y-%m-%d %T %Z');
}
parse_time("2012-07-12 18:00:00+09:00", '%Y-%m-%d %T+09:00');
#parse_time("2012-07-12 18:00:00 GMT", '%Y-%m-%d %T%Z'); 7/24訂正
parse_time("2012-07-12 18:00:00 GMT", '%Y-%m-%d %T %Z');
parse_time("2012-07-12 18:00:00+0900", '%Y-%m-%d %T%z');
問題を解りづらくしていたのは以下の様なコードの時にeval { Time::Piece->strptime($str, '%Y-%m-%d %T+09:00') } ||
eval { Time::Piece->strptime($str, '%Y-%m-%dT%T JST') }
2011-07-12 18:00:00+09:00のような日付が渡ってきた場合、パースはできるけど、Time Zoneを考慮しないという点でしょう。ちょっとはまりました。
コメント