Timezones Are Not a Joke
"Just store UTC" is good advice until you need to answer one of these:
- What time does the store open in Los Angeles *next Tuesday*? - How many billable hours did this contractor work across the DST change? - When the user types 2026-03-08 02:30 America/Los_Angeles, do we accept or reject?
Each of those is a wall-clock query, not a UTC instant. You need a tz database. Now Lateralus has one, built into the stdlib, typed all the way through.
See stdlib/time/tz.ltl.
◉ The core type
pub struct ZonedDateTime {
instant: UnixTime,
tz: Tz,
}
An instant and a zone. Arithmetic happens on instant. Formatting resolves through tz. You never accidentally add 24 hours to a wall-clock and skip a DST boundary.
◉ Parsing an IANA zone
let la = tz::load("America/Los_Angeles")?
let nyc = tz::load("America/New_York")?
let now_in_la = tz::now_in(la)
println(now_in_la |> tz::to_iso8601())
// => "2026-04-20T09:17:42-07:00"
let now_in_nyc = now_in_la |> tz::in_zone(nyc)
// same instant, different wall-clock
load reads /usr/share/zoneinfo/<name> (TZif v2) and parses the transition table. Zones are values — you can cache them, pass them around, ship them in config.
◉ The hard case: wall-clock to instant
tz::from_wall(2026, 3, 8, 2, 30, 0, la, AmbiguityPolicy::Reject)
// => Err(TzError::Missing)
tz::from_wall(2026, 11, 1, 1, 30, 0, la, AmbiguityPolicy::Earliest)
// => Ok(ZonedDateTime { ... first 1:30 AM, PDT offset })
When DST starts, 2:30 AM on the spring-forward day doesn't exist — the clock jumps from 1:59:59 straight to 3:00:00. When DST ends, 1:30 AM happens twice. Most APIs pick silently. Ours makes you pass an AmbiguityPolicy:
- Earliest — pick the first occurrence, usually the pre-transition offset - Latest — pick the second - Reject — return TzError::Ambiguous
Calendar apps want Latest. Log parsers want Reject. There is no universal right answer, so we don't hide the choice.
◉ What about leap seconds?
We don't apply them to UnixTime. POSIX says a Unix second is 1/86400 of a mean solar day, leap seconds are smeared or skipped by NTP upstream, and your database almost certainly doesn't track them either. If you need TAI, there's std::time::tai for that.
◉ Why ship this now?
Because every gap-fill file in this lane needs it. The Postgres driver wants timestamps with offsets. JWT exp and nbf want clock-skew comparison. OTEL spans want consistent wall-clock output. Observability pipelines drown without timezone sanity.
Now all of them share one type.
◉ Further reading
- time/tz.ltl — the source - RFC 9636 — TZif format - Falsehoods programmers believe about time — evergreen
Respect your future self. Store instants. Resolve wall-clocks at the edge.
Try it in the playground
Every snippet on this page runs in your browser. No install, no signup.
▶ Open Playground Star on GitHub