Skip to main content

std.time

Getting the current time

now() // milliseconds since Unix epoch
timestamp() // seconds since Unix epoch
date() // {year, month, day, hour, minute, second, weekday, yearday}
format(ms[, fmt]) // default: %Y-%m-%d %H:%M:%S
parse(str, fmt) // date string -> milliseconds (C++ chrono specifiers)
elapsed(start_ms) // ms elapsed since start_ms
var t = std.time.now()
print(std.time.format(t)) // 2026-08-31 12:00:00
var d = std.time.date()
print(d.year, d.month, d.day)

Unit conversion

nanoseconds(n) microseconds(n) milliseconds(n)
seconds(n) minutes(n) hours(n) days(n)
to_seconds(ms) to_minutes(ms) to_hours(ms) to_days(ms)

The * (n) helpers convert a number of that unit to milliseconds; the to_* (ms) helpers convert milliseconds to that unit (integer division — results truncate toward zero).

std.time.seconds(90) // 90000 (ms)
std.time.to_minutes(120000) // 2

Calendar

calendar([year][, month]) returns an object describing a month for planning and display:

var cal = std.time.calendar(2026, 3)

The object has these fields:

FieldMeaning
year, monthThe requested year and 1-based month
month_namee.g. "March"
days_in_monthNumber of days (handles leap years)
first_weekdayWeekday of the 1st, Monday = 1 … Sunday = 7
todayDay-of-month when the requested month is the current one, else nil
weeksRows of day numbers, nil-padded to seven slots
print(cal.month_name, cal.year) // March 2026
print(cal.first_weekday) // 7 (1st is a Sunday)
print(cal.weeks[0]) // [nil, nil, nil, nil, nil, nil, 1]

weeks lays out the month as Monday-first rows, padding with nil before the 1st and after the last day:

// March 2026, first_weekday = 7
[
[nil, nil, nil, nil, nil, nil, 1],
[2, 3, 4, 5, 6, 7, 8],
[9, 10, 11, 12, 13, 14, 15],
[16, 17, 18, 19, 20, 21, 22],
[23, 24, 25, 26, 27, 28, 29],
[30, 31, nil, nil, nil, nil, nil],
]

Render it into a terminal grid by mapping each week's nil padding to blanks:

var cal = std.time.calendar(2026, 3)
print(cal.month_name, cal.year)
print("Mo Tu We Th Fr Sa Su")
for (week : cal.weeks) {
var line = ""
for (d : week) {
if (d == nil) {
line += " "
} else {
line += (d < 10) ? " " + d + " " : d + " "
}
}
print(line)
}

This prints:

March 2026
Mo Tu We Th Fr Sa Su
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31

No arguments means the current month, and today is then set to today's day-of-month (so you can highlight it in the grid).

Timers

timer schedules a callback through the async scheduler:

var t = std.time.timer({
callback: fn() { print("tick") },
duration: 1000, // ms
repeat: true,
})
  • Non-repeating timers return a futureawait it for the result.
  • Repeating timers return a handle with cancel() / start() / last_error().

Getting the last error

Repeating-timer callbacks run on background threads with no caller to receive an exception, so each timer records the most recent callback failure on its own handle and exposes it through handle.last_error() (returns null when there is none). It is echoed to stderr as well, so it is visible even if you never query it.

var t = std.time.timer({
callback: fn() { does_not_exist },
duration: 20,
repeat: true,
})

std.async.sleep(80) // let the tick fire once
var err = t.last_error() // "Unknown variable: does_not_exist"
if (err != null) {
print("timer failed:", err)
}

Which surface an error takes depends on the timer kind:

Timer kindWhere the error goes
Non-repeatingThe returned future — re-thrown when you await() it (as Async error: <msg>)
RepeatingRecorded on that timer's handle via handle.last_error(); each tick keeps running
// Non-repeating: the error surfaces on await, not last_error()
var f = std.time.timer({
callback: fn() { does_not_exist },
duration: 10,
})
std.async.await(f) // RUNTIME_ERROR: Async error: Unknown variable: does_not_exist

last_error() is not cleared when read and holds only the most recent message, so a repeating timer's latest failure overwrites its previous one. Each timer keeps its own slot — one timer failing does not mask another's error.