← Back to Blog

Timezones Are Not a Joke

April 2026 stdlibtime

"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