Dates & Times
Three stdlib files cover dates and times, each handling a different layer:
std/calendar— civil types:Date,Time,NaiveDateTime,OffsetDateTime,DateTime. What humans read off a calendar or clock face.std/instant—Instant, the machine timestamp. What hardware records.std/duration—Duration, an exact span of time (5 seconds,30 minutes).
The everyday civil type in std/calendar is DateTime: when you say “10:30
AM on June 15 in New York,” you mean a real moment in history, and a
DateTime is what models it (IANA-zoned, DST-aware). NaiveDateTime carries
the “Naive” prefix as a footgun marker — it’s a wall-clock reading without a
zone, useful for templates and unzoned data, not for “the meeting happens at.”
Civil construction
Section titled “Civil construction”Each civil type has a typed-literal form. DateTime uses RFC 9557’s bracket
suffix to name the IANA zone:
import {
std/calendar: Date, DateTime, Error
}
fn main(): Result<Unit, Error> {
d = try Date"2026-06-15"
dbg d
dt = try DateTime"2026-06-15T14:30:00-04:00[America/New_York]"
dbg dt
dbg DateTime.year(dt)
dbg DateTime.month(dt)
dbg DateTime.hour(dt)
Ok(Unit)
}
Civil periods
Section titled “Civil periods”Date shifts by civil calendar periods with + and -. Days and weeks move
by whole dates; months and years clamp when the target month has fewer days:
import {
std/calendar: Civil, Date, Error
}
fn main(): Result<Unit, Error> {
start = try Date"2026-05-04"
jan31 = try Date"2026-01-31"
leap = try Date"2024-02-29"
dbg start + Civil.Days(10)
dbg start + Civil.Weeks(2)
dbg start - Civil.Weeks(1)
dbg jan31 + Civil.Months(1)
dbg jan31 - Civil.Months(1)
dbg leap + Civil.Years(1)
Ok(Unit)
}
Machine clock and durations
Section titled “Machine clock and durations”An Instant is a machine timestamp — an absolute point in time backed by
Unix epoch nanoseconds. It’s what Instant.now() returns, and what you store
when you need a moment independent of calendar names or time zones. A
Duration is an exact span of nanoseconds — the value you add to an instant,
compare between instants, or pass to Timer.sleep. Construct durations with
unit functions (Duration.seconds(5), Duration.minutes(30)) and combine
them with +, -, *, and /:
import {
std/duration: Duration
std/instant: Instant
}
fn main() {
start = Instant.from_seconds(1_700_000_000)
five_minutes = Duration.minutes(5)
dbg Duration.as_minutes(five_minutes)
later = start + five_minutes
dbg Instant.to_seconds(later)
// Instant.between returns the Duration between two Instants
gap = Instant.between(start, later)
dbg Duration.as_seconds(gap)
Unit
}
Instant.now() and Timer.sleep aren’t shown here because their output isn’t
deterministic — they read the real wall clock and pause for real time. See
the Concurrency chapter for Timer.sleep in practice.
Instant-only equality
Section titled “Instant-only equality”A DateTime holds a moment plus a display zone. Equality is instant-only
— same moment in two different zones compares equal, even though their
local readings differ:
import {
std/calendar: DateTime, Error
}
fn main(): Result<Unit, Error> {
ny = try DateTime"2026-06-15T12:00:00-04:00[America/New_York]"
paris = try DateTime"2026-06-15T18:00:00+02:00[Europe/Paris]"
dbg ny == paris
dbg ny
dbg paris
Ok(Unit)
}
DST is real
Section titled “DST is real”The 2026 spring-forward in America/New_York lands the small hours of
March 8th. Adding one calendar day to noon March 7 lands at noon March 8 —
the civil reading you’d want. Adding an exact 24-hour Duration lands an
hour later, because one wall-clock hour disappeared overnight:
import {
std/calendar: Civil, DateTime, Error
std/duration: Duration
}
fn main(): Result<Unit, Error> {
start = try DateTime"2026-03-07T12:00:00-05:00[America/New_York]"
civil = start + Civil.Days(1)
physical = start + Duration.hours(24)
dbg start
dbg civil
dbg physical
Ok(Unit)
}
Both answers are correct. + Civil.Days(1) preserves the local calendar
reading and re-resolves the UTC offset. + Duration.hours(24) advances the
underlying instant by exactly 24 elapsed hours. The split lets the caller
spell which intent applies.
Going deeper
Section titled “Going deeper”Five civil types
Section titled “Five civil types”std/calendar defines a ladder from less specific to more specific:
Date— calendar dayTime— time of dayNaiveDateTime— wall reading, no zone (not a real moment)OffsetDateTime— anchored at a fixed UTC offsetDateTime— anchored in an IANA zone (DST-aware)
Re-projecting a moment into another zone
Section titled “Re-projecting a moment into another zone”DateTime.with_zone(dt, zone) keeps the underlying instant and re-renders the wall
reading through a different IANA zone:
import {
std/calendar: DateTime, Error
}
fn main(): Result<Unit, Error> {
ny = try DateTime"2026-06-15T12:00:00-04:00[America/New_York]"
tokyo = try DateTime.with_zone(ny, "Asia/Tokyo")
dbg tokyo
dbg ny == tokyo
Ok(Unit)
}
DST: gaps and folds
Section titled “DST: gaps and folds”Projecting a wall reading into an IANA zone can hit a gap (the wall
reading never exists — clocks jumped over it) or a fold (the wall
reading exists twice — clocks rolled back). The Disambiguation enum
controls how DateTime.in_zone resolves them:
import {
std/calendar: DateTime, Disambiguation, Error, NaiveDateTime
std/instant: Instant
}
fn main(): Result<Unit, Error> {
// 2026-11-01 01:30 NY happens twice (2am EDT rolls back to 1am EST).
naive = try NaiveDateTime"2026-11-01T01:30:00"
earlier = try DateTime.in_zone(
naive,
"America/New_York",
Disambiguation.Earlier,
)
later = try DateTime.in_zone(naive, "America/New_York", Disambiguation.Later)
// Same wall reading, different offsets, different instants.
dbg earlier
dbg later
// One hour apart in the underlying instant.
earlier_sec = DateTime.to_instant(earlier) |> Instant.to_seconds()
later_sec = DateTime.to_instant(later) |> Instant.to_seconds()
dbg later_sec - earlier_sec
// Instant-only equality: they are NOT equal.
dbg earlier == later
Ok(Unit)
}
The default mode is Disambiguation.Compatible (total resolution matching
naive intuition — earlier in folds, advances forward through gaps).
Disambiguation.Reject is the strict mode for when the wall reading came
from user input and the right answer is to bounce the ambiguity back via
Err.
Reading the current time
Section titled “Reading the current time”DateTime.now_in(zone) reads the system clock and projects it into the
given zone in one call (sugar for Instant.now() |> DateTime.from_instant_in(zone)):
import {
std/calendar: DateTime, Error
}
fn main(): Result<DateTime, Error> {
now = try DateTime.now_in("America/New_York")
dbg now // example: dbg line 6: now = 2026-05-26T17:42:00-04:00[America/New_York]
Ok(now)
}
The output changes each time because this reads the real wall clock.