NAME
Time::Str::Util - Binary search and time zone database utilities
SYNOPSIS
use Time::Str::Util qw( find_tzdb_directory
valid_tzdb_timezone valid_posix_timezone
binary_search lower_bound upper_bound range_bounds );
# Locate system zoneinfo directory
my $tzdir = find_tzdb_directory(); # /usr/share/zoneinfo or undef
# Validate timezone names
valid_tzdb_timezone('America/New_York'); # true
valid_posix_timezone('EST5EDT,M3.2.0,M11.1.0'); # true
my @sorted = (10, 20, 30, 40, 50);
# Membership test
my $found = binary_search(\@sorted, 30); # true
my $miss = binary_search(\@sorted, 25); # false
my $i = lower_bound(\@sorted, 25); # 2 (first element >= 25)
my $j = upper_bound(\@sorted, 30); # 3 (first element > 30)
# With optional bounds
my $k = lower_bound(\@sorted, 25, 1, 4); # search within [1, 4)
# Range query: indices of elements in [15, 35]
my ($lo, $hi) = range_bounds(\@sorted, 15, 35); # (1, 3)
DESCRIPTION
This module provides binary search functions for sorted integer arrays and utilities for locating the system's IANA Time Zone Database. All functions are exportable on request. Use :all to import everything.
FUNCTIONS
Binary Search
binary_search
my $bool = binary_search($arrayref, $value);
my $bool = binary_search($arrayref, $value, $lo, $hi);
Returns true if $value is present in the sorted array, false otherwise.
Optional $lo and $hi parameters restrict the search to the half-open range [$lo, $hi). Defaults to [0, length).
lower_bound
my $index = lower_bound($arrayref, $value);
my $index = lower_bound($arrayref, $value, $lo, $hi);
Returns the index of the first element in the sorted array that is greater than or equal to $value. If all elements are less than $value, returns the length of the array (one past the last index).
Optional $lo and $hi parameters restrict the search to the half-open range [$lo, $hi). Defaults to [0, length).
upper_bound
my $index = upper_bound($arrayref, $value);
my $index = upper_bound($arrayref, $value, $lo, $hi);
Returns the index of the first element in the sorted array that is strictly greater than $value. If all elements are less than or equal to $value, returns the length of the array.
Optional $lo and $hi parameters restrict the search to the half-open range [$lo, $hi). Defaults to [0, length).
range_bounds
my ($lo, $hi) = range_bounds($arrayref, $min_value, $max_value);
Returns a pair of indices ($lo, $hi) defining the half-open range [$lo, $hi) of elements within [$min_value, $max_value]. $lo is the index of the first element >= $min_value (via binary search), and $hi is the index of the first element > $max_value (via linear scan from $lo).
This is optimized for cases where few elements fall within the range, as the linear scan avoids a second binary search.
Croaks if $min_value > $max_value.
Time Zone Database
find_tzdb_directory
my $dir = find_tzdb_directory();
Returns the path to the system's IANA Time Zone Database directory (zoneinfo), or undef if no valid directory is found.
The search order is:
- 1.
$ENV{TZDIR}if set and the directory exists. - 3. macOS: /var/db/timezone/zoneinfo (symlink to the active version).
A candidate directory is accepted only if it exists and contains a UTC file.
find_local_timezone
my $tz = find_local_timezone();
my $tz = find_local_timezone($tzdb_directory);
Determines the system's local timezone and returns it as a timezone object: a Time::TZif object for a zoneinfo file, or a Time::TZif::POSIX object for a POSIX TZ rule. Returns undef if the local timezone cannot be determined.
The optional $tzdb_directory is the root of the zoneinfo database used to resolve zone names; it defaults to the result of find_tzdb_directory. If no directory is available (neither given nor found), only forms that do not require the database can be resolved.
If the TZ environment variable is set, it is interpreted as follows (an empty TZ selects UTC, per the BSD/GNU convention):
As a zoneinfo name relative to
$tzdb_directory(e.g.Europe/Stockholm). This is tried before the POSIX form, matching the C library, so a name that exists as a zoneinfo file (e.g.EST5EDT) resolves to that file rather than to a bare POSIX rule.As a POSIX
TZrule (e.g.CET-1CEST,M3.5.0/2,M10.5.0/3or the quoted form<+05>-5), yielding a Time::TZif::POSIX object.As a path containing
zoneinfo/(e.g.:/usr/share/zoneinfo/Europe/Stockholm); the portion afterzoneinfo/is taken as the zone name and resolved under$tzdb_directory.As a zoneinfo name with an optional leading colon (e.g.
:Europe/Stockholm).As a literal path to a TZif file.
If TZ is not set, the symlink target of /etc/localtime is resolved to a zone name under $tzdb_directory when possible; otherwise /etc/localtime is read directly as a TZif file.
Zone names taken from TZ or from a resolved path are validated with valid_tzdb_timezone before being joined to $tzdb_directory, so a malicious TZ cannot escape the database directory.
Note that resolving a path that exists but is not a valid TZif file propagates the error from Time::TZif; this function only returns undef when no candidate matches at all.
Examples:
# TZ=Europe/Stockholm
my $tz = find_local_timezone(); # Time::TZif (zoneinfo file)
# TZ=CET-1CEST,M3.5.0/2,M10.5.0/3
my $tz = find_local_timezone(); # Time::TZif::POSIX (rule)
# TZ unset, /etc/localtime -> .../zoneinfo/Europe/Stockholm
my $tz = find_local_timezone(); # Time::TZif (zoneinfo file)
Validation
valid_tzdb_timezone
my $bool = valid_tzdb_timezone($string);
Returns true if $string is structurally valid as an IANA Time Zone Database timezone name (e.g., America/New_York, Europe/Stockholm, Etc/GMT+5, UTC).
A valid name consists of one or more path components separated by /. Each component starts with a letter, followed by letters or digits, with optional _, +, or - separators introducing additional alphanumeric segments.
This is a structural check only; it does not verify that the timezone exists in the database.
valid_posix_timezone
my $bool = valid_posix_timezone($string);
Returns true if $string is structurally valid as a POSIX TZ string (IEEE Std 1003.1).
A POSIX TZ string has the form:
std offset [dst [offset] , start [/time] , end [/time]]
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
Transition rules may use Mm.w.d (month/week/weekday), Jn (Julian day excluding Feb 29), or n (zero-based Julian day including Feb 29) forms.
This validates the syntactic structure of the string only, not the semantic validity of field values (e.g., month ranges, day ranges).
SEE ALSO
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.