Time and Dates (std:time)
Read the clock, convert between epoch milliseconds and ISO 8601 text, and sleep.
Overview
std:time exports four functions:
| Function | Returns | Description |
|---|---|---|
now() | Map {epoch_ms, iso, tz} | The current instant |
sleep(ms) | nil | Block for ms milliseconds |
parse_iso(text) | Map {epoch_ms, tz} | Parse an ISO 8601 / RFC 3339 timestamp |
format_iso(epoch_ms, tz = "Z") | String | Format epoch milliseconds |
Timestamps are epoch milliseconds — a plain number. There is no date value
type, no calendar arithmetic, no time:format and no strftime-style patterns.
Quick Start
import std:time
import std:println
now = time:now()
println(now:epoch_ms::is_number()) # true
println(now:tz) # Z
parsed = time:parse_iso("2023-11-10T15:30:00.000Z")
println(parsed:epoch_ms) # 1699630200000
println(time:format_iso(parsed:epoch_ms)) # 2023-11-10T15:30:00.000Z
now()
Returns a map with three fields:
| Field | Type | Description |
|---|---|---|
epoch_ms | Number | Milliseconds since the Unix epoch |
iso | String | UTC timestamp with milliseconds, e.g. 2023-11-10T15:30:00.000Z |
tz | String | Always "Z" — now() reports UTC |
import std:time
import std:println
now = time:now()
println(now::keys()) # [epoch_ms, iso, tz]
println(now:iso::length()) # 24
println(now:iso::ends_with("Z")) # true
println(now:epoch_ms > 1700000000000) # true
now() is the timing primitive for measuring elapsed work — os:uptime_ms() has
only whole-second resolution:
import std:time
import std:println
started = time:now():epoch_ms
time:sleep(50)
elapsed = time:now():epoch_ms - started
println(elapsed >= 50) # true
sleep(ms)
Blocks for ms milliseconds and returns nil. ms must be a non-negative
integer: a fraction raises
Type error: time:sleep requires a non-negative integer milliseconds, and a
negative value raises Invalid operation: time:sleep requires non-negative duration.
import std:time
import std:println
time:sleep(100)
println("waited") # waited
A simple retry-with-backoff loop:
import std:time
import std:println
attempt = 0
result = nil
loop {
attempt++
result = `echo ok`
match {
result == "ok" => { break }
attempt >= 3 => { break }
_ => { time:sleep(50 * attempt) }
}
}
println("${result} after ${attempt}") # ok after 1
parse_iso(text)
Parses an ISO 8601 / RFC 3339 timestamp and returns {epoch_ms, tz}. The
epoch_ms value is always UTC; tz reports the offset that appeared in the
input, normalised so that a trailing Z becomes "+00:00".
import std:time
import std:println
utc = time:parse_iso("2023-11-10T15:30:00.000Z")
println(utc:epoch_ms) # 1699630200000
println(utc:tz) # +00:00
offset = time:parse_iso("2023-11-10T15:30:00.000+05:00")
println(offset:epoch_ms) # 1699612200000
println(offset:tz) # +05:00
Milliseconds are optional in the input:
import std:time
import std:println
println(time:parse_iso("2023-11-10T15:30:00Z"):epoch_ms) # 1699630200000
println(time:parse_iso("2023-11-10T15:30:00.123Z"):epoch_ms) # 1699630200123
Unparsable text raises Invalid operation: Invalid ISO-8601 time and terminates
the program, so validate untrusted input before parsing:
import std:time
import std:println
parse_safe = |text| match {
text ~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}T/ => time:parse_iso(text):epoch_ms,
_ => nil,
}
println(parse_safe("2023-11-10T15:30:00Z")) # 1699630200000
println(parse_safe("not a date") == nil) # true
format_iso(epoch_ms, tz = "Z")
Formats epoch milliseconds as an ISO 8601 string with milliseconds. tz is
either "Z" (the default, meaning UTC) or an offset such as "+02:00" or
"-07:00"; the wall-clock part is shifted accordingly. An unrecognised offset
raises Invalid operation: invalid tz offset.
import std:time
import std:println
epoch_ms = 1699630200000
println(time:format_iso(epoch_ms)) # 2023-11-10T15:30:00.000Z
println(time:format_iso(epoch_ms, "+02:00")) # 2023-11-10T17:30:00.000+02:00
println(time:format_iso(epoch_ms, "-07:00")) # 2023-11-10T08:30:00.000-07:00
println(time:format_iso(0)) # 1970-01-01T00:00:00.000Z
Arithmetic on Timestamps
Because timestamps are numbers, offsets are ordinary arithmetic:
import std:time
import std:println
second = 1000
minute = 60 * second
hour = 60 * minute
day = 24 * hour
start = time:parse_iso("2023-11-10T15:30:00.000Z"):epoch_ms
println(time:format_iso(start + hour)) # 2023-11-10T16:30:00.000Z
println(time:format_iso(start + 7 * day)) # 2023-11-17T15:30:00.000Z
println((start - (start - 90 * second)) / second) # 90
Durations are easier to read when formatted yourself:
import std:println
humanize = |ms| {
total_seconds = (ms / 1000)::floor()
minutes = (total_seconds / 60)::floor()
seconds = total_seconds % 60
"${minutes}m ${seconds}s"
}
println(humanize(185000)) # 3m 5s
Timestamping Output
import std:time
import std:println
log = |message| println("[${time:now():iso}] ${message}")
log("started")
println(time:format_iso(time:parse_iso(time:now():iso):epoch_ms)::ends_with("Z")) # true
Date-only strings for filenames come from slicing the ISO text:
import std:time
import std:println
iso = time:format_iso(1699630200000)
println(iso[0;10]) # 2023-11-10
println(iso[11;19]) # 15:30:00
Gotchas
- There is no formatting language: build custom layouts by slicing the ISO string
or by doing arithmetic on
epoch_ms. now():tzis always"Z"; the module never reports the machine’s local timezone.format_isoshifts the displayed wall clock for an offset but always describes the same instant.sleeprequires an integer;time:sleep(0.5)is a type error.- Parse and offset errors terminate the program.