# `tzif`

The `tzif` library loads TZif v1, v2, and v3 files from the IANA time zone database into inspectable terms and answers UTC-based lookup queries over one or more zones.

Requires a backend Prolog compiler with unbounded integer arithmetic.

Current feature set:

- Loads sources given as `file(Path, ZoneId)`, `files(Root, Paths)`, `directory(Root)`, `stream(Stream, ZoneId)`, `bytes(Bytes, ZoneId)`, or `snapshot(File)` using `load/1` or `load/2`.

- Supports TZif v1, v2, and v3, including validated skipping of the v1 compatibility block in v2/v3 files.

- Parses POSIX footers, including signed-hour and signed-minute offsets.

- Exposes zone-aware lookup predicates over loaded `tzif(...)` terms, plus cached convenience variants for zone and single-zone queries.

- Exposes strict local civil-time lookup predicates that fail cleanly for ambiguous or nonexistent times.

- Exposes `*_with_resolution` local civil-time query predicates for explicit resolution modes (`strict`, `first`, `second`, and `all`).

- Exposes `*_reified` local civil-time query predicates that return `unique(...)`, `ambiguous(...)`, or `nonexistent` results.

- Caches successful `load/1` calls automatically, while `load/2` returns a loaded term without changing the cache.

- Provides `cache/1` for making an already loaded `tzif(...)` term the active cached term.

- Saves and reloads `tzif(...)` terms as plain Prolog snapshot files, with `save/1` providing a convenience predicate for saving the cached term.

- Returns the source term recorded in the active cached term via `cache_source/1`.

- Exposes the active cached term using `cached_tzif/1`.

- Exposes the cached zone list using `zones/1`.

- Accepts UTC queries as Unix seconds or `date_time/6` terms.

- Validates zone identifiers against bundled IANA TZDB 2026a canonical names plus backward-compatible aliases.

## API documentation

Open the ../../apis/library_index.html#tzif link in a web browser.

## Loading

To load all entities in this library, load the `loader.lgt` file:

    | ?- logtalk_load(tzif(loader)).

## Testing

To test this library predicates, load the `tester.lgt` file:

    | ?- logtalk_load(tzif(tester)).

## Representation

Loaded sources are returned by `load/2` as lists of `tzif(Zone, Source, ZoneData)` terms, one term per loaded zone.

Each nested `ZoneData` term contains the parsed block data, leap-second records, and parsed footer for a single zone. These per-zone `tzif(...)` terms are stable enough to be serialized directly using `save/2` and restored using `load(snapshot(File), TZifs)`.

For directory and file-set loads, zone identifiers are the relative paths used to locate the TZif files inside the root directory, and each loaded term records its own `file(File, ZoneId)` source.

When loading a `directory(Root)`, regular files whose relative paths are not recognized zone identifiers are ignored. This allows loading system zoneinfo trees that also contain metadata files such as `leapseconds`.

For single-zone file, stream, and byte-list loads, the caller must provide the zone identifier explicitly using `file(Path, ZoneId)`, `stream(Stream, ZoneId)`, or `bytes(Bytes, ZoneId)`.

The cache stores one `tzif(...)` term per zone id. `load/1` and `cache/1` replace only cached entries whose zone ids match the newly loaded terms.

## Zone identifier validation

The library validates zone identifiers using bundled data derived from the official IANA TZDB 2026a release. Accepted identifiers include both canonical zone names such as `America/New_York` and backward-compatible aliases such as `US/Eastern`.

The bundled data is generated from the IANA `zone1970.tab` and `backward` files and does not consult the host operating system’s installed zoneinfo tree.

To refresh the bundled table for a newer IANA release, run:

    $ ./update_zone_ids.sh 2026a

## Local Queries

The library provides three sets of predicates for local civil-time queries.

Strict predicates:

- `local_time_type/2-4`

- `local_offset/2-4`

- `local_daylight_saving_time/2-4`

- `local_abbreviation/2-4`

These predicates are intended for local civil times with a unique interpretation. They fail for ambiguous fold times and nonexistent gap times.

Explicit-resolution predicates:

- `local_time_type_with_resolution/3-5`

- `local_offset_with_resolution/3-5`

- `local_daylight_saving_time_with_resolution/3-5`

- `local_abbreviation_with_resolution/3-5`

These predicates accept an explicit resolution mode argument. Supported modes are `strict`, `first`, `second`, and `all`.

- `strict`: succeed only when the local civil time has a unique interpretation

- `first`: select the earliest valid interpretation

- `second`: select the latest valid interpretation

- `all`: enumerate all valid interpretations in chronological order

Reified predicates:

- `local_time_type_reified/2-4`

- `local_offset_reified/2-4`

- `local_daylight_saving_time_reified/2-4`

- `local_abbreviation_reified/2-4`

These predicates never use failure to distinguish ambiguity from absence. Instead, they return one of the following results:

- `unique(...)`

- `ambiguous(...)`

- `nonexistent`

## Examples

Load a single TZif payload without caching it:

    | ?- tzif::load(bytes(Bytes, 'America/New_York'), TZifs).

Cache an already loaded term explicitly:

    | ?- tzif::load(bytes(Bytes, 'America/New_York'), TZifs), tzif::cache(TZifs).

Load a directory tree of TZif files:

    | ?- tzif::load(directory('/usr/share/zoneinfo'), TZifs).

Query a specific zone in a loaded term:

    | ?- tzif::load(bytes(Bytes, 'America/New_York'), [TZif]).
    | ?- tzif::offset(TZif, 'America/New_York', date_time(2024, 7, 1, 12, 0, 0), Offset).

Query a specific zone in the cached term:

    | ?- tzif::load(directory('/usr/share/zoneinfo')).
    | ?- tzif::offset('America/New_York', date_time(2024, 7, 1, 12, 0, 0), Offset).

Persist and reload a snapshot:

    | ?- tzif::save(TZifs, 'snapshot.pl').
    | ?- tzif::load(snapshot('snapshot.pl'), ReloadedTZifs).

Save the cached terms directly:

    | ?- tzif::load(directory('/usr/share/zoneinfo')).
    | ?- tzif::save('snapshot.pl').

Load using a backward-compatible alias:

    | ?- tzif::load(bytes(Bytes, 'US/Eastern'), TZifs).

Use the automatic cache populated by `load/1`:

    | ?- tzif::load(snapshot('snapshot.pl')).
    | ?- tzif::abbreviation(date_time(2024, 7, 1, 12, 0, 0), Abbreviation).

Resolve an ambiguous local civil time explicitly:

    | ?- tzif::local_offset_with_resolution('America/New_York', date_time(2024, 11, 3, 1, 30, 0), first, Offset).

Inspect whether a local civil time is unique, ambiguous, or nonexistent:

    | ?- tzif::local_time_type_reified('America/New_York', date_time(2024, 11, 3, 1, 30, 0), Result).

List cached zones directly:

    | ?- tzif::load(directory('/usr/share/zoneinfo')).
    | ?- tzif::zones(Zones).

## Known issues

Some tests may fail on Windows depending on the backend. When using SICStus Prolog, test failures result from file name case-normalization by this system file and directory library predicates. When using SWI-Prolog, the same issues with file name case-normalization are fixed in version 10.1.12.