time
import time from "time";
import io from "io";
let start: time.Instant = time.now();
do_work();
io.println("took {start.elapsed().as_millis()} ms");
let today: time.DateTime = time.utc_now();
io.println(today.to_iso8601()); // 2026-10-02T14:03:00.250Ztime has two clocks. The monotonic clock (Instant) is for measuring
and never goes backwards. The wall clock (unix_nanos, DateTime) is
the date and time in UTC; it can jump when the clock is set. A Zoned
pairs an instant with a zone’s offset: the user’s own zone, or the zone a
mail was sent from.
Durations
Duration is a span of time in nanoseconds, made with:
| Function | |
|---|---|
nanos(n), micros(n), millis(n), seconds(n) | A Duration of n units |
| Method | Returns |
|---|---|
d.as_nanos(), d.as_micros(), d.as_millis(), d.as_secs() | The length in that unit, truncated |
d.as_secs_f64() | Seconds with the fraction |
d.add(other), d.sub(other) | A new Duration |
Measuring and waiting
| Function | Does |
|---|---|
now() -> Instant | Read the monotonic clock |
i.elapsed() -> Duration | Time since i |
i.since(earlier) -> Duration | Time from earlier to i |
i.add(d) -> Instant | i moved by d |
sleep(d: Duration) | Pause for at least d |
let deadline: time.Instant = time.now().add(time.seconds(5));
while (deadline.since(time.now()).as_nanos() > 0) {
poll();
time.sleep(time.millis(100));
}The OS may sleep a little longer than asked; Windows rounds up to whole milliseconds.
Dates
pub struct DateTime { year: i64; month: i64; day: i64; hour: i64; minute: i64; second: i64; nanos: i64; }| Function | Does |
|---|---|
unix_nanos() -> i64 | Nanoseconds since 1970-01-01 00:00:00 UTC |
utc_now() -> DateTime | The current UTC date and time |
from_unix_nanos(ns) -> DateTime | The date and time ns after (or before) 1970 |
dt.to_unix_nanos() -> i64 | Back to nanoseconds |
dt.to_iso8601() -> string | "2026-10-02T14:03:00Z" |
parse_iso8601(text) -> DateTime or TimeError | Read ISO 8601 / RFC 3339 text |
to_iso8601 adds a fraction only when there is one, with 3, 6 or 9
digits (...:07.250Z).
parse_iso8601 accepts a date (2026-10-02) or a date and time
(2026-10-02T14:03:00, also with t or a space), an optional fraction
(.5, up to 9 digits), and an optional zone: Z, or an offset like
+02:00, which is converted to UTC. Text without a zone is UTC.
let t: time.DateTime = time.parse_iso8601("2026-10-02T16:03:00+02:00") or {
io.println("bad date: {err}");
return 1;
};
io.println(t.to_iso8601()); // 2026-10-02T14:03:00ZIt fails with TimeError.InvalidFormat for text in another shape and
TimeError.OutOfRange for a field outside its range: month 13, February 30,
hour 24, second 60 (leap seconds aren’t represented).
modified_ns from fs.metadata is a unix_nanos value:
time.from_unix_nanos(m.modified_ns) is when the file last changed.
Time zones
pub struct Zoned { utc: DateTime; offset: i64; } // offset: minutes east of UTC| Function | Does |
|---|---|
local_offset(ns) -> i64 | The system zone’s offset in minutes at the instant ns, daylight saving time included |
local(ns) -> Zoned | The instant ns in the system zone |
local_now() -> Zoned | Now, in the system zone |
z.local() -> DateTime | The date and time a clock in that zone shows |
z.to_iso8601() -> string | "2026-10-13T09:12:03+03:00" |
dt.weekday() -> i64 | 1 Monday … 7 Sunday |
let z: time.Zoned = time.local_now();
let t: time.DateTime = z.local();
io.println("{t.hour}:{t.minute}"); // the user's wall clocklocal_offset asks the operating system, at the instant you give it: a
date from last winter gets winter time, a date in summer gets summer time.
Zones by name (Europe/Helsinki) aren’t supported; the system zone is the
one the user set. On Uusi the offset is 0.
Mail dates
| Function | Does |
|---|---|
parse_rfc5322(text) -> Zoned or TimeError | Read a mail’s Date header |
z.to_rfc5322() -> string | "Tue, 13 Oct 2026 09:12:03 +0300" |
let sent: time.Zoned = time.parse_rfc5322("Tue, 13 Oct 2026 09:12:03 +0300") or {
return 1;
};
io.println(sent.utc.to_iso8601()); // 2026-10-13T06:12:03Z
io.println("{sent.offset}"); // 180
let date_header: string = time.local_now().to_rfc5322();parse_rfc5322 is lenient, as mail needs. The day of the week is
optional. Names match on their first three letters in any case. Comments
like (EEST) and line folds are skipped. IMAP’s 13-Oct-2026 09:12:03 +0300
reads too. A 2-digit year is 1950-2049. The seconds are optional. The zone
can be +0300, +03:00, GMT, UT, or a US zone name like EST or
PDT. A zone that isn’t given or isn’t known (-0000, CEST) gives offset
0. Text after the zone is ignored.
It fails with InvalidFormat when the day, month, year, hour and minute
aren’t there, and OutOfRange for a field outside its range (32 Oct,
24:00).
Formatting
Build a Format once, call by call, then apply it to any number of dates:
var f: time.Format = time.format();
f.day();
f.text(".");
f.month();
f.text(".");
f.year();
f.text(" ");
f.hour();
f.text(":");
f.minute(2);
let z: time.Zoned = time.local_now();
io.println(f.apply_zoned(&z)); // 13.10.2026 9:05| Call | Writes |
|---|---|
year(pad), year_short() | 2026, 26 |
month(pad), month_name(), month_short() | 10, October, Oct |
day(pad) | 13 |
weekday(), weekday_name(), weekday_short() | 2 (Monday is 1), Tuesday, Tue |
hour(pad), hour12(pad), am_pm() | 9, 9, AM |
minute(pad), second(pad) | 5 (05 with pad 2) |
fraction(digits) | the second’s fraction: fraction(3) is milliseconds, 250 |
offset(), offset(false) | +03:00, +0300 |
text(s) | s as it is |
pad is the least number of digits, filled with zeros: month(2) writes
03. Without it the number is written as it is.
f.apply(d) writes a DateTime as it is (a UTC one, or a zone’s reading
from z.local()); f.apply_zoned(&z) writes a Zoned in its own zone,
with its offset. Day and month names are English for now.
Relative time and message-list dates
io.println(time.relative(sent_ns, time.unix_nanos())); // 5 minutes ago
io.println(time.brief(sent_ns, time.unix_nanos())); // 14:32relative(then, now) says how long ago (or how far ahead) then is:
“just now”, “1 minute ago”, “5 hours ago”, “3 days ago”, “7 months ago”,
“2 years ago”, “in 3 days”. It rounds to the nearest whole unit.
brief(then, now) writes a date as a message list does, in the user’s
time zone: the time for today (“14:32”), “Yesterday”, the day’s name for
the past week (“Tue”), the day and month this year (“13 Oct”), and the
year too for older dates (“13 Oct 2025”).
To change the shapes, take time.brief_format() and replace its fields
(today, yesterday, week, year, older), then call
b.apply(&then, &now) with two Zoned values:
var b: time.Brief = time.brief_format();
b.yesterday = "eilen";
let then: time.Zoned = time.local(sent_ns);
let now: time.Zoned = time.local_now();
io.println(b.apply(&then, &now));Changed in this release
The old time.nanos(), time.micros() and time.millis() (no argument,
returning a clock reading) are gone: those names now make Durations. Use
time.now() and Instant.elapsed():
let start: time.Instant = time.now(); // was: let start: i64 = time.nanos();
let ns: i64 = start.elapsed().as_nanos();Platforms
Windows, Linux and macOS have both clocks and the system zone (Windows’
time zone settings; on Linux and macOS TZ, else /etc/localtime). On Uusi the monotonic clock
works in millisecond steps and the wall clock in whole seconds (the
kernel’s real-time clock; on a kernel without it unix_nanos() is 0).
Measure time on Uusi with now(), not with differences of unix_nanos().
Uusi has no time zones: local_offset is 0.
Last modified: