🎓 A1Learner
🔍

Rust Named Time Zones

A named time zone is a real place with its own clock rules. Fixed offsets never change, and real places change their clocks.

One moment, three places

The UTC moment stays fixed. Each named zone reads it in its own way:

let utc = NaiveDate::from_ymd_opt(2026, 10, 22).unwrap().and_hms_opt(9, 0, 0).unwrap().and_utc();
println!("London = {}", utc.with_timezone(&London));
println!("New York = {}", utc.with_timezone(&New_York));
println!("Kolkata = {}", utc.with_timezone(&Kolkata));

A normal local time

Most wall times map to one moment. `.single()` gives that one answer:

let normal = London.with_ymd_and_hms(2026, 10, 22, 9, 0, 0).single().unwrap();
println!("normal = {}", normal);

A spring gap

London skips 01:30 on this date. The clocks jump over it:

let gap = London.with_ymd_and_hms(2026, 3, 29, 1, 30, 0);
println!("single exists = {}", gap.single().is_some());
println!("no result = {}", matches!(gap, MappedLocalTime::None));

An autumn fold

London repeats 01:30 on this date. Both readings are real:

let fold = London.with_ymd_and_hms(2026, 10, 25, 1, 30, 0);
let early = fold.earliest().unwrap();
let late = fold.latest().unwrap();
println!("early = {}", early);
println!("late = {}", late);
println!("seconds apart = {}", late.timestamp() - early.timestamp());

The machine zone is a type

`DateTime<Local>` means the moment is read in the machine zone. The reading differs from one machine to the next:

let utc = NaiveDate::from_ymd_opt(2026, 10, 22).unwrap().and_hms_opt(9, 0, 0).unwrap().and_utc();
let local: DateTime<Local> = utc.with_timezone(&Local);

Example

// Cargo.toml: chrono = "0.4", chrono-tz = "0.10" (verified with chrono 0.4.45, chrono-tz 0.10.4)
use chrono::{MappedLocalTime, NaiveDate, TimeZone};
use chrono_tz::{America::New_York, Asia::Kolkata, Europe::London};

fn main() {
    let call = NaiveDate::from_ymd_opt(2026, 10, 22).unwrap().and_hms_opt(9, 0, 0).unwrap().and_utc();
    println!("UTC = {}", call);
    println!("London = {}", call.with_timezone(&London));
    println!("New York = {}", call.with_timezone(&New_York));
    println!("Kolkata = {}", call.with_timezone(&Kolkata));
    let fold = London.with_ymd_and_hms(2026, 10, 25, 1, 30, 0);
    if let MappedLocalTime::Ambiguous(early, late) = fold {
        println!("London fold early = {}", early);
        println!("London fold late = {}", late);
    }
}

Key points

  • Use a named zone for a real place.
  • Use a fixed offset only for a fixed rule.
  • A named zone knows its clock changes.
  • The offset can differ from one date to the next.
  • `with_timezone` changes the reading.
  • It never changes the moment.
  • A local wall time can have one answer.
  • It can also have two answers or no answer.
  • Pick `earliest()` or `latest()` in a fold.
  • Make that choice in plain code.

In this example, the call starts at 09:00 UTC. London reads it as 10:00 in summer time. New York reads it as 05:00. Kolkata reads it as 14:30. The last part shows one repeated London wall time. It has two real moments.

Note

Store the moment in UTC or as a timestamp.

Turn it into a local reading only when you show it.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

A named-zone wall time falls in a spring clock jump. What should the mapping give?