{"name":"std/time.nu","source":"// stdlib/std/time.nu — wall-clock + monotonic time, sleep, calendar\n//\n//   ( now_ms )            → i      ms since Unix epoch (CLOCK_REALTIME)\n//   ( now_seconds )       → i      seconds since Unix epoch\n//   ( monotonic_ns )      → i      monotonic nanoseconds (NOT epoch-relative,\n//                                  use only for measuring elapsed time)\n//   ( sleep_ms ms )       → v      sleep for `ms` milliseconds\n//                                  (0 / negative ⇒ no-op; signal-safe retry)\n//   ( elapsed_ms_since t0 ) → i    convenience: monotonic_ns − t0 → ms\n//\n// Calendar (UTC):\n//\n//   ( time_from_unix i secs )      → Time         civil_from_days alg.\n//   ( time_to_unix Time t )        → i            inverse, days_from_civil\n//   ( time_now )                   → Time         convenience for now_seconds\n//   ( time_make Y Mo D H Mi S )    → ! i ParseErr   validated → Unix secs\n//   ( is_leap_year i y )           → b\n//   ( days_in_month i y i m )      → i             leap-aware month length\n//   ( time_yday Time t )           → i             day of year, 1..365/366\n//   ( time_cmp a b )               → i             -1 / 0 / 1\n//   ( time_eq / time_before / time_after a b ) → b\n//   ( time_add_seconds Time t i n )  → Time         (negative subtracts)\n//   ( time_add_days    Time t i n )  → Time         rolls month/year over\n//   ( time_diff_seconds a b )      → i             signed a − b\n//   ( time_format Time t s fmt )   → String        strftime subset\n//   ( time_format_iso  Time t )    → String        \"2026-05-06T17:30:45Z\"\n//   ( time_format_http Time t )    → String        \"Wed, 06 May 2026 17:30:45 GMT\"\n//   ( time_parse_iso s txt )       → ! i ParseErr   parsed → Unix secs\n//\n// time_make / time_parse_iso yield the Unix timestamp (an `i`), not a\n// `Time` — a narrow `i` payload is immune to the wide-`! T E`-payload\n// truncation bug. Convert with `time_from_unix` when you need fields.\n//\n// Typical benchmark pattern:\n//   : i t0 ( monotonic_ns )\n//   ( do_work )\n//   : i ms ( elapsed_ms_since t0 )\n//   ( nurl_print ( nurl_str_int ms ) )\n//\n// Caveats:\n//   - `now_ms` is wall-clock; subject to NTP slew. Use `monotonic_ns`\n//     for elapsed-time measurements.\n//   - All timestamps are i64; ns-precision overflows i64 in ~292 years.\n//   - The calendar core (above) is UTC only. Local-time conversion and\n//     DST live in §12b below (tz_parse / time_from_unix_tz / …), driven\n//     by a POSIX TZ string; there is no IANA tzdata region lookup.\n\n$ `stdlib/core/string.nu`\n$ `stdlib/core/errors.nu`\n$ `stdlib/core/posix.nu`  // posix_const + nurl_errno_get + nurl_native_sizeof\n// sleep_ms is fiber-aware via the runtime — parks on the reactor's\n// timer wheel when invoked from a fiber, blocks via nanosleep when\n// invoked from a plain OS thread.\n$ `stdlib/std/async_ffi.nu`\n\n// ── §12 Time — pure-NURL libc FFI ────────────────────────────────────\n//\n// PURIFY (2026-05-24): the four runtime helpers (`nurl_now_ms` /\n// `_now_seconds` / `_monotonic_ns` / `_sleep_ms`) are gone — every\n// supported target reaches `clock_gettime(2)` / `nanosleep(2)` through\n// libc directly. Linux + macOS expose them in their primary libc;\n// MinGW-w64 routes through winpthreads (we already link `-lpthread` on\n// Win32 from Phase 6); wasi-libc wraps the WASI snapshot-1 syscalls.\n//\n// Buffer layout: `struct timespec` is `{ time_t tv_sec; long tv_nsec; }`.\n// On every supported 64-bit POSIX target it is 16 bytes (8 + 8); on\n// MinGW Win32 it is 12 bytes packed (`long` is 32 bits on LLP64) but\n// the struct's natural alignment is 8, so its sizeof rounds to 16.\n// A zeroed two-word `Timespec` on the caller's stack covers every\n// layout: the trailing pad on Win32/WASI stays zero, so `nsec` reads the\n// 32-bit `tv_nsec` extended with zeros — the correct value. The C\n// functions write through `inout`, so no buffer is allocated or freed.\n\n: Timespec { i sec i nsec }\n\n& `c` @ clock_gettime i32 clock_id inout Timespec tp → i32\n\n& `c` @ nanosleep inout Timespec req inout Timespec rem → i32\n\n& `c` @ nurl_monotonic_ns → i\n\n& `c` @ nurl_realtime_ns → i\n\n// Does this runtime multiplex EVERYTHING onto the calling thread? 1 on\n// the freestanding runtime, where \"threads\" are coroutines on one vCPU;\n// 0 wherever the operating system schedules for us. It is the only\n// question `sleep_ms` needs to answer below, and only the runtime can\n// answer it.\n& `c` @ nurl_runtime_is_cooperative → i\n\n@ now_ms → i {\n    ^ / ( nurl_realtime_ns ) 1000000\n}\n\n@ now_seconds → i {\n    ^ / ( nurl_realtime_ns ) 1000000000\n}\n\n// CLOCK_MONOTONIC in nanoseconds. Answered by the runtime directly, in\n// one call a transport that timestamps every packet can afford. 0 if the\n// clock is missing.\n@ monotonic_ns → i {\n    ^ ( nurl_monotonic_ns )\n}\n\n// Blocking `nanosleep` loop — retries on EINTR with the remaining\n// time the kernel wrote into the second timespec. Negative / zero ms is\n// a no-op. The remainder becomes the next request, so EINTR retries\n// preserve the unslept time.\n@ __sleep_ms_blocking i ms → v {\n    ? > ms 0 {\n        : i secs / ms 1000\n        : i ms_rem - ms * secs 1000\n        : ~ Timespec req @ Timespec { secs * ms_rem 1000000 }\n        : ~ Timespec rem @ Timespec { 0 0 }\n        : ~ b done F\n        ~ ! done {\n            : i32 rc ( nanosleep req rem )\n            ? == rc 0 {\n                = done T\n            } {\n                : i e ( nurl_errno_get )\n                ? != e ( posix_const `EINTR` ) {\n                    // Any non-EINTR failure (EINVAL, EFAULT) is a programmer\n                    // bug, not a transient — stop retrying rather than\n                    // spin-loop. The remaining sleep is lost; callers that\n                    // care should not feed in bogus durations.\n                    = done T\n                } {\n                    = req rem\n                }\n            }\n        }\n    } {}\n}\n\n// Context-aware sleep: when called from inside a fiber, parks on\n// the async runtime's timer wheel so the worker pthread keeps\n// running other fibers. When called from a plain OS thread (or on\n// WASI, where the fiber runtime is stubbed), falls back to\n// `nanosleep`-style blocking. The dispatch is invisible to the\n// caller — same name, same signature, same observable wait time.\n//\n// THE MAIN CONTEXT ON A COOPERATIVE RUNTIME IS THE THIRD CASE, and it\n// used to take the second one. There, \"threads\" are coroutines on one\n// vCPU, so a `nanosleep` in main does not merely wait — it stops every\n// other coroutine for the duration. Measured, on the freestanding\n// runtime: a program that spawns a server thread, sleeps 500 ms in main\n// and then connects gets its connection REFUSED, because the server\n// never ran and never bound. The runtime's own fiber sleep already\n// handles the main context correctly (it runs whatever is runnable\n// while the clock catches up); it was simply never reached from there.\n@ sleep_ms i ms → v {\n    : i fcur ( nurl_fiber_current )\n    ? != fcur 0 {\n        : i unused ( nurl_fiber_sleep_ms ms )\n        ^\n    } {}\n    ? != ( nurl_runtime_is_cooperative ) 0 {\n        : i unused2 ( nurl_fiber_sleep_ms ms )\n        ^\n    } {}\n    ( __sleep_ms_blocking ms )\n}\n\n@ elapsed_ms_since i t0 → i {\n    : i now ( monotonic_ns )\n    ^ / - now t0 1000000\n}\n\n// ── Calendar (UTC) ──────────────────────────────────────────────────\n//\n// Howard Hinnant's `civil_from_days` / `days_from_civil` algorithms\n// (proleptic Gregorian, public domain — see\n// http://howardhinnant.github.io/date_algorithms.html). Constant-time,\n// branch-free, correct for the full range of i64 day counts.\n\n: Time {\n    i year  // proleptic Gregorian year (full, e.g. 2026 not 26)\n    i month  // 1..12\n    i day  // 1..31\n    i hour  // 0..23\n    i min  // 0..59\n    i sec  // 0..59 (no leap seconds — POSIX time convention)\n    i ns  // 0..999_999_999 (sub-second resolution; 0 in time_now)\n    i wday  // 0=Sun..6=Sat (Hinnant's weekday_from_days convention)\n}\n\n// Floor division: truncates toward negative infinity (NURL's `/` is\n// the C-style truncate-toward-zero operator). Civil-time arithmetic\n// needs floor semantics so dates before 1970 round correctly.\n@ __i_floor_div i a i b → i {\n    : i q / a b\n    : i r - a * q b\n    ? != r 0 {\n        ? & < r 0 > b 0 { ^ - q 1 } {}\n        ? & > r 0 < b 0 { ^ - q 1 } {}\n    } {}\n    ^ q\n}\n\n@ __i_mod i a i b → i {\n    : i r - a * ( __i_floor_div a b ) b\n    ^ r\n}\n\n// civil_from_days: convert days-since-1970-01-01 → (year, month, day, wday).\n@ __civil_from_days i z → Time {\n    : i zz + z 719468\n    : i era ( __i_floor_div zz 146097 )\n    : i doe - zz * era 146097\n    // yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365\n    : i yoe / - + - doe / doe 1460 / doe 36524 / doe 146096 365\n    : ~ i y + yoe * era 400\n    // doy = doe - (yoe*365 + yoe/4 - yoe/100)\n    : i doy - doe - + * 365 yoe / yoe 4 / yoe 100\n    // mp = (5*doy + 2) / 153\n    : i mp / + * 5 doy 2 153\n    // d = doy - (153*mp + 2)/5 + 1\n    : i d + - doy / + * 153 mp 2 5 1\n    // m = mp < 10 ? mp+3 : mp-9\n    : i m ? < mp 10 + mp 3 - mp 9\n    ? <= m 2 { = y + y 1 } {}\n    // weekday_from_days uses the ORIGINAL 1970-relative z, not zz.\n    : i wday ( __i_mod + z 4 7 )\n    ^ @ Time { y m d 0 0 0 0 wday }\n}\n\n// days_from_civil: inverse — (year, month, day) → days since 1970-01-01.\n@ __days_from_civil i y i m i d → i {\n    : i y2 ? <= m 2 - y 1 y\n    : i era ( __i_floor_div y2 400 )\n    : i yoe - y2 * era 400\n    : i doy - + / + * 153 + m ? > m 2 -3 9 2 5 d 1\n    : i doe + - + * yoe 365 / yoe 4 / yoe 100 doy\n    ^ - + * era 146097 doe 719468\n}\n\n@ time_from_unix i secs → Time {\n    : i day_secs ( __i_floor_div secs 86400 )\n    : i sod ( __i_mod secs 86400 )\n    : Time t ( __civil_from_days day_secs )\n    : i hh / sod 3600\n    : i mm / - sod * hh 3600 60\n    : i ss - sod + * hh 3600 * mm 60\n    ^ @ Time { . t year . t month . t day hh mm ss 0 . t wday }\n}\n\n@ time_to_unix Time t → i {\n    : i days ( __days_from_civil . t year . t month . t day )\n    ^ + + + * days 86400 * . t hour 3600 * . t min 60 . t sec\n}\n\n@ time_now → Time {\n    ^ ( time_from_unix ( now_seconds ) )\n}\n\n// ── Calendar helpers ─────────────────────────────────────────────────\n\n// Proleptic Gregorian leap year: divisible by 4, except centuries,\n// except every 400th.\n@ is_leap_year i y → b {\n    ? != ( __i_mod y 4 ) 0 { ^ F } {}\n    ? != ( __i_mod y 100 ) 0 { ^ T } {}\n    ^ == ( __i_mod y 400 ) 0\n}\n\n// Number of days in month `m` (1..12) of year `y`. February is\n// leap-year aware. `m` outside 1..12 yields 31 (callers validate).\n@ days_in_month i y i m → i {\n    ? == m 2 { ^ ? ( is_leap_year y ) 29 28 } {}\n    ? | | | == m 4 == m 6 == m 9 == m 11 { ^ 30 } {}\n    ^ 31\n}\n\n// Validated civil-time constructor. Every field is range-checked (day\n// against the actual month length, so 2023-02-29 is rejected but\n// 2024-02-29 is accepted); out-of-range input → `ParseErr.BadFormat`.\n//\n// Returns the **Unix timestamp** (seconds), not a `Time`. Pass it to\n// `time_from_unix` for the broken-down fields:\n//   ?? ( time_make 2026 5 21 14 39 0 ) {\n//     T secs → { : Time t ( time_from_unix secs )  … }\n//     F e → …\n//   }\n// This comment used to warn that a wide-payload `! T E` is silently\n// truncated when matched directly as `?? ( call ) { … }`, and to bind\n// the call to a `:` variable first. That compiler bug is fixed: the\n// direct and the bound spelling now produce the same value for an\n// `! i ParseErr` and for a multi-field struct payload alike. The\n// warning outlived the bug and was sending readers down a detour.\n@ time_make i year i month i day i hour i min i sec → !i ParseErr {\n    ? | < month 1 > month 12\n    { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n    ? | < day 1 > day ( days_in_month year month )\n    { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n    ? | < hour 0 > hour 23\n    { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n    ? | < min 0 > min 59\n    { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n    ? | < sec 0 > sec 59\n    { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n    ^ @ !i ParseErr { T ( time_to_unix @ Time { year month day hour min sec 0 0 } ) }\n}\n\n// Day of year, 1 (Jan 1) .. 365/366 (Dec 31).\n@ time_yday Time t → i {\n    : i d0 ( __days_from_civil . t year 1 1 )\n    : i dn ( __days_from_civil . t year . t month . t day )\n    ^ + - dn d0 1\n}\n\n// ── Comparison ───────────────────────────────────────────────────────\n//\n// All comparisons are by Unix-second value (sub-second `ns` is ignored\n// — `time_from_unix` never populates it). Suitable for sorting /\n// filtering timestamp columns in ETL.\n\n@ time_cmp Time a Time b → i {\n    : i ua ( time_to_unix a )\n    : i ub ( time_to_unix b )\n    ? < ua ub { ^ -1 } {}\n    ? > ua ub { ^ 1 } {}\n    ^ 0\n}\n\n@ time_eq Time a Time b → b {\n    ^ == ( time_to_unix a ) ( time_to_unix b )\n}\n\n@ time_before Time a Time b → b {\n    ^ < ( time_to_unix a ) ( time_to_unix b )\n}\n\n@ time_after Time a Time b → b {\n    ^ > ( time_to_unix a ) ( time_to_unix b )\n}\n\n// ── Arithmetic ───────────────────────────────────────────────────────\n//\n// Add/subtract via the Unix-second axis, then re-derive the calendar\n// fields — so `time_add_days t 1` correctly rolls over month and year\n// boundaries (and leap days). Negative arguments subtract.\n\n@ time_add_seconds Time t i secs → Time {\n    ^ ( time_from_unix + ( time_to_unix t ) secs )\n}\n\n@ time_add_days Time t i days → Time {\n    ^ ( time_from_unix + ( time_to_unix t ) * days 86400 )\n}\n\n// Signed second difference a − b.\n@ time_diff_seconds Time a Time b → i {\n    ^ - ( time_to_unix a ) ( time_to_unix b )\n}\n\n// ── Formatting ───────────────────────────────────────────────────────\n\n@ __push_2digit String out i n → v {\n    ( string_push_char out + 48 / n 10 )\n    ( string_push_char out + 48 ( __i_mod n 10 ) )\n}\n\n@ __push_4digit String out i n → v {\n    : i a / n 1000\n    : i b / ( __i_mod n 1000 ) 100\n    : i c / ( __i_mod n 100 ) 10\n    : i d ( __i_mod n 10 )\n    ( string_push_char out + 48 a )\n    ( string_push_char out + 48 b )\n    ( string_push_char out + 48 c )\n    ( string_push_char out + 48 d )\n}\n\n@ time_format_iso Time t → String {\n    : String out ( string_with_cap 24 )\n    ( __push_4digit out . t year )\n    ( string_push_char out 45 )\n    ( __push_2digit out . t month )\n    ( string_push_char out 45 )\n    ( __push_2digit out . t day )\n    ( string_push_char out 84 )\n    ( __push_2digit out . t hour )\n    ( string_push_char out 58 )\n    ( __push_2digit out . t min )\n    ( string_push_char out 58 )\n    ( __push_2digit out . t sec )\n    ( string_push_char out 90 )\n    ^ out\n}\n\n@ __wday_name i w → s {\n    ? == w 0 { ^ `Sun` } {}\n    ? == w 1 { ^ `Mon` } {}\n    ? == w 2 { ^ `Tue` } {}\n    ? == w 3 { ^ `Wed` } {}\n    ? == w 4 { ^ `Thu` } {}\n    ? == w 5 { ^ `Fri` } {}\n    ? == w 6 { ^ `Sat` } {}\n    ^ `???`\n}\n\n@ __month_name i m → s {\n    ? == m 1 { ^ `Jan` } {}\n    ? == m 2 { ^ `Feb` } {}\n    ? == m 3 { ^ `Mar` } {}\n    ? == m 4 { ^ `Apr` } {}\n    ? == m 5 { ^ `May` } {}\n    ? == m 6 { ^ `Jun` } {}\n    ? == m 7 { ^ `Jul` } {}\n    ? == m 8 { ^ `Aug` } {}\n    ? == m 9 { ^ `Sep` } {}\n    ? == m 10 { ^ `Oct` } {}\n    ? == m 11 { ^ `Nov` } {}\n    ? == m 12 { ^ `Dec` } {}\n    ^ `???`\n}\n\n// RFC 7231 §7.1.1.1 — IMF-fixdate format used by HTTP `Date:` and\n// `Expires:` headers: \"Wed, 06 May 2026 17:30:45 GMT\".\n@ time_format_http Time t → String {\n    : String out ( string_with_cap 32 )\n    ( string_push_str out ( __wday_name . t wday ) )\n    ( string_push_str out `, ` )\n    ( __push_2digit out . t day )\n    ( string_push_char out 32 )\n    ( string_push_str out ( __month_name . t month ) )\n    ( string_push_char out 32 )\n    ( __push_4digit out . t year )\n    ( string_push_char out 32 )\n    ( __push_2digit out . t hour )\n    ( string_push_char out 58 )\n    ( __push_2digit out . t min )\n    ( string_push_char out 58 )\n    ( __push_2digit out . t sec )\n    ( string_push_str out ` GMT` )\n    ^ out\n}\n\n@ __push_3digit String out i n → v {\n    ( string_push_char out + 48 / n 100 )\n    ( string_push_char out + 48 / ( __i_mod n 100 ) 10 )\n    ( string_push_char out + 48 ( __i_mod n 10 ) )\n}\n\n@ __wday_full i w → s {\n    ? == w 0 { ^ `Sunday` } {}\n    ? == w 1 { ^ `Monday` } {}\n    ? == w 2 { ^ `Tuesday` } {}\n    ? == w 3 { ^ `Wednesday` } {}\n    ? == w 4 { ^ `Thursday` } {}\n    ? == w 5 { ^ `Friday` } {}\n    ? == w 6 { ^ `Saturday` } {}\n    ^ `???`\n}\n\n@ __month_full i m → s {\n    ? == m 1 { ^ `January` } {}\n    ? == m 2 { ^ `February` } {}\n    ? == m 3 { ^ `March` } {}\n    ? == m 4 { ^ `April` } {}\n    ? == m 5 { ^ `May` } {}\n    ? == m 6 { ^ `June` } {}\n    ? == m 7 { ^ `July` } {}\n    ? == m 8 { ^ `August` } {}\n    ? == m 9 { ^ `September` } {}\n    ? == m 10 { ^ `October` } {}\n    ? == m 11 { ^ `November` } {}\n    ? == m 12 { ^ `December` } {}\n    ^ `???`\n}\n\n// Emit one strftime directive byte `d` into `out`. Returns T if `d` is\n// a recognised directive, F otherwise (the caller then emits the `%`\n// and the byte verbatim). Supported: %Y %y %m %d %H %M %S %j %a %A\n// %b %B %%.\n// Space-padded 2-digit — `%e` and `%k` pad with a space where `%d` and\n// `%H` pad with a zero.\n@ __push_2space String out i n → v {\n    ? < n 10 { ( string_push_char out 32 ) } {}\n    ( string_push_int out n )\n}\n\n// 12-hour clock: midnight and noon are both 12, not 0.\n@ __hour12 i h → i {\n    : i r ( __i_mod h 12 )\n    ^ ? == r 0 12 r\n}\n\n@ __time_emit_directive String out Time t i d → b {\n    ? == d 37 { ( string_push_char out 37 ) ^ T } {}\n    ? == d 89 { ( __push_4digit out . t year ) ^ T } {}\n    ? == d 121 { ( __push_2digit out ( __i_mod . t year 100 ) ) ^ T } {}\n    ? == d 109 { ( __push_2digit out . t month ) ^ T } {}\n    ? == d 100 { ( __push_2digit out . t day ) ^ T } {}\n    ? == d 72 { ( __push_2digit out . t hour ) ^ T } {}\n    ? == d 77 { ( __push_2digit out . t min ) ^ T } {}\n    ? == d 83 { ( __push_2digit out . t sec ) ^ T } {}\n    ? == d 106 { ( __push_3digit out ( time_yday t ) ) ^ T } {}\n    ? == d 97 { ( string_push_str out ( __wday_name . t wday ) ) ^ T } {}\n    ? == d 65 { ( string_push_str out ( __wday_full . t wday ) ) ^ T } {}\n    ? == d 98 { ( string_push_str out ( __month_name . t month ) ) ^ T } {}\n    ? == d 104 { ( string_push_str out ( __month_name . t month ) ) ^ T } {}\n    ? == d 66 { ( string_push_str out ( __month_full . t month ) ) ^ T } {}\n    ? == d 101 { ( __push_2space out . t day ) ^ T } {}\n    ? == d 107 { ( __push_2space out . t hour ) ^ T } {}\n    ? == d 73 { ( __push_2digit out ( __hour12 . t hour ) ) ^ T } {}\n    ? == d 108 { ( __push_2space out ( __hour12 . t hour ) ) ^ T } {}\n    ? == d 112 { ( string_push_str out ? < . t hour 12 `AM` `PM` ) ^ T } {}\n    ? == d 80 { ( string_push_str out ? < . t hour 12 `am` `pm` ) ^ T } {}\n    ? == d 67 { ( __push_2digit out ( __i_floor_div . t year 100 ) ) ^ T } {}\n    ? == d 119 { ( string_push_int out . t wday ) ^ T } {}\n    ? == d 117 { ( string_push_int out ? == . t wday 0 7 . t wday ) ^ T } {}\n    ? == d 110 { ( string_push_char out 10 ) ^ T } {}\n    ? == d 116 { ( string_push_char out 9 ) ^ T } {}\n    ? == d 70 {\n        ( __push_4digit out . t year )\n        ( string_push_char out 45 )\n        ( __push_2digit out . t month )\n        ( string_push_char out 45 )\n        ( __push_2digit out . t day )\n        ^ T\n    } {}\n    ? == d 68 {\n        ( __push_2digit out . t month )\n        ( string_push_char out 47 )\n        ( __push_2digit out . t day )\n        ( string_push_char out 47 )\n        ( __push_2digit out ( __i_mod . t year 100 ) )\n        ^ T\n    } {}\n    ? == d 84 {\n        ( __push_2digit out . t hour )\n        ( string_push_char out 58 )\n        ( __push_2digit out . t min )\n        ( string_push_char out 58 )\n        ( __push_2digit out . t sec )\n        ^ T\n    } {}\n    ? == d 82 {\n        ( __push_2digit out . t hour )\n        ( string_push_char out 58 )\n        ( __push_2digit out . t min )\n        ^ T\n    } {}\n    ^ F\n}\n\n// strftime-style formatter. `fmt` is a borrowed raw `s`; the result is\n// a fresh owned String. Directives:\n//\n//   %Y %y %C   year (4-digit, 2-digit, century)\n//   %m %d %e   month, day (zero-padded), day (space-padded)\n//   %H %k      hour 00-23, hour  0-23 space-padded\n//   %I %l %p %P  hour 01-12, space-padded, AM/PM, am/pm\n//   %M %S      minute, second\n//   %j         day of year, 3 digits\n//   %a %A      weekday, abbreviated / full\n//   %b %h %B   month name, abbreviated / abbreviated / full\n//   %w %u      weekday number (0-6 from Sunday / 1-7 from Monday)\n//   %F %D      %Y-%m-%d  /  %m/%d/%y\n//   %T %R      %H:%M:%S  /  %H:%M\n//   %n %t %%   newline, tab, literal percent\n//\n// The zone directives (%Z, %z) are deliberately absent: a `Time` carries\n// a calendar, not an offset, so it cannot answer them — see `tz_offset`\n// and `tz_name`, which take the epoch second the zone question needs.\n// An unrecognised %X is copied verbatim.\n// For the two common fixed shapes prefer `time_format_iso` /\n// `time_format_http` — they skip the directive scan.\n@ time_format Time t s fmt → String {\n    : String out ( string_with_cap 32 )\n    : i n ( nurl_str_len fmt )\n    : ~ i i 0\n    ~ < i n {\n        : i c ( nurl_str_at fmt n i )\n        ? & == c 37 < + i 1 n {\n            : i d ( nurl_str_at fmt n + i 1 )\n            ? ( __time_emit_directive out t d ) {} {\n                ( string_push_char out 37 )\n                ( string_push_char out d )\n            }\n            = i + i 2\n        } {\n            ( string_push_char out c )\n            = i + i 1\n        }\n    }\n    ^ out\n}\n\n// ── Local time ────────────────────────────────────────────────────────\n//\n// Everything above is UTC and pure — the calendar is arithmetic, so it\n// is the same on every machine. Local time is not: the offset from UTC\n// at a given instant comes from a zone database ($TZ, /etc/localtime)\n// that changes twice a year per zone, and owning a copy of tzdata in\n// NURL would be a large thing that is quietly wrong the season after it\n// ships. The platform is asked instead, once per conversion.\n//\n//   ( tz_offset secs )   → i        seconds EAST of UTC at that instant\n//   ( tz_name secs )     → ?String  `CET` / `EEST` / None\n//   ( time_local secs )  → Time     the local calendar at that instant\n//   ( time_now_local )   → Time\n//\n// A platform with no zone database (Windows without a set zone, the\n// unikernel) answers 0 / None, which is UTC — the truthful answer, not\n// a guess. A caller that must distinguish \"UTC\" from \"unknown\" reads\n// `tz_name`.\n\n& `c` @ nurl_tz_offset i secs → i\n\n& `c` @ nurl_tz_name i secs → sink s\n\n@ tz_offset i secs → i { ^ ( nurl_tz_offset secs ) }\n\n@ tz_name i secs → ?String {\n    : s raw ( nurl_tz_name secs )\n    ? == # i raw 0 { ^ @ ?String { F # String 0 } } {}\n    ^ @ ?String { T ( string_adopt raw ) }\n}\n\n// The local calendar at a Unix instant. The returned `Time` carries\n// local wall-clock fields; `time_to_unix` on it would give the WRONG\n// instant back (it reads its fields as UTC), so keep the epoch second\n// if you need to convert again.\n@ time_local i secs → Time {\n    ^ ( time_from_unix + secs ( nurl_tz_offset secs ) )\n}\n\n@ time_now_local → Time {\n    ^ ( time_local ( now_seconds ) )\n}\n\n// ── Parsing ───────────────────────────────────────────────────────────\n//\n// Strict ISO 8601 reader: \"YYYY-MM-DDTHH:MM:SS[Z|+HH:MM|-HH:MM]\".\n// 'T' separator may be lowercase. Trailing offset is folded to UTC.\n// Sub-second decimals (\".sss\" or \".ssssss\") are accepted but ignored\n// for now — a future revision can populate `Time.ns`.\n\n@ __char_at_eq s p i idx i target → b {\n    : i n ( nurl_str_len p )\n    ? < idx 0 { ^ F } {}\n    ? >= idx n { ^ F } {}\n    ^ == ( nurl_str_at p n idx ) target\n}\n\n@ __take_ndigits s p i start i count i max_idx → i {\n    ? > + start count max_idx { ^ -1 } {}\n    : ~ i acc 0\n    : ~ i k 0\n    : ~ b ok T\n    ~ & ok < k count {\n        : i c ( nurl_str_at p max_idx + start k )\n        ? & >= c 48 <= c 57 {\n            = acc + * acc 10 - c 48\n        } { = ok F }\n        = k + k 1\n    }\n    ? ! ok { ^ -1 } {}\n    ^ acc\n}\n\n@ time_parse_iso s p → !i ParseErr {\n    : i n ( nurl_str_len p )\n    ? < n 19 { ^ @ !i ParseErr { F @ ParseErr { Empty } } } {}\n\n    : i y ( __take_ndigits p 0 4 n )\n    ? < y 0 { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n    ? ! ( __char_at_eq p 4 45 ) { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n    : i mo ( __take_ndigits p 5 2 n )\n    ? < mo 0 { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n    ? ! ( __char_at_eq p 7 45 ) { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n    : i d ( __take_ndigits p 8 2 n )\n    ? < d 0 { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n\n    : i sep ( nurl_str_at p n 10 )\n    ? & != sep 84 != sep 116 { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n\n    : i hh ( __take_ndigits p 11 2 n )\n    ? < hh 0 { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n    ? ! ( __char_at_eq p 13 58 ) { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n    : i mm ( __take_ndigits p 14 2 n )\n    ? < mm 0 { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n    ? ! ( __char_at_eq p 16 58 ) { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n    : i ss ( __take_ndigits p 17 2 n )\n    ? < ss 0 { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n\n    : ~ i k 19\n    // Optional fractional seconds (ignored, just consume).\n    ? & < k n == ( nurl_str_at p n k ) 46 {\n        = k + k 1\n        ~ & < k n & >= ( nurl_str_at p n k ) 48 <= ( nurl_str_at p n k ) 57 {\n            = k + k 1\n        }\n    } {}\n\n    : ~ i offset_min 0\n    ? < k n {\n        : i tc ( nurl_str_at p n k )\n        ? | == tc 90 == tc 122 {\n            = k + k 1\n        } {\n            ? | == tc 43 == tc 45 {\n                : i sign ? == tc 45 -1 1\n                = k + k 1\n                : i oh ( __take_ndigits p k 2 n )\n                ? < oh 0 { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n                = k + k 2\n                ? & < k n == ( nurl_str_at p n k ) 58 { = k + k 1 } {}\n                : i om ( __take_ndigits p k 2 n )\n                ? < om 0 { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n                = k + k 2\n                = offset_min * sign + * oh 60 om\n            } { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } }\n        }\n    } {}\n    ? != k n { ^ @ !i ParseErr { F @ ParseErr { TrailingGarbage } } } {}\n\n    : i unix_local ( time_to_unix @ Time { y mo d hh mm ss 0 0 } )\n    : i unix_utc - unix_local * offset_min 60\n    ^ @ !i ParseErr { T unix_utc }\n}\n\n// ── §12c HTTP-date / RFC 2822 date parsing ───────────────────────────\n//\n// The server FORMATS HTTP dates (time_format_http) but until now could\n// not PARSE them — needed for `If-Modified-Since`, `If-Unmodified-Since`\n// and cookie `Expires`. RFC 7231 §7.1.1.1 requires a recipient to accept\n// all three historical forms, and email `Date:` (RFC 5322/2822) adds a\n// numeric zone. All return UTC seconds since the Unix epoch in the\n// `!i ParseErr` shape (pair with `time_from_unix` for fields), so they\n// drop in next to `time_parse_iso`:\n//\n//   IMF-fixdate (preferred):  Sun, 06 Nov 1994 08:49:37 GMT\n//   RFC 850 (obsolete):       Sunday, 06-Nov-94 08:49:37 GMT\n//   asctime (obsolete):       Sun Nov  6 08:49:37 1994\n//   RFC 2822 (email Date):    Mon, 02 Jan 2006 15:04:05 -0700\n//\n//   ( http_date_parse s )   → !i ParseErr   the three RFC 7231 forms (GMT)\n//   ( rfc2822_parse   s )   → !i ParseErr   email Date, numeric / GMT zone\n//\n// A 2-digit RFC 850 year uses the POSIX sliding pivot: YY < 70 → 20YY,\n// else 19YY. Obsolete alphabetic US zones (EST/PDT/…) in RFC 2822 are\n// not resolved (their offset is ambiguous by the RFC's own admission) —\n// only GMT/UT/UTC/Z (= +0000) and numeric ±HHMM offsets are accepted.\n\n// Month abbreviation at p[off..off+3] → 1..12, or -1.\n@ __month3_at s p i off i n → i {\n    ? > + off 3 n { ^ -1 } {}\n    : i c0 ( nurl_str_at p n off )\n    : i c1 ( nurl_str_at p n + off 1 )\n    : i c2 ( nurl_str_at p n + off 2 )\n    // J: Jan/Jun/Jul · F: Feb · M: Mar/May · A: Apr/Aug · S: Sep · O: Oct\n    // N: Nov · D: Dec  (compare the 2nd/3rd letters to disambiguate)\n    ? == c0 74 {  // 'J'\n        ? & == c1 97 == c2 110 { ^ 1 } {}  // Jan\n        ? & == c1 117 == c2 110 { ^ 6 } {}  // Jun\n        ? & == c1 117 == c2 108 { ^ 7 } {}  // Jul\n        ^ -1\n    } {}\n    ? & & == c0 70 == c1 101 == c2 98 { ^ 2 } {}  // Feb\n    ? == c0 77 {  // 'M'\n        ? & == c1 97 == c2 114 { ^ 3 } {}  // Mar\n        ? & == c1 97 == c2 121 { ^ 5 } {}  // May\n        ^ -1\n    } {}\n    ? == c0 65 {  // 'A'\n        ? & == c1 112 == c2 114 { ^ 4 } {}  // Apr\n        ? & == c1 117 == c2 103 { ^ 8 } {}  // Aug\n        ^ -1\n    } {}\n    ? & & == c0 83 == c1 101 == c2 112 { ^ 9 } {}  // Sep\n    ? & & == c0 79 == c1 99 == c2 116 { ^ 10 } {}  // Oct\n    ? & & == c0 78 == c1 111 == c2 118 { ^ 11 } {}  // Nov\n    ? & & == c0 68 == c1 101 == c2 99 { ^ 12 } {}  // Dec\n    ^ -1\n}\n\n// One token = a maximal run of non-(space/comma) bytes. Writes the\n// token's [start,end) into a 2-slot scratch and returns the index just\n// past it; -1 when no token remains from `from`.\n@ __hd_token s p i n i from inout i out_start inout i out_end → i {\n    : ~ i k from\n    ~ & < k n | == ( nurl_str_at p n k ) 32 == ( nurl_str_at p n k ) 44 { = k + k 1 }\n    ? >= k n { ^ -1 } {}\n    : i st k\n    ~ & < k n & != ( nurl_str_at p n k ) 32 != ( nurl_str_at p n k ) 44 { = k + k 1 }\n    = out_start st\n    = out_end k\n    ^ k\n}\n\n@ __hd_all_digits s p i st i en → b {\n    : ~ i k st\n    : ~ b ok > en st\n    ~ & ok < k en {\n        : i c ( nurl_str_at p en k )\n        ? & >= c 48 <= c 57 {} { = ok F }\n        = k + k 1\n    }\n    ^ ok\n}\n\n@ __hd_index_of s p i st i en i ch → i {\n    : ~ i k st\n    ~ < k en { ? == ( nurl_str_at p en k ) ch { ^ k } {} = k + k 1 }\n    ^ -1\n}\n\n// Shared flexible parser for every form above. Tokenizes on space/comma,\n// classifies each token (month name / `HH:MM:SS` / `DD-Mon-YY` / number /\n// zone), then folds to UTC seconds. `require_zone` makes a missing zone\n// an error (RFC 2822 mandates one; HTTP forms always end in GMT).\n@ __parse_date_flex s p b require_zone → !i ParseErr {\n    : i n ( nurl_str_len p )\n    ? == n 0 { ^ @ !i ParseErr { F @ ParseErr { Empty } } } {}\n    : ~ i ts 0\n    : ~ i te 0\n    : ~ i pos 0\n    : ~ i day -1\n    : ~ i mon -1\n    : ~ i year -1\n    : ~ i hh -1\n    : ~ i mm 0\n    : ~ i ss 0\n    : ~ i off_min 0\n    : ~ b have_zone F\n    : ~ b bad F\n    : ~ b going T\n    ~ & going ! bad {\n        : i nxt ( __hd_token p n pos ts te )\n        ? < nxt 0 { = going F } {\n            = pos nxt\n            : i st ts\n            : i en te\n            : i len - en st\n            : i m3 ( __month3_at p st n )\n            : i colon ( __hd_index_of p st en 58 )\n            : i dash ( __hd_index_of p st en 45 )\n            : i c0 ( nurl_str_at p n st )\n            ? >= m3 0 {\n                // bare month name token\n                ? < mon 0 { = mon m3 } {}\n            } {\n                ? >= colon 0 {\n                    // HH:MM[:SS]\n                    : i h ( __take_ndigits p st 2 n )\n                    : i m2c ( __hd_index_of p + colon 1 en 58 )\n                    : i mi ( __take_ndigits p + colon 1 2 n )\n                    ? | < h 0 < mi 0 { = bad T } {\n                        = hh h = mm mi\n                        ? >= m2c 0 {\n                            : i se ( __take_ndigits p + m2c 1 2 n )\n                            ? < se 0 { = bad T } { = ss se }\n                        } {}\n                    }\n                } {\n                    ? & >= dash 0 >= ( __month3_at p + dash 1 n ) 0 {\n                        // RFC 850 DD-Mon-YY\n                        : i d ( __take_ndigits p st 2 n )\n                        : i mn ( __month3_at p + dash 1 n )\n                        : i dash2 ( __hd_index_of p + dash 1 en 45 )\n                        ? | | < d 0 < mn 0 < dash2 0 { = bad T } {\n                            : i yy ( __take_ndigits p + dash2 1 2 n )\n                            ? < yy 0 { = bad T } {\n                                = day d = mon mn\n                                = year ? < yy 70 + 2000 yy + 1900 yy\n                            }\n                        }\n                    } {\n                        ? | | == c0 43 == c0 45 & ( __hd_all_digits p + st 1 en ) >= len 5 {\n                            // numeric zone ±HHMM\n                            : i sign ? == c0 45 -1 1\n                            : i oh ( __take_ndigits p + st 1 2 n )\n                            : i om ( __take_ndigits p + st 3 2 n )\n                            ? | < oh 0 < om 0 { = bad T } {\n                                = off_min * sign + * oh 60 om = have_zone T\n                            }\n                        } {\n                            ? ( __hd_all_digits p st en ) {\n                                // a number: 4 digits → year, else day\n                                : i v ( __take_ndigits p st len n )\n                                ? < v 0 { = bad T } {\n                                    ? & == len 4 < year 0 { = year v } {\n                                        ? < day 0 { = day v } {\n                                            ? < year 0 { = year v } {}\n                                        }\n                                    }\n                                }\n                            } {\n                                // alphabetic zone: GMT/UT/UTC/Z → 0; any\n                                // other word (e.g. the weekday \"Sun\") is\n                                // simply ignored. An alpha zone resolves\n                                // only for the UTC set.\n                                ? ( __hd_zone_is_utc p st en )\n                                { = have_zone T } {}\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n    ? bad { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n    ? | | | < day 0 < mon 0 < year 0 < hh 0 { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n    ? & require_zone ! have_zone { ^ @ !i ParseErr { F @ ParseErr { BadFormat } } } {}\n    : !i ParseErr civ ( time_make year mon day hh mm ss )\n    ^ ?? civ {\n        T unix_local → @ !i ParseErr { T - unix_local * off_min 60 }\n        F e → @ !i ParseErr { F e }\n    }\n}\n\n// True iff p[st..en) is GMT / UT / UTC / Z / Z-as-+0000 (all = UTC).\n@ __hd_zone_is_utc s p i st i en → b {\n    : i len - en st\n    ? & == len 1 == ( nurl_str_at p en st ) 90 { ^ T } {}  // Z\n    ? & & == len 2 == ( nurl_str_at p en st ) 85 == ( nurl_str_at p en + st 1 ) 84 { ^ T } {}  // UT\n    ? & & & == len 3 == ( nurl_str_at p en st ) 71 == ( nurl_str_at p en + st 1 ) 77 == ( nurl_str_at p en + st 2 ) 84 { ^ T } {}  // GMT\n    ? & & & == len 3 == ( nurl_str_at p en st ) 85 == ( nurl_str_at p en + st 1 ) 84 == ( nurl_str_at p en + st 2 ) 67 { ^ T } {}  // UTC\n    ^ F\n}\n\n@ http_date_parse s p → !i ParseErr {\n    ^ ( __parse_date_flex p F )\n}\n\n@ rfc2822_parse s p → !i ParseErr {\n    ^ ( __parse_date_flex p T )\n}\n\n// ── §12b Timezones / DST — POSIX TZ-string rules ─────────────────────\n//\n// Local-time conversion with daylight-saving transitions, driven by a\n// POSIX TZ string (the `TZ` env-var format, e.g. `EST5EDT,M3.2.0,M11.1.0`\n// or `CET-1CEST,M3.5.0,M10.5.0/3`). This is the format IANA tzdata\n// compiles each zone's final rule into, and it covers every real DST\n// regime in use (US / EU / Australia all use the `Mm.w.d` rule form).\n//\n// NOT in scope (and not needed for conversion): the full IANA tzdata\n// database keyed by region name like \"America/New_York\", and historical\n// rule changes — a single POSIX TZ string encodes one current ruleset.\n// The caller supplies the TZ string (read it from the `TZ` env var, a\n// config value, or hard-code the zone you target).\n//\n//   ( tz_utc )                       → TimeZone     UTC, no DST\n//   ( tz_fixed off_secs )            → TimeZone     fixed offset east of UTC\n//   ( tz_parse posix_str )           → ! TimeZone ParseErr\n//   ( tz_offset_at tz utc_secs )     → i            seconds east of UTC in effect\n//   ( tz_is_dst tz utc_secs )        → b            is DST active at that instant\n//   ( time_from_unix_tz utc tz )     → Time         broken-down LOCAL fields\n//   ( time_now_tz tz )               → Time         local \"now\"\n//   ( time_to_unix_tz local tz )     → i            local civil fields → UTC secs\n//   ( time_format_offset t off )     → String       ISO 8601 + numeric offset\n//   ( time_format_iso_tz utc tz )    → String       one-shot UTC instant → local ISO\n//\n// TimeZone is a plain value type (all integer fields) — copy it freely,\n// no free needed. Only the `Mm.w.d[/time]` rule form is parsed; the\n// `Jn` / `n` (Julian-day) forms return ParseErr.BadFormat. Ambiguous or\n// nonexistent local times across a transition resolve to the\n// standard-time interpretation.\n\n: TimeZone {\n    i std_off  // seconds east of UTC during standard time\n    i dst_off  // seconds east of UTC during DST (== std_off when no DST)\n    i has_dst  // 0 / 1\n    i s_mon  // DST start rule: month 1..12\n    i s_week  // week 1..5 (5 = last)\n    i s_dow  // weekday 0=Sun..6=Sat\n    i s_time  // seconds after local midnight (default 02:00:00)\n    i e_mon  // DST end rule\n    i e_week\n    i e_dow\n    i e_time\n}\n\n: __TzOff { i secs i endpos }\n: __TzRule { i mon i week i dow i time i endpos i ok }\n\n@ __tz_is_alpha i c → b {\n    ^ | & >= c 65 <= c 90 & >= c 97 <= c 122\n}\n\n@ __tz_is_digit i c → b {\n    ^ & >= c 48 <= c 57\n}\n\n// Skip a zone abbreviation: a `<...>` quoted name, or a run of letters.\n@ __tz_skip_name s buf i pos i n → i {\n    ? & < pos n == ( nurl_str_at buf n pos ) 60 {  // '<'\n        : ~ i p + pos 1\n        ~ & < p n != ( nurl_str_at buf n p ) 62 { = p + p 1 }\n        ? & < p n == ( nurl_str_at buf n p ) 62 { = p + p 1 } {}\n        ^ p\n    } {}\n    : ~ i p pos\n    ~ & < p n ( __tz_is_alpha ( nurl_str_at buf n p ) ) { = p + p 1 }\n    ^ p\n}\n\n// Parse `h[:m[:s]]` (no sign). endpos == pos when no digits were read.\n@ __tz_parse_hms s buf i pos → __TzOff {\n    : i n ( nurl_str_len buf )\n    : ~ i p pos\n    : ~ i hh 0\n    : ~ b any F\n    ~ ( __tz_is_digit ( nurl_str_at buf n p ) ) { = hh + * hh 10 - ( nurl_str_at buf n p ) 48 = p + p 1 = any T }\n    ? ! any { ^ @ __TzOff { 0 pos } } {}\n    : ~ i mm 0\n    ? == ( nurl_str_at buf n p ) 58 {  // ':'\n        = p + p 1\n        ~ ( __tz_is_digit ( nurl_str_at buf n p ) ) { = mm + * mm 10 - ( nurl_str_at buf n p ) 48 = p + p 1 }\n    } {}\n    : ~ i ss 0\n    ? == ( nurl_str_at buf n p ) 58 {\n        = p + p 1\n        ~ ( __tz_is_digit ( nurl_str_at buf n p ) ) { = ss + * ss 10 - ( nurl_str_at buf n p ) 48 = p + p 1 }\n    } {}\n    : i secs + + * hh 3600 * mm 60 ss\n    ^ @ __TzOff { secs p }\n}\n\n// Parse a POSIX offset `[+|-]h[:m[:s]]` → seconds EAST of UTC. POSIX\n// offsets are seconds WEST (added to local to reach UTC), so the sign is\n// inverted here. endpos == pos when no offset is present.\n@ __tz_parse_offset s buf i pos → __TzOff {\n    : i n ( nurl_str_len buf )\n    : ~ i p pos\n    : ~ i sign 1\n    : i c0 ( nurl_str_at buf n p )\n    ? == c0 45 { = sign -1 = p + p 1 } {}  // '-'\n    ? == c0 43 { = p + p 1 } {}  // '+'\n    : __TzOff h ( __tz_parse_hms buf p )\n    ? == . h endpos p { ^ @ __TzOff { 0 pos } } {}\n    : i east * -1 * sign . h secs\n    ^ @ __TzOff { east . h endpos }\n}\n\n// Parse one `Mm.w.d[/time]` transition rule. ok == 0 on any deviation\n// (including the unsupported Jn / n forms).\n@ __tz_parse_rule s buf i pos i n → __TzRule {\n    : ~ i p pos\n    ? | >= p n != ( nurl_str_at buf n p ) 77 { ^ @ __TzRule { 0 0 0 7200 p 0 } } {}  // 'M'\n    = p + p 1\n    : ~ i mon 0\n    : ~ b d1 F\n    ~ & < p n ( __tz_is_digit ( nurl_str_at buf n p ) ) { = mon + * mon 10 - ( nurl_str_at buf n p ) 48 = p + p 1 = d1 T }\n    ? | ! d1 != ( nurl_str_at buf n p ) 46 { ^ @ __TzRule { 0 0 0 7200 p 0 } } {}  // '.'\n    = p + p 1\n    : ~ i week 0\n    : ~ b d2 F\n    ~ & < p n ( __tz_is_digit ( nurl_str_at buf n p ) ) { = week + * week 10 - ( nurl_str_at buf n p ) 48 = p + p 1 = d2 T }\n    ? | ! d2 != ( nurl_str_at buf n p ) 46 { ^ @ __TzRule { 0 0 0 7200 p 0 } } {}  // '.'\n    = p + p 1\n    : ~ i dow 0\n    : ~ b d3 F\n    ~ & < p n ( __tz_is_digit ( nurl_str_at buf n p ) ) { = dow + * dow 10 - ( nurl_str_at buf n p ) 48 = p + p 1 = d3 T }\n    ? ! d3 { ^ @ __TzRule { 0 0 0 7200 p 0 } } {}\n    : ~ i tsec 7200\n    ? & < p n == ( nurl_str_at buf n p ) 47 {  // '/'\n        = p + p 1\n        : __TzOff th ( __tz_parse_hms buf p )\n        ? != . th endpos p { = tsec . th secs = p . th endpos } {}\n    } {}\n    ? | | | < mon 1 > mon 12 | < week 1 > week 5 | < dow 0 > dow 6 {\n        ^ @ __TzRule { 0 0 0 7200 p 0 }\n    } {}\n    ^ @ __TzRule { mon week dow tsec p 1 }\n}\n\n@ tz_utc → TimeZone {\n    ^ @ TimeZone { 0 0 0 0 0 0 7200 0 0 0 7200 }\n}\n\n@ tz_fixed i off → TimeZone {\n    ^ @ TimeZone { off off 0 0 0 0 7200 0 0 0 7200 }\n}\n\n@ tz_parse s p → !TimeZone ParseErr {\n    : i n ( nurl_str_len p )\n    ? == n 0 { ^ @ !TimeZone ParseErr { F @ ParseErr { Empty } } } {}\n    : ~ i pos 0\n    = pos ( __tz_skip_name p pos n )\n    : __TzOff so ( __tz_parse_offset p pos )\n    ? == . so endpos pos { ^ @ !TimeZone ParseErr { F @ ParseErr { BadFormat } } } {}\n    : i std_off . so secs\n    = pos . so endpos\n    : ~ i has_dst 0\n    : ~ i dst_off std_off\n    ? & < pos n != ( nurl_str_at p n pos ) 44 {  // not ',' → DST abbrev present\n        = has_dst 1\n        = pos ( __tz_skip_name p pos n )\n        : __TzOff doff ( __tz_parse_offset p pos )\n        ? == . doff endpos pos {\n            = dst_off + std_off 3600\n        } {\n            = dst_off . doff secs\n            = pos . doff endpos\n        }\n    } {}\n    : ~ i s_mon 0\n    : ~ i s_week 0\n    : ~ i s_dow 0\n    : ~ i s_time 7200\n    : ~ i e_mon 0\n    : ~ i e_week 0\n    : ~ i e_dow 0\n    : ~ i e_time 7200\n    ? == has_dst 1 {\n        ? | >= pos n != ( nurl_str_at p n pos ) 44 { ^ @ !TimeZone ParseErr { F @ ParseErr { BadFormat } } } {}\n        = pos + pos 1\n        : __TzRule r1 ( __tz_parse_rule p pos n )\n        ? == . r1 ok 0 { ^ @ !TimeZone ParseErr { F @ ParseErr { BadFormat } } } {}\n        = s_mon . r1 mon\n        = s_week . r1 week\n        = s_dow . r1 dow\n        = s_time . r1 time\n        = pos . r1 endpos\n        ? | >= pos n != ( nurl_str_at p n pos ) 44 { ^ @ !TimeZone ParseErr { F @ ParseErr { BadFormat } } } {}\n        = pos + pos 1\n        : __TzRule r2 ( __tz_parse_rule p pos n )\n        ? == . r2 ok 0 { ^ @ !TimeZone ParseErr { F @ ParseErr { BadFormat } } } {}\n        = e_mon . r2 mon\n        = e_week . r2 week\n        = e_dow . r2 dow\n        = e_time . r2 time\n        = pos . r2 endpos\n    } {}\n    ^ @ !TimeZone ParseErr { T @ TimeZone { std_off dst_off has_dst s_mon s_week s_dow s_time e_mon e_week e_dow e_time } }\n}\n\n// Day-of-month of the `week`-th `dow` (0=Sun) in (year, mon). week 1..4\n// selects the n-th; week 5 (or any overshoot) selects the last.\n@ __tz_nth_dow i year i mon i week i dow → i {\n    : i days1 ( __days_from_civil year mon 1 )\n    : i wday1 ( __i_mod + days1 4 7 )\n    : i first + 1 ( __i_mod - + dow 7 wday1 7 )\n    : i cand + first * - week 1 7\n    : i dim ( days_in_month year mon )\n    ? > cand dim { ^ - cand 7 } {}\n    ^ cand\n}\n\n// UTC instant of a transition in `year`. which: 0 = DST start, 1 = end.\n// The rule time is local; just before the START it is standard time, and\n// just before the END it is daylight time — so we subtract the offset in\n// effect immediately before the transition.\n@ __tz_trans_utc TimeZone tz i year i which → i {\n    : i mon ? == which 0 . tz s_mon . tz e_mon\n    : i week ? == which 0 . tz s_week . tz e_week\n    : i dow ? == which 0 . tz s_dow . tz e_dow\n    : i tsec ? == which 0 . tz s_time . tz e_time\n    : i dom ( __tz_nth_dow year mon week dow )\n    : i localdays ( __days_from_civil year mon dom )\n    : i localsecs + * localdays 86400 tsec\n    : i off ? == which 0 . tz std_off . tz dst_off\n    ^ - localsecs off\n}\n\n@ tz_is_dst TimeZone tz i utc → b {\n    ? == . tz has_dst 0 { ^ F } {}\n    : Time u ( time_from_unix utc )\n    : i start ( __tz_trans_utc tz . u year 0 )\n    : i end ( __tz_trans_utc tz . u year 1 )\n    ? < start end { ^ & >= utc start < utc end } {}\n    // Southern hemisphere: DST spans the year boundary.\n    ^ | >= utc start < utc end\n}\n\n@ tz_offset_at TimeZone tz i utc → i {\n    ? ( tz_is_dst tz utc ) { ^ . tz dst_off } {}\n    ^ . tz std_off\n}\n\n@ time_from_unix_tz i utc TimeZone tz → Time {\n    ^ ( time_from_unix + utc ( tz_offset_at tz utc ) )\n}\n\n@ time_now_tz TimeZone tz → Time {\n    ^ ( time_from_unix_tz ( now_seconds ) tz )\n}\n\n// Interpret broken-down LOCAL fields as a wall-clock time in `tz` and\n// return the UTC timestamp. Guesses with the standard offset, then\n// refines to the DST offset if that instant falls inside the DST window.\n@ time_to_unix_tz Time t TimeZone tz → i {\n    : i localsecs ( time_to_unix t )\n    : i guess - localsecs . tz std_off\n    ? ( tz_is_dst tz guess ) { ^ - localsecs . tz dst_off } {}\n    ^ guess\n}\n\n@ __tz_push_offset String out i off → v {\n    ? == off 0 {\n        ( string_push_char out 90 )  // 'Z'\n    } {\n        : i a ? < off 0 - 0 off off\n        : i sign ? < off 0 45 43\n        ( string_push_char out sign )\n        : i hh / a 3600\n        : i mm / - a * hh 3600 60\n        ( __push_2digit out hh )\n        ( string_push_char out 58 )  // ':'\n        ( __push_2digit out mm )\n    }\n}\n\n// ISO 8601 with a numeric UTC offset, e.g. \"2026-07-01T08:30:00-04:00\"\n// (or \"…Z\" when off == 0). `t` must already hold LOCAL fields.\n@ time_format_offset Time t i off → String {\n    : String out ( string_with_cap 32 )\n    ( __push_4digit out . t year )\n    ( string_push_char out 45 )\n    ( __push_2digit out . t month )\n    ( string_push_char out 45 )\n    ( __push_2digit out . t day )\n    ( string_push_char out 84 )  // 'T'\n    ( __push_2digit out . t hour )\n    ( string_push_char out 58 )\n    ( __push_2digit out . t min )\n    ( string_push_char out 58 )\n    ( __push_2digit out . t sec )\n    ( __tz_push_offset out off )\n    ^ out\n}\n\n// One-shot: a UTC instant → local ISO 8601 with the offset for `tz`.\n@ time_format_iso_tz i utc TimeZone tz → String {\n    : i off ( tz_offset_at tz utc )\n    : Time t ( time_from_unix + utc off )\n    ^ ( time_format_offset t off )\n}\n","bytes":47584}