clock is the friendly layer over Shoddy's three timing
built-ins: Ticks, Sleep, and Clock. It
serves any console program that needs to measure how long something took,
stamp a log line with the date and time, wait until a moment arrives, or turn
a raw duration into readable words. It gives you Elapsed and
TimeIt for timing, SleepUntil for pacing,
Stamp and friends for ISO-8601 timestamps (the international
year-first format, like 2026-07-20T14:03:07), and
FormatDuration for turning milliseconds into
"1m 04.2s". The built-ins underneath are always callable without
including anything. This machine is simply the derived, window-independent
convenience layer over them, in the same spirit that
vt100 is a derived layer over Chr.
When Unix needed a way to remember a moment, its authors did not store a
calendar date — they stored a count. Pick one instant, call it zero, and
every other moment is just a number of seconds before or after it. The first
edition of the manual, in 1971, counted sixtieths of a second from the start
of that year. That counter overflowed a 32-bit word — the fixed-size
number box computers of the day used — in under three years. So the
count was recut to whole seconds, and the zero settled where it has been ever
since: midnight at the start of the 1st of January 1970, UTC (the world's
reference time, unshifted by any time zone). That arbitrary midnight became
the most load-bearing instant in computing. Every timestamp in this
repository, every file's modified-time, every certificate's expiry is a
distance from it. The famous year-2038 problem is nothing more than the
seconds count outgrowing a signed 32-bit box — which a Shoddy
Number, being a double, sails past without noticing.
Clock() hands you the civil pieces of "now", and
julian turns them back into exactly this kind of
count. Fifty years on, nobody has found a better way to remember a moment
than a number.
Programs need to know about time in two quite different ways, and the whole design of this machine turns on keeping them apart. Sometimes you want to measure: how long did that computation take, how much time has passed since the last frame, is it time to draw again yet? And sometimes you want to stamp: what is the wall-clock date and time right now, so I can label this log line or name this file? Those sound similar, but they demand opposite things from a clock. Shoddy gives you two clocks precisely so you never have to choose the wrong one.
Ticks() is for measuring. It counts
milliseconds since your program started. And — this is the key word — it is
monotonic: it only ever goes forward, at a steady pace, never
jumping. That makes it perfect for deltas, frame pacing, and benchmarks, where
all you care about is how much time elapsed between two readings. It is also
the only clock animation should ever use, because a clock that never jumps
backward is a clock that never makes your animation stutter or leap.
Clock() is for stamping. It reports the
wall-clock time — the actual calendar date and time on the wall — as a
7-element array: [year, month, day, hour, minute, second, ms].
That's exactly what you want on a log line or in a filename. But wall-clock
time is not monotonic. Whenever the operating system adjusts the
system clock, Clock() jumps with it — a daylight-saving change,
or a network time sync nudging the clock a second or two either way. If you
measure a duration by subtracting two Clock() readings, a time
sync in the middle can hand you a negative answer, or a duration off by an
hour. That's why the two jobs get two clocks: use Ticks() to
measure, use Clock() to stamp, and never mix them.
For measuring, read Ticks() yourself and hand it to
Elapsed, or let TimeIt time a function for you. For
stamping, Stamp and its relatives read Clock() and
hand you a ready-made string. For pacing, SleepUntil waits until a
target tick arrives.
Include "clock.shoddy"
Def Work()
Sleep(1200) ' pretend this is real work
Def Main()
Let took = TimeIt(Work) ' run Work, return ms it took
Print(FormatDuration(took)) ' 1.2s
Let start = Ticks()
' ... some loop body ...
Print(Elapsed(start)) ' ms since start, monotonic
Print(Stamp()) ' 2026-07-20T14:03:07.042
Print(StampDate()) ' 2026-07-20
Print(StampTime()) ' 14:03:07.042
SleepUntil(Ticks() + 500) ' wait out the rest of a 500ms beat
A few things worth remembering:
Ticks() side — Elapsed,
TimeIt, SleepUntil. If you're asking "what time is it
now?", use the stamping words. Don't subtract two Stamps to time
something. A system-clock adjustment in the middle will lie to you.Clock() read per stamp.
Stamp reads the wall clock exactly once and formats both halves
from that single reading. So the date and time can never straddle midnight —
you'll never get yesterday's date bolted onto today's time because the clock
ticked over between two reads.Ticks, Sleep, or
Clock. A Def silently shadows a built-in of
the same name. Defining your own Ticks would therefore quietly
replace the real one everywhere — including inside this machine — and break
timing in ways that are miserable to track down. Steer clear of those three
names.SleepUntil never sleeps backwards. If the
target moment has already passed, it simply returns at once rather than
sleeping a negative amount — handy for pacing a loop that occasionally runs
slow.FormatDuration gives "4.2s" under a minute and
"1m 04.2s" above it, seconds always to one decimal place.The three timing words the runtime dispatches — not defined here, documented here
These are not clock's Defs. The engine dispatches
them, and a Def whose name is a builtin is refused. That is why
no word in this machine may be called Ticks, Sleep
or Clock. They are listed on this page because
clock is the machine whose domain they belong to, and they need
no Include. The same three are documented in
machines/clock.shoddy's own header block. All three are
effectful.
| Word | Description |
|---|---|
| Ticks() | Monotonic milliseconds since the program started,
fractional. The clock for measuring — frame pacing, deltas,
benchmarks — and the only one animation may use, because it never jumps.
Elapsed and TimeIt below are the ordinary ways to
reach it. |
| Sleep(ms) | Yield the thread for that many milliseconds; 0 or
less waits not at all. This is what a poll loop puts between tries, so a
non-blocking socket or a scribbler window does not
spin a core. SleepUntil below is the deadline form. |
| Clock() | Wall-clock now, as a 7-element Array:
year, month, day, hour, minute, second, millisecond. The clock for
stamping — logs, filenames — and not for measuring.
It jumps whenever the operating system adjusts system time, which is the whole
reason Ticks exists separately. It is a flat Array rather than a
record because a builtin cannot reach a Type;
Stamp and the words below lift it into readable shapes. |
Every word, over the Ticks/Sleep/Clock builtins
| Word | Description |
|---|---|
| Elapsed(since) | Milliseconds that have passed since an earlier
Ticks() reading you pass in as since. Monotonic and
drift-free — the right way to time things. |
| TimeIt(f) | Runs the function f and hands back how
many milliseconds it took. A one-line benchmark. |
| SleepUntil(t) | Sleeps until Ticks() reaches the
target t. If t is already in the past, returns
immediately rather than waiting — good for keeping a loop on a steady
beat. |
| Word | Description |
|---|---|
| Stamp() | The current date and time as one ISO-8601 string, like
"2026-07-20T14:03:07.042". Reads the wall clock exactly once, so
the date and time halves are always from the same instant. |
| StampDate() | Just the date part of now, like
"2026-07-20". |
| StampTime() | Just the time part of now, like
"14:03:07.042". |
| DateOf(c) | Formats the date from a Clock() array
c ([year, month, day, ...]) as
"YYYY-MM-DD". Use it when you've already taken a
Clock() reading of your own. |
| TimeOf(c) | Formats the time from a Clock() array
c as "HH:MM:SS.mmm", milliseconds and all. |
| ClockDate() | Today, as julian's
JulDate — the one bridge between this capability and the pure
calendar. One Clock() read, so it cannot straddle midnight.
Everything a date can do (serials, Julian day numbers, weekdays, date
arithmetic) happens over in julian, purely, after this word has
run. |
| Word | Description |
|---|---|
| FormatDuration(ms) | Turns a number of milliseconds into readable
words: "4.2s" under a minute, "1m 04.2s" at or above
one, seconds to one decimal place. Whole seconds stay bare, so 65 seconds
is "1m 05s" and not "1m 05.0s". |
Sharing a program with money?
Include both bare — they get along. (They once collided over twin private
Pad2 helpers. Both now lean on str's
PadZero instead, each bringing it in through its own
Include, and compiled include-once means one copy serves
everybody.)
| User | How | |
|---|---|---|
| pac-vt100 | SleepUntil
paces the loop to one tick per 100 ms frame without drift. |
| Machine | Why | |
|---|---|---|
| str | PadZero
keeps the stamp fields two and three digits wide — "07", never
"7". | |
| julian | For
one word: ClockDate(), the bridge from “now” into
the pure calendar. The direction is load-bearing. clock includes julian
and never the other way round, so fin, geo and ephemeris compute with
dates while staying out of the capability system entirely. |