NAME

TimeZone::TimeZoneDB - Interface to https://timezonedb.com for looking up Timezone data

VERSION

Version 0.05

SYNOPSIS

use TimeZone::TimeZoneDB;

my $tzdb = TimeZone::TimeZoneDB->new(key => 'XXXXXXXX');
my $tz = $tzdb->get_time_zone({ latitude => 0.1, longitude => 0.2 });

DESCRIPTION

The TimeZone::TimeZoneDB Perl module provides an interface to the https://timezonedb.com API, enabling users to retrieve timezone data based on geographic coordinates. It supports configurable HTTP user agents, allowing for proxy settings and request throttling. The module includes robust error handling, ensuring proper validation of input parameters and secure API interactions. JSON responses are safely parsed with error handling to prevent crashes. Designed for flexibility, it allows users to override default configurations while maintaining a lightweight and efficient structure for querying timezone information.

METHODS

new

my $tzdb = TimeZone::TimeZoneDB->new(key => 'XXXXX');

# With a throttled user-agent that respects free-tier rate limits
use LWP::UserAgent::Throttled;
my $ua = LWP::UserAgent::Throttled->new();
$ua->env_proxy(1);
$tzdb = TimeZone::TimeZoneDB->new(ua => $ua, key => 'XXXXX');

# Retrieve the timezone for Ramsgate, UK
my $tz = $tzdb->get_time_zone({ latitude => 51.34, longitude => 1.42 })->{'zoneName'};
print "Ramsgate timezone: $tz\n";

Creates and returns a new TimeZone::TimeZoneDB instance. When invoked on an existing object rather than a class name, it returns a shallow clone of that object with any supplied parameters merged in. Passing ua => undef in a clone call is silently ignored so that the original user-agent is inherited unchanged.

ARGUMENTS

RETURNS

A blessed TimeZone::TimeZoneDB reference. Croaks if key is absent.

SIDE EFFECTS

None.

NOTES

An optional logger key may be passed; if present it must be an object implementing warn() and error() (e.g. Log::Log4perl).

API SPECIFICATION

INPUT

{
  'key'          => { type => 'string' },
  'ua'           => { type => 'object', can => 'get',    optional => 1 },
  'host'         => { type => 'string',                  optional => 1 },
  'cache'        => { type => 'object',                  optional => 1 },
  'min_interval' => { type => 'number', min => 0,        optional => 1 },
}

OUTPUT

{ type => 'object' }   # a blessed TimeZone::TimeZoneDB reference

get_time_zone

my $result = $tzdb->get_time_zone({ latitude => 51.34, longitude => 1.42 });
print $result->{'zoneName'}, "\n";

# Also accepts a Geo::Location::Point-compatible object
use Geo::Location::Point;
my $ramsgate = Geo::Location::Point->new({ latitude => 51.34, longitude => 1.42 });
my $tz = $tzdb->get_time_zone($ramsgate)->{'zoneName'};

Queries the timezonedb.com API for the IANA timezone name and associated metadata at the supplied geographic coordinates. Identical queries are served from cache without making a network request.

ARGUMENTS

RETURNS

A hashref containing at least zoneName on success. Returns undef when the API responds with a non-OK status. Croaks on HTTP errors or invalid arguments.

SIDE EFFECTS

Updates the internal response cache and the last_request timestamp.

NOTES

The API key is transmitted as a URL query parameter because the timezonedb.com API does not support an Authorization header. The key is redacted from all error and warning messages to prevent accidental secret leakage into log aggregators or crash reporters.

API SPECIFICATION

INPUT

{
  'latitude'  => { type => 'number', min => -90,  max => 90  },
  'longitude' => { type => 'number', min => -180, max => 180 },
}

OUTPUT

Argument error : croak
HTTP error     : croak
Non-OK status  : undef
Success        : { type => 'hashref', min => 1 }

ua

# Getter: retrieve the current user-agent
my $ua = $tzdb->ua();
$ua->env_proxy(1);

# Setter: swap in a throttled agent (returns the new agent for compatibility)
use LWP::UserAgent::Throttled;
my $new_ua = LWP::UserAgent::Throttled->new();
$new_ua->throttle('timezonedb.com' => 1);
$tzdb->ua($new_ua);

Gets or sets the HTTP user-agent object used for API requests. The return value is always the current user-agent (after any update), consistent with the convention used by LWP::UserAgent and related packages that expose a ua() accessor.

ARGUMENTS

RETURNS

The user-agent object stored on the instance -- the supplied value when called as a setter, the existing value when called as a getter. Croaks if a defined but invalid object (no get() method) is supplied, or if undef is explicitly passed.

SIDE EFFECTS

When used as a setter, all subsequent API calls on this object use the new user-agent.

NOTES

Free timezonedb.com accounts are rate-limited to one request per second. Use LWP::UserAgent::Throttled to enforce this transparently.

The accessor always returns the user-agent rather than $self so that callers can do $tzdb->ua()->env_proxy(1) in a single expression without ambiguity about what was returned.

API SPECIFICATION

INPUT

# Getter (no argument)
{}

# Setter
{ 'ua' => { type => 'object', can => 'get' } }

OUTPUT

{ type => 'object' }   # the stored user-agent (getter or setter)

AUTHOR

Nigel Horne, <njh@nigelhorne.com>

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

Lots of thanks to the folks at https://timezonedb.com.

BUGS

This module is provided as-is without any warranty.

Please report any bugs or feature requests to bug-timezone-timezonedb at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=TimeZone-TimeZoneDB. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SEE ALSO

FORMAL SPECIFICATION

new

TimeZoneDB-State ::= [
  key          : STRING ;
  ua           : USERAGENT ;
  host         : STRING ;
  cache        : CACHE ;
  min_interval : ℕ ;
  last_request : ℕ
]

Init
  key?          : STRING
  ua?           : USERAGENT ∪ {⊥}
  host?         : STRING ∪ {⊥}
  cache?        : CACHE ∪ {⊥}
  min_interval? : ℕ ∪ {⊥}
  result!       : TimeZoneDB-State
────────────────────────────────────────────────────────
  key? ≠ "" ∧
  result!.key          = key? ∧
  result!.ua           = (if ua? ≠ ⊥ then ua? else DefaultUA) ∧
  result!.host         = (if host? ≠ ⊥ then host? else config.host) ∧
  result!.cache        = (if cache? ≠ ⊥ then cache? else NewCache) ∧
  result!.min_interval = (if min_interval? ≠ ⊥ then min_interval? else 0) ∧
  result!.last_request = 0

get_time_zone

GetTimeZone
  Δ TimeZoneDB-State   (writes cache and last_request)
  lat? : {n : ℝ | -90 ≤ n ≤ 90}
  lng? : {n : ℝ | -180 ≤ n ≤ 180}
  result! : HASHREF ∪ {⊥}
────────────────────────────────────────────────────────
  let k == sprintf(CACHE_KEY_FMT, lat?, lng?)
  ∧ cache.has(k) ⇒
        result! = cache.get(k)
      ∧ last_request' = last_request
      ∧ cache' = cache
  ∧ ¬cache.has(k) ⇒
        let r == ua.get(ApiUrl(lat?, lng?, key))
        ∧ ¬r.ok ⇒ ⊥
        ∧ r.ok ∧ r.json.status = "OK" ⇒
              result! = r.json
            ∧ cache' = cache ⊕ {k ↦ r.json}
            ∧ last_request' = now
        ∧ r.ok ∧ r.json.status ≠ "OK" ⇒
              result! = ⊥
            ∧ cache' = cache
            ∧ last_request' = now

ua

UA
  Delta TimeZoneDB-State
  ua? : USERAGENT ∪ {⊥}   (⊥ = not supplied)
  ua! : USERAGENT
────────────────────────────────────────────────────────
  (ua? = ⊥ ∧ ua' = ua) ∨
  (ua? ≠ ⊥ ∧ defined(ua?) ∧ ua? can 'get'
           ∧ ua' = ua?
           ∧ ∀ x : {key, host, cache, min_interval, last_request} • x' = x)
  ∧ ua! = ua'

LICENSE AND COPYRIGHT

Copyright 2023-2026 Nigel Horne.

Usage is subject to the GPL2 licence terms. If you use it, please let me know.