仕事で久々にTime::Pieceを扱っていて、localtimeの取り方がちょっと良くわからなかったので調べてみました。

事の発端はデータの時刻が一部ずれるという問題で、どうも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でパースして、グリニッジ標準時の時刻を返します。実際実行してみると・・
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.
ローカルタイムゾーンが全く無視された、GMTとしての2012-07-12 18:00:00が帰ってきます。
ちなみに、こっちだと意図した結果が帰ってきます。
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では、タイムゾーンの定義はこんな感じ
ISO8601
TZD = time zone designator (Z or +hh:mm or -hh:mm)  
perldocにも書いてありますが、Time::Pieceの時刻フォーマットはFreeBSDコマンドのstrftimeに準拠していて、ISO8601ではなくて、RFC822に依存します。
RFC822
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)
ということで、HH:MMな表記はHHMMに直さねばなりません。そして%zで受ける必要があります。ちょと不思議なのは、Time::Pieceでこれが出来ないことです。※7/24追記 下記のDateTime::Format::Strptimeはできる
parse_time("2012-07-12 18:00:00 JST", '%Y-%m-%d %T %Z'); # error!
これは自前で±HHMMに置き換えるしか無いのかなぁ。

なおDateTime::Format::Strptimeでも同じような事ができますが、同じく正しいのは一番下のみ下の2つになります。7/24追記※GMTのケースは%Zの前にスペースが無いだけでした
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を考慮しないという点でしょう。ちょっとはまりました。