tzif-codec
tzif-codec is a small Rust crate for serializing, deserializing, validating,
and building TZif files as specified by RFC 9636.
The crate keeps its data model close to the binary TZif layout. It is intended for applications that need to generate TZif bytes, inspect TZif structure, or round-trip TZif data for use with timezone libraries such as Jiff.
Scope
- TZif v1, v2, v3, and v4 deserialization
- TZif v1, v2, v3, and v4 serialization
- Transition times and transition type indexes
- Local time type records
- Time zone designation tables
- Leap-second records
- Standard/wall and UT/local indicators
- TZif footer strings
- RFC 9636 Appendix A interoperability warnings
- TZDIST media type and truncation helpers
MSRV
Minimum Supported Rust Version (MSRV): tzif-codec supports Rust 1.85.0 and later.
Installation
Add the crate to your Cargo.toml:
[]
= "0.1"
The crate currently has no feature flags and uses std.
This crate does not implement a timezone engine. It does not expose APIs to evaluate local time for arbitrary timestamps, resolve DST gaps or folds, or compile IANA tzdb source files. It does parse POSIX TZ footer strings where RFC 9636 validation requires syntax checks and last-transition consistency checks. Builders can generate common POSIX TZ footer strings from structured offset and transition-rule inputs.
This crate also does not implement a TZDIST server or client. It provides the TZif-specific building blocks a TZDIST implementation needs: media type identifiers, capability checks, media type validation, and truncation-shape validation.
Example
use ;
let tzif = transitions
.designation
.designation
.local_time_type
.local_time_type
.transition
.transition
.posix_footer
.build?;
let bytes = tzif.serialize?;
let deserialized = deserialize?;
assert_eq!;
# Ok::
Validation
TzifFile and DataBlock are low-level, mutable representations of the wire
format. Callers that construct or edit them directly can validate RFC 9636
constraints without serializing first:
use ;
let tzif = v1;
tzif.validate?;
# Ok::
serialize(), deserialize(), validate_for_media_type(),
validate_tzdist_truncation(), and interoperability warnings also run the same
structural validation before using a file.
Builders
The low-level TzifFile and DataBlock types are available when callers need
full control over the TZif layout. Most applications should start with
builders, which choose the lowest non-legacy TZif version needed by default and
hide designation table indexes. Version 1 can still be requested explicitly for
legacy interoperability.
use TzifBuilder;
let kathmandu = fixed_offset
.build?;
assert_eq!;
# Ok::
Explicit transitions can be used for zones whose transitions have already been
computed by the caller. POSIX TZ footers can be generated from structured
rules, so callers do not need to hand-write strings such as
EST5EDT,M3.2.0,M11.1.0.
use ;
let eastern = transitions
.designation
.designation
.local_time_type
.local_time_type
.transition
.transition
.posix_footer
.build?;
assert_eq!;
# Ok::
Raw footer strings remain available through .footer(...) for unusual cases
that the structured PosixFooter API does not yet cover. Raw footers are
validated when the resulting TzifFile is validated or serialized.
Builder designations are always validated as RFC 9636 designations: ASCII only,
[A-Za-z0-9+-], and 3 to 6 characters. This keeps build() from returning a
TzifFile that later fails serialization because of designation table data.
Interoperability Warnings
RFC 9636 Appendix A documents common compatibility issues in older or buggy
TZif readers. Most of these issues do not make a TZif file invalid, so
tzif-codec reports them as warnings instead of rejecting the file.
use ;
let tzif = v3;
let warnings = tzif.interoperability_warnings?;
assert!;
# Ok::
The warning API covers statically detectable Appendix A issues such as incomplete version 1 data, footer-dependent files, version 4 leap-second tables, missing early no-op transitions, extreme transition timestamps, non-portable designations, negative DST, leap seconds with sub-minute offsets, and unusual UTC offsets.
TZDIST Helpers
RFC 9636 permits TZDIST servers to serve TZif using application/tzif and
application/tzif-leap. tzif-codec keeps HTTP concerns out of scope, but
provides helpers for the TZif-specific rules.
use ;
validate_tzdist_capability_formats?;
# Ok::
Available helpers:
TzifMediaTypeTzifMediaType::APPLICATION_TZIFTzifMediaType::APPLICATION_TZIF_LEAPTzifFile::suggested_media_typeTzifFile::validate_for_media_typevalidate_tzdist_capability_formatsTzifFile::validate_tzdist_truncationTzdistTruncation
The truncation helper checks the TZif-shape rules from RFC 9636 Section 6.1:
start truncation requires the first version 2+ transition to match the start
point and time type 0 to be a -00 placeholder; end truncation requires the
last version 2+ transition to match the end point, an empty footer, and the
last transition type to be a -00 placeholder.
The helper cannot prove that represented information inside a truncation range matches a corresponding untruncated TZif file. That comparison requires the untruncated source data and belongs in the TZDIST service implementation.
Minimal Axum Response Example
The HTTP server, routing, authentication, TLS, ETags, and Accept negotiation
belong to the TZDIST implementation. Existing IANA zones can be served by
reading the system zoneinfo file, which is already TZif. Custom zones can be
encoded with tzif-codec and returned with the same media type.
use ;
use ;
async
async
In this example, Custom/Office is the application-defined zone identifier used
by the TZDIST service. WORK is the TZif local time type designation stored
inside the file. These names do not need to match, but builder designations must
still satisfy the RFC 9636 designation rules described above.
Conformance
The test suite includes byte-for-byte RFC 9636 Appendix B examples:
- B.1 Version 1 File Representing UTC with Leap Seconds
- B.2 Version 2 File Representing Pacific/Honolulu
- B.3 Truncated Version 2 File Representing Pacific/Johnston
- B.4 Truncated Version 3 File Representing Asia/Jerusalem
- B.5 Truncated Version 4 File Representing Europe/London
Each vector is generated from the Rust data model, deserialized back, and serialized again with byte-for-byte equality.
Test Strategy
The crate includes unit tests for builders, deserialization, serialization, validation, interoperability warnings, TZDIST helpers, and RFC 9636 Appendix B byte vectors.
Run the Rust test suite with:
Run lint checks with:
Code of Conduct
See CODE_OF_CONDUCT.md.
Contributing
See CONTRIBUTING.md.
License
See LICENSE.md.