Chapter 16 19 min read

Time

Wall-clock and monotonic-clock helpers in pure Glide on top of libc / WinAPI. The module exposes two small value types — Time (an instant) and Duration (a signed nanosecond span) — plus a monotonic Stopwatch, RFC 3339 parsing, the Weekday/Month enums, and coroutine-friendly timer channels (after, tick).

Both Time and Duration are small structs copied by value — no heap, no .free(). Civil components (year, month, day, weekday, …) are computed on demand, so the structs stay tiny (16 / 8 bytes).

Import

import stdlib::time::*;

Constants

Conversion factors, all i64 unless noted. Handy when you need a raw count instead of building a Duration.

Constant Type Value
NANOS_PER_MICRO i64 1000
NANOS_PER_MILLI i64 1000000
NANOS_PER_SEC i64 1000000000
NANOS_PER_MIN i64 60000000000
NANOS_PER_HOUR i64 3600000000000
NANOS_PER_DAY i64 86400000000000
MICROS_PER_SEC i64 1000000
MILLIS_PER_SEC i64 1000
SECS_PER_MIN i64 60
SECS_PER_HOUR i64 3600
SECS_PER_DAY i64 86400
MINS_PER_HOUR i32 60
HOURS_PER_DAY i32 24
DAYS_PER_WEEK i32 7

Duration

pub struct Duration {
    !nanos: i64,
}

A Duration is an exact nanosecond span (no month/year arithmetic). The range is roughly ±292 years. The from_* constructors build one; the as_* family reads it back (truncating any finer fraction); arithmetic methods return new Duration values.

Constructors

Function Signature Description
zero Duration::zero() -> Duration Zero span (seed for sums).
from_nanos Duration::from_nanos(n: i64) -> Duration n nanoseconds.
from_micros Duration::from_micros(n: i64) -> Duration n microseconds.
from_millis Duration::from_millis(n: i64) -> Duration n milliseconds.
from_secs Duration::from_secs(n: i64) -> Duration n seconds.
from_minutes Duration::from_minutes(n: i64) -> Duration n minutes.
from_hours Duration::from_hours(n: i64) -> Duration n hours.
from_days Duration::from_days(n: i64) -> Duration n days (24×60×60 s — no DST adjustment).

Conversions

Each truncates toward zero (drops the sub-unit remainder).

Method Signature Description
as_nanos as_nanos(self: Duration) -> i64 Total nanoseconds (the underlying field).
as_micros as_micros(self: Duration) -> i64 Total microseconds.
as_millis as_millis(self: Duration) -> i64 Total milliseconds.
as_secs as_secs(self: Duration) -> i64 Total seconds.
as_minutes as_minutes(self: Duration) -> i64 Total minutes.
as_hours as_hours(self: Duration) -> i64 Total hours.
as_days as_days(self: Duration) -> i64 Total days.

Arithmetic

Method Signature Description
add add(self: Duration, other: Duration) -> Duration self + other.
sub sub(self: Duration, other: Duration) -> Duration self - other (may be negative).
mul mul(self: Duration, factor: i64) -> Duration self * factor.
neg neg(self: Duration) -> Duration -self.

Predicates & comparisons

Method Signature Description
is_zero is_zero(self: Duration) -> bool true for a zero span.
is_positive is_positive(self: Duration) -> bool true if strictly positive.
is_negative is_negative(self: Duration) -> bool true if strictly negative.
before before(self: Duration, other: Duration) -> bool self < other.
after after(self: Duration, other: Duration) -> bool self > other.
equal equal(self: Duration, other: Duration) -> bool self == other.

Rendering

pub fn to_string(self: Duration) -> string

Renders as <H>h <M>m <S>s (sub-second fraction appended as .<cs>). Always positive magnitude; a negative duration gets a leading -. Hours/minutes are omitted when zero.

Duration::from_secs(3725).to_string();   // "1h 2m 5s"
Duration::from_millis(1500).to_string(); // "1.50s"

Example — build, convert, compare

import stdlib::time::*;

fn main() -> i32 {
    let d: Duration = Duration::from_hours(2).add(Duration::from_minutes(30));
    println!(d.to_string());            // "2h 30m 0s"
    println!(d.as_minutes());           // 150
    println!(d.as_millis());            // 9000000

    let a: Duration = Duration::from_secs(90);
    println!(a.is_positive());          // true
    println!(a.before(d));              // true
    println!(a.sub(Duration::from_secs(30)).as_secs());  // 60
    println!(Duration::zero().is_zero());

    let neg: Duration = Duration::from_secs(5).neg();
    println!(neg.is_negative());        // true
    return 0;
}

Example — scale, sum, render edge cases

mul scales a span; sum a list by folding add over Duration::zero(). Conversions truncate toward zero, and a negative span keeps its magnitude with a leading -.

import stdlib::time::*;

fn main() -> i32 {
    let half_day: Duration = Duration::from_hours(12);
    let day: Duration = half_day.mul(2);
    println!(day.as_hours());                 // 24

    // Fold a running total from the zero() seed.
    let mut total: Duration = Duration::zero();
    total = total.add(Duration::from_minutes(90));
    total = total.add(Duration::from_secs(30));
    println!(total.to_string());              // "1h 30m 30s"
    println!(total.as_secs());                // 5430

    // Conversions truncate toward zero; to_string shows centiseconds.
    let d: Duration = Duration::from_millis(1500);
    println!(d.as_secs());                    // 1  (drops .5)
    println!(d.to_string());                  // "1.50s"

    // Negative span: sign moves to the prefix.
    let back: Duration = Duration::from_secs(5).neg();
    println!(back.to_string());               // "-5s"
    println!(back.is_negative());             // true

    // Comparisons treat equal magnitudes as equal regardless of unit.
    println!(Duration::from_secs(60).equal(Duration::from_minutes(1)));  // true
    println!(Duration::from_secs(30).before(Duration::from_minutes(1))); // true
    println!(Duration::from_hours(2).after(Duration::from_minutes(90))); // true
    return 0;
}

Time

pub struct Time {
    !unix_nanos: i64,
    !tz_offset_min: i32,
}

Time is an instant: the underlying unix_nanos is always UTC, and tz_offset_min (minutes east of UTC) only shifts how the civil component accessors (year, hour, …) and the formatters render it. Comparisons (before/after/equal) work on the instant and ignore the offset.

Constructors

Function Signature Description
epoch Time::epoch() -> Time 1970-01-01T00:00:00Z (sentinel).
now Time::now() -> Time Wall-clock now in the system's local timezone.
now_utc Time::now_utc() -> Time Wall-clock now in UTC (tz_offset == 0).
from_unix Time::from_unix(secs: i64) -> Time UTC time from a Unix-seconds timestamp.
from_unix_millis Time::from_unix_millis(ms: i64) -> Time UTC time from Unix milliseconds.
from_unix_nanos Time::from_unix_nanos(ns: i64) -> Time UTC time from Unix nanoseconds (highest precision).
from_date Time::from_date(year: i32, month: i32, day: i32) -> Time UTC midnight on the given civil date.
from_datetime Time::from_datetime(year: i32, month: i32, day: i32, hour: i32, minute: i32, second: i32) -> Time UTC time from full civil components.

Raw accessors

Method Signature Description
unix unix(self: Time) -> i64 Seconds since the Unix epoch.
unix_millis unix_millis(self: Time) -> i64 Milliseconds since the epoch.
unix_micros unix_micros(self: Time) -> i64 Microseconds since the epoch.
unix_nanos unix_nanos(self: Time) -> i64 Nanoseconds since the epoch (the underlying field).
tz_offset tz_offset(self: Time) -> i32 Timezone offset in minutes east of UTC (0 for any UTC time).

Civil components

Computed in the time's own timezone.

Method Signature Description
year year(self: Time) -> i32 Civil year (negative for BCE).
month_int month_int(self: Time) -> i32 Month as 1..12.
month month(self: Time) -> Month Month as a typed Month enum.
day day(self: Time) -> i32 Day-of-month 1..31.
weekday weekday(self: Time) -> Weekday Day of week (Sunday-first enum).
hour hour(self: Time) -> i32 0..23.
minute minute(self: Time) -> i32 0..59.
second second(self: Time) -> i32 0..59 (60 only via leap-second parse).
nanosecond nanosecond(self: Time) -> i32 Sub-second component 0..999_999_999.
day_of_year day_of_year(self: Time) -> i32 1..366.
is_leap_year is_leap_year(self: Time) -> bool true in a leap year.

Queries (comparisons)

Method Signature Description
before before(self: Time, other: Time) -> bool self is strictly earlier.
after after(self: Time, other: Time) -> bool self is strictly later.
equal equal(self: Time, other: Time) -> bool Same instant (offset ignored).

Arithmetic

Method Signature Description
add add(self: Time, d: Duration) -> Time Add a Duration; tz offset preserved.
sub sub(self: Time, d: Duration) -> Time Subtract a Duration; tz offset preserved.
diff diff(self: Time, other: Time) -> Duration self - other as a Duration (may be negative).
truncate truncate(self: Time, d: Duration) -> Time Round down to a multiple of d (epoch-anchored, UTC). Duration::zero() is a no-op.
round round(self: Time, d: Duration) -> Time Round to the nearest multiple of d (ties round up).

Timezone transforms

Method Signature Description
to_utc to_utc(self: Time) -> Time Same instant viewed in UTC (offset dropped).
with_tz with_tz(self: Time, offset_min: i32) -> Time Same instant rendered at offset_min minutes east of UTC.

Formatting

Method Signature Description
date_string date_string(self: Time) -> string YYYY-MM-DD.
time_string time_string(self: Time) -> string HH:MM:SS.
rfc3339 rfc3339(self: Time) -> string RFC 3339 / ISO 8601 (...Z or ...+02:00).
to_string to_string(self: Time) -> string Default rendering = rfc3339().
format format(self: Time, spec: string) -> string Render with a placeholder spec (see below).

format placeholders (matched longest-first, anything else copied verbatim):

Token Meaning Example
YYYY 4-digit year 2024
YY 2-digit year 24
MMMM full month name January
MMM short month name Jan
MM 2-digit month 01..12
DD 2-digit day 01..31
HH 2-digit hour 00..23
mm 2-digit minute 00..59
ss 2-digit second 00..59
Day full weekday name Monday
Dy short weekday name Mon

Parsing

Method Signature Description
parse_rfc3339 Time::parse_rfc3339(s: string) -> !Time Parse 2024-01-15T14:23:45Z / ...+02:00 / ...-05:30.
parse_date Time::parse_date(s: string) -> !Time Parse a bare YYYY-MM-DD as UTC midnight.

Both return a !Time: read .ok/.err/.val, or propagate with ?. Fractional seconds and lowercase variants are not accepted by parse_rfc3339 yet.

Example — construct, inspect, format, compare

import stdlib::time::*;

fn main() -> i32 {
    let t: Time = Time::from_datetime(2026, 5, 8, 14, 23, 45);
    println!(t.rfc3339());              // 2026-05-08T14:23:45Z
    println!(t.date_string());          // 2026-05-08
    println!(t.time_string());          // 14:23:45
    println!(t.format("Day, MMMM DD")); // Friday, May 08

    println!(t.year());                 // 2026
    println!(t.month().short_name());   // May
    println!(t.weekday().name());       // Friday
    println!(t.day_of_year());
    println!(t.is_leap_year());

    let a: Time = Time::from_date(2026, 1, 1);
    let b: Time = Time::from_date(2026, 5, 8);
    println!(a.before(b));              // true
    println!(b.diff(a).as_days());      // days between

    let later: Time = t.add(Duration::from_hours(6));
    println!(later.rfc3339());
    return 0;
}

Example — civil components & Unix round-trips

Every component accessor is computed on demand from unix_nanos in the time's own timezone. The from_unix* constructors and the unix* accessors are exact inverses at their respective precisions.

import stdlib::time::*;

fn main() -> i32 {
    let t: Time = Time::from_datetime(2024, 2, 29, 23, 59, 59);
    println!(t.year(), t.month_int(), t.day());   // 2024 2 29
    println!(t.hour(), t.minute(), t.second());   // 23 59 59
    println!(t.is_leap_year());                   // true (2024)
    println!(t.day_of_year());                    // 60
    println!(t.weekday().name());                 // Thursday

    let u: Time = Time::from_unix(1735689600);    // 2025-01-01T00:00:00Z
    println!(u.rfc3339());
    println!(u.unix());                            // 1735689600
    println!(u.unix_millis());                     // 1735689600000
    println!(Time::from_unix_millis(u.unix_millis()).equal(u));  // true

    // Sub-second precision survives from_unix_nanos.
    let p: Time = Time::from_unix_nanos(1500000000_500000000);
    println!(p.nanosecond());                      // 500000000
    return 0;
}

Example — add/sub, diff, truncate & round

add/sub are exact and preserve the tz offset; diff yields a signed Duration. truncate snaps down to a multiple of d, round to the nearest (ties up). Both anchor on the Unix epoch in UTC and treat Duration::zero() as a no-op.

import stdlib::time::*;

fn main() -> i32 {
    let t: Time = Time::from_datetime(2026, 5, 8, 14, 37, 50);

    let later: Time = t.add(Duration::from_hours(2));
    println!(later.time_string());            // 16:37:50
    let earlier: Time = t.sub(Duration::from_minutes(37).add(Duration::from_secs(50)));
    println!(earlier.time_string());          // 14:00:00

    let d: Duration = later.diff(t);
    println!(d.as_hours());                   // 2
    println!(t.diff(later).is_negative());    // true

    println!(t.truncate(Duration::from_hours(1)).time_string());   // 14:00:00
    println!(t.round(Duration::from_hours(1)).time_string());      // 15:00:00 (37m rounds up)
    println!(t.round(Duration::from_minutes(15)).time_string());   // 14:45:00
    println!(t.truncate(Duration::zero()).equal(t));               // true
    return 0;
}

Example — parsing & timezones

import stdlib::time::*;

fn main() -> i32 {
    let r: !Time = Time::parse_rfc3339("2024-03-21T18:45:30+02:00");
    if r.ok {
        let t: Time = r.val;
        println!("unix:", t.unix());
        println!(t.tz_offset());        // 120
    } else {
        println!("parse failed:", r.err);
    }

    let dr: !Time = Time::parse_date("2026-05-08");
    if dr.ok { println!(dr.val.rfc3339()); }

    let utc: Time = Time::from_datetime(2026, 5, 8, 12, 0, 0);
    println!(utc.with_tz(540).rfc3339()); // ...+09:00 (JST)
    println!(utc.to_utc().tz_offset());   // 0
    return 0;
}

Example — propagation, ?? fallback & the fractional-seconds gotcha

parse_rfc3339/parse_date return !Time, so they compose with ? (propagate the err) and ?? (substitute a fallback). The parser is strict: it rejects fractional seconds, lowercase t/z, and any trailing characters.

import stdlib::time::*;

fn round_trip(s: string) -> !string {
    let t: Time = Time::parse_rfc3339(s)?;   // propagate err on failure
    return ok(t.rfc3339());
}

fn main() -> i32 {
    let r: !string = round_trip("2024-03-21T18:45:30+02:00");
    if r.ok { println!(r.val); }              // 2024-03-21T18:45:30+02:00

    // Fractional seconds are NOT accepted yet -> err.
    let bad: !Time = Time::parse_rfc3339("2024-03-21T18:45:30.5Z");
    println!(bad.ok);                         // false
    println!(bad.err);                        // rfc3339: expected Z or ±HH:MM

    let dr: !Time = Time::parse_date("2026-05-08");
    if dr.ok { println!(dr.val.rfc3339()); }  // 2026-05-08T00:00:00Z

    // ?? supplies a fallback instant when the parse fails.
    let parsed: Time = Time::parse_rfc3339("nope") ?? Time::epoch();
    println!(parsed.unix());                  // 0
    return 0;
}

Weekday

pub enum Weekday {
    Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday,
}
Item Signature Description
from_int Weekday::from_int(n: i32) -> Weekday Numeric weekday to enum (Sunday=0, Saturday=6). Out-of-range wraps with Euclidean modulo.
name name(self: Weekday) -> string Full English name ("Friday").
short_name short_name(self: Weekday) -> string 3-letter abbreviation ("Fri").

Month

pub enum Month {
    January, February, March, April, May, June,
    July, August, September, October, November, December,
}
Item Signature Description
from_int Month::from_int(n: i32) -> Month 1..12 to enum; out-of-range clamps to the boundary.
to_int to_int(self: Month) -> i32 Conventional 1..12.
name name(self: Month) -> string Full English name ("May").
short_name short_name(self: Month) -> string 3-letter abbreviation ("May").
days days(self: Month, year: i32) -> i32 Days in this month for the given year (leap-aware February).

Example — enums

import stdlib::time::*;

fn main() -> i32 {
    println!(Weekday::from_int(5).short_name());   // Fri
    println!(Weekday::Monday().name());            // Monday
    println!(Month::from_int(5).short_name());     // May
    println!(Month::December().to_int());          // 12
    println!(Month::February().days(2024));        // 29 (leap)
    println!(Month::February().days(2025));        // 28
    return 0;
}

Stopwatch

pub struct Stopwatch {
    !start_ns: i64,
}

A monotonic-clock stopwatch. It reads the runtime's monotonic clock (unaffected by wall-clock changes) on start/reset and reports an elapsed Duration on demand.

Method Signature Description
start Stopwatch::start() -> Stopwatch Start; reads the monotonic clock immediately.
elapsed elapsed(self: Stopwatch) -> Duration Time since the last start/reset. The stopwatch keeps running, so repeated calls are cumulative.
reset reset(self: *Stopwatch) -> Duration Restart the clock; returns the elapsed Duration up to the reset point.

Example — total elapsed vs. per-lap reset

import stdlib::time::*;

fn work() {
    let mut x: i64 = 0;
    for let i: i32 = 0; i < 10000; i++ { x = x + (i as i64); }
}

fn main() -> i32 {
    // elapsed() is cumulative — the clock keeps running.
    let sw: Stopwatch = Stopwatch::start();
    work();
    let t1: Duration = sw.elapsed();
    work();
    let t2: Duration = sw.elapsed();   // total since start, not since t1
    println!(t1.as_nanos() <= t2.as_nanos());   // true
    println!("elapsed:", t2.to_string());

    // reset() mutates in place — call it via &mut and chain laps.
    let mut lap: Stopwatch = Stopwatch::start();
    work();  let a: Duration = (&mut lap).reset();
    work();  let b: Duration = (&mut lap).reset();
    println!("lap a:", a.as_nanos(), "lap b:", b.as_nanos());
    return 0;
}

Timer channels

Function Signature Description
after after(d: Duration) -> chan<i64> Spawns a coroutine that sleeps d, then sends now_ns() once on the returned chan. Capacity 1.
tick tick(d: Duration) -> chan<i64> Spawns a coroutine that emits now_ns() on the returned chan every d, forever (no stop() yet). Capacity 4, minimum interval 1 ms.

after is the timeout sentinel: race it against your work in a select!. tick is for process-lifetime tickers (heartbeats, periodic flushes).

The chans carry now_ns() (the monotonic timestamp at fire time). You usually don't care about the value — only that the channel became ready.

Example — blocking timer & ticker

import stdlib::time::*;

fn main() -> i32 {
    let timer: chan<i64> = after(Duration::from_millis(50));
    let fired_at: i64 = timer.recv();   // wakes ~50ms later
    println!("fired at", fired_at);

    let beat: chan<i64> = tick(Duration::from_millis(10));
    println!("tick", beat.recv());
    return 0;
}

Example — timeout via after() + select!

The idiomatic use of after is as a deadline arm in a select!: whichever channel becomes ready first wins. Spawn your work onto a channel, race it against the timer, and branch on which arm fired.

import stdlib::time::*;

fn _work(c: chan<i32>) {
    sleep_ms(5);
    c.send(42);
}

fn main() -> i32 {
    let work: chan<i32> = make_chan(1);
    spawn _work(work);

    let timeout: chan<i64> = after(Duration::from_millis(200));

    let mut result: i32 = -1;
    select! {
        v = work.recv()    => { result = v; }
        t = timeout.recv() => { result = -1; }
    }

    if result < 0 {
        println!("timed out");
    } else {
        println!("got", result);
    }
    return 0;
}

Example — periodic tick() heartbeat

tick fires repeatedly. Multiplex it against your work channel in a select! to interleave periodic actions (heartbeats, metric flushes) with request handling.

import stdlib::time::*;

fn _producer(c: chan<i32>) {
    sleep_ms(5);
    c.send(1);
    sleep_ms(5);
    c.send(2);
}

fn main() -> i32 {
    let work: chan<i32> = make_chan(2);
    spawn _producer(work);

    let beat: chan<i64> = tick(Duration::from_millis(1));

    let mut got: i32 = 0;
    let mut beats: i32 = 0;
    while got < 2 {
        select! {
            v = work.recv() => { got = v; }
            t = beat.recv() => { beats = beats + 1; }
        }
    }
    println!("done, received", got);
    return 0;
}

Free-function clock helpers

Direct monotonic-clock access — reach for these when you want a single-i64 handle instead of a Stopwatch.

Function Signature Description
time_now_ns time_now_ns() -> i64 Monotonic timestamp in nanoseconds.
time_now_us time_now_us() -> i64 Monotonic timestamp in microseconds.
time_now_ms time_now_ms() -> i64 Monotonic timestamp in milliseconds.
time_since_ns time_since_ns(start_ns: i64) -> i64 Nanoseconds elapsed since start_ns (clamps to 0 if the clock ran backwards).
time_since_us time_since_us(start_ns: i64) -> i64 Microseconds elapsed since start_ns.
time_since_ms time_since_ms(start_ns: i64) -> i64 Milliseconds elapsed since start_ns.
import stdlib::time::*;

fn work() {
    let mut x: i64 = 0;
    for let i: i32 = 0; i < 1000; i++ { x = x + (i as i64); }
}

fn main() -> i32 {
    let t0: i64 = time_now_ns();
    work();
    println!(time_since_ns(t0), "ns");
    println!(time_now_ms(), time_now_us());
    return 0;
}