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.1
export 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.1
export 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.6
export 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
}