NAME
Time::TZif - TZif timezone file parser with configurable local time resolution
SYNOPSIS
use Time::TZif;
my $tz = Time::TZif->new(
filename => '/usr/share/zoneinfo/US/Eastern',
gap_policy => 'later', # default policy for gaps
overlap_policy => 'earlier', # default policy for overlaps
);
# UTC to offset (always unambiguous)
my $offset = $tz->offset_for_utc($epoch);
# Local time to offset (uses constructor defaults)
my $offset = $tz->offset_for_local($epoch);
# Local time to offset (override per call)
my $offset = $tz->offset_for_local($epoch, gap_policy => 'reject');
# Full type info: ($offset, $is_dst, $abbreviation)
my ($off, $dst, $abbr) = $tz->type_info_for_utc($epoch);
my ($off, $dst, $abbr) = $tz->type_info_for_local($epoch, gap_policy => 'std');
DESCRIPTION
Note: This module is experimental and may change without prior notice.
Time::TZif parses TZif binary timezone files (RFC 8536) and provides methods for resolving UTC and local times to UTC offsets.
Supports TZif version 1 (32-bit timestamps), version 2, and version 3 (64-bit timestamps). When a v2/v3 file is detected and the platform supports 64-bit integers, the 64-bit data block is used automatically.
The key challenge in timezone handling is converting local times to UTC, because DST transitions create two problematic cases:
- Gaps (spring forward)
-
When clocks spring forward, a range of local times does not exist. For example, in US/Eastern on 2024-03-10, local times 02:00:00 through 02:59:59 do not exist because clocks jump from 02:00 EST to 03:00 EDT.
- Overlaps (fall back)
-
When clocks fall back, a range of local times occurs twice. For example, in US/Eastern on 2024-11-03, local times 01:00:00 through 01:59:59 occur once in EDT and again in EST.
offset_for_local handles both cases through configurable policies that can be set as defaults in the constructor or overridden per call.
CONSTRUCTOR
new
my $tz = Time::TZif->new(
filename => $filename,
gap_policy => 'reject', # optional, default: 'reject'
overlap_policy => 'reject', # optional, default: 'reject'
);
Creates a new Time::TZif object by parsing the specified TZif file.
filename(required)-
Path to a TZif binary timezone file (e.g.,
/usr/share/zoneinfo/US/Eastern). gap_policy(optional, default:'reject')-
Default policy for resolving non-existing local times (during spring-forward gaps). See "POLICIES". Can be overridden per call in
offset_for_localandtype_info_for_local. overlap_policy(optional, default:'reject')-
Default policy for resolving ambiguous local times (during fall-back overlaps). See "POLICIES". Can be overridden per call in
offset_for_localandtype_info_for_local.
Croaks if the file cannot be opened or is not a valid TZif file.
METHODS
offset_for_utc
my $offset = $tz->offset_for_utc($time);
Returns the UTC offset in seconds for the given UTC epoch time. This is always unambiguous.
offset_for_local
my $offset = $tz->offset_for_local($time);
my $offset = $tz->offset_for_local($time, %opts);
use Time::Str::Time qw[timegm_modern];
# Treat wall-clock 2024-03-10 02:30:00 as a UTC epoch
my $local = timegm_modern(0, 30, 2, 10, 3, 2024);
my $offset = $tz->offset_for_local($local);
# The actual UTC epoch is:
my $utc = $local - $offset;
Returns the UTC offset in seconds for the given local epoch time (i.e., the epoch value obtained by treating the wall-clock time as if it were UTC).
Options
gap_policy(default: constructor value)-
Policy for non-existing local times. See "POLICIES".
overlap_policy(default: constructor value)-
Policy for ambiguous local times. See "POLICIES".
type_info_for_utc
my ($offset, $is_dst, $abbreviation) = $tz->type_info_for_utc($time);
Returns the full type information for the given UTC epoch time:
$offset- UTC offset in seconds$is_dst- 1 if daylight saving time, 0 otherwise$abbreviation- timezone abbreviation (e.g., "EST", "EDT")
type_info_for_local
my ($offset, $is_dst, $abbreviation) = $tz->type_info_for_local($time, %opts);
Returns the full type information for the given local epoch time. Accepts the same gap_policy and overlap_policy options as offset_for_local.
filename
my $filename = $tz->filename;
Returns the filename used to construct the object.
gap_policy
my $policy = $tz->gap_policy;
Returns the default gap policy set in the constructor.
overlap_policy
my $policy = $tz->overlap_policy;
Returns the default overlap policy set in the constructor.
POLICIES
The following policies control how offset_for_local and type_info_for_local handle the two problematic local time cases that arise from DST transitions.
For gaps (spring forward), the local time does not exist - clocks skipped over it. For overlaps (fall back), the local time occurs twice - once before the transition and once after.
earlier-
Use the pre-transition offset. For gaps, this interprets the time as if the clock had not yet sprung forward. For overlaps, this selects the first occurrence (the earlier UTC instant).
later-
Use the post-transition offset. For gaps, this interprets the time as if the clock had already sprung forward. For overlaps, this selects the second occurrence (the later UTC instant).
std-
Use whichever offset corresponds to standard time (
is_dst = 0), regardless of which side of the transition it falls on. dst-
Use whichever offset corresponds to daylight saving time (
is_dst = 1), regardless of which side of the transition it falls on. reject-
Reject the time outright by throwing an exception.
COMPATIBILITY WITH OTHER MODULES
If you are migrating from or interoperating with other Perl timezone modules, the following constructor settings reproduce their local time resolution behaviour.
DateTime::TimeZone
DateTime::TimeZone->offset_for_local_datetime() throws an exception on gaps and selects the later (post-transition) offset for overlaps:
my $tz = Time::TZif->new(
filename => $filename,
gap_policy => 'reject',
overlap_policy => 'later',
);
Time::Local
Time::Local::timelocal() folds gap times back into the pre-transition period and likewise resolves overlaps to the earlier (pre-transition) offset:
my $tz = Time::TZif->new(
filename => $filename,
gap_policy => 'earlier',
overlap_policy => 'earlier',
);
Note: Time::Local assumes DST transitions of at least 60 minutes. Zones with smaller transitions (e.g., Australia/Lord_Howe, 30 minutes) may not resolve correctly.
POSIX
The behaviour of POSIX::mktime() with tm_isdst = -1 for non-existing (gap) and ambiguous (overlap) local times is unspecified by the C standard and varies across platforms.
DIAGNOSTICS
All error messages are thrown as exceptions via Carp::croak.
Unable to parse TZif
Unable to parse TZif: could not open '%s': '%s'-
The specified file could not be opened for reading.
Unable to parse TZif: could not read from filehandle: '%s'-
An I/O error occurred while reading the TZif file.
Unable to parse TZif: premature end of data (got: %d, expected: %d)-
The file ended before the expected number of bytes could be read, indicating a truncated or corrupt TZif file.
Unable to parse TZif: not a TZif file-
The file does not begin with the TZif magic number (
0x545A6966). Unable to parse TZif: invalid v2/v3 header-
The v2/v3 data block does not begin with a valid TZif header.
Unable to parse TZif: expected newline before POSIX TZ string-
The v2/v3 data block is not followed by the expected newline delimiter before the POSIX TZ string footer.
Unable to parse TZif: must have at least one type-
The TZif file declares zero type records, which is invalid per RFC 8536.
Unable to parse TZif: too many transitions times: %d (max: %d)-
The TZif file declares more transitions than the compiled limit of 2400. This guard prevents excessive memory allocation from malformed files.
Unable to parse TZif: too many type records: %d (max: %d)-
The TZif file declares more type records than the compiled limit of 256.
Unable to parse TZif: too many chars: %d (max: %d)-
The TZif file declares more abbreviation characters than the compiled limit of 256.
Unable to parse TZif: invalid type index: %d (max: %d)-
A transition references a type index that is out of range.
Unable to parse TZif: invalid UTC offset: %d-
A type record contains a UTC offset outside the valid range (
-86400 < offset < 86400). Unable to parse TZif: invalid DST flag: %d-
A type record contains a DST flag that is not 0 or 1.
Unable to parse TZif: invalid abbreviation index: %d-
A type record references an abbreviation index that is out of range.
Unable to resolve local time
Unable to resolve local time: non-existing time (gap)-
The given local time falls within a gap created by a spring-forward transition and the
gap_policypolicy is set to'reject'. Unable to resolve local time: ambiguous time (overlap)-
The given local time falls within an overlap created by a fall-back transition and the
overlap_policypolicy is set to'reject'.
Usage errors
Parameter 'filename' is required-
The
newconstructor was called without the requiredfilenameparameter. Invalid policy value for the parameter '%s'-
The value given for
policy_gaporpolicy_overlapis not one of the recognised policies (earlier,later,std,dst,reject). See "POLICIES". Unrecognised named parameter: '%s'-
An unrecognised named parameter was given.
TZIF FORMAT
TZif is the binary format used by the IANA Time Zone Database (often called the Olson database). These files are typically found in /usr/share/zoneinfo/ on Unix-like systems.
This module supports:
TZif version 1 (32-bit transition timestamps, epoch range limited to 1901-2038)
TZif version 2 and 3 (64-bit transition timestamps)
Version 2/3 files contain both a v1 data block (for backward compatibility) and a v2/v3 data block with 64-bit timestamps. This module uses the v2/v3 data block when available and when the platform supports 64-bit integers.
SEE ALSO
RFC 8536 - The Time Zone Information Format (TZif)
AUTHOR
Christian Hansen
COPYRIGHT AND LICENSE
Copyright (C) 2026 by Christian Hansen
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.