Time::Piece とタイムゾーンの甘い罠

use Time::Piece;
use Time::Seconds;
use POSIX qw(strftime);

sub print_tp {
    my $tp = shift;
    printf("date    : %s\ntzoffset: %d\nepoch   : %d\nstrftime: %s\n\n",
           $tp->strftime('%Y-%m-%d %T'),
           $tp->tzoffset,
           $tp->epoch,
           strftime('%Y-%m-%d %T', localtime($tp->epoch)),
          );
}

print "Time::Piece ",$Time::Piece::VERSION,"\n";
my $tp;
$tp = Time::Piece->strptime("2011-01-01", "%Y-%m-%d");
print_tp($tp);

$tp = localtime( Time::Piece->strptime("2011-01-01", "%Y-%m-%d") );
print_tp($tp);

$tp = $tp + ONE_DAY;
print_tp($tp);

まずこれ。

$tp = Time::Piece->strptime("2011-01-01", "%Y-%m-%d");
print_tp($tp);
↓
date    : 2011-01-01 00:00:00
tzoffset: 0
epoch   : 1293840000
strftime: 2011-01-01 09:00:00

うっかり何も考えずに strptime を使うと、環境変数TZがどうであれ、得られたインスタンスタイムゾーンGMT(UTC)になります。このことを忘れて $tp->epoch の値を使うと、日本だと9時間ずれた結果になってしまいます。例えば、日次の統計処理で数値がどうも合わないとか。。。

これは、

$tp = localtime( Time::Piece->strptime("2011-01-01", "%Y-%m-%d") );
print_tp($tp);
↓
date    : 2011-01-01 00:00:00
tzoffset: 32400
epoch   : 1293807600
strftime: 2011-01-01 00:00:00

のようにlocaltime()したインスタンスを使えばOKです。

また、このように、

$tp = $tp + ONE_DAY;
print_tp($tp);
↓
date    : 2011-01-02 00:00:00
tzoffset: 32400
epoch   : 1293894000
strftime: 2011-01-02 00:00:00

元のインスタンスタイムゾーンがついていれば、それを演算をしてもタイムゾーンの情報はちゃんと受け継がれます。

ここまででひとハマり。

次。

同じスクリプトなのに、ちゃんと localtime() しているのに、僕は全然悪くないはずなのに、なぜか環境によって期待したようにタイムゾーンがつかないケースがあったのです。

こんな感じに。

$tp = localtime( Time::Piece->strptime("2011-01-01", "%Y-%m-%d") );
print_tp($tp);
↓
date    : 2011-01-01 00:00:00
tzoffset: 0
epoch   : 1293840000
strftime: 2011-01-01 09:00:00

よくよくみると、Time::Piece のバージョンが異なり、よくよくよくよくみると、Changesにこんな記述が。。。

1.16
	- Implement %z for the internal implementation of strptime(). 
	  Unfortunately this doesn't get picked up everywhere, so there are no 
	  tests for it (yet - patches welcome).

Time::Piece 1.15を含みそれより古いバージョンを使ってる場合は、窓から投げ捨てて早急に新しいバージョンを入れましょう!!

1.15_01もダメかは確認してないのですが、Perl本体に同梱されるバージョンは結構危ういので気をつけてね。

$ corelist -a Time::Piece

Time::Piece was first released with perl v5.9.5
  v5.9.5     1.11_02
  v5.10.0    1.12
  v5.10.1    1.15
  v5.11.0    1.15
  v5.11.1    1.15
  v5.11.2    1.15
  v5.11.3    1.15
  v5.11.4    1.15
  v5.11.5    1.15
  v5.12.0    1.15_01
  v5.12.1    1.15_01
  v5.12.2    1.15_01
  v5.13.0    1.15_01
  v5.13.1    1.15_01
  v5.13.2    1.15_01
  v5.13.3    1.20_01
  v5.13.4    1.20_01
  v5.13.5    1.20_01
  v5.13.6    1.20_01
  v5.13.7    1.20_01