<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">
package Time::Zone;

=head1 NAME

Time::Zone -- miscellaneous timezone manipulations routines

=head1 SYNOPSIS

	use Time::Zone;
	print tz2zone();
	print tz2zone($ENV{'TZ'});
	print tz2zone($ENV{'TZ'}, time());
	print tz2zone($ENV{'TZ'}, undef, $isdst);
	$offset = tz_local_offset();
	$offset = tz_offset($TZ);

=head1 DESCRIPTION

This is a collection of miscellaneous timezone manipulation routines.

C&lt;tz2zone()&gt; parses the TZ environment variable and returns a timezone
string suitable for inclusion in L&lt;date(1)&gt;-like output.  It opionally takes
a timezone string, a time, and a is-dst flag.

C&lt;tz_local_offset()&gt; determins the offset from GMT time in seconds.  It
only does the calculation once.

C&lt;tz_offset()&gt; determines the offset from GMT in seconds of a specified
timezone.  

C&lt;tz_name()&gt; determines the name of the timezone based on its offset

=head1 AUTHORS

Graham Barr &lt;gbarr@pobox.com&gt;
David Muir Sharnoff &lt;muir@idiom.com&gt;
Paul Foley &lt;paul@ascent.com&gt;

=cut

require 5.002;

require Exporter;
use Carp;
use strict;
use vars qw(@ISA @EXPORT $VERSION @tz_local);

@ISA = qw(Exporter);
@EXPORT = qw(tz2zone tz_local_offset tz_offset tz_name);
$VERSION = "2.24";

# Parts stolen from code by Paul Foley &lt;paul@ascent.com&gt;

sub tz2zone (;$$$)
{
	my($TZ, $time, $isdst) = @_;

	use vars qw(%tzn_cache);

	$TZ = defined($ENV{'TZ'}) ? ( $ENV{'TZ'} ? $ENV{'TZ'} : 'GMT' ) : ''
	    unless $TZ;

	# Hack to deal with 'PST8PDT' format of TZ
	# Note that this can't deal with all the esoteric forms, but it
	# does recognize the most common: [:]STDoff[DST[off][,rule]]

	if (! defined $isdst) {
		my $j;
		$time = time() unless $time;
		($j, $j, $j, $j, $j, $j, $j, $j, $isdst) = localtime($time);
	}

	if (defined $tzn_cache{$TZ}-&gt;[$isdst]) {
		return $tzn_cache{$TZ}-&gt;[$isdst];
	}
      
	if ($TZ =~ /^
		    ( [^:\d+\-,] {3,} )
		    ( [+-] ?
		      \d {1,2}
		      ( : \d {1,2} ) {0,2} 
		    )
		    ( [^\d+\-,] {3,} )?
		    /x
	    ) {
		my $dsttz = defined($4) ? $4 : $1;
		$TZ = $isdst ? $dsttz : $1;
		$tzn_cache{$TZ} = [ $1, $dsttz ];
	} else {
		$tzn_cache{$TZ} = [ $TZ, $TZ ];
	}
	return $TZ;
}

sub tz_local_offset (;$)
{
	my ($time) = @_;

	$time = time() unless $time;
	my (@l) = localtime($time);
	my $isdst = $l[8];

	if (defined($tz_local[$isdst])) {
		return $tz_local[$isdst];
	}

	$tz_local[$isdst] = &amp;calc_off($time);

	return $tz_local[$isdst];
}

sub calc_off
{
	my ($time) = @_;

	my (@l) = localtime($time);
	my (@g) = gmtime($time);

	my $off;

	$off =     $l[0] - $g[0]
		+ ($l[1] - $g[1]) * 60
		+ ($l[2] - $g[2]) * 3600;

	# subscript 7 is yday.

	if ($l[7] == $g[7]) {
		# done
	} elsif ($l[7] == $g[7] + 1) {
		$off += 86400;
	} elsif ($l[7] == $g[7] - 1) {
		$off -= 86400;
	} elsif ($l[7] &lt; $g[7]) {
		# crossed over a year boundry!
		# localtime is beginning of year, gmt is end
		# therefore local is ahead
		$off += 86400;
	} else {
		$off -= 86400;
	}

	return $off;
}

# constants

CONFIG: {
	use vars qw(%dstZone %zoneOff %dstZoneOff %Zone);

	my @dstZone = (
	#   "ndt"  =&gt;   -2*3600-1800,	 # Newfoundland Daylight   
	    "brst" =&gt;   -2*3600,         # Brazil Summer Time (East Daylight)
	    "adt"  =&gt;   -3*3600,  	 # Atlantic Daylight   
	    "edt"  =&gt;   -4*3600,  	 # Eastern Daylight
	    "cdt"  =&gt;   -5*3600,  	 # Central Daylight
	    "mdt"  =&gt;   -6*3600,  	 # Mountain Daylight
	    "pdt"  =&gt;   -7*3600,  	 # Pacific Daylight
	    "akdt" =&gt;   -8*3600,         # Alaska Daylight
	    "ydt"  =&gt;   -8*3600,  	 # Yukon Daylight
	    "hdt"  =&gt;   -9*3600,  	 # Hawaii Daylight
	    "bst"  =&gt;   +1*3600,  	 # British Summer   
	    "mest" =&gt;   +2*3600,  	 # Middle European Summer   
	    "metdst" =&gt; +2*3600, 	 # Middle European DST
	    "sst"  =&gt;   +2*3600,  	 # Swedish Summer
	    "fst"  =&gt;   +2*3600,  	 # French Summer
            "cest" =&gt;   +2*3600,         # Central European Daylight
            "eest" =&gt;   +3*3600,         # Eastern European Summer
            "msd"  =&gt;   +4*3600,         # Moscow Daylight
	    "wadt" =&gt;   +8*3600,  	 # West Australian Daylight
	    "kdt"  =&gt;  +10*3600,	 # Korean Daylight
	#   "cadt" =&gt;  +10*3600+1800,	 # Central Australian Daylight
	    "aedt" =&gt;  +11*3600,  	 # Eastern Australian Daylight
	    "eadt" =&gt;  +11*3600,  	 # Eastern Australian Daylight
	    "nzd"  =&gt;  +13*3600,  	 # New Zealand Daylight   
	    "nzdt" =&gt;  +13*3600,  	 # New Zealand Daylight   
	);

	my @Zone = (
	    "gmt"	=&gt;   0,  	 # Greenwich Mean
	    "ut"        =&gt;   0,  	 # Universal (Coordinated)
	    "utc"       =&gt;   0,
	    "wet"       =&gt;   0,  	 # Western European
	    "wat"       =&gt;  -1*3600,	 # West Africa
	    "at"        =&gt;  -2*3600,	 # Azores
	    "fnt"	=&gt;  -2*3600,	 # Brazil Time (Extreme East - Fernando Noronha)
	    "brt"	=&gt;  -3*3600,	 # Brazil Time (East Standard - Brasilia)
	# For completeness.  BST is also British Summer, and GST is also Guam Standard.
	#   "bst"       =&gt;  -3*3600,	 # Brazil Standard
	#   "gst"       =&gt;  -3*3600,	 # Greenland Standard
	#   "nft"       =&gt;  -3*3600-1800,# Newfoundland
	#   "nst"       =&gt;  -3*3600-1800,# Newfoundland Standard
	    "mnt"	=&gt;  -4*3600,	 # Brazil Time (West Standard - Manaus)
	    "ewt"       =&gt;  -4*3600,	 # U.S. Eastern War Time
	    "ast"       =&gt;  -4*3600,	 # Atlantic Standard
	    "est"       =&gt;  -5*3600,	 # Eastern Standard
	    "act"	=&gt;  -5*3600,	 # Brazil Time (Extreme West - Acre)
	    "cst"       =&gt;  -6*3600,	 # Central Standard
	    "mst"       =&gt;  -7*3600,	 # Mountain Standard
	    "pst"       =&gt;  -8*3600,	 # Pacific Standard
	    "akst"      =&gt;  -9*3600,     # Alaska Standard
	    "yst"	=&gt;  -9*3600,	 # Yukon Standard
	    "hst"	=&gt; -10*3600,	 # Hawaii Standard
	    "cat"	=&gt; -10*3600,	 # Central Alaska
	    "ahst"	=&gt; -10*3600,	 # Alaska-Hawaii Standard
	    "nt"	=&gt; -11*3600,	 # Nome
	    "idlw"	=&gt; -12*3600,	 # International Date Line West
	    "cet"	=&gt;  +1*3600, 	 # Central European
	    "mez"	=&gt;  +1*3600, 	 # Central European (German)
	    "ect"	=&gt;  +1*3600, 	 # Central European (French)
	    "met"	=&gt;  +1*3600, 	 # Middle European
	    "mewt"	=&gt;  +1*3600, 	 # Middle European Winter
	    "swt"	=&gt;  +1*3600, 	 # Swedish Winter
	    "set"	=&gt;  +1*3600, 	 # Seychelles
	    "fwt"	=&gt;  +1*3600, 	 # French Winter
	    "eet"	=&gt;  +2*3600, 	 # Eastern Europe, USSR Zone 1
	    "ukr"	=&gt;  +2*3600, 	 # Ukraine
	    "bt"	=&gt;  +3*3600, 	 # Baghdad, USSR Zone 2
            "msk"       =&gt;  +3*3600,     # Moscow
	#   "it"	=&gt;  +3*3600+1800,# Iran
	    "zp4"	=&gt;  +4*3600, 	 # USSR Zone 3
	    "zp5"	=&gt;  +5*3600, 	 # USSR Zone 4
	#   "ist"	=&gt;  +5*3600+1800,# Indian Standard
	    "zp6"	=&gt;  +6*3600, 	 # USSR Zone 5
	# For completeness.  NST is also Newfoundland Stanard, and SST is also Swedish Summer.
	#   "nst"	=&gt;  +6*3600+1800,# North Sumatra
	#   "sst"	=&gt;  +7*3600, 	 # South Sumatra, USSR Zone 6
	#   "jt"	=&gt;  +7*3600+1800,# Java (3pm in Cronusland!)
	    "wst"	=&gt;  +8*3600, 	 # West Australian Standard
	    "hkt"	=&gt;  +8*3600, 	 # Hong Kong
	    "cct"	=&gt;  +8*3600, 	 # China Coast, USSR Zone 7
	    "jst"	=&gt;  +9*3600,	 # Japan Standard, USSR Zone 8
	    "kst"	=&gt;  +9*3600,	 # Korean Standard
	#   "cast"	=&gt;  +9*3600+1800,# Central Australian Standard
	    "aest"	=&gt; +10*3600,	 # Eastern Australian Standard
	    "east"	=&gt; +10*3600,	 # Eastern Australian Standard
	    "gst"	=&gt; +10*3600,	 # Guam Standard, USSR Zone 9
	    "nzt"	=&gt; +12*3600,	 # New Zealand
	    "nzst"	=&gt; +12*3600,	 # New Zealand Standard
	    "idle"	=&gt; +12*3600,	 # International Date Line East
	);

	%Zone = @Zone;
	%dstZone = @dstZone;
	%zoneOff = reverse(@Zone);
	%dstZoneOff = reverse(@dstZone);

}

sub tz_offset (;$$)
{
	my ($zone, $time) = @_;

	return &amp;tz_local_offset($time) unless($zone);

	$time = time() unless $time;
	my(@l) = localtime($time);
	my $dst = $l[8];

	$zone = lc $zone;

	if($zone =~ /^(([\-\+])\d\d?)(\d\d)$/) {
		my $v = $2 . $3;
		return $1 * 3600 + $v * 60;
	} elsif (exists $dstZone{$zone} &amp;&amp; ($dst || !exists $Zone{$zone})) {
		return $dstZone{$zone};
	} elsif(exists $Zone{$zone}) {
		return $Zone{$zone};
	}
	undef;
}

sub tz_name (;$$)
{
	my ($off, $dst) = @_;

	$off = tz_offset()
		unless(defined $off);

	$dst = (localtime(time))[8]
		unless(defined $dst);

	if (exists $dstZoneOff{$off} &amp;&amp; ($dst || !exists $zoneOff{$off})) {
		return $dstZoneOff{$off};
	} elsif (exists $zoneOff{$off}) {
		return $zoneOff{$off};
	}
	sprintf("%+05d", int($off / 60) * 100 + $off % 60);
}

1;
</pre></body></html>