A Rust port of upstream tzcode localtime.c runtime semantics, provided as
the localtime library crate. Given an admitted TZif file (or a POSIX TZ
string) and a POSIX timestamp, it produces the local broken-down time — gmtoff,
isdst, the zone abbreviation, and the calendar fields — reproducing what
tzcode's localtime_rz observes.
let bytes = std::fs::read("/usr/share/zoneinfo/America/New_York")?;
let tz = localtime::TimeZone::from_tzif_bytes(&bytes)?;
let lt = tz.local_time(1620000000)?;
assert_eq!((lt.year, lt.month, lt.abbreviation.as_str()), (2021, 5, "EDT"));This starts the runtime-interpretation half of the Rust tzdb toolchain
(TZif file → runtime interpretation → local civil time), beside zic-rs (the
compiler) and the producer/QA crates.
localtime-rs does not replace libc, does not define civil-time truth, and does
not claim global timezone correctness. It ports the observable runtime
semantics of upstream tzcode localtime.c into Rust for admitted TZif inputs and
verifies timestamp-to-local-time behaviour against the upstream C oracle under
pinned TZ/TZDIR environments. It is not an ergonomic date/time framework,
a chrono/jiff competitor, a full libc layer, or a policy-correct civil-time
engine.
TZif v1/v2/v3/v4 loading · UTC timestamp → local broken-down time · gmtoff ·
isdst · abbreviation · transition search · POSIX footer projection · pre-1970 ·
post-last-transition · corrupt-TZif refusal (typed errors), plus the four sealed
deepening phases below (POSIX-TZ · RIGHT-LEAP · HOSTILE · MKTIME). Deferred:
strftime/asctime/ctime (separate crates), difftime (difftime-rs),
locale behaviour, the thread-local/global libc API shape, environment-mutation
semantics, and mktime on bare POSIX-string / right/ leap zones.
let tz = TimeZone::from_tzif_bytes(bytes)?; // or TimeZone::from_posix("EST5EDT,M3.2.0,M11.1.0")?
let lt = tz.local_time(unix_timestamp)?; // LocalTime { year, month, day, hour,
// minute, second, weekday, yearday,
// utc_offset, isdst, abbreviation }
// The inverse: broken-down local civil time → time_t (gaps/folds/tm_isdst/normalization).
let t = tz.mktime(&Tm { tm_year: 2021 - 1900, tm_mon: 6, tm_mday: 1,
tm_hour: 8, tm_min: 0, tm_sec: 0, tm_isdst: -1 })?; // 1625140800
tz.version(); // 1/2/3/4, or 0 for a POSIX zone
tz.is_leap_aware(); // a right/ buildThe oracle is a C harness linking upstream localtime.c (tzalloc +
localtime_rz), emitting structured rows ts|year|mon|mday|hour|min|sec|isdst| gmtoff|abbr. localtime-rs is compared field-for-field.
- Sweep: 36 / 0. 19 fixture zones + 6 POSIX strings + 4
right/zones, each over 34 timestamps (epoch, ±boundaries, the 2³¹ boundary, deep i64, DST edges, pre-1970 LMT, footer-projected 2200, leap-second instants), plus 7 hostile TZif files (all refused). Receipt:reports/localtime-oracle/.- Posix zones are verified against the standalone
localtime.charness;right/(leap) zones against glibc's vendored tzcodelocaltime— the standalone build ships with runtime leap application off (TZ_RUNTIME_LEAPS), a classified build quirk; both harnesses are byte-identical on non-leap zones.
- Posix zones are verified against the standalone
- Kani: the transition-search index (
lo - 1) is proven in bounds for any transition count andt. - Fuzz:
cargo +nightly fuzz run tzif_runtimeover arbitrary TZif bytes — found and fixed 3 panics (a leap-correction subtraction overflow, a POSIX footer offset-multiply overflow, and an out-of-range footer-rule index), then 21.4M executions clean. Receipt:reports/fuzz/.
- POSIX-TZ (
reports/posix-tz/) —from_posixparsing + footer projection vs the standalone oracle: 40 strings/zones × 25,980 timestamps = 1,039,200 field-rows bit-identical (northern/southern hemisphere,M/J/nrules, angle-bracket numeric names, half-hour offsets, far-future 2200+). - RIGHT-LEAP (
reports/right-leap/) — every one of the 27 leap instants (135:60rows), the 1→27s correction ladder, pre-first-leap, and future no-new-leap, across 5right/zones, 5/0 vs the leap-applying glibc oracle. Root-causes the standalone build's leap-off quirk (a +3-byte v1→v2 over-skip). - HOSTILE (
reports/hostile/) — malformed TZif/TZfail closed with typed errors, never a panic: ~16,500 bit-flips + 2,069 truncations + crafted cases, and fuzz 10.6M (TZif) + 35.8M (POSIX) runs, 0 new crashes. - MKTIME (
reports/mktime/) —mktime/mktime_zover 14 zones × 38,921 rows = 544,894 comparisons bit-identical (gaps, folds,tm_isdsthints, out-of-range normalization, overflow); a footer-type-ordering bug found + fixed; mktime fuzz 33,887 runs clean.
cargo test
cargo clippy --all-targets -- -D warnings
cargo kani --harness transition_search_index_in_bounds
python3 lab/oracle/sweep.py # the oracle sweep (needs the compiled harness)
BSD-3-Clause, retaining the upstream Regents of the University of California /
tz contributors copyright (localtime.c is BSD-licensed). An independent
reimplementation.