Time

Simulation-time helpers and wall-clock timestamps.

Use input.time for animation and simulation. The host samples one Cycle per update!; elapsed_seconds is the simulation duration of that cycle.

now! reads the nondeterministic wall clock. It is legal in init!, update!, and tasks, and refused in render!; it does not wait.

first_cycle : Cycle

Initial cycle sample used before any simulation time has elapsed.

to_seconds : U64 -> F32

Convert a nanosecond duration to seconds.

expect Time.to_seconds(500_000_000) == 0.5
delta_seconds : U64, U64 -> F32

Seconds elapsed between two clock samples. current must be at or after previous. Derive a delta from consecutive timestamp samples:

dt = Time.delta_seconds(model.last_tick, timestamp_nanos)
now! : () => Timestamp

Read the world's clock.

Legal in init!, update!, and tasks; refused in render!. render! is given the model rather than an input, and should draw the instant the model already decided on.

Two calls in one update! can return different values, and an app that wants one instant for a whole cycle should read it once and keep it in the model. Nothing else about the platform changes with it: the calendar is explicitly nondeterministic, it is not what a capture paces, and it is not what animation should move on.

Cycle : { cycle_count : U64, simulation_nanos : U64, monotonic_nanos : U64, elapsed_seconds : F32 }

Timing samples attached to one host cycle.

Timestamp

Time.Timestamp :: # (opaque)

An instant on the world's clock, as seconds and nanoseconds since the Unix epoch.

Always normalized: nanosecond is less than 1,000,000,000, and seconds are floor-based, so one nanosecond before the epoch is { seconds: -1, nanosecond: 999_999_999 } rather than a negative fraction of second zero. That is what makes ordering and differences work the same on both sides of 1970.

A timestamp has no time zone, no calendar policy, and no leap-second model. Everything this module formats is UTC; local time and calendar arithmetic belong to a pure Roc package, and a Timestamp is the value to pass to it.

is_eq : _

Compare two of these values.

epoch : Timestamp

The Unix epoch, 1970-01-01T00:00:00Z.

A neutral value for a model field that has not read the clock yet, and the base every other instant is measured from.

from_parts : { seconds : I64, nanosecond : U32 } -> Try(Timestamp, [InvalidNanosecond])

Build a timestamp from its two parts.

InvalidNanosecond is a whole second or more of fractional part, which is a value that does not describe an instant rather than a clock that failed.

from_nanos_since_epoch : I128 -> Try(Timestamp, [OutOfRange])

Build a timestamp from signed nanoseconds since the epoch.

OutOfRange is an instant further from 1970 than a signed 64-bit count of seconds can hold, which is about 292 billion years either way.

difference_nanos : Timestamp, Timestamp -> I128

Nanoseconds from the first instant to the second, negative when the second is the earlier one.

to_iso_8601 : Timestamp -> Str

Format as an ISO 8601 instant in UTC, to the second.

expect Time.Timestamp.epoch.to_iso_8601() == "1970-01-01T00:00:00Z"

The Z is always literal: this platform formats UTC and nothing else, so there is no offset to get wrong. Years are padded to four digits, and a year before the common era is written with a leading minus and as many digits as it needs.

to_iso_8601_nanos : Timestamp -> Str

Format as an ISO 8601 instant in UTC, to the nanosecond.

The fractional part is always nine digits, so timestamps sort as strings in the same order they sort as instants.

to_file_stamp : Timestamp -> Str

Format as an instant that is legal in a filename on every platform.

The same shape as to_iso_8601 with - where the colons would be, because a colon cannot appear in a path on Windows:

expect Time.Timestamp.epoch.to_file_stamp() == "1970-01-01T00-00-00Z"