Standard library / primitives
Time & random
-- Doc example: dates as types (date / datetime / duration, IANA zones, DST), formatting and parsing,
-- and randomness: seeded generators (pure, numpy's numbers) and OS randomness (`require random`).
intent: "doc example: time and random"
require random -- only for random() / random_int(lo, hi) without a generator
let g be rng(42)
print("rng(42)() = " + text(g()) + ", Madrid 01:30 + 1h = " + text(datetime("2026-03-29T01:30:00", "Europe/Madrid") + duration(hours = 1)))
test "format_time / parse_time: unix timestamps, pure (no clock), date-only = midnight UTC"
assert_eq(format_time(0), "1970-01-01T00:00:00Z")
assert_eq(format_time(0, "%Y-%m-%d %H:%M"), "1970-01-01 00:00")
assert_eq(parse_time("1970-01-01T00:00:00Z"), 0)
assert_eq(parse_time("2026-01-03", "%Y-%m-%d"), 1767398400.0)
assert_eq(date_parts(0).year, 1970)
assert(now() > 1000000000) -- now() reads the clock: `time`, auto-granted under run/test
test "three types: date, datetime, duration"
assert_eq(type_of(date(2026, 1, 31)), "date")
assert_eq(date("2026-01-31"), date(2026, 1, 31))
assert_eq(type_of(datetime("2026-01-03T10:00:00Z")), "datetime")
assert_eq(type_of(duration(hours = 1)), "duration")
assert_eq(text(date(2026, 1, 31)), "2026-01-31")
assert_eq(text(datetime("2026-01-03T10:00:00Z")), "2026-01-03T10:00:00Z")
assert_eq(text(datetime(2026, 1, 3, 10, 0, 0, "UTC")), "2026-01-03T10:00:00Z")
assert_eq(text(datetime("2026-01-03")), "2026-01-03T00:00:00Z") -- a bare date is midnight UTC
assert_eq(text(datetime(0)), "1970-01-01T00:00:00Z") -- from unix seconds
assert_eq(text(datetime(1767434400)), "2026-01-03T10:00:00Z")
assert_eq(text(duration(hours = 1, minutes = 30)), "PT1H30M")
assert_eq(text(duration(hours = 1.5)), "PT1H30M")
assert_eq(text(duration(weeks = 1)), "P7D")
assert_eq(text(duration(days = 1)), "P1D")
assert_eq(text(duration(seconds = 90)), "PT1M30S")
assert_eq(text(duration(milliseconds = 5)), "PT0.005S")
assert_error(() => date(2026, 2, 30)) -- not a calendar date
assert_error(() => date("31/01/2026")) -- date(text) is ISO only: parse_date for others
test "zones: IANA names follow DST, fixed offsets are kept"
let madrid be datetime("2026-03-29T01:30:00", "Europe/Madrid") -- local time + zone
assert_eq(text(madrid), "2026-03-29T01:30:00+01:00[Europe/Madrid]")
assert_eq(text(datetime("2026-01-03T10:00:00-03:00")), "2026-01-03T10:00:00-03:00")
assert_eq(text(datetime("2026-09-24T02:00:00+0530")), "2026-09-24T02:00:00+05:30")
assert_eq(date_parts(datetime("2026-09-24T02:00:00+05:30")).hour, 2)
assert_eq(date_parts(datetime("2026-09-24T02:00:00+05:30")).zone, "+05:30")
assert_eq(text(datetime("2026-01-03T10:00:00[Europe/Madrid]")), "2026-01-03T10:00:00+01:00[Europe/Madrid]")
assert_error(() => datetime("2026-01-03T10:00:00+05:30[Europe/Madrid]")) -- offset and zone disagree
assert_eq(text(datetime("2026-01-03T10:00:00Z", "America/Sao_Paulo")), "2026-01-03T07:00:00-03:00[America/Sao_Paulo]")
assert_eq(text(datetime(date(2026, 1, 31), "Europe/Madrid")), "2026-01-31T00:00:00+01:00[Europe/Madrid]")
assert_eq(text(datetime(0, "Asia/Kolkata")), "1970-01-01T05:30:00+05:30[Asia/Kolkata]")
assert_eq(date(datetime("2026-01-03T23:30:00-03:00")), date(2026, 1, 3)) -- the day in its own zone
assert_error(() => datetime("2026-03-29T02:30:00", "Europe/Madrid")) -- falls in the DST gap
assert_eq(text(datetime("2026-10-25T02:30:00", "Europe/Madrid")), "2026-10-25T02:30:00+02:00[Europe/Madrid]") -- repeated hour: the first
assert_error(() => datetime("2026-01-03T10:00:00Z", "Mars/Olympus")) -- unknown zone
assert_eq(text(to_timezone(datetime("2026-01-03T10:00:00Z"), "-0300")), "2026-01-03T07:00:00-03:00")
assert_eq(text(to_timezone(datetime("2026-01-03T10:00:00Z"), "+05")), "2026-01-03T15:00:00+05:00")
let parts be date_parts(datetime("2026-03-29T01:30:00", "Europe/Madrid"))
assert_eq([parts.year, parts.month, parts.day, parts.hour, parts.minute, parts.second, parts.weekday, parts.yearday, parts.zone], [2026, 3, 29, 1, 30, 0, 7, 88, "Europe/Madrid"])
assert_eq(text(to_timezone(datetime("2026-01-03T10:00:00Z"), "Asia/Kolkata")), "2026-01-03T15:30:00+05:30[Asia/Kolkata]")
assert_eq(text(to_timezone(datetime("2026-01-03T10:00:00Z"), "-03:00")), "2026-01-03T07:00:00-03:00")
test "arithmetic: elapsed time vs calendar steps"
assert_eq(text(datetime("2026-03-29T01:30:00", "Europe/Madrid") + duration(hours = 1)), "2026-03-29T03:30:00+02:00[Europe/Madrid]")
assert_eq(date("2026-03-01") - date(2026, 1, 31), duration(days = 29))
assert_eq(datetime("2026-01-01T12:00:00Z") - datetime("2026-01-01T09:00:00Z"), duration(hours = 3))
assert_eq(date(2026, 1, 31) + duration(days = 1), date(2026, 2, 1))
assert_error(() => date(2026, 1, 1) + duration(hours = 1)) -- a date moves by whole days
assert_eq(duration(hours = 3) / duration(minutes = 30), 6.0)
assert_eq(duration(hours = 1) * 2, duration(hours = 2))
assert_eq(duration(hours = 1) / 4, duration(minutes = 15))
assert_eq(text(duration(hours = 1) - duration(hours = 2)), "-PT1H")
let noon be datetime("2026-03-28T12:00:00", "Europe/Madrid")
assert_eq(text(add_days(noon, 1)), "2026-03-29T12:00:00+02:00[Europe/Madrid]") -- same local time
assert_eq(text(noon + duration(days = 1)), "2026-03-29T13:00:00+02:00[Europe/Madrid]") -- exactly 24 h
assert_eq(text(add_days(datetime("2026-03-28T02:30:00", "Europe/Madrid"), 1)), "2026-03-29T03:30:00+02:00[Europe/Madrid]") -- lands in the gap: moved forward
assert_eq(add_days(date(2026, 1, 31), 1), date(2026, 2, 1))
assert_eq(add_months(date(2026, 1, 31), 1), date(2026, 2, 28)) -- clamps to the month's end
assert_eq(add_months(date(2024, 1, 31), 1), date(2024, 2, 29))
assert_eq(add_months(date(2026, 3, 31), -1), date(2026, 2, 28))
test "comparison is by instant; a date is not a datetime"
assert(datetime("2026-01-01T12:00:00Z") == datetime("2026-01-01T09:00:00-03:00"))
assert(date(2026, 1, 1) < date(2026, 1, 2))
assert_error(() => date(2026, 1, 1) < datetime("2026-01-01T00:00:00Z"))
assert_eq(min([date(2026, 2, 1), date(2026, 1, 1)]), date(2026, 1, 1))
assert_eq(sort([duration(hours = 2), duration(minutes = 1)]), [duration(minutes = 1), duration(hours = 2)])
assert_eq(max([datetime("2026-01-01T12:00:00Z"), datetime("2026-01-01T10:00:00-03:00")]), datetime("2026-01-01T13:00:00Z"))
assert_eq(sort_by([{"d": date(2026, 2, 1)}, {"d": date(2026, 1, 1)}], (r) => r.d)[0].d, date(2026, 1, 1))
test "truncate: the start of a period (group by month, week...)"
let t be datetime("2026-05-14T15:42:10", "Europe/Madrid")
assert_eq(text(truncate(t, "month")), "2026-05-01T00:00:00+02:00[Europe/Madrid]")
assert_eq(truncate(date(2026, 5, 14), "year"), date(2026, 1, 1))
assert_eq(truncate(date(2026, 5, 14), "quarter"), date(2026, 4, 1))
assert_eq(truncate(date(2026, 5, 14), "week"), date(2026, 5, 11)) -- Monday
assert_eq(truncate(date(2026, 5, 14), "day"), date(2026, 5, 14))
let u be datetime("2026-05-14T15:42:10Z")
assert_eq(text(truncate(u, "hour")), "2026-05-14T15:00:00Z")
assert_eq(text(truncate(u, "minute")), "2026-05-14T15:42:00Z")
assert_eq(text(truncate(datetime("2026-05-14T15:42:10.5Z"), "second")), "2026-05-14T15:42:10Z")
-- a day without midnight (DST jump at 00:00) starts when it starts
assert_eq(text(truncate(datetime("2024-09-08T15:00:00", "America/Santiago"), "day")), "2024-09-08T01:00:00-03:00[America/Santiago]")
test "date_range: inclusive, by calendar unit or by elapsed time"
assert_eq(date_range(date(2026, 1, 1), date(2026, 1, 3)), [date(2026, 1, 1), date(2026, 1, 2), date(2026, 1, 3)])
assert_eq(date_range(date(2026, 1, 1), date(2026, 3, 1), "month"), [date(2026, 1, 1), date(2026, 2, 1), date(2026, 3, 1)])
assert_eq(length(date_range(date(2026, 1, 1), date(2026, 1, 15), "week")), 3)
assert_eq(length(date_range(date(2026, 1, 1), date(2027, 1, 1), "quarter")), 5)
assert_eq(length(date_range(date(2020, 1, 1), date(2026, 1, 1), "year")), 7)
let hours be date_range(datetime("2026-01-01T00:00:00Z"), datetime("2026-01-01T02:00:00Z"), "hour")
assert_eq(length(hours), 3)
assert_eq(length(date_range(datetime("2026-01-01T00:00:00Z"), datetime("2026-01-01T00:02:00Z"), "minute")), 3)
assert_eq(length(date_range(datetime("2026-01-01T00:00:00Z"), datetime("2026-01-01T00:00:02Z"), "second")), 3)
assert_eq(length(date_range(datetime("2026-01-01T00:00:00Z"), datetime("2026-01-01T01:00:00Z"), duration(minutes = 20))), 4)
let days be date_range(datetime("2026-03-28T12:00:00", "Europe/Madrid"), datetime("2026-03-30T12:00:00", "Europe/Madrid"))
assert_eq(text(days[1]), "2026-03-29T12:00:00+02:00[Europe/Madrid]") -- the local time survives DST
assert_error(() => date_range(date(2026, 1, 1), date(2026, 1, 2), "hour")) -- a date has no hours
assert_error(() => date_range(date(2026, 1, 1), datetime("2026-01-02T00:00:00Z"))) -- one type for both ends
test "timestamp and in_units"
assert_eq(timestamp(datetime("1970-01-02T00:00:00Z")), 86400.0)
assert_error(() => timestamp(date(2026, 1, 1))) -- a datetime only
assert_eq(in_units(duration(hours = 1, minutes = 30), "minutes"), 90.0)
assert_eq(in_units(duration(days = 14), "weeks"), 2.0)
assert_eq(in_units(duration(days = 1), "days"), 1.0)
assert_eq(in_units(duration(hours = 1), "hours"), 1.0)
assert_eq(in_units(duration(minutes = 1), "seconds"), 60.0)
assert_eq(in_units(duration(seconds = 1), "milliseconds"), 1000.0)
test "parse_date / parse_datetime / format_time"
assert_eq(parse_date("31/01/2026", "%d/%m/%Y"), date(2026, 1, 31))
assert_eq(parse_date("2026-01-31"), date(2026, 1, 31))
assert_error(() => parse_date("2026/01/31", "%d/%m/%Y"))
assert_eq(text(parse_datetime("2026-01-03T10:00:00Z")), "2026-01-03T10:00:00Z")
assert_eq(text(parse_datetime("24/09/2026 02:00 +0530", "%d/%m/%Y %H:%M %z")), "2026-09-24T02:00:00+05:30")
assert_eq(text(parse_datetime("24/09/2026 02:00 +0530", "%d/%m/%Y %H:%M %z", "UTC")), "2026-09-23T20:30:00Z")
assert_eq(text(parse_datetime("24/09/2026 02:00", "%d/%m/%Y %H:%M", "Europe/Madrid")), "2026-09-24T02:00:00+02:00[Europe/Madrid]")
assert_eq(text(parse_datetime("03/01/2026", "%d/%m/%Y")), "2026-01-03T00:00:00Z") -- date-only: midnight
assert_eq(text(parse_datetime("03/01/2026", "%d/%m/%Y", "Europe/Madrid")), "2026-01-03T00:00:00+01:00[Europe/Madrid]")
-- %Z: only UTC and GMT identify a zone; any other abbreviation is an error
assert_eq(text(parse_datetime("Thu, 24 Sep 2026 10:00:00 GMT", "%a, %d %b %Y %H:%M:%S %Z")), "2026-09-24T10:00:00Z")
assert_eq(text(parse_datetime("24/09/2026 10:00 utc", "%d/%m/%Y %H:%M %Z")), "2026-09-24T10:00:00Z")
assert_error(() => parse_datetime("24/09/2026 10:00 IST", "%d/%m/%Y %H:%M %Z"))
assert_eq(text(parse_datetime("24/09/2026 10:00 +0530 IST", "%d/%m/%Y %H:%M %z %Z")), "2026-09-24T10:00:00+05:30") -- the offset decides
assert_eq(text(parse_datetime("24/09/2026 10:00", "%d/%m/%Y %H:%M", "Asia/Kolkata")), "2026-09-24T10:00:00+05:30[Asia/Kolkata]")
assert_eq(format_time(date(2026, 1, 31), "%d/%m/%Y"), "31/01/2026")
assert_eq(format_time(datetime("2026-03-29T01:30:00", "Europe/Madrid"), "%H:%M %z"), "01:30 +0100") -- in its own zone
assert_eq(format_time(datetime("2026-01-03T10:00:00Z")), "2026-01-03T10:00:00Z")
assert_error(() => format_time(date(2026, 1, 31), "%H")) -- a date has no hours
assert_error(() => format_time(0, "%Q")) -- not a strftime specifier
assert_eq(date_parts(date(2026, 1, 31)), {"year": 2026, "month": 1, "day": 31, "weekday": 6, "yearday": 31})
test "rng(seed): numpy's default_rng, bit for bit — pure, no capability"
assert_eq(rng(42)(), 0.7739560485559633) -- np.random.default_rng(42).random()
let g be rng(42)
assert_eq(random(g), 0.7739560485559633) -- random(g) is g()
assert_eq(random(g), 0.4388784397520523)
let d be random_int(g, 1, 6) -- inclusive: integers(1, 7)
assert(d >= 1 and d <= 6)
assert_eq(is_integer(d), true)
let x be random_normal(g, mean = 10, std = 2)
assert_eq(type_of(x), "number")
assert_eq(type_of(random_normal(g)), "number") -- mean 0, std 1
assert_error(() => random_normal(g, std = -1))
assert_error(() => random_int(g, 1.5, 6)) -- bounds are integers
assert_error(() => rng(-1))
test "same seed, same sequence; shuffle, choice, sample, rng_spawn"
assert_eq(shuffle(rng(0), ["a", "b", "c"]), shuffle(rng(0), ["a", "b", "c"]))
assert_eq(sort(shuffle(rng(1), [3, 1, 2])), [1, 2, 3]) -- a NEW list, same items
assert(choice(rng(1), ["a", "b", "c"]) in ["a", "b", "c"])
let picked be sample(rng(1), range(0, 100), 5)
assert_eq(length(unique(picked)), 5) -- distinct
assert_error(() => sample(rng(1), [1, 2], 3))
assert_error(() => choice(rng(1), []))
let kids be rng_spawn(rng(42), 2)
assert_eq(length(kids), 2)
assert_eq(kids[0](), 0.9167441575549085) -- np default_rng(42).spawn(2)[0].random()
assert(kids[0]() != kids[1]())
test "a generator is a process: an alias shares the sequence"
let a be rng(7)
let b be a
let first be a()
assert(b() != first) -- b continued a's sequence
assert_eq(rng(7)(), first)
test "parallel_map: one child per item with rng_spawn; the same generator twice is an error"
let draws be parallel_map((c) => c(), rng_spawn(rng(42), 3))
assert_eq(draws[0], 0.9167441575549085)
let c be rng(3)
assert_error(() => parallel_map((x) => x(), [c, c]))
assert_error(() => parallel_map((x) => g(), [1, 2])) -- g was created at the top level
assert_error(() => json_encode({"g": rng(1)})) -- a generator is not data
test "random() / random_int(lo, hi) without a generator: OS entropy, `require random`"
let r be random()
assert(r >= 0 and r < 1)
let d be random_int(1, 6)
assert(d >= 1 and d <= 6)The clock§
Only two builtins read or wait on the clock: now() (unix timestamp, a number) and sleep(seconds). They need the time capability, which is auto-granted under run/test (declare require time under serve). Everything else on this page — building, parsing, formatting, comparing and computing dates — is pure: no capability, the same answer every run.
now() -- unix timestamp (number) — needs time
sleep(0.5) -- pause — needs time
format_time(0) -- "1970-01-01T00:00:00Z" — ISO 8601 by default
format_time(0, "%Y-%m-%d %H:%M") -- strftime pattern
parse_time("2026-06-30T12:00:00Z") -- inverse of format_time: unix seconds (float)
parse_time("2026-01-03", "%Y-%m-%d") -- a date-only pattern is midnight UTC
date_parts(0) -- {year, month, day, hour, minute, second, ...}
format_time, parse_time and date_parts accept a plain unix timestamp. For real date work, use the three types below.
Dates as types: date, datetime, duration§
| Type | What it is | Build it | Shows as |
|---|---|---|---|
date | a civil day — no time, no zone | date(2026, 1, 31), date("2026-01-31"), date(dt) (the day in dt's own zone) | 2026-01-31 |
datetime | an instant + its zone | see below | 2026-01-03T10:00:00Z, 2026-01-03T10:00:00-03:00, 2026-03-29T01:30:00+01:00[Europe/Madrid] |
duration | an exact amount of time | duration(days =, hours =, minutes =, seconds =, milliseconds =, weeks =) — all by name, all optional, fractions allowed | ISO 8601: PT1H30M, P29D, -PT1H |
type_of returns "date", "datetime" or "duration". date(text) reads only YYYY-MM-DD (for other formats, parse_date); an impossible day (date(2026, 2, 30)) is an error. A day inside a duration is exactly 24 hours.
Every way to build a datetime:
datetime("2026-01-03T10:00:00Z") -- UTC
datetime("2026-01-03T10:00:00-03:00") -- keeps its offset (+0530 without the colon reads too)
datetime("2026-03-29T01:30:00", "Europe/Madrid") -- local time in an IANA zone
datetime("2026-01-03T10:00:00[Europe/Madrid]") -- RFC 9557: local time in that zone
datetime("2026-01-03T10:00:00Z", "America/Sao_Paulo") -- a text with an offset + a zone: the same instant, converted
datetime("2026-01-03") -- midnight UTC
datetime(2026, 1, 3, 10, 0, 0, "UTC") -- hour/minute/second optional, zone last (default UTC)
datetime(1767434400) -- from unix seconds (a zone may follow)
datetime(date(2026, 1, 31), "Europe/Madrid") -- midnight of that day in that zone
Zones§
A zone is an IANA name ("Europe/Madrid", "America/Buenos_Aires"), "UTC", or a fixed offset ("+05:30", "-0300", "+05") — anywhere a zone goes (datetime, to_timezone, parse_datetime). An unknown zone is an error that suggests both forms.
- An IANA zone follows its daylight-saving rules. A fixed offset has none: for a city, use its IANA zone.
- A datetime written with an offset keeps it, as in Python, java.time, Temporal and pandas:
datetime("2026-09-24T02:00:00+05:30")prints as such, anddate_partsgives hour 2 and zone"+05:30". Calendar operations (date,truncate,add_days,add_months,date_range,format_time) work in that local time. …T10:00:00[Asia/Kolkata]is 10:00 local time there; an offset that does not match its bracketed zone (…+05:30[Europe/Madrid]) is an error.- A local time inside a DST gap (Madrid, 2026-03-29 02:30) is an error:
… does not exist in Europe/Madrid (it falls in a daylight-saving gap). A local time in the hour that repeats when clocks go back takes the first of the two. to_timezone(dt, zone)→ the same instant seen in another zone (IANA or offset).date(dt)→ the civil day indt's own zone.
Arithmetic and comparison§
datetime ± durationis elapsed time, so it is DST-correct: Madrid2026-03-29T01:30+ 1 h =03:30+02:00(02:00–03:00 did not exist that night).date ± durationmoves by whole days only;date(2026, 1, 1) + duration(hours = 1)is an error (use a datetime).datetime − datetimeanddate − date→ a duration (date("2026-03-01") - date(2026, 1, 31)→P29D).duration ± duration,duration * number,duration / number→ duration;duration / duration→ float (duration(hours = 3) / duration(minutes = 30)→6.0).- Calendar steps are not durations.
add_days(t, n)keeps the local time across a DST change (add_daysof Madrid 03-28 12:00 → 03-29 12:00+02:00, while+ duration(days = 1)gives 13:00); a step that lands in a DST gap moves forward by the gap.add_months(t, n)clamps the day to the month's end (add_months(date(2026, 1, 31), 1)→2026-02-28); a negativengoes back; one year isadd_months(t, 12). <,==,sort,sort_by,min/maxwork within one type. Datetimes compare by instant, whatever their zones (12:00Z == 09:00-03:00). A date and a datetime do not compare (cannot order a date and a datetime): convert withdatetime(d, tz)ordate(dt).
Periods: truncate and date_range§
truncate(t, unit) → the start of the period: "year", "quarter", "month", "week" (Monday), "day", and on a datetime "hour", "minute", "second" — in the value's own zone. On a day with no midnight (a DST jump at 00:00, as in Santiago), "day" gives the first instant that exists (01:00). It is the tool for grouping by period: summarize(rows, (r) => truncate(r.when, "month"), {…}) — see Data analysis.
date_range(start, end, step?) → a list from start to end, inclusive. step is "day" (default), "week", "month", "quarter", "year", on datetimes also "hour", "minute", "second", or any positive duration. Calendar steps keep the local time across DST; hours, minutes, seconds and durations are elapsed time. Start and end must be the same type; asking a date for "hour" is an error.
Parsing and formatting§
parse_date("31/01/2026", "%d/%m/%Y") -- 2026-01-31
parse_datetime("24/09/2026 02:00 +0530", "%d/%m/%Y %H:%M %z") -- keeps +05:30
parse_datetime("24/09/2026 02:00", "%d/%m/%Y %H:%M", "Europe/Madrid") -- no offset: local time in that zone
parse_datetime("03/01/2026", "%d/%m/%Y") -- date-only: 2026-01-03T00:00:00Z
format_time(date(2026, 1, 31), "%d/%m/%Y") -- "31/01/2026"
parse_date(text, format?)andparse_datetime(text, format?, tz?)use strftime specifiers; without a format they read ISO 8601. A format without an offset reads local time intz(default UTC). With%zthe text's offset is kept — or converted totzif you pass one. A date-only format is midnight (intz, if given).%Zaccepts onlyUTCandGMT(any case, as in Python — an HTTP date parses). Any other abbreviation is an error, because it does not identify a zone (ISTis India, Ireland or Israel). Write%zfor an offset, or drop the abbreviation from the text and pass the zone:parse_datetime(text, format, "Asia/Kolkata"). When the format also has%z("%z %Z"on"+0530 IST"), the offset decides and the abbreviation is just text.- A text that does not match the format is an error that names both.
format_time(value, pattern?)takes a timestamp, adateor adatetime; a datetime formats in its own zone. An unknown specifier (%Q) or one the value lacks (%Hof a date) is a catchable error that names it.timestamp(dt)→ float unix seconds (a datetime only).in_units(d, unit)→ float, with unit"weeks","days","hours","minutes","seconds"or"milliseconds".date_parts(x)→ a map: a datetime givesyear,month,day,hour,minute,second,weekday(1 = Monday … 7 = Sunday),yeardayandzone, in its own zone; a date givesyear,month,day,weekday,yearday.json_encodeandcsv_encodewrite the ISO text. JSON has no date type, sojson_decodegives text back: re-type it withdate(x)/datetime(x). CSV and Parquet read typed columns: see Files & I/O.
Seeded randomness — rng(seed), pure§
rng(seed) returns a generator: bit for bit numpy.random.default_rng(seed) (SeedSequence + PCG64), so the same seed gives numpy's numbers on every platform. It is pure — no capability, allowed under --deterministic, in sandbox and in tests — which makes it the tool for train/test splits, simulations and reproducible samples.
let g be rng(42)
g() -- 0.7739560485559633, like np.random.default_rng(42).random()
random(g) -- the same as g()
random_int(g, 1, 6) -- inclusive: g.integers(1, 7)
random_normal(g, mean = 0, std = 1) -- g.normal(0, 1); mean and std by name, optional
shuffle(g, [1, 2, 3]) -- a NEW list: g.permutation(xs)
choice(g, ["a", "b", "c"]) -- one item: g.choice(xs)
sample(g, range(0, 100), 5) -- 5 distinct items: g.choice(xs, 5, replace=False)
rng_spawn(g, 4) -- 4 independent generators: g.spawn(4)
seedis an integer ≥ 0 of any size.random_intbounds must be integers;stdmust be ≥ 0;choiceof an empty list andsampleof more items than the list has are errors.- A generator is a process, not a value, as in numpy:
let h be gor passinggto a task gives the SAME generator, so they take turns on one sequence. For independent streams, userng_spawn(g, n)or separate seeds. - With
parallel_map, pass one child per item:parallel_map(f, rng_spawn(g, n))gives exactly numpy's[c.random() for c in g.spawn(n)], and the caller's generators advance by what the workers drew. The same generator in two items is an error (each worker would draw the same numbers). A generator created at the top level cannot be used inside aparallel_mapworker or aserverequest — it would restart the same sequence each time; create it there from something per-request (rng(int(params.seed))). - A generator is not data:
json_encoderefuses it.
OS randomness — deny-by-default§
Without a generator, randomness comes from the operating system and needs require random — even under run. Randomness is what tokens and nonces are made of, so it is a capability, not a freebie.
require random
random() -- float in [0, 1)
random_int(1, 6) -- integer in [min, max], integer bounds
random_bytes(16) -- n bytes from the OS CSPRNG
token() -- 32 CSPRNG bytes as base64url — session ids, CSRF tokens
Neither random() nor rng is for secrets: tokens, nonces and salts go through random_bytes/token, which read the OS CSPRNG. More in Login & sessions.