A faithful, memory-safe Rust port of upstream tzselect.ksh from the
IANA time zone database — the interactive
program that helps a user pick a TZ value by continent → country → region, by
geographic coordinates (-c), by a proleptic POSIX TZ string, or by current
local time.
TZDIR=/usr/share/zoneinfo tzselect-rs # interactive (stderr/stdin); TZ on stdout
echo -e '8\n3\n1' | tzselect-rs # scriptable, like upstream tzselect
tzselect-rs -c +4852+00220 # nearest zones to a coordinate
tzselect-rs --help / --version
Upstream tzselect.ksh is an interactive Korn/Bash script wrapping several
POSIX-awk programs. tzselect-rs ports all of it to native Rust — the menu
select loop, the country/region/TZ derivation, the great-circle coordinate
distance ranking, and the POSIX-TZ-string grammar (hand-parsed, so there are
no runtime dependencies). Like the upstream it shells out to date for the
current-time displays.
tzselect-rs does not define timezone policy or choose a timezone for the
user. It ports the interactive selection behaviour of upstream tzselect.ksh
into Rust, using the same tzdb table surfaces (iso3166.tab, zone1970.tab),
and verifies representative prompt/output paths against the upstream shell
oracle. It is the user-selection layer of the Rust tzdb toolchain, beside
zic-rs and the producer/QA
crates (ziguard-rs,
zishrink-rs,
checktab-rs,
checklinks-rs,
checknow-rs,
leapseconds-rs); it does
not enter libc localtime/strftime runtime territory.
tzselect.ksh is interactive, so the oracle is a driven shell process and the
claim is prompt/output-sequence parity. The deterministic oracle is
LC_ALL=C.UTF-8 COLUMNS=1 tzselect — single-column select, raw-UTF-8 country
names, bytewise sort. Against it, tzselect-rs is byte-identical on stderr
(the prompts/menus), stdout (the chosen TZ), and exit status.
Classified (not byte-compared), exactly as upstream allows:
- the two live-clock lines
Selected time is now:/Universal Time is now:and the interactivetime/nowlocal-time menus — time-dependent; - the
read tzEOF case — upstreamtzselect.kshinfinite-loops on EOF duringTZ-string entry (a shellwhile … readwith no|| exit); tzselect-rs exits cleanly (a deliberate, classified safer divergence); - wide-terminal multi-column
selectlayout and non-UTF-8-localeiconvtransliteration — shell/terminal/locale rendering details.
- Exhaustive enumeration: all 525 continent → country → region leaf
paths (every menu choice in
zone1970.tab) are byte-identical to the oracle (stderr + stdout + exit). - Differential sweep: 1402 / 0 random scripted navigation paths match
(continent/country/region, coord,
TZ-string, bad-input re-prompts, EOF), with 85 time-dependent and 38 read-EOF-hang cases cleanly classified. Receipt:reports/oracle/. - Kani: the menu-index guard is proven sound (
items[n-1]always in bounds). - Fuzz:
cargo +nightly fuzz—posix_tz31M execs,driver574k execs, 0 crashes. Receipt:reports/fuzz/.
cargo build --release
cargo test # mock-host golden regression + posix_tz
cargo clippy --all-targets -- -D warnings
cargo kani --harness menu_index_guard_is_sound
bash lab/oracle/sweep.py 1000 # the oracle differential sweep (needs the 2026b lab)
Apache-2.0. The upstream tzselect.ksh and the tzdb tables are public domain;
this Rust port is an independent reimplementation.