tokuhirom's Blog

Time::Piece::strftime and time zone issue

https://rt.cpan.org/Ticket/Display.html?id=75046 This entry is just thinking note.

use strict;
use warnings;
use utf8;

use Time::Piece;

$ENV{TZ} = 'JST-9';

print gmtime(1363791599)->strftime('%Y-%m-%dT%H:%M:%S%z'), "\n";
print localtime(1363791599)->strftime('%Y-%m-%dT%H:%M:%S%z'), "\n";

2013-03-20T14:59:59+0900
2013-03-20T23:59:59+0900

OMG!!!!

Why?

sub strftime {
    my $time = shift;
    my $tzname = $time->[c_islocal] ? '%Z' : 'UTC';
    my $format = @_ ? shift(@_) : "%a, %d %b %Y %H:%M:%S $tzname";
    if (!defined $time->[c_wday]) {
        if ($time->[c_islocal]) {
            return _strftime($format, CORE::localtime($time->epoch));
        }
        else {
            return _strftime($format, CORE::gmtime($time->epoch));
        }
    }
    return _strftime($format, (@$time)[c_sec..c_isdst]);
}

And, _strftime() is a wrapper of strftime(3). strftime(3) converts "%z" and "%Z" to system time zone.

Solution

Replace %Z and %z

Very ad-hoc, but may works.

Here is a pretty monkey patch.

{
    my $orig = Time::Piece->can('strftime');
    no warnings 'redefine';
    *Time::Piece::strftime = sub {
        my ($time, $format) = @_;
        if (!$time->[Time::Piece::c_islocal]) {
            $format =~ s/(?<!%)%Z/UTC/;
            $format =~ s/(?<!%)%z/+0000/;
            warn $format;
        }
        $orig->($time, $format);
    };
}

Use tm.tm_zone

OSX, GNU linux, and other systems support tm.tm_zone. I think, if T::Piece::_strftime call a gmtime, and it set to tm.tm_zone, it works well.

(But, it does not work well on win32, mattn says.)

Use Time::Piece own strftime

Implement yet another srtrftime() implementation.

Is good? But it breaks your code on some operating system, that extended?