tags:

views:

1871

answers:

4

In Perl, I'd like to look up the localtime in a specific timezone. I had been using this technique:

$ENV{TZ} = 'America/Los_Angeles';
my $now = scalar localtime;
print "It is now $now\n";
# WORKS: prints the current time in LA

However, this is not reliable -- notably, if I prepend another localtime() call before setting $ENV{TZ}, it breaks:

localtime();
$ENV{TZ} = 'America/Los_Angeles';
my $now = scalar localtime;
print "It is now $now\n";
# FAILS: prints the current time for here instead of LA

Is there a better way to do this?

+5  A: 

Use POSIX::tzset.

use POSIX qw(tzset);

my $was = localtime;
print "It was      $was\n";

$ENV{TZ} = 'America/Los_Angeles';

$was = localtime;
print "It is still $was\n";

tzset;

my $now = localtime;
print "It is now   $now\n";
$ perl -v

This is perl, v5.8.8 built for x86_64-linux-thread-multi

Copyright 1987-2006, Larry Wall

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl".  If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.

$ perl tzset-test.pl
It was      Wed Apr 15 15:58:10 2009
It is still Wed Apr 15 15:58:10 2009
It is now   Wed Apr 15 12:58:10 2009
ephemient
Nice -- loved the proof, too.
mike
but it would still be nice to know what version of Perl you're using that actually had the problem...
Alnitak
A: 
use Time::Zone;

my $TZ = 'America/Los_Angeles';
my $now = scalar localtime time() + tz_offset($TZ);
print "It is now $now\n";

seems to work here. (The 'scalar' is redundant here since $now gives it scalar context, but it's also nice to be explicit.)

As per the comment, I got the original problem. This seems to fix it for me, but given that others aren't having the original problem, the "seems to work here" bit is intended as an invitation for those people to try this solution as well to ensure it doesn't break anything. (I have to wonder if alnitak noticed the difference between what I posted and the original post?)

Tanktalus
you're right - I didn't spot the tz_offset call. downvote reversed, but the original text could have been clearer....
Alnitak
I also think that this solution is good as it doesn't mess with the system environment.
Dana the Sane
-1 because the timezone offset is dependent on the time due to: 1. history (time zone offset change over time) 2. daylight saving in some areas
dolmen
+3  A: 

Whilst your code works fine for me on both Linux (Perl 5.10.0) and MacOS X (5.8.9), there is a possible solution.

The underlying C functons used by Perl (ctime(), localtime(), etc) call tzset() the first time they're invoked, but not necessarily afterwards. By calling it yourself you should ensure that the timezone structures are correctly re-initialised after any change to $TZ.

Fortunately this is easy - the tzset() function is available in the POSIX module:

#!/usr/bin/perl -w
use POSIX qw[tzset];

$ENV{'TZ'} = 'Europe/London';
tzset();
print scalar localtime();

NB: some Google searches suggest that this is only necessary with Perl versions up to and including 5.8.8. Later versions always call tzset() automatically before each call to localtime().

Alnitak
Why is it wise to change the timezone setting at all? If the TZ is correct for the machine's location, it should probably stay that way. Making this type of runtime change seems to be asking for trouble.
Dana the Sane
because this change only affects the current running process, and it's the mechanism provided by the underlying C functions in POSIX. It's perfectly safe.
Alnitak
It doesn't just change the current running process, it also modifies any children created after this point. Can be confusing in multi-threaded apps. However, if that's what you want to do, then absolutely you should use this method.
Tanktalus
Since the user doesn't say if this code is threaded webserver perhaps, etc. I think it should be noted that synchronization may be needed.
Dana the Sane
good point about child processes, although Dana's comment (and deleted answer) implied a belief that changing $TZ would change it for the whole system.
Alnitak
+5  A: 

I'd strongly suggest using a module to do this. Specifically, I'd suggest using DateTime (see Perl DateTime Wiki or CPAN

Then you should be able to do something like the following:

use strict;
use warnings;
use DateTime;
my $dt = DateTime->now(); # *your* local time assuming your system knows it!


my $clone1 = $dt->clone; # taking a copy.
$clone1->set_time_zone('America/Los_Angeles');


print "$clone1\n";   # output using ISO 8601 format (there a lot of choices)
print "$dt\n";
Nic Gibson