Blame lib/DateTime/TimeZone/Tzfile.pm

Packit 1c5982
=head1 NAME
Packit 1c5982
Packit 1c5982
DateTime::TimeZone::Tzfile - tzfile (zoneinfo) timezone files
Packit 1c5982
Packit 1c5982
=head1 SYNOPSIS
Packit 1c5982
Packit 1c5982
    use DateTime::TimeZone::Tzfile;
Packit 1c5982
Packit 1c5982
    $tz = DateTime::TimeZone::Tzfile->new(
Packit 1c5982
	    name => "local timezone",
Packit 1c5982
	    filename => "/etc/localtime");
Packit 1c5982
    $tz = DateTime::TimeZone::Tzfile->new("/etc/localtime");
Packit 1c5982
Packit 1c5982
    if($tz->is_floating) { ...
Packit 1c5982
    if($tz->is_utc) { ...
Packit 1c5982
    if($tz->is_olson) { ...
Packit 1c5982
    $category = $tz->category;
Packit 1c5982
    $tz_string = $tz->name;
Packit 1c5982
Packit 1c5982
    if($tz->has_dst_changes) { ...
Packit 1c5982
    if($tz->is_dst_for_datetime($dt)) { ...
Packit 1c5982
    $offset = $tz->offset_for_datetime($dt);
Packit 1c5982
    $abbrev = $tz->short_name_for_datetime($dt);
Packit 1c5982
    $offset = $tz->offset_for_local_datetime($dt);
Packit 1c5982
Packit 1c5982
=head1 DESCRIPTION
Packit 1c5982
Packit 1c5982
An instance of this class represents a timezone that was encoded in a
Packit 1c5982
file in the L<tzfile(5)> format.  These can express arbitrary patterns
Packit 1c5982
of offsets from Universal Time, changing over time.  Offsets and change
Packit 1c5982
times are limited to a resolution of one second.
Packit 1c5982
Packit 1c5982
This class implements the L<DateTime::TimeZone> interface, so that its
Packit 1c5982
instances can be used with L<DateTime> objects.
Packit 1c5982
Packit 1c5982
=cut
Packit 1c5982
Packit 1c5982
package DateTime::TimeZone::Tzfile;
Packit 1c5982
Packit 1c5982
{ use 5.006; }
Packit 1c5982
use warnings;
Packit 1c5982
use strict;
Packit 1c5982
Packit 1c5982
use Carp qw(croak);
Packit 1c5982
use Date::ISO8601 0.000 qw(present_ymd);
Packit 1c5982
use IO::File 1.13;
Packit 1c5982
use IO::Handle 1.08;
Packit 1c5982
use Params::Classify 0.000 qw(is_undef is_string is_ref);
Packit 1c5982
Packit 1c5982
our $VERSION = "0.011";
Packit 1c5982
Packit 1c5982
my $rdn_epoch_cjdn = 1721425;
Packit 1c5982
Packit 1c5982
# _fdiv(A, B), _fmod(A, B): divide A by B, flooring remainder
Packit 1c5982
#
Packit 1c5982
# B must be a positive Perl integer.  A must be a Perl integer.
Packit 1c5982
Packit 1c5982
sub _fdiv($$) {
Packit 1c5982
	my($a, $b) = @_;
Packit 1c5982
	if($a < 0) {
Packit 1c5982
		use integer;
Packit 1c5982
		return -(($b - 1 - $a) / $b);
Packit 1c5982
	} else {
Packit 1c5982
		use integer;
Packit 1c5982
		return $a / $b;
Packit 1c5982
	}
Packit 1c5982
}
Packit 1c5982
Packit 1c5982
sub _fmod($$) { $_[0] % $_[1] }
Packit 1c5982
Packit 1c5982
=head1 CONSTRUCTOR
Packit 1c5982
Packit 1c5982
=over
Packit 1c5982
Packit 1c5982
=item DateTime::TimeZone::Tzfile->new(ATTR => VALUE, ...)
Packit 1c5982
Packit 1c5982
Reads and parses a L<tzfile(5)> format file, then constructs and returns
Packit 1c5982
a L<DateTime>-compatible timezone object that implements the timezone
Packit 1c5982
encoded in the file.  The following attributes may be given:
Packit 1c5982
Packit 1c5982
=over
Packit 1c5982
Packit 1c5982
=item B<name>
Packit 1c5982
Packit 1c5982
Name for the timezone object.  This will be returned by the C<name>
Packit 1c5982
method described below, and will be included in certain error messages.
Packit 1c5982
Packit 1c5982
=item B<category>
Packit 1c5982
Packit 1c5982
The string or C<undef> that will be returned by the C<category> method
Packit 1c5982
described below.  Default C<undef>.
Packit 1c5982
Packit 1c5982
=item B<is_olson>
Packit 1c5982
Packit 1c5982
The truth value that will be returned by the C<is_olson> method described
Packit 1c5982
below.  Default false.
Packit 1c5982
Packit 1c5982
=item B<filename>
Packit 1c5982
Packit 1c5982
Name of the file from which to read the timezone data.  The filename
Packit 1c5982
must be understood by L<IO::File>.
Packit 1c5982
Packit 1c5982
=item B<filehandle>
Packit 1c5982
Packit 1c5982
An L<IO::Handle> object from which the timezone data can be read.
Packit 1c5982
This does not need to be a regular seekable file; it is read sequentially.
Packit 1c5982
After the constructor has finished, the handle can still be used to read
Packit 1c5982
any data that follows the timezone data.
Packit 1c5982
Packit 1c5982
=back
Packit 1c5982
Packit 1c5982
Either a filename or filehandle must be given.  If a timezone name is not
Packit 1c5982
given, then the filename is used instead if supplied; a timezone name
Packit 1c5982
must be given explicitly if no filename is given.
Packit 1c5982
Packit 1c5982
=item DateTime::TimeZone::Tzfile->new(FILENAME)
Packit 1c5982
Packit 1c5982
Simpler way to invoke the above constructor in the usual case.  Only the
Packit 1c5982
filename is given; this will also be used as the timezone name.
Packit 1c5982
Packit 1c5982
=cut
Packit 1c5982
Packit 1c5982
sub _saferead($$) {
Packit 1c5982
	my($fh, $len) = @_;
Packit 1c5982
	my $data;
Packit 1c5982
	my $rlen = $fh->read($data, $len);
Packit 1c5982
	croak "can't read tzfile: $!" unless defined($rlen);
Packit 1c5982
	croak "bad tzfile: premature EOF" unless $rlen == $len;
Packit 1c5982
	return $data;
Packit 1c5982
}
Packit 1c5982
Packit 1c5982
sub _read_u32($) { unpack("N", _saferead($_[0], 4)) }
Packit 1c5982
Packit 1c5982
sub _read_s32($) {
Packit 1c5982
	my $uval = _read_u32($_[0]);
Packit 1c5982
	return ($uval & 0x80000000) ? ($uval & 0x7fffffff) - 0x80000000 :
Packit 1c5982
				      $uval;
Packit 1c5982
}
Packit 1c5982
Packit 1c5982
sub _read_u8($) { ord(_saferead($_[0], 1)) }
Packit 1c5982
Packit 1c5982
my $unix_epoch_rdn = 719163;
Packit 1c5982
Packit 1c5982
sub _read_tm32($) {
Packit 1c5982
	my $t = _read_s32($_[0]);
Packit 1c5982
	return [ $unix_epoch_rdn + _fdiv($t, 86400), _fmod($t, 86400) ];
Packit 1c5982
}
Packit 1c5982
Packit 1c5982
sub _read_tm64($) {
Packit 1c5982
	my($fh) = @_;
Packit 1c5982
	my $th = _read_s32($fh);
Packit 1c5982
	my $tl = _read_u32($fh);
Packit 1c5982
	my $dh = _fdiv($th, 86400);
Packit 1c5982
	$th = (_fmod($th, 86400) << 10) | ($tl >> 22);
Packit 1c5982
	my $d2 = _fdiv($th, 86400);
Packit 1c5982
	$th = (_fmod($th, 86400) << 10) | (($tl >> 12) & 0x3ff);
Packit 1c5982
	my $d3 = _fdiv($th, 86400);
Packit 1c5982
	$th = (_fmod($th, 86400) << 12) | ($tl & 0xfff);
Packit 1c5982
	my $d4 = _fdiv($th, 86400);
Packit 1c5982
	$th = _fmod($th, 86400);
Packit 1c5982
	my $d = $dh * 4294967296 + $d2 * 4194304 + (($d3 << 12) + $d4);
Packit 1c5982
	return [ $unix_epoch_rdn + $d, $th ];
Packit 1c5982
}
Packit 1c5982
Packit 1c5982
my $factory_abbr = "Local time zone must be set--see zic manual page";
Packit 1c5982
Packit 1c5982
sub new {
Packit 1c5982
	my $class = shift;
Packit 1c5982
	unshift @_, "filename" if @_ == 1;
Packit 1c5982
	my $self = bless({}, $class);
Packit 1c5982
	my($filename, $fh);
Packit 1c5982
	while(@_) {
Packit 1c5982
		my $attr = shift;
Packit 1c5982
		my $value = shift;
Packit 1c5982
		if($attr eq "name") {
Packit 1c5982
			croak "timezone name specified redundantly"
Packit 1c5982
				if exists $self->{name};
Packit 1c5982
			croak "timezone name must be a string"
Packit 1c5982
				unless is_string($value);
Packit 1c5982
			$self->{name} = $value;
Packit 1c5982
		} elsif($attr eq "category") {
Packit 1c5982
			croak "category value specified redundantly"
Packit 1c5982
				if exists $self->{category};
Packit 1c5982
			croak "category value must be a string or undef"
Packit 1c5982
				unless is_undef($value) || is_string($value);
Packit 1c5982
			$self->{category} = $value;
Packit 1c5982
		} elsif($attr eq "is_olson") {
Packit 1c5982
			croak "is_olson flag specified redundantly"
Packit 1c5982
				if exists $self->{is_olson};
Packit 1c5982
			$self->{is_olson} = !!$value;
Packit 1c5982
		} elsif($attr eq "filename") {
Packit 1c5982
			croak "filename specified redundantly"
Packit 1c5982
				if defined($filename) || defined($fh);
Packit 1c5982
			croak "filename must be a string"
Packit 1c5982
				unless is_string($value);
Packit 1c5982
			$filename = $value;
Packit 1c5982
		} elsif($attr eq "filehandle") {
Packit 1c5982
			croak "filehandle specified redundantly"
Packit 1c5982
				if defined($filename) || defined($fh);
Packit 1c5982
			$fh = $value;
Packit 1c5982
		} else {
Packit 1c5982
			croak "unrecognised attribute `$attr'";
Packit 1c5982
		}
Packit 1c5982
	}
Packit 1c5982
	croak "file not specified" unless defined($filename) || defined($fh);
Packit 1c5982
	unless(exists $self->{name}) {
Packit 1c5982
		croak "timezone name not specified" unless defined $filename;
Packit 1c5982
		$self->{name} = $filename;
Packit 1c5982
	}
Packit 1c5982
	unless(exists $self->{category}) {
Packit 1c5982
		$self->{category} = undef;
Packit 1c5982
	}
Packit 1c5982
	unless(exists $self->{is_olson}) {
Packit 1c5982
		$self->{is_olson} = !!0;
Packit 1c5982
	}
Packit 1c5982
	if(defined $filename) {
Packit 1c5982
		($fh = IO::File->new($filename, "r")) && $fh->binmode
Packit 1c5982
			or croak "can't read $filename: $!";
Packit 1c5982
	}
Packit 1c5982
	croak "bad tzfile: wrong magic number"
Packit 1c5982
		unless _saferead($fh, 4) eq "TZif";
Packit 1c5982
	my $fmtversion = _saferead($fh, 1);
Packit 1c5982
	croak "bad tzfile: malformed version number"
Packit 1c5982
		unless $fmtversion =~ /\A[2-9\0]\z/;
Packit 1c5982
	_saferead($fh, 15);
Packit 1c5982
	my($ttisgmtcnt, $ttisstdcnt, $leapcnt, $timecnt, $typecnt, $charcnt) =
Packit 1c5982
		map { _read_u32($fh) } 1 .. 6;
Packit 1c5982
	croak "bad tzfile: no local time types" if $typecnt == 0;
Packit 1c5982
	my @trn_times = map { _read_tm32($fh) } 1 .. $timecnt;
Packit 1c5982
	my @obs_types = map { _read_u8($fh) } 1 .. $timecnt;
Packit 1c5982
	my @types = map {
Packit 1c5982
		[ _read_s32($fh), !!_read_u8($fh), _read_u8($fh) ]
Packit 1c5982
	} 1 .. $typecnt;
Packit 1c5982
	my $chars = _saferead($fh, $charcnt);
Packit 1c5982
	for(my $i = $leapcnt; $i--; ) { _saferead($fh, 8); }
Packit 1c5982
	for(my $i = $ttisstdcnt; $i--; ) { _saferead($fh, 1); }
Packit 1c5982
	for(my $i = $ttisgmtcnt; $i--; ) { _saferead($fh, 1); }
Packit 1c5982
	my $late_rule;
Packit 1c5982
	if($fmtversion ge "2") {
Packit 1c5982
		croak "bad tzfile: wrong magic number"
Packit 1c5982
			unless _saferead($fh, 4) eq "TZif";
Packit 1c5982
		_saferead($fh, 16);
Packit 1c5982
		($ttisgmtcnt, $ttisstdcnt, $leapcnt,
Packit 1c5982
		 $timecnt, $typecnt, $charcnt) =
Packit 1c5982
			map { _read_u32($fh) } 1 .. 6;
Packit 1c5982
		croak "bad tzfile: no local time types" if $typecnt == 0;
Packit 1c5982
		@trn_times = map { _read_tm64($fh) } 1 .. $timecnt;
Packit 1c5982
		@obs_types = map { _read_u8($fh) } 1 .. $timecnt;
Packit 1c5982
		@types = map {
Packit 1c5982
			[ _read_s32($fh), !!_read_u8($fh), _read_u8($fh) ]
Packit 1c5982
		} 1 .. $typecnt;
Packit 1c5982
		$chars = _saferead($fh, $charcnt);
Packit 1c5982
		for(my $i = $leapcnt; $i--; ) { _saferead($fh, 12); }
Packit 1c5982
		for(my $i = $ttisstdcnt; $i--; ) { _saferead($fh, 1); }
Packit 1c5982
		for(my $i = $ttisgmtcnt; $i--; ) { _saferead($fh, 1); }
Packit 1c5982
		croak "bad tzfile: missing newline"
Packit 1c5982
			unless _saferead($fh, 1) eq "\x0a";
Packit 1c5982
		$late_rule = "";
Packit 1c5982
		while(1) {
Packit 1c5982
			my $c = _saferead($fh, 1);
Packit 1c5982
			last if $c eq "\x0a";
Packit 1c5982
			$late_rule .= $c;
Packit 1c5982
		}
Packit 1c5982
	}
Packit 1c5982
	$fh = undef;
Packit 1c5982
	for(my $i = @trn_times - 1; $i-- > 0; ) {
Packit 1c5982
		unless(($trn_times[$i]->[0] <=> $trn_times[$i+1]->[0] ||
Packit 1c5982
			$trn_times[$i]->[1] <=> $trn_times[$i+1]->[1]) == -1) {
Packit 1c5982
			croak "bad tzfile: unsorted change times";
Packit 1c5982
		}
Packit 1c5982
	}
Packit 1c5982
	my $first_std_type_index;
Packit 1c5982
	my %offsets;
Packit 1c5982
	for(my $i = 0; $i != $typecnt; $i++) {
Packit 1c5982
		my $abbrind = $types[$i]->[2];
Packit 1c5982
		croak "bad tzfile: invalid abbreviation index"
Packit 1c5982
			if $abbrind > $charcnt;
Packit 1c5982
		pos($chars) = $abbrind;
Packit 1c5982
		$chars =~ /\G([^\0]*)/g;
Packit 1c5982
		$types[$i]->[2] = $1;
Packit 1c5982
		$first_std_type_index = $i
Packit 1c5982
			if !defined($first_std_type_index) && !$types[$i]->[1];
Packit 1c5982
		$self->{has_dst} = 1 if $types[$i]->[1];
Packit 1c5982
		if($types[$i]->[0] == 0 && !$types[$i]->[1] &&
Packit 1c5982
				$types[$i]->[2] eq "zzz") {
Packit 1c5982
			# "zzz" means the zone is not defined at this time,
Packit 1c5982
			# due for example to the location being uninhabited
Packit 1c5982
			$types[$i] = "zone disuse";
Packit 1c5982
		} else {
Packit 1c5982
			$offsets{$types[$i]->[0]} = undef;
Packit 1c5982
		}
Packit 1c5982
	}
Packit 1c5982
	unshift @obs_types,
Packit 1c5982
		defined($first_std_type_index) ? $first_std_type_index : 0;
Packit 1c5982
	foreach my $obs_type (@obs_types) {
Packit 1c5982
		croak "bad tzfile: invalid local time type index"
Packit 1c5982
			if $obs_type >= $typecnt;
Packit 1c5982
		$obs_type = $types[$obs_type];
Packit 1c5982
	}
Packit 1c5982
	if(defined($late_rule) && $late_rule eq "<$factory_abbr>0" &&
Packit 1c5982
			defined($obs_types[-1]) && $obs_types[-1]->[0] == 0 &&
Packit 1c5982
			!$obs_types[-1]->[1] &&
Packit 1c5982
			$obs_types[-1]->[2] eq $factory_abbr) {
Packit 1c5982
		# This bizarre timezone abbreviation is used in the Factory
Packit 1c5982
		# timezone in the Olson database.  It's not valid in a
Packit 1c5982
		# SysV-style TZ value, because it contains spaces, but zic
Packit 1c5982
		# puts it into one anyway because the file format demands
Packit 1c5982
		# it.  DT:TZ:SystemV would object, so as a special
Packit 1c5982
		# exception we ignore the TZ value in this case.
Packit 1c5982
		$late_rule = undef;
Packit 1c5982
	}
Packit 1c5982
	if(defined $late_rule) {
Packit 1c5982
		if($late_rule eq "") {
Packit 1c5982
			$obs_types[-1] = "missing data";
Packit 1c5982
		} elsif($late_rule =~
Packit 1c5982
				/\A(?:zzz|<zzz>)[-+]?00?(?::00(?::00)?)?\z/) {
Packit 1c5982
			$obs_types[-1] = "zone disuse";
Packit 1c5982
		} else {
Packit 1c5982
			require DateTime::TimeZone::SystemV;
Packit 1c5982
			DateTime::TimeZone::SystemV->VERSION("0.009");
Packit 1c5982
			$obs_types[-1] =
Packit 1c5982
				DateTime::TimeZone::SystemV->new(
Packit 1c5982
					system => $fmtversion ge "3" ?
Packit 1c5982
							"tzfile3" : "posix",
Packit 1c5982
					recipe => $late_rule);
Packit 1c5982
		}
Packit 1c5982
	}
Packit 1c5982
	$self->{trn_times} = \@trn_times;
Packit 1c5982
	$self->{obs_types} = \@obs_types;
Packit 1c5982
	$self->{offsets} = [ sort { $a <=> $b } keys %offsets ];
Packit 1c5982
	return $self;
Packit 1c5982
}
Packit 1c5982
Packit 1c5982
sub _present_rdn_sod($$) {
Packit 1c5982
	my($rdn, $sod) = @_;
Packit 1c5982
	return sprintf("%sT%02d:%02d:%02d",
Packit 1c5982
		present_ymd($rdn + $rdn_epoch_cjdn),
Packit 1c5982
		int($sod/3600), int($sod/60)%60, $sod%60);
Packit 1c5982
}
Packit 1c5982
Packit 1c5982
=back
Packit 1c5982
Packit 1c5982
=head1 METHODS
Packit 1c5982
Packit 1c5982
These methods are all part of the L<DateTime::TimeZone> interface.
Packit 1c5982
See that class for the general meaning of these methods; the documentation
Packit 1c5982
below only comments on the specific behaviour of this class.
Packit 1c5982
Packit 1c5982
=head2 Identification
Packit 1c5982
Packit 1c5982
=over
Packit 1c5982
Packit 1c5982
=item $tz->is_floating
Packit 1c5982
Packit 1c5982
Returns false.
Packit 1c5982
Packit 1c5982
=cut
Packit 1c5982
Packit 1c5982
sub is_floating { 0 }
Packit 1c5982
Packit 1c5982
=item $tz->is_utc
Packit 1c5982
Packit 1c5982
Returns false.
Packit 1c5982
Packit 1c5982
=cut
Packit 1c5982
Packit 1c5982
sub is_utc { 0 }
Packit 1c5982
Packit 1c5982
=item $tz->is_olson
Packit 1c5982
Packit 1c5982
Returns the truth value that was provided to the constructor for this
Packit 1c5982
purpose, default false.  This nominally indicates whether the timezone
Packit 1c5982
data is from the Olson database.  The files interpreted by this class
Packit 1c5982
are very likely to be from the Olson database, but there is no explicit
Packit 1c5982
indicator for this in the file, so this information must be supplied to
Packit 1c5982
the constructor if required.
Packit 1c5982
Packit 1c5982
=cut
Packit 1c5982
Packit 1c5982
sub is_olson { $_[0]->{is_olson} }
Packit 1c5982
Packit 1c5982
=item $tz->category
Packit 1c5982
Packit 1c5982
Returns the value that was provided to the constructor for this purpose,
Packit 1c5982
default C<undef>.  This is intended to indicate the general region
Packit 1c5982
(continent or ocean) in which a geographical timezone is used, when
Packit 1c5982
the timezone is named according to the hierarchical scheme of the Olson
Packit 1c5982
timezone database.
Packit 1c5982
Packit 1c5982
=cut
Packit 1c5982
Packit 1c5982
sub category { $_[0]->{category} }
Packit 1c5982
Packit 1c5982
=item $tz->name
Packit 1c5982
Packit 1c5982
Returns the timezone name.  Usually this is the filename that was supplied
Packit 1c5982
to the constructor, but it can be overridden by the constructor's B<name>
Packit 1c5982
attribute.
Packit 1c5982
Packit 1c5982
=cut
Packit 1c5982
Packit 1c5982
sub name { $_[0]->{name} }
Packit 1c5982
Packit 1c5982
=back
Packit 1c5982
Packit 1c5982
=head2 Offsets
Packit 1c5982
Packit 1c5982
=over
Packit 1c5982
Packit 1c5982
=item $tz->has_dst_changes
Packit 1c5982
Packit 1c5982
Returns a truth value indicating whether any of the observances in the file
Packit 1c5982
are marked as DST.  These DST flags are potentially arbitrary, and don't
Packit 1c5982
affect any of the zone's behaviour.
Packit 1c5982
Packit 1c5982
=cut
Packit 1c5982
Packit 1c5982
sub has_dst_changes { $_[0]->{has_dst} }
Packit 1c5982
Packit 1c5982
#
Packit 1c5982
# observance lookup
Packit 1c5982
#
Packit 1c5982
Packit 1c5982
sub _type_for_rdn_sod {
Packit 1c5982
	my($self, $utc_rdn, $utc_sod) = @_;
Packit 1c5982
	my $lo = 0;
Packit 1c5982
	my $hi = @{$self->{trn_times}};
Packit 1c5982
	while($lo != $hi) {
Packit 1c5982
		my $try = do { use integer; ($lo + $hi) / 2 };
Packit 1c5982
		if(($utc_rdn <=> $self->{trn_times}->[$try]->[0] ||
Packit 1c5982
		    $utc_sod <=> $self->{trn_times}->[$try]->[1]) == -1) {
Packit 1c5982
			$hi = $try;
Packit 1c5982
		} else {
Packit 1c5982
			$lo = $try + 1;
Packit 1c5982
		}
Packit 1c5982
	}
Packit 1c5982
	return $self->{obs_types}->[$lo];
Packit 1c5982
}
Packit 1c5982
Packit 1c5982
sub _type_for_datetime {
Packit 1c5982
	my($self, $dt) = @_;
Packit 1c5982
	my($utc_rdn, $utc_sod) = $dt->utc_rd_values;
Packit 1c5982
	$utc_sod = 86399 if $utc_sod >= 86400;
Packit 1c5982
	my $type = $self->_type_for_rdn_sod($utc_rdn, $utc_sod);
Packit 1c5982
	if(is_string($type)) {
Packit 1c5982
		croak "time @{[_present_rdn_sod($utc_rdn, $utc_sod)]}Z ".
Packit 1c5982
			"is not represented ".
Packit 1c5982
			"in the @{[$self->{name}]} timezone ".
Packit 1c5982
			"due to $type";
Packit 1c5982
	}
Packit 1c5982
	return $type;
Packit 1c5982
}
Packit 1c5982
Packit 1c5982
=item $tz->offset_for_datetime(DT)
Packit 1c5982
Packit 1c5982
I
must be a L<DateTime>-compatible object (specifically, it must
Packit 1c5982
implement the C<utc_rd_values> method).  Returns the offset from UT that
Packit 1c5982
is in effect at the instant represented by I
, in seconds.
Packit 1c5982
Packit 1c5982
=cut
Packit 1c5982
Packit 1c5982
sub offset_for_datetime {
Packit 1c5982
	my($self, $dt) = @_;
Packit 1c5982
	my $type = $self->_type_for_datetime($dt);
Packit 1c5982
	return is_ref($type, "ARRAY") ? $type->[0] :
Packit 1c5982
		$type->offset_for_datetime($dt);
Packit 1c5982
}
Packit 1c5982
Packit 1c5982
=item $tz->is_dst_for_datetime(DT)
Packit 1c5982
Packit 1c5982
I
must be a L<DateTime>-compatible object (specifically, it must
Packit 1c5982
implement the C<utc_rd_values> method).  Returns a truth value indicating
Packit 1c5982
whether the timezone's observance at the instant represented by I
Packit 1c5982
is marked as DST.  This DST flag is potentially arbitrary, and doesn't
Packit 1c5982
affect anything else.
Packit 1c5982
Packit 1c5982
=cut
Packit 1c5982
Packit 1c5982
sub is_dst_for_datetime {
Packit 1c5982
	my($self, $dt) = @_;
Packit 1c5982
	my $type = $self->_type_for_datetime($dt);
Packit 1c5982
	return is_ref($type, "ARRAY") ? $type->[1] :
Packit 1c5982
		$type->is_dst_for_datetime($dt);
Packit 1c5982
}
Packit 1c5982
Packit 1c5982
=item $tz->short_name_for_datetime(DT)
Packit 1c5982
Packit 1c5982
I
must be a L<DateTime>-compatible object (specifically, it must
Packit 1c5982
implement the C<utc_rd_values> method).  Returns the abbreviation
Packit 1c5982
used to label the time scale at the instant represented by I
.
Packit 1c5982
This abbreviation is potentially arbitrary, and does not uniquely identify
Packit 1c5982
either the timezone or the offset.
Packit 1c5982
Packit 1c5982
=cut
Packit 1c5982
Packit 1c5982
sub short_name_for_datetime {
Packit 1c5982
	my($self, $dt) = @_;
Packit 1c5982
	my $type = $self->_type_for_datetime($dt);
Packit 1c5982
	return is_ref($type, "ARRAY") ? $type->[2] :
Packit 1c5982
		$type->short_name_for_datetime($dt);
Packit 1c5982
}
Packit 1c5982
Packit 1c5982
=item $tz->offset_for_local_datetime(DT)
Packit 1c5982
Packit 1c5982
I
must be a L<DateTime>-compatible object (specifically, it
Packit 1c5982
must implement the C<local_rd_values> method).  Takes the local
Packit 1c5982
time represented by I
(regardless of what absolute time it also
Packit 1c5982
represents), and interprets that as a local time in the timezone of the
Packit 1c5982
timezone object (not the timezone used in I
). Returns the offset
Packit 1c5982
from UT that is in effect at that local time, in seconds.
Packit 1c5982
Packit 1c5982
If the local time given is ambiguous due to a nearby offset change,
Packit 1c5982
the numerically lowest offset (usually the standard one) is returned
Packit 1c5982
with no warning of the situation.  (Equivalently: the latest possible
Packit 1c5982
absolute time is indicated.)  If the local time given does not exist
Packit 1c5982
due to a nearby offset change, the method C<die>s saying so.
Packit 1c5982
Packit 1c5982
=cut
Packit 1c5982
Packit 1c5982
sub _local_to_utc_rdn_sod($$$) {
Packit 1c5982
	my($rdn, $sod, $offset) = @_;
Packit 1c5982
	$sod -= $offset;
Packit 1c5982
	while($sod < 0) {
Packit 1c5982
		$rdn--;
Packit 1c5982
		$sod += 86400;
Packit 1c5982
	}
Packit 1c5982
	while($sod >= 86400) {
Packit 1c5982
		$rdn++;
Packit 1c5982
		$sod -= 86400;
Packit 1c5982
	}
Packit 1c5982
	return ($rdn, $sod);
Packit 1c5982
}
Packit 1c5982
Packit 1c5982
sub offset_for_local_datetime {
Packit 1c5982
	my($self, $dt) = @_;
Packit 1c5982
	my($lcl_rdn, $lcl_sod) = $dt->local_rd_values;
Packit 1c5982
	$lcl_sod = 86399 if $lcl_sod >= 86400;
Packit 1c5982
	my %seen_error;
Packit 1c5982
	foreach my $offset (@{$self->{offsets}}) {
Packit 1c5982
		my($utc_rdn, $utc_sod) =
Packit 1c5982
			_local_to_utc_rdn_sod($lcl_rdn, $lcl_sod, $offset);
Packit 1c5982
		my $ttype = $self->_type_for_rdn_sod($utc_rdn, $utc_sod);
Packit 1c5982
		if(is_string($ttype)) {
Packit 1c5982
			$seen_error{$ttype} = undef;
Packit 1c5982
			next;
Packit 1c5982
		}
Packit 1c5982
		my $local_offset = is_ref($ttype, "ARRAY") ? $ttype->[0] :
Packit 1c5982
			eval { local $SIG{__DIE__};
Packit 1c5982
				$ttype->offset_for_local_datetime($dt);
Packit 1c5982
			};
Packit 1c5982
		return $offset
Packit 1c5982
			if defined($local_offset) && $local_offset == $offset;
Packit 1c5982
	}
Packit 1c5982
	my $error;
Packit 1c5982
	foreach("zone disuse", "missing data") {
Packit 1c5982
		if(exists $seen_error{$_}) {
Packit 1c5982
			$error = $_;
Packit 1c5982
			last;
Packit 1c5982
		}
Packit 1c5982
	}
Packit 1c5982
	$error ||= "offset change";
Packit 1c5982
	croak "local time @{[_present_rdn_sod($lcl_rdn, $lcl_sod)]} ".
Packit 1c5982
		"does not exist in the @{[$self->{name}]} timezone ".
Packit 1c5982
		"due to $error";
Packit 1c5982
}
Packit 1c5982
Packit 1c5982
=back
Packit 1c5982
Packit 1c5982
=head1 SEE ALSO
Packit 1c5982
Packit 1c5982
L<DateTime>,
Packit 1c5982
L<DateTime::TimeZone>,
Packit 1c5982
L<DateTime::TimeZone::Olson>,
Packit 1c5982
L<Time::OlsonTZ::Data>,
Packit 1c5982
L<Time::OlsonTZ::Download>,
Packit 1c5982
L<tzfile(5)>
Packit 1c5982
Packit 1c5982
=head1 AUTHOR
Packit 1c5982
Packit 1c5982
Andrew Main (Zefram) <zefram@fysh.org>
Packit 1c5982
Packit 1c5982
=head1 COPYRIGHT
Packit 1c5982
Packit 1c5982
Copyright (C) 2007, 2009, 2010, 2011, 2012, 2013, 2017
Packit 1c5982
Andrew Main (Zefram) <zefram@fysh.org>
Packit 1c5982
Packit 1c5982
=head1 LICENSE
Packit 1c5982
Packit 1c5982
This module is free software; you can redistribute it and/or modify it
Packit 1c5982
under the same terms as Perl itself.
Packit 1c5982
Packit 1c5982
=cut
Packit 1c5982
Packit 1c5982
1;