# Time and single-threaded animation `sys.time` uses bare `i64` values: absolute instants are milliseconds since the Unix epoch in UTC; durations are milliseconds. Monotonic readings belong to a separate clock domain. Never subtract a wall-clock reading from a monotonic deadline. ## Clocks and waits | Call | Result | | --- | --- | | `time.now()` | Unix-epoch milliseconds; may jump when the system clock changes. | | `time.monotonic()` | Monotonic milliseconds for elapsed time and deadlines. | | `time.nanoTime()` | Monotonic nanoseconds, with the clock's available resolution. | | `time.sleep(ms)` | Sleep for a nonnegative duration. | | `time.sleepUntil(deadline)` | Wait for a monotonic millisecond deadline; returns immediately if past. | | `time.remaining(deadline, current)` | Nonnegative difference, saturating on overflow. | Clock errors return `-1`; sleeps return zero on success or `-1` on error, with `sys.libc.errno()` available. Interrupted sleeps resume rather than returning early. Wakeup times are approximate, and these calls do not wake a suspended laptop. Sleeping blocks the one Simple execution thread; use `gfx.wait` when window input must remain responsive. `seconds(n)`, `minutes(n)`, `hours(n)`, and `days(n)` convert signed counts to milliseconds. `toSeconds(ms)`, `toMinutes(ms)`, `toHours(ms)`, and `toDays(ms)` truncate toward zero. A duration day is exactly 24 hours, independent of daylight saving. `toNanos(ms)` and `fromNanos(ns)` convert between millisecond and nanosecond units. Multiplication overflow returns `-1`. Constants `SECOND`, `MINUTE`, `HOUR`, and `DAY` are also available. ## String interpolation Time formatting is built into the existing string interpolation syntax: | Format | Example | Named style | | --- | --- | --- | | `%t(instant)` or `%tF(instant)` | `2026-11-12T00:34:05.123Z` | `time.ISO` | | `%tr(instant)` | `12:34am` | `time.CLOCK12` | | `%tR(instant)` | `00:34` | `time.CLOCK24` | | `%tT(instant)` | `00:34:05` | `time.CLOCK24_SECONDS` | | `%tD(instant)` | `12/11/26` | `time.DATE` | | `%tM(duration)` | `02:03.456` | `time.ELAPSED` | For example, `"UTC %tR(sys.time.now())"`. Width and left alignment work as for strings: `%30t(instant)` and `%-30t(instant)`. Precision and numeric flags are rejected. `%tM` formats a duration, with total minutes rather than wrapping at an hour; negative durations keep their sign. Interpolation uses UTC, making its output stable across machines. `time.format(instant, style)` is the equivalent library call; `time.formatLocal(instant, style)` uses the system's local zone. Local ISO output includes a numeric offset instead of `Z` when nonzero, retaining offset seconds for historical zones. Invalid calendar values/styles produce ``. The ISO form retains all millisecond bits within the supported calendar range; short formats intentionally omit information. ## Calendar values and absolute parsing `time.utc(instant)` and `time.local(instant)` return a `DateTime` with fields `year`, `month`, `day`, `hour`, `minute`, `second`, `millisecond`, `weekday` (Sunday = 0), `offsetSeconds` (east of UTC), and `daylight`. A zero year means conversion failed. `time.zoneName(instant)` returns the local abbreviation or an empty string on error. Local conversion uses the operating system's zone rules, including DST at that instant; no time-zone database is downloaded. `time.makeUTC(year, month, day, hour, minute, second, millisecond)` validates and constructs an instant, returning `-1` on invalid fields. `time.valid(...)`, `time.leap(year)`, and `time.monthDays(year, month)` support calendar checks. Calendar conversion supports Gregorian years 1 through 9999, bounded by `time.MIN` and `time.MAX`, and rejects leap seconds. `time.parse(text)` accepts strict absolute ISO text: `YYYY-MM-DDTHH:mm:ss[.SSS]Z` or the same with an explicit `+HH:MM`/`-HH:MM` offset (optionally with `:SS`). Fractions have exactly three digits. No relative time parsing, implicit local zone, locale-dependent parsing, or named-zone conversion is provided. Offsets must have hours 00–23 and minutes/seconds 00–59. Invalid text or an out-of-range result returns `-1`. By design, this sentinel is indistinguishable from the valid instant `1969-12-31T23:59:59.999Z`; formatting and calendar conversion still support that instant. ## Cooperative timers Create `val timers = new sys.time.Timers();` on the game thread: | Operation | Meaning | | --- | --- | | `after(delay)` | Register a one-shot timer; return its positive ID, or `-1`. | | `every(period)` | Register a repeating timer; period must be positive. | | `afterAt(current, delay)`, `everyAt(current, period)` | Supply a monotonic reading explicitly, useful for deterministic tests. | | `cancel(id)` | Remove a timer; return whether it was present. | | `nextDeadline()` | Earliest monotonic deadline, or `-1` if empty. | | `poll(current)` | Return the next due ID, or `-1` if none. | Poll repeatedly to drain due timers. Deadlines order delivery, then registration ID breaks ties. Repeating timers advance from their previous deadline, skipping missed ticks to the first future deadline rather than drifting or replaying a backlog. Deadline overflow rejects registration or cancels a repeating timer. There are no callbacks or timer threads: the caller handles each returned ID. `gfx.wait(timeoutMs)` processes events after waiting for input or a timeout: zero polls, `-1` waits indefinitely, values below `-1` fail. It returns the same `OK`, `CLOSED`, or `ERROR` statuses as `gfx.poll`, and may wake early. Re-read the monotonic clock after each wake. This allows one thread to handle timers, input, simulation, and drawing without busy waiting. The [digital and analog clock example](examples/Clock.smp) combines these pieces. From the repository root, with graphics dependencies already cached: ```sh make -C chapter31 clock-example CTAGS= # Build without opening a window. make -C chapter31 run-clock CTAGS= # Build the backend and run. ``` Click the button or press Space to switch between green seven-segment digits and an analog dial with smoothly moving hands. Both display system-local time; Q/Escape exits. Passing any argument directly to the executable closes it after ten frames, switching faces halfway through. Hands are computed from the current time, so delayed frames do not create a simulation catch-up backlog.