Time
Time: instants, wall-clock timestamps, durations, and RFC 3339.
Three value types keep the two kinds of clock reading from being confused:
Instantis a monotonic reading (nanoseconds from an unspecified origin). Only differences of instants are meaningful; the origin is arbitrary. Use it to measure elapsed time, never to name a point in the calendar. -Wallis a system-clock reading: nanoseconds since the Unix epoch (1970-01-01T00:00:00Z), in UTC. Use it to stamp and format a moment. -Durationis a signed span of nanoseconds, the difference of two readings.
Both readings come from the one Clock capability (declared in Concurrent): mono_clock performs mono_now, wall_clock performs wall_now. Which reading a program actually gets is a property of the installed handler, not of the call site. Concurrent.run_clock serves every Clock op from a virtual counter, so a test’s time is a pure function of its sleeps; run_clock_real here serves the two real reads from the OS clock. Both real reads are recorded capability observations, so a time-reading program replays byte-for-byte.
RFC 3339 formatting and parsing are pure, total, UTC-only string code: no locale, no time zone database (tzdb is a political data blob and belongs in a content-addressed package, not a frozen Std). Timestamps at or after the epoch (non-negative wall readings) are supported.
Types
Duration
type Duration = Duration(Int)
A signed span of time, in nanoseconds.
Instant
type Instant = Instant(Int)
A monotonic clock reading, in nanoseconds from an unspecified origin.
Wall
type Wall = Wall(Int)
A wall-clock reading: nanoseconds since the Unix epoch, in UTC.
Functions and Values
nanos_per_sec
nanos_per_sec : () -> Int
Nanoseconds in one second (the Duration scale factor for seconds).
nanos_per_sec()
1000000000
nanos_per_milli
nanos_per_milli : () -> Int
Nanoseconds in one millisecond.
nanos_per_micro
nanos_per_micro : () -> Int
Nanoseconds in one microsecond.
dur_nanos
dur_nanos : (Time.Duration) -> Int
The span in whole nanoseconds.
dur_nanos(seconds(2))
2000000000
nanos
nanos : (Int) -> Time.Duration
A duration of n nanoseconds.
micros
micros : (Int) -> Time.Duration
A duration of n microseconds.
dur_nanos(micros(3))
3000
millis
millis : (Int) -> Time.Duration
A duration of n milliseconds.
seconds
seconds : (Int) -> Time.Duration
A duration of n seconds.
dur_nanos(seconds(1))
1000000000
minutes
minutes : (Int) -> Time.Duration
A duration of n minutes.
dur_as_secs(minutes(2))
120
hours
hours : (Int) -> Time.Duration
A duration of n hours.
dur_as_secs(hours(1))
3600
dur_as_secs
dur_as_secs : (Time.Duration) -> Int
The whole-second part of a duration (truncated toward zero).
dur_as_secs(dur_add(seconds(90), millis(500)))
90
dur_as_millis
dur_as_millis : (Time.Duration) -> Int
The whole-millisecond part of a duration (truncated toward zero).
dur_as_millis(seconds(2))
2000
dur_add
dur_add : (Time.Duration, Time.Duration) -> Time.Duration
Sum of two durations.
dur_nanos(dur_add(seconds(1), millis(500)))
1500000000
dur_sub
dur_sub : (Time.Duration, Time.Duration) -> Time.Duration
Difference of two durations (a - b).
dur_nanos(dur_sub(seconds(2), millis(500)))
1500000000
dur_scale
dur_scale : (Time.Duration, Int) -> Time.Duration
A duration scaled by an integer factor.
dur_nanos(dur_scale(seconds(1), 3))
3000000000
dur_negate
dur_negate : (Time.Duration) -> Time.Duration
A duration with the sign flipped.
dur_nanos(dur_negate(seconds(1)))
-1000000000
dur_cmp
dur_cmp : (Time.Duration, Time.Duration) -> Int
Ordering of two durations (-1, 0, 1).
dur_cmp(seconds(1), seconds(2))
-1
dur_eq
dur_eq : (Time.Duration, Time.Duration) -> Bool
True when the two durations are equal.
dur_eq(millis(1000), seconds(1))
true
instant_nanos
instant_nanos : (Time.Instant) -> Int
The raw monotonic reading in nanoseconds. The absolute value is meaningless across processes; subtract two instants to get an elapsed Duration.
instant_nanos(Instant(500))
500
elapsed
elapsed : (Time.Instant, Time.Instant) -> Time.Duration
The span from earlier to later (later - earlier). Named elapsed, not between, to leave Base’s numeric between range test unshadowed.
dur_nanos(elapsed(Instant(100), Instant(450)))
350
instant_add
instant_add : (Time.Instant, Time.Duration) -> Time.Instant
An instant advanced by a duration.
instant_nanos(instant_add(Instant(100), micros(1)))
1100
wall_nanos
wall_nanos : (Time.Wall) -> Int
The reading in nanoseconds since the Unix epoch (UTC).
wall_nanos(wall_of_nanos(1500000000))
1500000000
wall_of_nanos
wall_of_nanos : (Int) -> Time.Wall
A wall reading from nanoseconds since the Unix epoch.
wall_unix_secs
wall_unix_secs : (Time.Wall) -> Int
Whole seconds since the Unix epoch (truncated toward zero).
wall_unix_secs(wall_of_nanos(1500000000))
1
wall_add
wall_add : (Time.Wall, Time.Duration) -> Time.Wall
A wall reading advanced by a duration.
wall_nanos(wall_add(wall_of_nanos(0), seconds(1)))
1000000000
wall_diff
wall_diff : (Time.Wall, Time.Wall) -> Time.Duration
The span from earlier to later wall readings (later - earlier).
dur_nanos(wall_diff(wall_of_nanos(0), wall_of_nanos(500)))
500
wall_cmp
wall_cmp : (Time.Wall, Time.Wall) -> Int
Ordering of two wall readings (-1, 0, 1).
wall_cmp(wall_of_nanos(1), wall_of_nanos(2))
-1
mono_clock
mono_clock : () -> Time.Instant ! {Concurrent.Clock}
Read the monotonic clock. Deterministic under run_clock, real (and recorded) under run_clock_real.
run_clock_real(\() -> mono_clock())
wall_clock
wall_clock : () -> Time.Wall ! {Concurrent.Clock}
Read the wall clock. Deterministic under run_clock, real (and recorded) under run_clock_real.
format_rfc3339(run_clock_real(\() -> wall_clock()))
run_clock_real
run_clock_real : forall e0 a. (() -> a ! {Concurrent.Clock, IO, e0}) -> a ! {IO, e0}
Run action against the real OS clock. now/sleep still thread a virtual counter (they are the scheduler’s logical time), so this is a superset of run_clock: wall_now and mono_now read the real clock through the prim_* builtins, each a recorded observation, and everything else stays virtual.
run_clock_real(\() -> wall_unix_secs(wall_clock()))
format_rfc3339
format_rfc3339 : (Time.Wall) -> String
Format a wall reading as an RFC 3339 timestamp in UTC, e.g. 2026-07-04T12:34:56Z. A nonzero sub-second part is emitted as exactly nine fractional digits (nanoseconds); an exact second emits no fraction. The output is canonical: one reading formats to one string, so timestamps are comparable and hashable as text.
format_rfc3339(wall_of_nanos(0))
1970-01-01T00:00:00Z
parse_rfc3339
parse_rfc3339 : (String) -> Option(Time.Wall)
Parse an RFC 3339 timestamp to a wall reading in UTC, or None if the string is not a well-formed YYYY-MM-DDTHH:MM:SS[.frac](Z|(+|-)HH:MM). The date/time separators are checked, field ranges are validated, an offset is folded into UTC, and any trailing input is rejected, so the parser is total: a malformed or overlong string is None, never a partial or wrapped value.
wall_unix_secs(unwrap_or(wall_of_nanos(0), parse_rfc3339("2000-01-01T00:00:00Z")))
946684800