Three types cover Nova's time model: Duration (signed interval), Timestamp (Unix epoch moment), and Monotonic (process-local monotonic clock). All store nanoseconds in a single i64 field. Integer and float extension methods let you write 5.seconds(), 1.hour(), 500.millis() directly.
let timeout = 30.seconds()
let deadline = Timestamp.from_unix_secs(1_700_000_000) + timeout
let d = Duration.from_secs(3661) // 1h 1min 1s
assert(d.into_human().contains("1h"))
Types
Duration
stable since 0.1export type Duration { nanos i64 }
Signed time interval with nanosecond precision. Range: ±292 years (i64::MAX nanoseconds). Supports arithmetic, comparison, and formatting operators.
Timestamp
stable since 0.1export type Timestamp { nanos i64 }
A moment in time stored as nanoseconds since Unix epoch (1970-01-01 UTC). Signed, so it supports dates before 1970. Timestamp - Timestamp yields a Duration. Methods that need the current wall clock require the Time effect.
Monotonic
stable since 0.6export type Monotonic { nanos i64 }
A point on the process-local monotonic clock. Immune to NTP / DST / manual clock adjustments. Use for timer deadlines, profiling intervals, and retry budgets. Cannot be serialized or compared across processes.
Examples
let start = Monotonic.now()
// ... work ...
let elapsed = Monotonic.now().elapsed_since(start)
Constants
const ZERO Duration = { nanos: 0 } // zero duration
const SECOND Duration = { nanos: 1_000_000_000 } // 1 second
const MINUTE Duration = { nanos: 60_000_000_000 } // 1 minute
const HOUR Duration = { nanos: 3_600_000_000_000 }
const EPOCH Timestamp = { nanos: 0 } // 1970-01-01 00:00:00 UTC
Duration — constructors
fn Duration.from_nanos(n i64) -> Self // 1 ns
fn Duration.from_micros(n i64) -> Self // 1 μs = 1_000 ns
fn Duration.from_millis(n i64) -> Self // 1 ms = 1_000_000 ns
fn Duration.from_secs(n i64) -> Self // 1 s = 1_000_000_000 ns
fn Duration.from_mins(n i64) -> Self // 60 s
fn Duration.from_hours(n i64) -> Self // 3600 s
fn Duration.from_days(n i64) -> Self // 86400 s
fn Duration.from_weeks(n i64) -> Self // 7 * 86400 s
fn Duration.from_secs_f64(s f64) -> Self // e.g. 0.5 → 500ms
Duration — accessors
fn Duration @as_nanos() -> i64 // raw nanoseconds
fn Duration @as_micros() -> () // truncating
fn Duration @as_millis() -> ()
fn Duration @as_secs() -> ()
fn Duration @as_mins() -> ()
fn Duration @as_hours() -> ()
fn Duration @as_days() -> ()
fn Duration @as_secs_f64() -> f64 // fractional seconds (preserves sub-second)
Examples
assert(Duration.from_secs(2).as_millis() == 2000)
assert(Duration.from_millis(1500).as_secs_f64() == 1.5)
Duration — arithmetic
fn Duration @plus(other Duration) -> Duration // d1 + d2
fn Duration @minus(other Duration) -> Duration // d1 - d2 (may be negative)
fn Duration @neg() -> Duration // unary -
fn Duration @abs() -> Duration // |d|
fn Duration @times(n i64) -> Duration // d * n
fn Duration @times(n f64) -> Duration
fn Duration @div(n i64) -> Duration // d / n (truncating)
fn Duration @div(n f64) -> Duration
fn Duration @ratio(other Duration) -> f64 // d1 / d2 dimensionless
Examples
assert(1.hour() + 30.minutes() == 90.minutes())
assert(30.seconds().ratio(1.minute()) == 0.5)
Duration — formatting
fn Duration @into() -> str // auto-scale: "500ns" / "42ms" / "2.5s" / "5min" / "2h"
fn Duration @into_human() -> str // decomposed: "1d 2h 30min 45s"
fn Duration @parts() -> DurationParts
Examples
let d = Duration.from_secs(3661) // 1h 1min 1s
let p = d.parts()
assert(p.hours == 1 && p.minutes == 1 && p.seconds == 1)
assert(2.hours().into() == "2h")
assert(500.nanos().into() == "500ns")
Integer & float extensions
Extension methods on int and f64 allow natural duration literals:
// int extensions
fn int @nanos() -> Duration
fn int @micros() -> Duration
fn int @millis() -> Duration
fn int @seconds() -> Duration // also: .second() (singular)
fn int @minutes() -> Duration // also: .minute()
fn int @hours() -> Duration // also: .hour()
fn int @days() -> Duration // also: .day()
fn int @weeks() -> Duration // also: .week()
// f64 extensions (allows fractional units)
fn f64 @seconds() -> Duration
fn f64 @millis() -> Duration
fn f64 @hours() -> Duration
Examples
assert(30.seconds() == Duration.from_secs(30))
assert(500.nanos() == Duration.from_nanos(500))
assert(0.5.seconds() == 500.millis())
Timestamp — constructors
fn Timestamp.from_unix_secs(s i64) -> Self
fn Timestamp.from_unix_millis(ms i64) -> Self
fn Timestamp.from_unix_nanos(n i64) -> Self
Timestamp — arithmetic
fn Timestamp @plus(d Duration) -> Timestamp // shift forward
fn Timestamp @minus(d Duration) -> Timestamp // shift back
fn Timestamp @minus(other Timestamp) -> Duration // elapsed (signed)
Examples
let start = Timestamp.from_unix_secs(1000)
let deadline = start + 30.seconds()
assert(deadline.as_unix_secs() == 1030)
assert((deadline - start) == 30.seconds())
Timestamp — Time-effect queries
These methods require the Time effect from the ambient scope. In tests, inject a deterministic handler:
fn Timestamp @is_past() Time -> bool
fn Timestamp @elapsed() Time -> Duration // positive if in the past
fn Timestamp @time_until() Time -> Duration // positive if in the future
fn deadline_in(d Duration) Time -> Timestamp // now() + d
Examples
with Time = th.fixed_ms(10_000) {
let deadline = Timestamp.from_unix_secs(5)
assert(deadline.is_past())
let (result, elapsed) = measure(|| 42)
assert(result == 42)
assert(elapsed == Duration.ZERO) // fixed clock doesn't advance
}