timez
Package timez.
Calendar time, written in slang on top of the native time clock: instants, dates, durations, text formats, time zones, test clocks and cron. Import "timez".
import "timez";
let t = timez.now(); // a Time: an instant, UTC
println(t.rfc3339()); // 2026-10-13T09:30:15Z
println(t.rfc3339_milli()); // 2026-10-13T09:30:15.000Z (toISOString)
println(t.http_date()); // Tue, 13 Oct 2026 09:30:15 GMT
println(t.format("%A %e %B %Y, %H:%M")); // Tuesday 13 October 2026, 09:30
let r = timez.parse_rfc3339("2026-10-13T11:30:15+02:00"); // offsets applied
let due = t.date().add_months(1); // Jan 31 + 1 month = Feb 28/29
let ttl = timez.parse_duration("1h30m") ?? 0; // nanoseconds; "7d" works too
println(timez.relative(t.add(-3 * timez.MINUTE), t)); // 3 minutes ago
let zr = timez.load_zone("Europe/Berlin"); // the system's tz database
guard let berlin = zr else let e = err_of(zr) { println(e); exit(1); }
println(t.in_zone(berlin).format("%H:%M %Z")); // 11:30 CEST
let cr = timez.parse_cron("*/15 9-17 * * MON-FRI");
guard let cron = cr else let e = err_of(cr) { println(e); exit(1); }
let next = cron.next_after(t, berlin); // opt[Time], DST-awareValues.
Timeis Unix seconds plus nanoseconds, so it spans 0001-01-01 to 9999-12-31 and beyond. 64-bit nanoseconds stop at 1677-2262.Dateis a calendar day: birthdays, due dates, report days.- Durations are
intnanoseconds, the unittime.sleepand deadlines take. Constants run fromNANOSECONDtoHOURandDAY(24 hours). - The calendar is proleptic Gregorian, with no leap seconds.
Calendar.
- Fields:
year()tonanosecond(),weekday(),year_day(),iso_week(),is_leap_year,days_in_month. - Arithmetic:
add(d),sub(t), andadd_days,add_monthsandadd_years, which clamp to the end of the month. - Rounding and periods:
truncate(d),round(d), andstart_of_*andend_of_*for minute, hour, day, week (Monday), month and year. - Comparing:
before,after,equal,compare,min,max. - Dates:
days_until, andyears_until, which counts an age by whether the anniversary has passed.
Text.
rfc3339,rfc3339_milli,rfc3339_nanoandrfc3339_at(offset).parse_rfc3339is strict: it refuses a timestamp with no offset, because one without names no instant.parse_date.http_date, andparse_http_datefor all three forms RFC 9110 has recipients accept.rfc2822andparse_rfc2822.format(layout):strftimedirectives, locale-free.parse_durationandformat_duration, Go's format plusd.relative.
Time zones.
- Loading.
load_zone(name)reads the IANA database, from$ZONEINFOor/usr/share/zoneinfo. It follows each file's POSIX-TZ rule past its last transition, and refuses names that could reach other files. A missing database is an error that says how to install it, never a silent UTC.fixed_zone(name, offset)gives a zone at a fixed offset. - Reading.
t.in_zone(z)gives aZonedTime: local fields,abbrev,is_dst,rfc3339with the offset, andformatwith real%zand%Z. - Local arithmetic.
add_days,add_monthsandstart_of_daywork in local wall time, so a day across a DST change is 23 or 25 hours. - Wall time to instant.
zoned(z, y, m, d, h, mi, s, how)converts a local wall time to an instant.howdecides the skipped and repeated hours:COMPATIBLEmoves forward through a gap and takes the earlier of an overlap, as Temporal and java.time do. The others areEARLIER,LATERandREJECT.
Operations.
system_clock()/fixed_clock(t): aClockto pass in state, so testsadvanceandsetthe time instead of sleeping.start_stopwatch(), which measures on the monotonic clock.interval(a, b): half-open, withcontains,overlapsandintersection.deadline(t)gives theuntilthat socket and channel calls take.parse_cron: five fields, names, steps and@dailymacros. When both day fields are set, a day matches either (Vixie).next_after(t, zone)evaluates in the zone's wall time: a run in a skipped hour happens when the clock reaches it, and a repeated hour runs once.
In JSON and Postgres. A timez.Time field of a struct is written by json.encode as an RFC 3339 string and read back by json.decode (see json). pg.arg_time(t) binds one for a timestamptz parameter and pg.get_time(rows, r, c) reads one out; pg.timestamp_rfc3339(cell, has_tz) turns Postgres's own timestamp text into RFC 3339.
API#
"2026-10-13T09:30:15Z": seconds, UTC.
"2026-10-13T09:30:15.000Z": always milliseconds, as JavaScript's toISOString() writes them -- what a browser client parses most happily.
"2026-10-13T09:30:15.5Z": every nanosecond the instant has, no trailing zeros -- what round-trips exactly.
The same instant as the wall time at a fixed offset from UTC (seconds east, -64800 to 64800): "2026-10-13T11:30:15+02:00".
"Tue, 13 Oct 2026 09:30:15 GMT": the HTTP date (RFC 9110 IMF-fixdate) for Date, Last-Modified, Expires and cookie Expires.
"Tue, 13 Oct 2026 09:30:15 +0000": email (RFC 5322) dates.
strftime-style formatting, locale-free (English names), UTC: %Y year (2026) %m month 01-12 %d day 01-31 %e day, space-padded %H hour 00-23 %I hour 01-12 %M minute %S second %p AM/PM %L milliseconds (000-999) %f microseconds %N nanoseconds (9 digits) %a Tue %A Tuesday %b Oct %B October %j day of year 001-366 %u weekday 1-7 (Monday 1) %G ISO year %V ISO week 01-53 %s Unix seconds %z +0000 %Z UTC %F %Y-%m-%d %T %H:%M:%S %% % An unknown directive is written as it is, so a typo shows in the output instead of vanishing.
"2026-10-13" (and only that): a calendar date.
An RFC 3339 timestamp: "2026-10-13T09:30:15Z", with an optional fraction of up to 9 digits and a "Z" or "+hh:mm"/"-hh:mm" offset, which is applied: the result is the instant, in UTC. "t" and "z" and a space for "T" are accepted, as RFC 3339 allows. A missing offset is refused: a timestamp without one names no instant.
An HTTP date. RFC 9110 has recipients accept three forms, and so does this: Tue, 13 Oct 2026 09:30:15 GMT IMF-fixdate (what everything sends) Tuesday, 13-Oct-26 09:30:15 GMT RFC 850 (two-digit year: more than 50 years ahead means last century) Tue Oct 13 09:30:15 2026 asctime
An email date (RFC 5322): an optional weekday, a 1-2 digit day, a month, a 4-digit year, hh:mm with optional :ss, and a "+hhmm"/"-hhmm" offset or UT/GMT/Z.
How long ago t was, or how far ahead, from now_t, the way people say it: "just now", "45 seconds ago", "3 minutes ago", "in 2 hours", "yesterday", "5 days ago", "in 3 weeks", "2 months ago", "1 year ago". Rounded down, so "1 hour ago" means at least an hour.
Where code gets the time, so a test can set it instead of sleeping. Pass a Clock in your state; production uses system_clock().
A clock that stays at t until advanced or set.
Moves a fixed clock forward by d (no effect on the system clock).
Measures elapsed time on the monotonic clock: a wall-clock correction (NTP, a person setting the time) cannot make it jump or go backwards.
Nanoseconds since the stopwatch started.
"1.502s".
A half-open span [start, end): it contains its start, not its end, so back-to-back intervals (09:00-10:00, 10:00-11:00) do not overlap.
err when end is before start.
The span both cover, none when they do not overlap.
Nanoseconds from start to end.
A five-field cron schedule: minute hour day-of-month month day-of-week. Each field takes *, numbers, ranges (9-17), lists (1,15), steps (*/15, 0-30/10), month names (JAN-DEC) and weekday names (SUN-SAT; 0 and 7 are both Sunday). Macros: @yearly @annually @monthly @weekly @daily @midnight @hourly. As in Vixie cron, when both day fields are restricted a day matches either one.
The first run strictly after t, read on the clocks of z; none when the schedule never matches (Feb 30) within eight years. A run whose wall time a daylight-saving change skips happens when the clock reaches it (moved forward); one the change repeats runs once.
24 hours.
An instant, in UTC.
A calendar day, with no time and no zone: a birthday, a due date, a day of a report.
An ISO 8601 week: weeks start on Monday, and week 1 is the one with the year's first Thursday, so early January can belong to the previous year's last week.
Days since 1970-01-01 for a civil date (Howard Hinnant's algorithm: exact for every proleptic Gregorian date in range).
The civil date days after 1970-01-01.
Monday 0 through Sunday 6.
"Monday" ... "Sunday".
"January" ... "December"; "" outside 1-12.
The current instant, from the system clock.
fn utc(year: int, month: int, day: int, hour: int, minute: int, second: int, nanos: int) -> result[Time, str] stdlib/timez/timez.sl:223#The instant at a UTC civil date and time; err names the first field out of range. Seconds run 0-59: there are no leap seconds.
A calendar date; err names what is out of range.
Today's date, in UTC.
Nanoseconds since t (negative when t is in the future).
Nanoseconds until t (negative when it has passed).
The until a socket or channel call takes, for the wall-clock instant t: deadlines run on the monotonic clock, so this measures the distance to t once and counts it from now.
Nanoseconds since the epoch; saturates outside 1677-2262.
1 to 366.
self plus d nanoseconds (negative goes back).
Nanoseconds from o to self; saturates past ~292 years.
Calendar days later, at the same time of day.
Calendar months later, at the same time of day. A day past the end of the target month is clamped to its last day: Jan 31 + 1 month is Feb 28 (29 in a leap year), not Mar 3.
Calendar years later; Feb 29 + 1 year is Feb 28.
-1, 0 or 1.
Rounded down to a multiple of d since the epoch (d <= 0 leaves it as is): truncate(MINUTE) drops seconds.
Rounded to the nearest multiple of d, halves away from zero.
Monday 00:00 of this ISO week.
The last nanosecond of the day, month and year: for inclusive range queries ("created <= end of day").
Jan 31 + 1 month is Feb 28 (29 in a leap year).
Days from self to o: 1 for tomorrow, negative for the past.
Whole years from self to o, counting a year only once its anniversary has passed: an age, a tenure.
00:00:00 UTC on this date.
fn at(self: Date, hour: int, minute: int, second: int) -> result[Time, str] stdlib/timez/timez.sl:588#The instant at this date and a UTC time of day.
"2026-10-13" (ISO 8601).
A duration written as Go writes them -- "1h30m", "1.5s", "300ms", "-2m", "2us" -- plus "d" for 24-hour days ("7d"), the unit config files reach for. "0" alone is zero. err names what was wrong; values past ~292 years are refused, not wrapped.
A duration the way Go prints one and parse_duration reads it back: "1h30m0s", "1.5s", "300ms", "2µs", "0s".
UTC, as a Zone.
A zone at a fixed offset (seconds east of UTC) that never changes.
"UTC" and fixed zones need no database; anything else is read from it.
RFC 8536: version 2 or later, whose 64-bit block and footer this reads (the 32-bit block before them is skipped).
Seconds east of UTC at instant t.
"CEST", "PST", "+0530": the abbreviation in use at t.
An instant together with the zone it is read in: what a person in that zone sees on a clock.
fn zoned(z: Zone, year: int, month: int, day: int, hour: int, minute: int, second: int, how: Disambiguation) -> result[Time, str] stdlib/timez/zone.sl:627#The instant for a wall time in z, by how when daylight saving makes it skipped or repeated.
"2026-10-13T11:30:15+02:00".
As Time.format, with %z the real offset (+0200) and %Z the zone's abbreviation (CEST).
Calendar days later at the same wall time, across daylight-saving changes (a day is then 23 or 25 hours). A wall time that does not exist on the target day moves forward, as COMPATIBLE does.
Local midnight: the first instant of this date in the zone (01:00 where midnight is skipped).