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;
}