NAME

Time::TZif::POSIX - POSIX TZ string parser with configurable local time resolution

SYNOPSIS

use Time::TZif::POSIX;

my $tz = Time::TZif::POSIX->new(
  tz_string      => 'EST5EDT,M3.2.0,M11.1.0',
  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::POSIX parses POSIX TZ strings as defined in IEEE Std 1003.1 (POSIX.1) and RFC 8536 §3.3.1, and provides the same API as Time::TZif for resolving UTC and local times to UTC offsets.

A POSIX TZ string encodes timezone rules algorithmically, without storing individual transition timestamps. In TZif v2/v3 files, a POSIX TZ string appears as a footer and describes the rules that apply after the last stored transition. This module implements those rules as a standalone parser.

POSIX TZ String Format

The general form is:

std offset [dst [offset] , start [/time] , end [/time]]
std, dst

Timezone designation (abbreviation). Unquoted names consist of three or more ASCII letters (e.g., EST). Quoted names are enclosed in angle brackets and may include letters, digits, +, and - (e.g., <+05>). A minimum of three characters is required inside the brackets.

offset

UTC offset in the form [+-]hh[:mm[:ss]]. Hours <= 24, minutes <= 59, seconds <= 59, total <= ±86400. Note the POSIX sign convention: a positive offset indicates a zone west of Greenwich (the sign is inverted internally to yield the conventional UTC offset).

If the DST offset is omitted, it defaults to one hour ahead of standard time.

start, end

Transition rules specifying when DST begins (start) and ends (end). Three rule forms are supported:

Mm.w.d

The d-th day of week (0=Sunday .. 6=Saturday) of the w-th week of month m (1-12). w=5 means the last occurrence of that day in the month.

Jn

Julian day n (1-365). February 29 is never counted, so day 60 is always March 1.

n

Zero-based day of year (0-365). February 29 is counted in leap years.

time

The wall-clock time at which the transition occurs, in the form [+-]hh[:mm[:ss]]. Defaults to 02:00:00 if omitted. Hours may range up to 167 (RFC 8536 §3.3.1 extension).

Examples

EST5EDT,M3.2.0,M11.1.0            US Eastern
CET-1CEST,M3.5.0/2,M10.5.0/3      Central European
<+05>-5                           Fixed UTC+5 (no DST)
NZST-12NZDT,M9.5.0,M4.1.0/3       New Zealand

CONSTRUCTOR

new

my $tz = Time::TZif::POSIX->new(
  tz_string      => $string,
  gap_policy     => 'reject',    # optional, default: 'reject'
  overlap_policy => 'reject',    # optional, default: 'reject'
);

Creates a new Time::TZif::POSIX object by parsing the given POSIX TZ string.

tz_string (required)

A POSIX TZ string (e.g., 'EST5EDT,M3.2.0,M11.1.0').

gap_policy (optional, default: 'reject')

Default policy for resolving non-existing local times (during spring-forward gaps). See "POLICIES" in Time::TZif. Can be overridden per call in offset_for_local and type_info_for_local.

overlap_policy (optional, default: 'reject')

Default policy for resolving ambiguous local times (during fall-back overlaps). See "POLICIES" in Time::TZif. Can be overridden per call in offset_for_local and type_info_for_local.

Croaks if the string cannot be parsed or contains out-of-range values.

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);

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" in Time::TZif.

overlap_policy (default: constructor value)

Policy for ambiguous local times. See "POLICIES" in Time::TZif.

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.

tz_string

my $string = $tz->tz_string;

Returns the POSIX TZ string 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.

CROSS-YEAR TRANSITIONS

When a transition rule combined with its time and offset causes the computed UTC epoch to fall in an adjacent year (e.g., a late December rule with a large positive time, or a January rule with a negative time), the module correctly handles this by computing transitions for the target year and both adjacent years, sorting them by UTC epoch, and walking the full list.

DIAGNOSTICS

All error messages are thrown as exceptions via Carp::croak.

Unable to parse POSIX TZ string

Unable to parse POSIX TZ string: '%s'

The string does not match the expected POSIX TZ format.

Unable to parse POSIX TZ string: invalid offset '%s'

An offset component could not be parsed.

Unable to parse POSIX TZ string: offset time is out of range: %s

The offset exceeds the allowed range (hours <= 24, minutes <= 59, seconds <= 59).

Unable to parse POSIX TZ string: standard offset out of range: %d

The computed standard offset exceeds ±86400 seconds.

Unable to parse POSIX TZ string: daylight offset out of range: %d

The computed daylight offset exceeds ±86400 seconds.

Unable to parse POSIX TZ string: invalid rule time '%s'

A transition rule time could not be parsed.

Unable to parse POSIX TZ string: rule time is out of range: %s

The rule time exceeds the allowed range (hours <= 167, minutes <= 59, seconds <= 59).

Unable to parse POSIX TZ string: invalid rule '%s'

A transition date rule could not be parsed.

Unable to parse POSIX TZ string: rule month out of range [1, 12]: %d

The month in an Mm.w.d rule is outside the valid range.

Unable to parse POSIX TZ string: Julian day out of range [1, 365]: %d

The day in a Jn rule is outside the valid range.

Unable to parse POSIX TZ string: zero-based day out of range [0, 365]: %d

The day in an n rule is outside the valid 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_policy 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_policy is set to 'reject'.

Usage errors

Usage: %s

A method was called with an incorrect number of arguments.

Parameter 'tz_string' is required

The new constructor was called without the required tz_string parameter.

Invalid policy value for the parameter '%s'

The value given for gap_policy or overlap_policy is not one of the recognised policies (earlier, later, std, dst, reject). See "POLICIES" in Time::TZif.

Unrecognised named parameter: '%s'

An unrecognised named parameter was given.

SEE ALSO

Time::TZif - TZif binary timezone file parser

IEEE Std 1003.1 - POSIX.1 environment variables (TZ specification)

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.