module time

module lib/time.mu

import "time"

time provides helpers for time-related operations.

Imports

Functions

date_from_days#

fn date_from_days(days)

date_from_days returns [year, month, day] for days since 1970-01-01.

Source lib/time.mu:166
fn date_from_days(days) {
    return _date_from_days(days)
}

days_in_month#

fn days_in_month(year, month)

days_in_month reports the number of days for the given year/month (handles leap years).

Source lib/time.mu:171
fn days_in_month(year, month) {
    return _days_in_month(year, month)
}

format#

fn format(epoch)

format returns an ISO‑8601 timestamp (UTC) for the provided Unix epoch seconds.

Source lib/time.mu:150
fn format(epoch) {
    dm := math.div_mod(epoch, 86400)
    days := dm[0]
    secs_of_day := dm[1]
    date := _date_from_days(days)
    year := date[0]
    month := date[1]
    day := date[2]
    hour := secs_of_day / 3600
    minute := (secs_of_day % 3600) / 60
    second := secs_of_day % 60
    return text.pad_number(year, 4) + "-" + text.pad_number(month, 2) + "-" + text.pad_number(day, 2) +
        "T" + text.pad_number(hour, 2) + ":" + text.pad_number(minute, 2) + ":" + text.pad_number(second, 2)
}

is_leap_year#

fn is_leap_year(year)

is_leap_year reports whether the provided year is a leap year per the Gregorian rule.

Source lib/time.mu:176
fn is_leap_year(year) {
    return _is_leap_year(year)
}

monotonic_ns#

fn monotonic_ns()

monotonic_ns returns a monotonic clock reading in nanoseconds: it moves only forward and survives wall-clock adjustments, so it is the right clock for measuring elapsed time. Linux only -- darwin has no monotonic clock reachable as a syscall, and answering wall time here would be a lie a benchmark cannot detect, so it answers an error there instead.

Source lib/time.mu:65
fn monotonic_ns() {
    platformKey := platform()
    if platformKey == "darwin/amd64" || platformKey == "darwin/arm64" {
        return error("time.monotonic_ns: no monotonic clock syscall on " + platformKey)
    }
    b := _make_zero_buffer(16)
    result := syscall("clock_gettime", 1, b)
    if is_error(result) {
        return result
    }
    return _time_word(b, 0, 8) * 1000000000 + _time_word(b, 8, 8)
}

now_ms#

fn now_ms()

now_ms returns the current Unix time in milliseconds.

Source lib/time.mu:52
fn now_ms() {
    ns := now_ns()
    if is_error(ns) {
        return ns
    }
    return ns / 1000000
}

now_ns#

fn now_ns()

now_ns returns the current Unix time in NANOSECONDS. This is the first clock in the language finer than a second: before it, an elapsed interval under 1s measured as 0 and every process started within the same second seeded rand identically.

clock_gettime(CLOCK_REALTIME) on linux -- the asm-generic ABI has no gettimeofday with a writable timeval in its modern form and clock_gettime is the one clock every linux has -- and gettimeofday on darwin, whose kernel has no clock_gettime syscall (libc fakes one in userspace), so the resolution there is the timeval's microseconds.

Source lib/time.mu:32
fn now_ns() {
    platformKey := platform()
    b := _make_zero_buffer(16)
    if platformKey == "darwin/amd64" || platformKey == "darwin/arm64" {
        result := syscall("gettimeofday", b, 0)
        if is_error(result) {
            return result
        }
        // struct timeval: tv_sec int64 at 0, tv_usec int32 at 8.
        return _time_word(b, 0, 8) * 1000000000 + _time_word(b, 8, 4) * 1000
    }
    result := syscall("clock_gettime", 0, b)
    if is_error(result) {
        return result
    }
    // struct timespec: tv_sec at 0, tv_nsec at 8.
    return _time_word(b, 0, 8) * 1000000000 + _time_word(b, 8, 8)
}

sleep#

fn sleep(seconds)

sleep pauses execution for the requested number of seconds, using platform syscalls plus a fallback busy-wait.

The duration may be an INTEGER or a Decimal: both syscalls underneath take a seconds/sub-seconds pair, so sleep(0.15) is representable and sleeps for 150ms. It used to reject anything non-INTEGER with an ERROR value, which nobody checks on a sleep, so a fractional sleep silently did nothing at all.

Source lib/time.mu:119
fn sleep(seconds) {
    if !typing.is_int(seconds) && type(seconds) != "Decimal" {
        return error("time.sleep expects a number of seconds, got " + type(seconds))
    }
    if seconds < 0 {
        return error("time.sleep expects a non-negative duration")
    }
    platformKey := platform()
    if platformKey == "linux/amd64" || platformKey == "linux/arm64" ||
        platformKey == "linux/riscv64" {
        parts := split_duration(seconds, 1000000000)
        req := _make_timespec(parts[0], parts[1])
        syscall("nanosleep", req, 0)
        return nil
    }
    if platformKey == "darwin/amd64" || platformKey == "darwin/arm64" {
        parts := split_duration(seconds, 1000000)
        req := _make_timeval(parts[0], parts[1])
        syscall("select", 0, 0, 0, 0, req)
        return nil
    }
    // Fallback for a platform with neither syscall. time() has one-second
    // resolution, so a fractional duration is only honoured to the extent the
    // clock can see it.
    target := time() + seconds
    while time() < target {
    }
    return nil
}

split_duration#

fn split_duration(seconds, unit)

split_duration splits a duration in seconds into whole seconds plus the sub-second remainder expressed in unitths of a second (10^9 for a timespec's nanoseconds, 10^6 for a timeval's microseconds).

An INTEGER passes straight through with a zero remainder, so sleep(0) stays exactly zero -- §4.4 makes a zero-duration sleep a pure yield, and a stray nanosecond would turn it into a real one. A Decimal is truncated toward zero: a sub-unit tail is dropped rather than rounded up, matching what the kernel would do with the digits it cannot represent.

The Decimal round-trips through .str() because int() does not accept a Decimal directly, and the syscall staging buffers take INTEGERs.

Source lib/time.mu:103
fn split_duration(seconds, unit) {
    if typing.is_int(seconds) {
        return [seconds, 0]
    }
    whole := seconds.trunc(0)
    frac := ((seconds - whole) * unit).trunc(0)
    return [int(whole.str()), int(frac.str())]
}

time#

fn time()

time returns the current Unix time in seconds using the available host syscall.

Source lib/time.mu:8
fn time() {
    platformKey := platform()
    if platformKey == "darwin/amd64" || platformKey == "darwin/arm64" ||
        platformKey == "linux/arm64" || platformKey == "linux/riscv64" {
        buf := _make_zero_buffer(16)
        syscall("gettimeofday", buf, 0)
        return _unixtime_from_timeval(buf)
    }
    if platformKey == "linux/amd64" {
        return syscall("time", 0)
    }
    return error("time.time: unsupported platform " + platformKey)
}

Internal helpers

Underscore-prefixed names are implementation detail. They are listed so the module's source reads without surprises, not as API — they may change at any time.

_date_from_days(days)
_days_in_month(year, month)
_is_leap_year(year)
_make_timespec(seconds, nanos)
_make_timeval(seconds, micros)
_make_zero_buffer(length)
_put_uint64_le(b, off, value)put_uint64_le writes value into b as eight little-endian bytes at off.
_time_word(b, off, width)_time_word reads a little-endian unsigned word of width bytes at off.
_unixtime_from_timeval(b)unixtime_from_timeval reads the seconds field from a 16-byte timeval BUFFER.