# Clock
`clock/*` is the TCL-style date and time ensemble. Every value crossing
the namespace boundary is a number: epoch seconds is the universal
currency (all of `format`, `scan`, and `add` take or return it), and
`clock/format` / `clock/scan` are strict duals driven by one
strptime-style specifier table. `clock/clicks` is the exception — a
monotonic nanosecond counter, meaningful only as a difference between
two samples. All functions are registered in every host and need no
import.
## Functions
| Function | Purpose |
|---|---|
| `clock/seconds` | Current wall-clock time as integer seconds since the Unix epoch |
| `clock/millis` | Current wall-clock time in epoch milliseconds |
| `clock/micros` | Current wall-clock time in epoch microseconds |
| `clock/clicks` | High-resolution monotonic timestamp in nanoseconds (not epoch-anchored) |
| `clock/format time ?format? ?timezone?` | Format epoch seconds as a string; spec-map form `%{ :format :timezone :locale }` |
| `clock/scan str %{ :format ... }` | Parse a string into epoch seconds; `:format` is required |
| `clock/add time n :unit ...` | Calendar-aware date arithmetic; spec-map form `%{ :days n :months n :timezone ... }` |
Wall clock vs monotonic clock:
- `clock/seconds`, `clock/millis`, and `clock/micros` read the system
wall clock. They can jump backwards when the clock is adjusted, so
they are wrong for measuring durations.
- `clock/clicks` reads a monotonic counter. Its origin is unspecified
and may differ between runs and processes — only the difference
between two readings is meaningful. Use it for elapsed-time and
profiling measurements.
Formatting and parsing:
- `clock/format` defaults to `"%Y-%m-%dT%H:%M:%S%z"` (ISO-8601) and to
the local timezone. Timezones are `"UTC"`, `"local"`, or a signed
`+HHMM` / `+HHMMSS` offset.
- Both functions share one specifier table — `%Y %m %d %H %M %S %j`
%u %A %B %z` and the rest, plus the composites `%T %R %D %F %r %+`.`
- `clock/scan` is deliberately strict: free-form parsing is not
supported and `:format` is mandatory. Missing time-of-day fields
resolve to midnight; missing date fields fall back to `:base`
(default: now); a `%z` in the input overrides the `:timezone` option.
Malformed input returns `%Err{:message}`.
Calendar arithmetic:
- Units are `:second :minute :hour :day :week :month :year`; positional
deltas apply left to right. `:second`/`:minute`/`:hour` add fixed
durations directly to the epoch value.
- `:day` and `:week` are wall-clock-preserving: `clock/add` converts to
broken-down time in `:timezone`, adds the days, and converts back.
- `:month` and `:year` are calendar arithmetic and clamp the day to the
target month's last day (Jan 31 + 1 month becomes Feb 28/29).
- `clock/add` interprets calendar units in `:timezone` (default:
`"local"`). Pass the timezone in the spec-map form so the arithmetic
happens in the same zone as the timestamp you built with
`clock/scan`.
## Examples
### Reading the wall clock
set now [clock/seconds] echo [clock/format $now] # ISO-8601, local tz echo [clock/format $now "%Y-%m-%d %H:%M:%S" "UTC"] echo [clock/format 0 "%A, %B %d %Y" "UTC"] # Thursday, January 01 1970 echo [clock/millis] # epoch milliseconds
### Parsing and calendar arithmetic
set t [clock/scan "2024-01-31 14:30:00" %{ :format "%Y-%m-%d %H:%M:%S" :timezone "UTC" }] # Month-end clamping, using the same timezone for the arithmetic: echo [clock/format [clock/add $t %{ :months 1 :timezone "UTC" }] "%F" "UTC"] # => "2024-02-29" # Fixed-duration units add to the epoch value directly: echo [clock/format [clock/add $t 26 :hours] "%F %T" "UTC"] # => "2024-02-01 16:30:00"
### Elapsed time with the monotonic clock
set start [clock/clicks] # ... work ... set elapsed-ns ([clock/clicks] - $start) echo "took" ($elapsed-ns / 1000000) "ms"
## See Also
- Concurrency — `sleep`, `after`, and timer scheduling
- Logging — log records carry millisecond epoch timestamps