19

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?

mike
  • 46,876
  • 44
  • 102
  • 112

6 Answers6

25

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
cjm
  • 61,471
  • 9
  • 126
  • 175
ephemient
  • 198,619
  • 38
  • 280
  • 391
  • Nice -- loved the proof, too. – mike Apr 15 '09 at 20:12
  • but it would still be nice to know what version of Perl you're using that actually had the problem... – Alnitak Apr 15 '09 at 20:17
  • The effect of setting `$ENV{TZ}` may also be immediate, like with v5.10.1 on Cygwin; or might just reset the timezone to UTC, like with all four versions of perl for Win32 I tested (5.10.1, 5.12.3, 5.12.4, 5.14.1). Poor Windows Perl won't go to America. :( – Lumi Mar 05 '12 at 11:22
  • On Windows 10, using Strawberry Perl 5.30.2.1, got "ERROR: POSIX::tzset not implemented on this architecture". – Louis Strous Aug 16 '20 at 14:49
11

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
  • 7,051
  • 4
  • 31
  • 40
  • I also strongly encourage this if you just want time information locally in the script. Here's why: The above approach changes an environment variable, which is much more intrusive. Nic's example just creates a variable that the script works with. Also, as mentioned above, POSIX::tzset doesn't work in windows 10 as of this writing in 2021. – kovacsbv Sep 16 '21 at 14:10
5

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
  • 334,560
  • 70
  • 407
  • 495
  • 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 Apr 15 '09 at 20:07
  • 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 Apr 15 '09 at 20:10
  • 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 Apr 15 '09 at 20:12
  • 1
    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 Apr 15 '09 at 20:16
  • 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 Apr 15 '09 at 20:16
  • Any way to verify if the string "Europe/London" is valid via POSIX? (w/o extra modules). The tzset() function always returns undef even if we set $ENV{TZ} = "Mars/Starbase_3" – FlorianB Jul 05 '22 at 16:02
2
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
  • 21,664
  • 5
  • 41
  • 68
  • you're right - I didn't spot the tz_offset call. downvote reversed, but the original text could have been clearer.... – Alnitak Apr 15 '09 at 20:08
  • I also think that this solution is good as it doesn't mess with the system environment. – Dana the Sane Apr 15 '09 at 20:10
  • -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 Jul 15 '10 at 15:55
1

Expanding on BrianP007 answer you use both TZ and _tzset

$was = localtime;
print "It was $was\n";
$ENV{TZ} = 'CST6CDT'; # America/Chicago
Time::Piece::_tzset(); # Local time is now Chicago Time
$was = localtime;
print "It is $was\n"; # Right now in Chicago

The trick is the TZ is set from your location to GMT. So normally you would think Chicago is UTC-6, but from Chicago it is 6 hours to UTC which = 'CST6'.

See http://science.ksc.nasa.gov/software/winvn/userguide/3_1_4.htm

ozcoder
  • 41
  • 2
-1

Executive Summary:

Setting $ENV{TZ}='/*&+000000000005' and calling Time::Piece::_tzset() fixes localtime() to agree with the windoz system clock.

Sanguinarily gory details:

On Strawberry Perl, windoz 7/64, none of the "Standard" time zones works in the TZ environmental variable to localize localtime(). 'America/Chicago' gives exactly the same time as 'America/Los_Angeles' == 'CDT' == 'CST' == 'UTC' == '-01:00', etc. The list is infinite.

Every timezone on http://www.timeanddate.com/time/zones/ that I tried gives the right time if you are in Greenwich.

Every time from: http://en.wikipedia.org/wiki/List_of_tz_database_time_zones also fails to change localtime() at all. And, there is no apparent indication. They do nothing and say nothing.

There is NO tzset() on windoz: POSIX::tzset not implemented on this architecture

There is not even any concept of POSIX ??? C:\bin>cpan install POSIX ... Warning: Cannot install POSIX, don't know what it is. Try the command i /POSIX/

It appears to be baked into win8 and there are some dot NOT libraries for it.

For Austin, Texas, in the very Center of Central Intergalactic Time, thee correct $ENV{TZ} which gives me a scalar localtime() which ~agrees with the o/s level time function and the windoz clock is: '/*&+5' !!! Yes Slash-Star-Ampersand-Plus-5 works!

P:\br1\sxsw.2015\sx-2015.0318\done>time The current time is: 16:36:39.44 ... Time=Apr 14 16:36:42 2015, ENV->TZ=/*&+5

By running a for loop and trying random values from various posts, for Strawberry Perl uname='Win32 strawberry-perl 5.18.2.2...' with known timezone bugs, any 3 chars I tried (didn't try + or -) followed by +/- and a small number worked. Here is an array of text values and their output below:

use Time::Piece;
@tz = ('', 'CDT+5', 'CST+5', 'FKU+5', 'XYZ+5', '+5', '+05', '+05.00', 
    'America/Chicago', 'America/Los_Angeles', 'CDT', 
    'CST', 'UTC', 'PDT', 'PST', '-01:00', '+01:00', '-05:00'.
    'ACDT', 'EASST', '5000', '+0500', '+5:00', '+05:00', 'SSS+1', 'SSS+0',
    'zzz-1', 'ZZ1+5', '123+5', '___+5', '/*&+5', , '/*&+05', '/*&+005',
    '/*&+000000000005');
foreach $tz (@tz)  {
    $ENV{TZ} = $tz         if $tz;
    Time::Piece::_tzset()  if $tz;
    printf("T%s, ENV->TZ=%s\n", scalar localtime, $ENV{TZ}  ||  'NoTZ');
}

Most every try with anything but XXX . +|- . integer gave UTC, but many were an hour off for no reason (America/Los_Angeles and America/Chicago gave the same value). I am almost sure I used to get away with just CDT and CST, possibly on Activestate (switched to Strawberry to compile my own Perl modules rather than rely on Activestate for everything). This is the first major snarl.

I rebuilt DateTime from scratch and it worked fine. DateTime::TimeZone::Local::Win32 "failed for 'Win32::TieRegistry'"

Here's the sorted result of the attempted zones above:

    P:\br1\sxsw.2015\sx-2015.0318\done>bb | sort
    Running c:/bin/bb.pl  Tue Apr 14 21:43:56 2015
    TTue Apr 14 16:43:56 2015, ENV->TZ=/*&+000000000005
    TTue Apr 14 16:43:56 2015, ENV->TZ=/*&+005
    TTue Apr 14 16:43:56 2015, ENV->TZ=/*&+05
    TTue Apr 14 16:43:56 2015, ENV->TZ=/*&+5
    TTue Apr 14 16:43:56 2015, ENV->TZ=___+5
    TTue Apr 14 16:43:56 2015, ENV->TZ=123+5
    TTue Apr 14 16:43:56 2015, ENV->TZ=CDT+5
    TTue Apr 14 16:43:56 2015, ENV->TZ=CST+5
    TTue Apr 14 16:43:56 2015, ENV->TZ=FKU+5
    TTue Apr 14 16:43:56 2015, ENV->TZ=XYZ+5
    TTue Apr 14 16:43:56 2015, ENV->TZ=ZZ1+5
    ABOVE ALL WORKED Below most failed with UTC or +1 hour???
    TTue Apr 14 20:43:56 2015, ENV->TZ=SSS+1
    TTue Apr 14 21:43:56 2015, ENV->TZ=-01:00
    TTue Apr 14 21:43:56 2015, ENV->TZ=+01:00
    TTue Apr 14 21:43:56 2015, ENV->TZ=+05
    TTue Apr 14 21:43:56 2015, ENV->TZ=+05:00
    TTue Apr 14 21:43:56 2015, ENV->TZ=+0500
    TTue Apr 14 21:43:56 2015, ENV->TZ=+5
    TTue Apr 14 21:43:56 2015, ENV->TZ=+5:00
    TTue Apr 14 21:43:56 2015, ENV->TZ=5000
    TTue Apr 14 21:43:56 2015, ENV->TZ=CDT
    TTue Apr 14 21:43:56 2015, ENV->TZ=CDT
    TTue Apr 14 21:43:56 2015, ENV->TZ=CST
    TTue Apr 14 21:43:56 2015, ENV->TZ=PDT
    TTue Apr 14 21:43:56 2015, ENV->TZ=PST
    TTue Apr 14 21:43:56 2015, ENV->TZ=SSS+0
    TTue Apr 14 21:43:56 2015, ENV->TZ=UTC
    TTue Apr 14 22:43:56 2015, ENV->TZ=-05:00ACDT
    TTue Apr 14 22:43:56 2015, ENV->TZ=+05.00
    TTue Apr 14 22:43:56 2015, ENV->TZ=America/Chicago
    TTue Apr 14 22:43:56 2015, ENV->TZ=America/Los_Angeles
    TTue Apr 14 22:43:56 2015, ENV->TZ=EASST
    TTue Apr 14 22:43:56 2015, ENV->TZ=zzz-1
  • Even after finding and installing the Holy Grail, the TzFile module for the Olsen Database, it is still screwed, no difference!

    Installing C:\bin\strawberry_perl_5_18\perl\site\lib\DateTime\TimeZone\Tzfile.pm ZEFRAM/DateTime-TimeZone-Tzfile-0.010.tar.gz C:\bin\strawberry_perl_5_18\perl\bin\perl.exe ./Build install --uninst 1 -- OK

Here are all of the alleged timezones which do nothing on this platform from: @atz = DateTime::TimeZone->all_names(); printf("All tz names [%d] = %s\n", scalar @atz, join(", ", @atz));

All tz names [349] = Africa/Abidjan, Africa/Accra, Africa/Algiers, Africa/Bissau, Africa/Cairo, Africa/Casablanca, Africa/Ceuta, Africa/El_Aaiun, Africa/Johannesburg, Africa/Khartoum, Africa/Lagos, Africa/Maputo, Africa/Monrovia, Africa/Nairobi, Africa/Ndjamena, Africa/Tripoli, Africa/Tunis, Africa/Windhoek, America/Adak, America/Anchorage, America/Araguaina, America/Argentina/Buenos_Aires, America/Argentina/Catamarca, America/Argentina/Cordoba, America/Argentina/Jujuy, America/Argentina/La_Rioja, America/Argentina/Mendoza, America/Argentina/Rio_Gallegos, America/Argentina/Salta, America/Argentina/San_Juan, America/Argentina/San_Luis, America/Argentina/Tucuman, America/Argentina/Ushuaia, America/Asuncion, America/Atikokan, America/Bahia, America/Bahia_Banderas, America/Barbados, America/Belem, America/Belize, America/Blanc-Sablon, America/Boa_Vista, America/Bogota, America/Boise, America/Cambridge_Bay, America/Campo_Grande, America/Cancun, America/Caracas, America/Cayenne, America/Chicago, America/Chihuahua, America/Costa_Rica, America/Creston, America/Cuiaba, America/Curacao, America/Danmarkshavn, America/Dawson, America/Dawson_Creek, America/Denver, America/Detroit, America/Edmonton, America/Eirunepe, America/El_Salvador, America/Fortaleza, America/Glace_Bay, America/Godthab, America/Goose_Bay, America/Grand_Turk, America/Guatemala, America/Guayaquil, America/Guyana, America/Halifax, America/Havana, America/Hermosillo, America/Indiana/Indianapolis, America/Indiana/Knox, America/Indiana/Marengo, America/Indiana/Petersburg, America/Indiana/Tell_City, America/Indiana/Vevay, America/Indiana/Vincennes, America/Indiana/Winamac, America/Inuvik, America/Iqaluit, America/Jamaica, America/Juneau, America/Kentucky/Louisville, America/Kentucky/Monticello, America/La_Paz, America/Lima, America/Los_Angeles, America/Maceio, America/Managua, America/Manaus, America/Martinique, America/Matamoros, America/Mazatlan, America/Menominee, America/Merida, America/Metlakatla, America/Mexico_City, America/Miquelon, America/Moncton, America/Monterrey, America/Montevideo, America/Montreal, America/Nassau, America/New_York, America/Nipigon, America/Nome, America/Noronha, America/North_Dakota/Beulah, America/North_Dakota/Center, America/North_Dakota/New_Salem, America/Ojinaga, America/Panama, America/Pangnirtung, America/Paramaribo, America/Phoenix, America/Port-au-Prince, America/Port_of_Spain, America/Porto_Velho, America/Puerto_Rico, America/Rainy_River, America/Rankin_Inlet, America/Recife, America/Regina, America/Resolute, America/Rio_Branco, America/Santa_Isabel, America/Santarem, America/Santiago, America/Santo_Domingo, America/Sao_Paulo, America/Scoresbysund, America/Sitka, America/St_Johns, America/Swift_Current, America/Tegucigalpa, America/Thule, America/Thunder_Bay, America/Tijuana, America/Toronto, America/Vancouver, America/Whitehorse, America/Winnipeg, America/Yakutat, America/Yellowknife, Antarctica/Casey, Antarctica/Davis, Antarctica/DumontDUrville, Antarctica/Macquarie, Antarctica/Mawson, Antarctica/Palmer, Antarctica/Rothera, Antarctica/Syowa, Antarctica/Troll, Antarctica/Vostok, Asia/Almaty, Asia/Amman, Asia/Anadyr, Asia/Aqtau, Asia/Aqtobe, Asia/Ashgabat, Asia/Baghdad, Asia/Baku, Asia/Bangkok, Asia/Beirut, Asia/Bishkek, Asia/Brunei, Asia/Chita, Asia/Choibalsan, Asia/Colombo, Asia/Damascus, Asia/Dhaka, Asia/Dili, Asia/Dubai, Asia/Dushanbe, Asia/Gaza, Asia/Hebron, Asia/Ho_Chi_Minh, Asia/Hong_Kong, Asia/Hovd, Asia/Irkutsk, Asia/Jakarta, Asia/Jayapura, Asia/Jerusalem, Asia/Kabul, Asia/Kamchatka, Asia/Karachi, Asia/Kathmandu, Asia/Khandyga, Asia/Kolkata, Asia/Krasnoyarsk, Asia/Kuala_Lumpur, Asia/Kuching, Asia/Macau, Asia/Magadan, Asia/Makassar, Asia/Manila, Asia/Nicosia, Asia/Novokuznetsk, Asia/Novosibirsk, Asia/Omsk, Asia/Oral, Asia/Pontianak, Asia/Pyongyang, Asia/Qatar, Asia/Qyzylorda, Asia/Rangoon, Asia/Riyadh, Asia/Sakhalin, Asia/Samarkand, Asia/Seoul, Asia/Shanghai, Asia/Singapore, Asia/Srednekolymsk, Asia/Taipei, Asia/Tashkent, Asia/Tbilisi, Asia/Tehran, Asia/Thimphu, Asia/Tokyo, Asia/Ulaanbaatar, Asia/Urumqi, Asia/Ust-Nera, Asia/Vladivostok, Asia/Yakutsk, Asia/Yekaterinburg, Asia/Yerevan, Atlantic/Azores, Atlantic/Bermuda, Atlantic/Canary, Atlantic/Cape_Verde, Atlantic/Faroe, Atlantic/Madeira, Atlantic/Reykjavik, Atlantic/South_Georgia, Atlantic/Stanley, Australia/Adelaide, Australia/Brisbane, Australia/Broken_Hill, Australia/Currie, Australia/Darwin, Australia/Eucla, Australia/Hobart, Australia/Lindeman, Australia/Lord_Howe, Australia/Melbourne, Australia/Perth, Australia/Sydney, CET, CST6CDT, EET, EST, EST5EDT, Europe/Amsterdam, Europe/Andorra, Europe/Athens, Europe/Belgrade, Europe/Berlin, Europe/Brussels, Europe/Bucharest, Europe/Budapest, Europe/Chisinau, Europe/Copenhagen, Europe/Dublin, Europe/Gibraltar, Europe/Helsinki, Europe/Istanbul, Europe/Kaliningrad, Europe/Kiev, Europe/Lisbon, Europe/London, Europe/Luxembourg, Europe/Madrid, Europe/Malta, Europe/Minsk, Europe/Monaco, Europe/Moscow, Europe/Oslo, Europe/Paris, Europe/Prague, Europe/Riga, Europe/Rome, Europe/Samara, Europe/Simferopol, Europe/Sofia, Europe/Stockholm, Europe/Tallinn, Europe/Tirane, Europe/Uzhgorod, Europe/Vienna, Europe/Vilnius, Europe/Volgograd, Europe/Warsaw, Europe/Zaporozhye, Europe/Zurich, HST, Indian/Chagos, Indian/Christmas, Indian/Cocos, Indian/Kerguelen, Indian/Mahe, Indian/Maldives, Indian/Mauritius, Indian/Reunion, MET, MST, MST7MDT, PST8PDT, Pacific/Apia, Pacific/Auckland, Pacific/Bougainville, Pacific/Chatham, Pacific/Chuuk, Pacific/Easter, Pacific/Efate, Pacific/Enderbury, Pacific/Fakaofo, Pacific/Fiji, Pacific/Funafuti, Pacific/Galapagos, Pacific/Gambier, Pacific/Guadalcanal, Pacific/Guam, Pacific/Honolulu, Pacific/Kiritimati, Pacific/Kosrae, Pacific/Kwajalein, Pacific/Majuro, Pacific/Marquesas, Pacific/Nauru, Pacific/Niue, Pacific/Norfolk, Pacific/Noumea, Pacific/Pago_Pago, Pacific/Palau, Pacific/Pitcairn, Pacific/Pohnpei, Pacific/Port_Moresby, Pacific/Rarotonga, Pacific/Tahiti, Pacific/Tarawa, Pacific/Tongatapu, Pacific/Wake, Pacific/Wallis, UTC, WET

BrianP007
  • 162
  • 12