8

I want to convert a timestamp from localtime to GMT. I have legacy code that does this "manually" with Time::Local::timelocal() and gmtime. It works, but I don't like it and wanted to use Time::Piece instead. I used this answer to do so (albeit they are converting the other way round, but I was able to replace + with - :-)).

This is my code:

#!/usr/bin/env perl

use strict;
use warnings;
use Time::Local;
use Time::Piece;
use POSIX qw(strftime);

sub local_to_utc_timelocal
{
    my $local_ts = shift;
    my ( $year, $mon, $mday, $hour, $min, $sec ) 
        = ( $local_ts =~ /(\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/ );
    my $local_secs = Time::Local::timelocal( $sec, $min, $hour, $mday, $mon - 1, $year );
    return POSIX::strftime( '%y-%m-%d %H:%M:%S', gmtime($local_secs) );
}

sub local_to_utc_timepiece
{
    my $local_ts = shift;
    my $local_tp = Time::Piece->strptime( $local_ts, '%y-%m-%d %H:%M:%S' );
    my $utc_tp   = $local_tp - localtime->tzoffset(); # ***
    return $utc_tp->strftime('%y-%m-%d %H:%M:%S');
}

my $local;

# DST in effect (April 19 2016):
$local = '16-04-19 14:30:00';
print "DST in effect:\n";
printf("utc(%s) = %s (using timelocal)\n", $local, local_to_utc_timelocal($local));
printf("utc(%s) = %s (using timepiece)\n\n", $local, local_to_utc_timepiece($local));

# DST NOT in effect (Feb 19 2016):
$local = '16-02-19 14:30:00';
print "DST NOT in effect:\n";
printf("utc(%s) = %s (using timelocal)\n", $local, local_to_utc_timelocal($local));
printf("utc(%s) = %s (using timepiece)\n", $local, local_to_utc_timepiece($local));

Unfortunately the Time::Piece code doesn't seem to work properly wrt DST. I'm living in Germany, so currently (spring/summer, DST in effect) we are UTC+2. For "April 19 2016" the above code gives:

DST in effect:
utc(16-04-19 14:30:00) = 16-04-19 12:30:00 (using timelocal)
utc(16-04-19 14:30:00) = 16-04-19 12:30:00 (using timepiece)

which is correct. But for "Feb 19 2016" (when we are UTC+1) the same code gives:

DST NOT in effect:
utc(16-02-19 14:30:00) = 16-02-19 13:30:00 (using timelocal)
utc(16-02-19 14:30:00) = 16-02-19 12:30:00 (using timepiece)

That is: The gmtime(Time::Local::timelocal($timestamp)) gives correct 1 hour offset while Time::Piece still gives 2 hours offset.

Is this a bug in Time::Piece or do I use it wrongly?

I know there are plenty of ways to convert localtime to UTC but I'd like to use Time::Piece because of its simplicity. Plus I cannot use DateTime because that would involve deploying it on a dozen of production machines.

Community
  • 1
  • 1
PerlDuck
  • 5,610
  • 3
  • 20
  • 39

1 Answers1

10

Problem 1

localtime returns now, so localtime->tzoffset() returns the offset for now. Change

my $utc_tp = $local_tp - localtime->tzoffset();

to

my $utc_tp = $local_tp - $local_tp->tzoffset();

However, that leaves $utc_tp flagged as a localtime, so you really want:

my $utc_tp = gmtime( $local_tp->epoch );

Problem 2

Despite its name, $local_tp is not a local time, so $local_tp->tzoffset() is zero. Change

Time::Piece->strptime( $local_ts, '%y-%m-%d %H:%M:%S' )

to

localtime->strptime( $local_ts, '%y-%m-%d %H:%M:%S' );
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • Thank you! I tried your solution back and forth and it works perfect. I suspected the `localtime->tzoffset()` line but had no idea that `localtime->strptime()` is the _real_ solution. – PerlDuck Apr 19 '16 at 21:08
  • 2
    To expand on "`$local_tp` is not a local time": if you call `strptime` as a class method (i.e. `Time::Piece->strptime`) and don't specify a timezone in the format with `%z` or `%Z`, UTC is assumed. On the other hand, if you call `strptime` as an object method (e.g. `localtime->strptime`), the object's timezone is used. – ThisSuitIsBlackNot Apr 19 '16 at 21:10
  • Updated my answer. – ikegami Apr 25 '16 at 17:28
  • I've been banging my head against the wall on this. The `localtime->strptime` technique just didn't work for me; it always gave me a UTC time. It turns out ([Perl epoch returns different values on 2 different machines (UTC and CET)](http://stackoverflow.com/questions/13821230/perl-epoch-returns-different-values-on-2-different-machines-utc-and-cet)) that I'm running an older version of perl (5.10) and there's a bug in Time::Piece. I've had to resort to using the form of `localtime` where I pass in arguments for month, day, year, etc., rather than using `strptime`. – Bob H Sep 19 '16 at 19:12
  • @Bob H, You could also have upgraded your Time::Piece. Out of curiosity, what version of Time::Piece do you have? – ikegami Sep 19 '16 at 19:30
  • I think I'm using v1.12 on the machine where it doesn't work and 1.20_01 on the machine where it does. My understanding is that Time::Piece is part of the core now. (Also, in my previous reply, I meant to say `timelocal` rather than `localtime`, but I can't figure out how to edit it.) – Bob H Sep 19 '16 at 19:38