NAME
DateTime::Lite::TimeZone - Lightweight timezone support for DateTime::Lite
SYNOPSIS
use DateTime::Lite::TimeZone;
my $tz = DateTime::Lite::TimeZone->new( name => 'Asia/Tokyo' ) ||
die( DateTime::Lite::TimeZone->error );
my $dt = DateTime::Lite->now( time_zone => $tz );
# Alias
my $tz2 = DateTime::Lite::TimeZone->new( name => 'US/Eastern' );
# Fixed offset
my $tz3 = DateTime::Lite::TimeZone->new( name => '+09:00' );
# Special zones
my $utc = DateTime::Lite::TimeZone->new( name => 'UTC' );
my $flt = DateTime::Lite::TimeZone->new( name => 'floating' );
# Single-argument shorthand
my $tz4 = DateTime::Lite::TimeZone->new( 'Europe/Paris' );
# Using latitude and longitude
my $tz = DateTime::Lite::TimeZone->new(
latitude => 35.658558,
longitude => 139.745504,
) || die( "Could not find a timezone: ", DateTime::Lite::TimeZone->error );
# You can also use 'lat' and 'lon'
my $tz = DateTime::Lite::TimeZone->new(
lat => 35.658558,
lon => 139.745504,
) || die( "Could not find a timezone: ", DateTime::Lite::TimeZone->error );
say $tz->name; # Asia/Tokyo
# Memory cache (three-layer: object + span + POSIX footer)
# Enable once at application start-up for best performance:
DateTime::Lite::TimeZone->enable_mem_cache;
# Or per-call:
my $tz5 = DateTime::Lite::TimeZone->new(
name => 'America/New_York',
use_cache_mem => 1,
);
DateTime::Lite::TimeZone->disable_mem_cache; # disables and clears
DateTime::Lite::TimeZone->clear_mem_cache; # clears without disabling
# Offset and DST queries
use DateTime::Lite;
my $dt = DateTime::Lite->now( time_zone => $tz );
my $offset_secs = $tz->offset_for_datetime( $dt ); # e.g. -18000
my $local_off = $tz->offset_for_local_datetime( $dt ); # from wall-clock time
my $is_dst = $tz->is_dst_for_datetime( $dt ); # 1 or 0
my $abbr = $tz->short_name_for_datetime( $dt ); # e.g. "EDT"
printf "%s", $tz->offset_as_string( $offset_secs ); # "-0500"
printf "%s", $tz->offset_as_string( $offset_secs, ':' ); # "-05:00"
# Parse an offset string to seconds:
my $secs = DateTime::Lite::TimeZone->offset_as_seconds( '-05:00' ); # -18000
# Zone metadata
$tz->name; # canonical name, e.g. "America/New_York"
$tz->is_olson; # 1 if an IANA named zone
$tz->is_utc; # 1 if UTC
$tz->is_floating; # 1 if floating
$tz->has_dst; # 1 if zone ever observes DST
$tz->country_codes; # arrayref of ISO 3166-1 alpha-2 codes, e.g. ['US']
$tz->countries; # arrayref of hashrefs with full country data
$tz->coordinates; # e.g. "+404251-0740023"
$tz->comment; # free-text annotation from IANA data
$tz->latitude;
$tz->longitude;
$tz->tz_version; # IANA release string, e.g. "2026a"
$tz->tzif_version; # TZif binary format version (1, 2, 3, or 4)
$tz->footer_tz_string; # POSIX TZ string for recurring DST rules
$tz->transition_count;
$tz->type_count;
$tz->leap_count;
# Zone discovery
my $all = DateTime::Lite::TimeZone->all_names; # array reference
my @all = DateTime::Lite::TimeZone->all_names;
my $cats = DateTime::Lite::TimeZone->categories; # array reference
my @cats = DateTime::Lite::TimeZone->categories;
my $in_cat = DateTime::Lite::TimeZone->names_in_category('America'); # array reference
my @in_cat = DateTime::Lite::TimeZone->names_in_category('America');
my $in_cc = DateTime::Lite::TimeZone->names_in_country('JP'); # array reference
my @in_cc = DateTime::Lite::TimeZone->names_in_country('JP');
my $is_valid = DateTime::Lite::TimeZone->is_valid_name('Asia/Tokyo'); # 1
my $aliases = DateTime::Lite::TimeZone->aliases; # hashref alias => canonical
my %aliases = DateTime::Lite::TimeZone->aliases; # hash alias => canonical
my $links = $tz->links; # arrayref of alias names
# Resolve a timezone abbreviation against the IANA types table
my $results = DateTime::Lite::TimeZone->resolve_abbreviation( 'JST' );
# $results = [
# {
# ambiguous => 0,
# extended => 0,
# first_trans_time => -2587712400,
# is_active => 1,
# is_dst => 0,
# last_trans_time => -577962000,
# utc_offset => 32400,
# zone_name => "Asia/Tokyo",
# },
# {
# ambiguous => 0,
# extended => 0,
# first_trans_time => -1830414600,
# is_active => 0,
# is_dst => 0,
# last_trans_time => -1830414600,
# utc_offset => 32400,
# zone_name => "Asia/Pyongyang",
# },
# # etc...
# ]
# Narrow by co-parsed numeric offset
my $pst = DateTime::Lite::TimeZone->resolve_abbreviation( 'PST',
utc_offset => -28800
);
# Period filter: zones that still used JST after 1950
my $modern = DateTime::Lite::TimeZone->resolve_abbreviation( 'JST',
period => '>1950-01-01'
);
# Period filter with two ISO date bounds
my $wartime = DateTime::Lite::TimeZone->resolve_abbreviation( 'JST',
period => ['>1941-01-01', '<1946-01-01']
);
# Period filter with a raw epoch integer (post-1970 value, safe on all platforms)
my $epoch_2010 = 1262304000; # 2010-01-01 00:00:00 UTC
my $recent = DateTime::Lite::TimeZone->resolve_abbreviation( 'EST',
period => ">$epoch_2010"
);
# Period filter: only zones currently on this abbreviation
my $current = DateTime::Lite::TimeZone->resolve_abbreviation( 'JST',
period => 'current'
);
# Extended mode: fall back to extended_aliases if not in IANA types
# (covers real-world abbreviations such as AFT, AMST, CEST, HAEC, ...)
my $aft = DateTime::Lite::TimeZone->resolve_abbreviation( 'AFT',
extended => 1
);
# $aft = [
# {
# ambiguous => 0,
# extended => 1,
# is_dst => undef,
# is_primary => 1,
# utc_offset => undef,
# zone_name => "Asia/Kabul",
# },
# ]
# Database access (low-level)
my $path = DateTime::Lite::TimeZone->datafile; # path to tz.sqlite3
# Raw SQLite queries via public view methods (return DBI statement handles):
# $tz->zones, $tz->spans, $tz->transition, $tz->types,
# $tz->aliases, $tz->countries, $tz->leap_second, $tz->metadata
# Error handling
my $bad = DateTime::Lite::TimeZone->new( name => 'Mars/Olympus' );
if( !defined( $bad ) )
{
warn DateTime::Lite::TimeZone->error; # "Unknown time zone 'Mars/Olympus'"
}
$tz->fatal(1); # make errors die instead of warn+return undef
# Object context is detected even in errors, but allows the chain to unfold until the end to avoid the typical: "Can't call method "%s" on an undefined value"
# See https://perldoc.perl.org/perldiag#Can't-call-method-%22%25s%22-on-an-undefined-value
my $bad = DateTime::Lite::TimeZone->new( name => 'Mars/Olympus' )->name;
VERSION
v0.5.5
DESCRIPTION
DateTime::Lite::TimeZone is a drop-in replacement for DateTime::TimeZone designed to eliminate its heavy dependency and memory footprint.
DateTime::TimeZone loads 85 modules at startup, including the entire Specio, Params::ValidationCompiler, and Exception::Class stacks, simply to validate constructor arguments. DateTime::Lite::TimeZone replaces all of that with a single DBD::SQLite query against a compact bundled database (tz.sqlite3).
You may also be interested in the Unicode CLDR (Common Locale Data Repository) with the module Locale::Unicode::Data, which provides richer timezone information, such as metazones, regions, and historical timezone data.
For example:
my $cldr = Locale::Unicode::Data->new;
my $ref = $cldr->timezone( timezone => 'Asia/Tokyo' );
This would return an hash reference with the following information:
{
timezone_id => 281,
timezone => 'Asia/Tokyo',
territory => 'JP',
region => 'Asia',
tzid => 'japa',
metazone => 'Japan',
tz_bcpid => 'jptyo',
is_golden => 1,
is_primary => 0,
is_preferred => 0,
is_canonical => 0,
}
You can also returns all the timezones for a country code:
my $array_ref = $cldr->timezones( territory => 'US' );
Would return 55 results, such as:
{
alias => [qw( America/Atka US/Aleutian )],
is_canonical => 1,
is_golden => 1,
is_preferred => 0,
is_primary => 0,
metazone => "Hawaii_Aleutian",
region => "America",
territory => "US",
timezone => "America/Adak",
timezone_id => 55,
tz_bcpid => "usadk",
tzid => "haal",
}
You can also get the localised city name for a time zone:
my $ref = $cldr->timezone_city(
locale => 'de',
timezone => 'Asia/Tokyo',
);
which would return:
{
tz_city_id => 7486,
locale => 'de',
timezone => 'Asia/Tokyo',
city => 'Tokio',
alt => undef,
}
And if you want to access historical information:
my $ref = $cldr->timezone_info(
timezone => 'Europe/Simferopol',
start => '1994-04-30T21:00:00',
);
which would return:
{
tzinfo_id => 594,
timezone => 'Europe/Simferopol',
metazone => 'Moscow',
start => '1994-04-30T21:00:00',
until => '1997-03-30T01:00:00',
}
or, maybe:
my $ref = $cldr->timezone_info(
timezone => 'Europe/Simferopol',
start => ['>1992-01-01', '<1995-01-01'],
);
This is handy if you do not know the exact date, and want to provide a range instead.
Database schema
The bundled tz.sqlite3 uses the following main tables:
aliases-
Alias-to-zone_id FK mappings (such as
US/EasterntoAmerica/New_York) metadata-
Key/value pairs including the tzdata version
spans-
Pre-computed time spans derived from transitions and types, indexed for fast range lookup
types-
Local time type records from the TZif files
zones-
Canonical IANA zone names with country codes and coordinates
Fallback mode
If DBD::SQLite is not available, or the bundled tz.sqlite3 cannot be found, DateTime::Lite::TimeZone falls back transparently to DateTime::TimeZone and emits a one-time warning, if warning is permitted.
If DateTime::TimeZone is not available, then it dies.
CONSTRUCTOR
new
my $zone = DateTime::Lite::TimeZone->new( 'Asia/Tokyo' );
my $zone = DateTime::Lite::TimeZone->new(
name => 'Asia/Tokyo',
fatal => 1, # Makes all error fatal
);
# Using latitude and longitude
my $tz = DateTime::Lite::TimeZone->new(
latitude => 35.658558,
longitude => 139.745504,
) || die( "Could not find a timezone: ", DateTime::Lite::TimeZone->error );
# You can also use 'lat' and 'lon'
my $tz = DateTime::Lite::TimeZone->new(
lat => 35.658558,
lon => 139.745504,
) || die( "Could not find a timezone: ", DateTime::Lite::TimeZone->error );
say $tz->name; # Asia/Tokyo
A new DateTime::Lite::TimeZone object can be instantiated by either passing the timezone as a single argument, or as an hash, such as name => 'Asia/Tokyo'
Recognised forms:
- Named IANA timezones such as
America/New_York,Europe/Paris. - Aliases such as
US/Eastern,Japan. - Fixed-offset strings such as
+09:00,-0500. - The special names
UTC,floating, andlocal. -
The
localname instructsDateTime::Lite::TimeZoneto determine the system's local timezone automatically, without requiring any external modules. The detection strategy is OS-specific, relying on $^O:- Linux, macOS (darwin), FreeBSD, OpenBSD, NetBSD, Solaris, AIX, HP-UX, OS/2, Cygwin
-
Tries, in order:
$ENV{TZ}the
/etc/localtimesymlink target or a binary match against/usr/share/zoneinfo/etc/timezone(Debian/Ubuntu)/etc/TIMEZONEwith aTZ=line (Solaris, HP-UX)/etc/sysconfig/clockwith aZONE=orTIMEZONE=line (RedHat/CentOS)/etc/default/initwith aTZ=line (older Unix)
- Windows (MSWin32, NetWare)
-
Tries
$ENV{TZ}first, then reads the timezone name from the Windows Registry (SYSTEM/CurrentControlSet/Control/TimeZoneInformation) and maps it to an IANA name using the CLDRwindowsZones.xmltable. RequiresWin32::TieRegistry(available on CPAN; not a hard dependency). - Android
-
Tries
$ENV{TZ}, thengetprop persist.sys.timezone, then falls back toUTC. - VMS
-
Checks the environment variables
TZ,SYS$TIMEZONE_RULE,SYS$TIMEZONE_NAME,UCX$TZ, andTCPIP$TZ. - Symbian, EPOC, MS-DOS, Mac OS 9 and earlier
-
Checks
$ENV{TZ}only.
If the local timezone cannot be determined, an error is set and
undefis returned in scalar context, or an empty list in list context. In chaining (object context), it returns a dummy object (DateTime::Lite::Null) to avoid the typicalCan't call method '%s' on an undefined value. - Coordinates via
latitudeandlongitudearguments. -
As an alternative to a
name, you can pass decimal-degree coordinates to haveDateTime::Lite::TimeZoneresolve the nearest IANA timezone automatically:my $tz = DateTime::Lite::TimeZone->new( latitude => 35.658558, longitude => 139.745504, ); say $tz->name; # Asia/TokyoThe resolution uses the reference coordinates stored in the IANA
zone1970.tabfile (one representative point per canonical zone) and finds the nearest zone by the haversine great-circle distance. This is an approximation: it is accurate for most locations, but may give incorrect results near timezone boundaries, in disputed territories, or for enclaves such as Kaliningrad. If you need boundary-precise resolution, consider Geo::Location::TimeZoneFinder instead.latitudemust be in the range-90to90;longitudein-180to180. An error object is set andundefis returned in scalar context, or an empty list in list context, if the values are out of range or if no zone with coordinates is found in the database.The haversine formula is computed in SQLite when the database was compiled with
-DSQLITE_ENABLE_MATH_FUNCTIONS(SQLite version >= 3.35.0, released on March 2021).On older systems or builds where the math functions are absent, the required functions (
sqrt,sin,cos,asin) are registered automatically as Perl UDFs (User Defined Functions) via "sqlite_create_function" in DBD::SQLite on first use, so coordinate resolution works transparently on all supported SQLite versions.Detection is version-aware. Thus:
on SQLite with version >= 3.35.0, the special système table
pragma_function_listis queried forsqrtbefore any UDF is registered, to ensure a native function is used in priority.on SQLite with version < 3.35.0, where the math functions did not yet exist, UDFs are registered directly without querying
pragma_function_list.on SQLite version < 3.16.0,
pragma_function_listis not available as a table-valued function, so UDFs are registered directly.
UDFs are available on all SQLite version >= 3.0.0.
On older systems that ships SQLite 3.31.1, the required functions (
sqrt,sin,cos,asin) are registered automatically as Perl UDFs (User Defined Functions) via "sqlite_create_function" in DBD::SQLite on first use, so coordinate resolution works transparently on all supported SQLite versions.
A boolean option use_cache_mem set to a true value activates the process-level memory cache for this call. When set, subsequent calls with the same zone name (or its alias) return the cached object without a database query. See "MEMORY CACHE" for details and for the class-level "enable_mem_cache" alternative.
# Each of these hits the cache after the first construction:
my $tz = DateTime::Lite::TimeZone->new(
name => 'America/New_York',
use_cache_mem => 1,
);
A boolean option extended set to a true value enables abbreviation resolution as a fallback when the name is not recognised as a valid IANA timezone name. This is useful when the caller receives a timezone abbreviation such as JST, CET, or EST from an external source and wishes to resolve it to a canonical IANA zone without calling "resolve_abbreviation" explicitly.
When extended is set and the name is unknown as an IANA timezone, new calls resolve_abbreviation with the extended option set to true internally and, if a single unambiguous candidate is found, recurses with the resolved canonical name. If the abbreviation is ambiguous or not found even in the extended aliases table, the standard Unknown time zone error is returned.
my $tz = DateTime::Lite::TimeZone->new( name => 'JST', extended => 1 );
say $tz->name; # Asia/Tokyo
Returns the new object on success. On error, sets the exception object with error() and returns undef in scalar context, or an empty list in list context. In method-chaining (object) context, returns a DateTime::Lite::NullObject to avoid the error Can't call method '%s' on an undefined value. At the end of the chain, undef or an empty list will still be returned though.
MEMORY CACHE
By default, each call to "new" constructs a fresh object with a SQLite query. For applications that construct DateTime::Lite::TimeZone objects repeatedly with the same zone name, a three-layer cache is available.
Layer 1 - Object cache: When enabled, the second and subsequent calls for the same zone name return the original object directly from a hash, bypassing the database entirely.
Layer 2 - Span cache: Each cached TimeZone object stores the last matched UTC and local time span. Calls to offset_for_datetime and offset_for_local_datetime skip the SQLite query when the timestamp falls within the cached span's [utc_start, utc_end) or [local_start, local_end) range.
Layer 3 - POSIX footer cache: For zones where current dates are governed by a recurring DST rule (POSIX TZ footer string), the result of the footer calculation is cached by calendar day. DST transitions happen twice a year; on all other days the cached result is returned without re-evaluating the rule.
Together these three layers reduce the per-call cost of DateTime::Lite->new( time_zone => 'America/New_York' ) from ~430 µs to ~25 µs, putting it on par with DateTime.
Cache entries are keyed by the name passed to "new", plus the canonical name (after alias resolution). Both US/Eastern and America/New_York therefore map to the same cached object.
Cached objects are immutable in normal use. All public accessors are read-only, so sharing an object across callers is safe.
enable_mem_cache
Class method. Activates the memory cache for all subsequent "new" calls.
DateTime::Lite::TimeZone->enable_mem_cache;
# Every new() call now hits the cache after the first construction:
my $tz = DateTime::Lite::TimeZone->new( name => 'America/New_York' );
my $tz2 = DateTime::Lite::TimeZone->new( name => 'America/New_York' );
# $tz and $tz2 are the same object
Equivalent to passing use_cache_mem => 1 on every "new" call, but more convenient when you want the cache active for the lifetime of the process. Returns the class name to allow chaining.
disable_mem_cache
Class method. Disables the memory cache and clears all cached entries. Subsequent "new" calls will construct fresh objects.
DateTime::Lite::TimeZone->disable_mem_cache;
Returns the class name.
clear_mem_cache
Class method. Empties the cache without disabling it. The next "new" call for any zone name will re-query the database and re-populate the cache.
Useful if the tz.sqlite3 database has been replaced at runtime (an unusual operation):
DateTime::Lite::TimeZone->clear_mem_cache;
Returns the class name.
METHODS
aliases
# Checking for errors too
my $aliases = DateTime::Lite::TimeZone->aliases ||
die( DateTime::Lite::TimeZone->error );
my( %aliases ) = DateTime::Lite::TimeZone->aliases ||
die( DateTime::Lite::TimeZone->error );
my $aliases = $zone->aliases ||
die( $zone->error );
my( %aliases ) = $zone->aliases ||
die( $zone->error );
This can be called as an instance method, or as a class function.
This returns a hash of all the zones aliases (the old, deprecated names) to their corresponding canonical names.
For example:
Japan -> Asia/Tokyo
In scalar context, it returns an hash reference, and in list context, it returns an hash.
If an error occurred, this sets an exception object, and returns undef in scalar context, and an empty list in list context. The exception object can then be retrieved with "error"
all_names
# Checking for errors too
my $names = DateTime::Lite::TimeZone->all_names ||
die( DateTime::Lite::TimeZone->error );
my( @names ) = DateTime::Lite::TimeZone->all_names ||
die( DateTime::Lite::TimeZone->error );
my $names = $zone->all_names ||
die( $zone->error );
my( @names ) = $zone->all_names ||
die( $zone->error );
This can be called as an instance method, or as a class function.
This returns a list of all the time zone names sorted alphabetically. This list does not include zone alias (a.k.a. "links").
In scalar context, it returns an array reference, and in list context, it returns an array.
If an error occurred, this sets an exception object, and returns undef in scalar context, and an empty list in list context. The exception object can then be retrieved with "error"
categories
# Checking for errors too
my $categories = DateTime::Lite::TimeZone->categories ||
die( DateTime::Lite::TimeZone->error );
my( @categories ) = DateTime::Lite::TimeZone->categories ||
die( DateTime::Lite::TimeZone->error );
my $categories = $zone->categories ||
die( $zone->error );
my( @categories ) = $zone->categories ||
die( $zone->error );
This can be called as an instance method, or as a class function.
This returns a list of all time zone categories. A category is the part, if any, that precedes the forward slash of a zone name. For example, in Asia/Tokyo, the category would be Asia. However, with the special zone Factory, there would not be any category.
In scalar context, it returns an array reference, and in list context, it returns an array.
If an error occurred, this sets an exception object, and returns undef in scalar context, and an empty list in list context. The exception object can then be retrieved with "error"
category
my $zone = DateTime::Lite::TimeZone->new( name => "Asia/Tokyo" );
say $zone->category; # Asia
my $zone = DateTime::Lite::TimeZone->new( name => "UTC" );
say $zone->category; # undef
Returns the part of the time zone name before the first slash, such as Asia in Asia/Tokyo
comment
Returns the optional zone comment from zone1970.tab, such as "Mountain Time - south Idaho and east Oregon".
Returns undef in scalar context, or an empty list in list context if no comment is recorded.
coordinates
Returns the compact coordinate string from zone1970.tab, such as +3518+13942 for Tokyo. Returns undef in scalar context, or an empty list in list context when there are no coordinates, such as UTC, floating, and fixed-offset zones.
country_codes
Returns an arrayref of ISO 3166-1 alpha-2 country codes associated with this timezone, such as ["JP"] or ["US","CA"].
Returns undef in scalar context, or an empty list in list context when the timezone has no countries associated, such as UTC, floating, and fixed-offset zones.
countries
# Checking for errors too
my $countries = DateTime::Lite::TimeZone->countries ||
die( DateTime::Lite::TimeZone->error );
my( @countries ) = DateTime::Lite::TimeZone->countries ||
die( DateTime::Lite::TimeZone->error );
my $countries = $zone->countries ||
die( $zone->error );
my( @countries ) = $zone->countries ||
die( $zone->error );
This can be called as an instance method, or as a class function.
This returns a list of all the ISO 3166 2-letters country codes sorted alphabetically, and in lower-case. Those codes can be used to call "names_in_country".
In scalar context, it returns an array reference, and in list context, it returns an array.
If an error occurred, this sets an exception object, and returns undef in scalar context, and an empty list in list context. The exception object can then be retrieved with "error"
If you want to convert a country to its locale name, you can use the Unicode CLDR database designed specifically for this.
For example, using the locale en:
use Locale::Unicode::Data;
my $cldr = Locale::Unicode::Data->new;
my $ref = $cldr->territory_l10n( locale => 'en', territory => 'JP', alt => undef );
# Returns an hash reference like this:
{
terr_l10n_id => 13385,
locale => 'en',
territory => 'JP',
locale_name => 'Japan',
alt => undef,
}
And, if you want to look up the ISO3166 code based on the locale country name, you could do something like this. Here we search for the country code matching アメリカ, which is America in Japanese:
use strict;
use warnings;
use utf8;
use open ':std' => ':utf8';
use Data::Pretty qw( dump );
use Locale::Unicode::Data;
my $cldr = Locale::Unicode::Data->new;
my $all = $cldr->territories_l10n( locale => 'ja' );
foreach my $ref ( @$all )
{
if( $ref->{locale_name} =~ /アメリカ/ &&
$ref->{territory} =~ /^[A-Z]{2}$/ ) # Because a territory, in Unicode CLDR, can also be a 3-digits code
{
say dump( $ref );
}
}
which would produce something like this:
{
alt => undef,
locale => "ja",
locale_name => "アメリカ合衆国",
terr_l10n_id => 26334,
territory => "US",
}
datafile
Returns the absolute path to the bundled tz.sqlite3 database file.
designation_charcount
Returns the total size of abbreviation string table (in bytes).
This is equivalent to TZif header field charcnt, including trailing NUL bytes.
error
my $ex = $zone->error;
Returns the last exception object, if any.
fatal
Sets or gets the fatal property for this object.
When enabled, any error will trigger a fatal exception and call "die" in perlfunc
footer_tz_string
Returns the footer portion of the timezone.
has_dst
This is an alias for "has_dst_changes"
has_dst_changes
Returns true if the timezone observes daylight saving time transitions.
is_canonical
my $zone = DateTime::Lite::TimeZone->new( 'Japan' );
say $zone->is_canonical; # false
my $zone = DateTime::Lite::TimeZone->new( 'Asia/Tokyo' );
say $zone->is_canonical; # true
Returns true if the timezone name provided is a canonical one, false otherwise.
is_dst_for_datetime( $dt )
Returns true if $dt falls within a DST period for this timezone.
is_floating
Returns true for the special floating timezone.
is_olson
Returns true for IANA/Olson-sourced timezones.
is_utc
Returns true for the UTC timezone and for fixed-offset +0000.
is_valid_name
say DateTime::Lite::TimeZone->is_valid_name( 'Singapore' ); # true
say $zone->is_valid_name( 'Singapore' ); # true
say DateTime::Lite::TimeZone->is_valid_name( 'Paris' ); # false
say $zone->is_valid_name( 'Paris' ); # false
say DateTime::Lite::TimeZone->is_valid_name( 'Asia/Seoul' ); # true
say $zone->is_valid_name( 'Asia/Seoul' ); # true
This takes a canonical timezone or a timezone alias, and returns true if the value provided is valid, or false otherwise.
This sets an exception object, an returns an error only if no value was provided, so you may want to check if the value returned is defined.
Contrary to DateTime::TimeZone, passin a DateTime::TimeZone::Alias does not make that zone valid. This class, adhere strictly to the IANA time zones.
isstd_count
Returns the number of standard time (a.k.a "standard/wall") indicators.
This "must either be zero or equal to "typecnt".
isut_count
Returns the number of UT/local time indicators.
This "must either be zero or equal to "typecnt".
latitude
Returns the latitude for this zone, as a real number, if any.
links
This is an alias for "aliases"
longitude
Returns the longitude for this zone, as a real number, if any.
name
my $zone = DateTime::Lite::TimeZone->new( name => 'Japan' );
say $zone->name; # Asia/Tokyo
Returns the canonical timezone name, such as Asia/Tokyo.
This means that if you provide an alias upon instantiation, it will be resolved, and accessible with this method.
names_in_category
# Checking for errors too
my $names = DateTime::Lite::TimeZone->names_in_category( 'Asia' ) ||
die( DateTime::Lite::TimeZone->error );
my( @names ) = DateTime::Lite::TimeZone->names_in_category( 'Asia' ) ||
die( DateTime::Lite::TimeZone->error );
my $names = $zone->names_in_category( 'America' ) ||
die( $zone->error );
my( @names ) = $zone->names_in_category( 'America' ) ||
die( $zone->error );
This takes a category, which under this class means the left-hand side of the zone name, separated by a forward slash. So, with the example of Asia/Seoul, the category would be Asia.
With this category provided, this returns a list of the name on the left-hand side of the first forward slash.
For example:
For Asia/Taipei, the category would be Asia, and the list would return among the 74 results, the name Taipei.
For the category America, there would be 121 results, and of which Indiana/Vincennes whose full timezone is America/Indiana/Vincennes, would also be returned.
In scalar context, it returns an array reference, and in list context, it returns an array.
If an error occurred, this sets an exception object, and returns undef in scalar context, and an empty list in list context. The exception object can then be retrieved with "error"
names_in_country
# Checking for errors too
my $names = DateTime::Lite::TimeZone->names_in_country( 'US' ) ||
die( DateTime::Lite::TimeZone->error );
my( @names ) = DateTime::Lite::TimeZone->names_in_country( 'US' ) ||
die( DateTime::Lite::TimeZone->error );
my $names = $zone->names_in_country( 'US' ) ||
die( $zone->error );
my( @names ) = $zone->names_in_country( 'US' ) ||
die( $zone->error );
This takes a 2-letter ISO3166 country code, and returns a list of all the time zones associated with it.
This is case insensitive, so a country code provided, such as US or us would be treated equally.
In scalar context, it returns an array reference, and in list context, it returns an array.
If an error occurred, this sets an exception object, and returns undef in scalar context, and an empty list in list context. The exception object can then be retrieved with "error"
The order of the time zones returned is the same ones as set by IANA database.
offset_as_seconds
This takes an offset as a string, such as +09:00, and this returns the number of seconds represented by that offset either as a signed integer.
If no value was provided, or if that value is not comprised in the range -99:59:59 to +99:59:59, or, if the offset string provided does not match any of the following 2 patterns, then this sets an error object, and returns undef in scalar context or an empty list in list context.
The supported offset patterns are (sign defaults to + if absent):
- Colon form:
[+-]H:MM,[+-]HH:MM,[+-]HH:MM:SS -
The regular expression is:
\A([+-])?(\d{1,2}):(\d{2})(?::(\d{2}))?\zExamples:
+09:00,-02:00,9:0:0 - Compact form:
[+-]HHMM,[+-]HHMMSS -
The regular expression is:
/\A([+-])?(\d{2})(\d{2})(\d{2})?\z/Examples:
+0900,-0200,0900,+090000,090000 - The special string
"0"(returns0).
offset_as_string
say DateTime::Lite::TimeZone->offset_as_string(32400); # +0900
say DateTime::Lite::TimeZone->offset_as_string(32400, ':' ); # +09:00
say $zone->offset_as_string(32400); # +0900
say $zone->offset_as_string(32400, ':'); # +09:00
Class or instance method. This converts a numeric UTC offset in seconds to a formatted string such as +0900 (default) or +09:00 (with : as separator).
Drop-in compatible with "offset_as_string" in DateTime::TimeZone.
offset_for_datetime
my $offset = $zone->offset_for_datetime( $dt );
This takes a DateTime::Lite object, and returns the UTC offset in seconds applicable to that object.
Upon error, then this sets an error object, and returns undef in scalar context or an empty list in list context.
offset_for_local_datetime
my $offset = $zone->offset_for_local_datetime( $dt );
This takes a DateTime::Lite object, and returns the UTC offset in seconds given a local (wall-clock) time.
Used internally during timezone conversion.
Upon error, then this sets an error object, and returns undef in scalar context or an empty list in list context.
resolve_abbreviation
# Unambiguous: JST maps to a single UTC offset
# Results sorted by is_active DESC, first_trans_time ASC, last_trans_time DESC, name ASC.
my $results = DateTime::Lite::TimeZone->resolve_abbreviation( 'JST' );
# $results = [
# {
# ambiguous => 0,
# extended => 0,
# first_trans_time => -2587712400,
# is_active => 1,
# is_dst => 0,
# last_trans_time => -577962000,
# utc_offset => 32400,
# zone_name => "Asia/Tokyo",
# },
# {
# ambiguous => 0,
# extended => 0,
# first_trans_time => -1830414600,
# is_active => 0,
# is_dst => 0,
# last_trans_time => -1830414600,
# utc_offset => 32400,
# zone_name => "Asia/Pyongyang",
# },
# # etc...
# ]
# Truly ambiguous: CST has different offsets in Asia and America
my $cst = DateTime::Lite::TimeZone->resolve_abbreviation( 'CST' );
# $cst->[0]{ambiguous} == 1
# Narrow by offset when already known (such as from a co-parsed %z token)
my $filtered = DateTime::Lite::TimeZone->resolve_abbreviation(
'PST', utc_offset => -28800
);
# Period filter: only zones that used JST after 1950
my $modern = DateTime::Lite::TimeZone->resolve_abbreviation(
'JST', period => '>1950-01-01'
);
# Period filter with two bounds: zones that used JST during WWII
my $wartime = DateTime::Lite::TimeZone->resolve_abbreviation(
'JST', period => ['>1941-01-01', '<1946-01-01']
);
# Period filter: only zones currently on this abbreviation
my $current = DateTime::Lite::TimeZone->resolve_abbreviation(
'JST', period => 'current'
);
# Extended mode: fall back to extended_aliases if not in IANA types
my $aft = DateTime::Lite::TimeZone->resolve_abbreviation(
'AFT', extended => 1
);
# $aft = [
# { zone_name => 'Asia/Kabul', utc_offset => undef, is_dst => undef,
# ambiguous => 0, is_primary => 1, extended => 1 },
# ]
# extended => 1 is a no-op for abbreviations already in IANA types (such as IST, CST):
# the IANA result is returned and the extended_aliases table is not consulted.
Class or instance method. Resolves a timezone abbreviation such as JST or EST against the IANA data in the bundled tz.sqlite3 database, returning all canonical zones that have ever used that abbreviation.
IANA results are sorted by a four-level key:
- 2.
first_trans_timeascending: among zones sharing the sameis_activevalue, the one that adopted the abbreviation first appears first. - 3.
last_trans_timedescending: among zones sharing the first two keys, the one that used it most recently appears first. - 4.
zone_nameascending: final deterministic tie-breaker.
In practice this means that for an abbreviation like CEST, the currently-active Central European zones come first (ordered by their date of adoption), followed by zones that have since migrated away from CEST (such as Europe/Kaliningrad, which abandoned CEST in 2014).
Extended alias results (when extended => 1 falls back to the extended_aliases table) use a different ordering: is_primary descending, then zone_name ascending. See "is_primary" below.
The single required argument is the abbreviation string. The following optional keyword arguments are accepted:
extended-
Boolean. When true and the abbreviation is not found in the IANA types table, the method falls back to querying the
extended_aliasestable. This covers real-world abbreviations (such asAFT,AMST, orHAEC) that appear in date strings but are not stored as TZif type abbreviations in the IANA database.When an extended result is returned,
utc_offsetandis_dstareundefsince the extended alias table maps abbreviations to zone names only. If you need the offset, instantiate aDateTime::Lite::TimeZoneobject from the returnedzone_name. period-
Restricts results to zones whose most recent matching transition (
MAX(trans_time)) falls within a given time window. Accepts either a single string or an array reference of strings for multiple conditions.Each value may be prefixed with a comparison operator:
>(default when no operator is given)-
Greater than. The most common operator: zones whose last use of the abbreviation is more recent than the given date.
>=-
Greater than or equal.
<-
Less than. Returns zones whose last use is older than the given date.
<=-
Less than or equal.
The operators
=and!=are accepted but map to SQLISandIS NOT. They have no practical use for timestamp comparisons and are not recommended.Value types: ISO date strings such as
1950-01-01are converted to Unix epoch via SQLitestrftime('%s', ...). Plain integers are treated as epoch seconds and passed asCAST(? AS INTEGER)to ensure correct numeric comparison regardless of how the Perl scalar is internally represented. For portability, use post-1970 epoch values when passing raw integers; pre-1970 negatives may behave unexpectedly on some platforms.The special value
currentreturns only zones whose most recent use of the abbreviation is in the past and whose next scheduled transition has not yet occurred, which means zones that are on this abbreviation right now.period => '>1950-01-01' # last used after 1950 (ISO date) period => ['>1941-01-01', '<1946-01-01'] # last used within WWII window period => '>1262304000' # last used after 2010-01-01 (epoch int) period => 'current' # currently active onlyPeriod filtering does not apply to extended alias results.
utc_offset-
Integer seconds east of UTC. Narrows the results to candidates with a matching offset, which is useful when the numeric offset has already been parsed from the same string (such as from a co-parsed
%ztoken). Only applies to the IANA types lookup; not used for the extended aliases fallback.
Returns an array reference of hashrefs on success, each with the following keys:
zone_name-
The canonical IANA zone name, such as
Asia/Tokyo. utc_offset-
The UTC offset in seconds east of UTC for this abbreviation in this zone.
undeffor extended alias results. is_dst-
1if this abbreviation represents a DST period,0otherwise.undeffor extended alias results. ambiguous-
For IANA results:
1if the abbreviation maps to multiple distinct UTC offsets (a genuine ambiguity such asISTorCST);0if all candidates share the same UTC offset.For extended alias results:
1if there are multiple candidates and none or more than one is markedis_primary;0if exactly one candidate hasis_primary = 1. extended-
1if this result came from theextended_aliasestable;0if it came from the IANA types table. is_active-
Only present in IANA results (
extended => 0).1if the zone's POSIXTZfooter string still references this abbreviation (meaning the zone continues to cycle through this abbreviation under its current daylight-saving rules, or uses it as its permanent abbreviation);0if the footer no longer mentions the abbreviation, which typically means the zone has transitioned away from it.For example,
Europe/Berlin's footer isCET-1CEST,M3.5.0,M10.5.0/3, so bothCETandCESTyieldis_active => 1. In contrast,Europe/Kaliningrad's footer isEET-2, soresolve_abbreviation('CEST')still lists Kaliningrad (it used CEST until 2014), but withis_active => 0.This field is used as the primary sort key, so zones with
is_active => 1appear before zones withis_active => 0. Callers that only want currently-active zones can filter withgrep { $_->{is_active} } @$results.The detection is a word-boundary regex against the footer string: alphabetic abbreviations must be adjacent to non-alphabetic characters (so
CSTdoes not matchCEST), and numeric/sign-prefixed abbreviations (such as-03,+0430) must appear inside<...>per POSIX TZ syntax. The regex was validated against all 1449 (zone, abbreviation) pairs in the bundled database with zero false negatives against an empirical ground truth; see the internalRESOLVE_ABBREVIATION_DESIGN_NOTES.mdif curious about the details.Absent from extended alias results, where the ordering signal is the editorial
is_primarymarker instead. is_primary-
Only present in extended alias results (
extended => 1). When multiple candidates match,1marks the editorially-chosen canonical zone for this abbreviation (such asAmerica/Sao_PauloforBRT), and0marks the others. Absent from IANA results, because no single zone is canonically designated among IANA candidates of an abbreviation; theis_activemarker plus the sort order (first-used first, still-active first) serve the analogous role for IANA results.If you need a canonical or preferred zone designation in the CLDR sense, use Locale::Unicode::Data which exposes CLDR's
is_golden,is_primary, andis_preferredflags on a per-timezone basis. See "USING Locale::Unicode::Data FOR CANONICAL DESIGNATION" below for an example. first_trans_time-
Unix epoch of the earliest transition in this zone using the abbreviation. Absent from extended alias results. Used as a sort key (ascending) after
is_active, so among zones with the sameis_activevalue, the one that adopted the abbreviation first appears first. last_trans_time-
Unix epoch of the most recent transition in this zone using the abbreviation. Absent from extended alias results. Used as a secondary sort key (descending): among zones with the same
is_activeand samefirst_trans_time, the one that has used the abbreviation most recently appears first.
If an error occurred, this sets an exception object, and returns undef in scalar context, and an empty list in list context. The exception object can then be retrieved with "error".
Note that many abbreviations such as EST or PST match multiple zone names that all share the same UTC offset. These are not genuinely ambiguous for the purpose of parsing a datetime string; the ambiguous flag will be 0 in those cases. Genuinely ambiguous abbreviations such as IST (Irish Summer Time, Indian Standard Time, or Israel Standard Time) will have ambiguous => 1.
short_name_for_datetime
say $zone->short_name_for_datetime( $dt );
This takes a DateTime::Lite object, and returns the abbreviated timezone name applicable, such as JST or EDT.
transition_count
Returns the number of transitions record for this timezone.
Equivalent to TZif header field timecnt
type_count
Returns the number of types for this timezone.
tz_version
Returns the IANA tzdata version string from the database metadata table, such as 2026a.
tzif_version
Returns the timezone version string from the timezone data.
The possible values are 1, 2, 3 or 4
USING Locale::Unicode::Data FOR CANONICAL DESIGNATION
When "resolve_abbreviation" returns several IANA candidates for an abbreviation (such as the 30 zones that match CEST), the result set contains no is_primary marker. DateTime::Lite::TimeZone deliberately does not try to pick a canonical zone among IANA candidates, because no single heuristic gives a satisfying answer across all abbreviations.
Locale::Unicode::Data exposes CLDR's own canonical-designation flags (is_golden, is_primary, is_preferred, is_canonical) on a per-timezone basis, and is the recommended source of truth when a single representative zone is needed. A typical pattern is to resolve the abbreviation here, then ask Locale::Unicode::Data which candidate is the CLDR golden zone:
use DateTime::Lite::TimeZone;
use Locale::Unicode::Data;
my $candidates = DateTime::Lite::TimeZone->resolve_abbreviation( 'CEST' );
my $cldr = Locale::Unicode::Data->new;
my $golden;
foreach my $c ( @$candidates )
{
my $info = $cldr->timezone( timezone => $c->{zone_name} ) || next;
if( $info->{is_golden} )
{
$golden = $c->{zone_name};
last;
}
}
# $golden is now 'Europe/Paris' (the CLDR golden zone for the
# 'Europe_Central' metazone)
Alternatively, if the abbreviation is known to map to a specific metazone, you can query the golden zone directly:
my $zones = $cldr->timezones( metazone => 'Europe_Central', is_golden => 1 );
my $golden = $zones->[0]->{timezone}; # 'Europe/Paris'
For abbreviations that are not in the IANA types table (such as BRT, HAEC, AFT, and many others), use "resolve_abbreviation" with the extended flag instead: the extended_aliases table carries its own editorial is_primary marker that identifies the preferred zone among the candidates.
ERROR HANDLING
Upon error, this class methods sets an exception object, and return undef in scalar context, and an empty list in list context. The exception is accessible via:
my $err = DateTime::Lite::TimeZone->error; # class method
my $err = $tz->error; # instance method
The exception stringifies to a human-readable message including the source file and line number.
If the instance option fatal has been enabled, then any error triggered will be fatal.
EPOCH CONVENTION
The tz.sqlite3 database stores span boundaries as Unix seconds (seconds since 1970-01-01T00:00:00 UTC), matching the raw values from the TZif binary files. DateTime::Lite uses Rata Die seconds (seconds since 0001-01-01T00:00:00).
The conversion constant is:
UNIX_TO_RD = 62_135_683_200
All lookup methods subtract UNIX_TO_RD from $dt->utc_rd_as_seconds before querying the database. NULL span boundaries represent ±infinity (before the first recorded transition, and after the last).
BUILDING THE DATABASE
The bundled tz.sqlite3 is generated by running:
perl scripts/build_tz_database.pl [--verbose, --debug 3]
This fetches the latest tzcode and tzdata release from IANA, verifies the GPG signature, compiles it with zic(1), and populates the database. Run this script once per tzdata release, then commit the updated lib/DateTime/Lite/tz.sqlite3.
SQL SCHEMA
The SQLite SQL schema is available in the file scripts/cldr-schema.sql
The data are populated into the SQLite database using the script located in scripts/build_tz_database.pl and the data accessible from https://ftp.iana.org/tz/releases
The SQL schema used to create the SQLite database is available in the scripts directory of this distribution in the file tz_schema.sql
The tables used are as follows, in alphabetical order:
aliases
aliasA string field, case insensitive.
zone_idAn integer field.
countries
codeA string field, case insensitive.
nameA string field, case insensitive.
extended_aliases
abbr_idAn integer field.
abbreviationA string field, case insensitive.
zone_idAn integer field.
is_primaryA boolean field.
Defaults to false
commentA string field.
leap_second
leap_sec_idAn integer field.
zone_idAn integer field.
leap_indexAn integer field.
occurrence_timeAn integer field.
correctionAn integer field.
is_expirationA boolean field.
Defaults to false
metadata
keyA string field.
valueA string field.
spans
span_idAn integer field.
zone_idAn integer field.
type_idAn integer field.
span_indexAn integer field.
utc_startAn integer field.
utc_endAn integer field.
local_startAn integer field.
local_endAn integer field.
offsetAn integer field.
is_dstA boolean field.
Defaults to false
short_nameA string field, case insensitive.
transition
trans_idAn integer field.
zone_idAn integer field.
trans_indexAn integer field.
trans_timeAn integer field.
type_idAn integer field.
types
type_idAn integer field.
zone_idAn integer field.
type_indexAn integer field.
utc_offsetAn integer field.
is_dstA boolean field.
abbreviationA string field, case insensitive.
designation_indexAn integer field.
is_standard_timeA boolean field.
is_ut_timeA boolean field.
is_placeholderA boolean field.
Defaults to false
zones
zone_idAn integer field.
nameA string field, case insensitive.
canonicalA boolean field.
Defaults to true
has_dstA boolean field.
Defaults to false
countriesA string array field.
coordinatesA string field.
latitudeA real field.
longitudeA real field.
commentA string field.
tzif_versionAn integer field.
footer_tz_stringA string field.
transition_countAn integer field.
type_countAn integer field.
leap_countAn integer field.
isstd_countAn integer field.
isut_countAn integer field.
designation_charcountAn integer field.
categoryA string field, case insensitive.
subregionA string field, case insensitive.
locationA string field, case insensitive.
SEE ALSO
DateTime::Lite, DateTime::TimeZone, Locale::Unicode::Data
RFC 9636 (The Time Zone Information Format (TZif)) https://www.rfc-editor.org/rfc/rfc9636
Locale::Unicode::Data for historical data of time zones, metazones, and BCP47 time zones data.
AUTHOR
Jacques Deguest <jack@deguest.jp>
COPYRIGHT & LICENSE
Copyright(c) 2026 DEGUEST Pte. Ltd.
All rights reserved
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.