std/time

Instant, Duration and Timestamp, Date, Weekday, TimeOfDay, DateTime, UtcOffset and OffsetDateTime for the wall clock, RFC 3339, HTTP date and compact ISO 8601 text, plus Clock and sleep, which read the two clocks.

The generated reference of std/time has every public construct with its signature, its doc comment and its examples.

std/time is time: points in time (Instant) and the spans between them (Duration), and a point on the wall clock (Timestamp). All three are a count of nanoseconds in an Int64 and cost what the number costs. They are values, read nothing, and are in the prelude. Clock, which reads the two clocks of the machine, and sleep, which waits on one, are an import: reading the time is a capability, and the import is what says a file does it.

Date is a day of the calendar, Weekday is its day of the week, and Timestamp.parse reads RFC 3339 text; the HTTP date of the Date field and the compact ISO 8601 form of S3's signing have their own pair of methods. The wall clock without a zone database is TimeOfDay, DateTime, UtcOffset and OffsetDateTime. They read no clock either, but they are not in the prelude: a name like Date in every file would clash with the types of the program, so they are an import that reaches no capability.

Import#

use Clock, sleep from "std/time"
use Date, Weekday, TimeParseError from "std/time"
use TimeOfDay, DateTime, UtcOffset, OffsetDateTime from "std/time"
const wait = 2.seconds() + 500.milliseconds()
print wait.seconds()

2.seconds() and 500.milliseconds() are members std/time adds to Int64, which std/number owns, so a file names where they come from - except that the prelude already does (public use Int64.seconds, Int64.milliseconds from "std/time"), which is why the snippet above needs no use at all. Duration.seconds is a member of the type itself and needs nothing either way.

Declarations#

Instant#

type Instant with Compare, Subtract<Instant, Duration>, Add<Duration, Instant> {
  fn compare(other: Self): Ordering
  fn subtract(other: Self): Duration
  fn add(other: Duration): Self
}

A point in time. Only the difference between two Instants is meaningful, never the value on its own - end - start is what Subtract<Instant, Duration> gives back, and start + 2.seconds() is a deadline. The field is private: an Instant comes from Clock.now() and from nowhere else.

Duration#

type Duration with Compare, Show, Add, Subtract {
  fn seconds(): Float64
  fn nanoseconds(): Int64
  fn compare(other: Self): Ordering
  fn add(other: Self): Self
  fn subtract(other: Self): Self
  fn show(): String
}

The span between two Instants, or a length of time asked for on its own; signed, so a difference taken the wrong way round is negative. It shows as its seconds with an s behind them (2.5s), and two of them add and subtract. 2.seconds() and 250.milliseconds() come from extend Int64 { fn seconds(): Duration } and its sibling, which the prelude re-exports by name; mainly for sandbox and task limits. nanoseconds() is the exact count, which is what a Duration crosses into the runtime as (Task.within).

Timestamp#

type Timestamp with Compare, Equals, Hash, Show, Subtract<Timestamp, Duration>, Add<Duration, Timestamp> {
  static fn fromUnixNanoseconds(nanoseconds: Int64): Self
  fn unixNanoseconds(): Int64
  static fn parse(text: String): Result<Self, TimeParseError>
  fn date(): Date
  fn compare(other: Self): Ordering
  fn subtract(other: Self): Duration
  fn add(other: Duration): Self
  fn show(): String
  fn httpDate(): String
  static fn parseHttpDate(text: String): Result<Self, TimeParseError>
  fn iso8601Basic(): String
  static fn parseIso8601Basic(text: String): Result<Self, TimeParseError>
  fn at(offset: UtcOffset): OffsetDateTime
}

A point on the wall clock: nanoseconds since 1970-01-01 00:00:00 UTC. It means something on its own - when a file was last written, which is what File.metadata answers it for (std/fs) - where an Instant only means something against another reading, and it can jump, because the clock of a machine is set. It shows as RFC 3339 in UTC, with as many digits of the second's fraction as it has: 2026-09-21T14:13:20.5Z. The difference of two is a Duration, and a Duration added to one is another. The current one is Clock.timestamp().

A Timestamp is in the prelude, like Duration and Instant, because it reads nothing: a type with a timestamp in a field - a row of a database, a token that expires - carries one without its file reading the clock, and torb publish reports no clock for a package that only names the type (see torb publish). There is no Timestamp.now() for the same reason: the value type would read the clock without an import saying so.

const written = Timestamp.fromUnixNanoseconds 1_790_000_000_500_000_000
print written
print(written + 2.seconds())

Timestamp.parse reads an RFC 3339 date-time and is the inverse of show: Timestamp.parse(t.show()) is Ok(t) for every t. The date and the time are separated by T, t or one space. The offset is mandatory, because a text without one is not an instant: Z, z, +hh:mm or -hh:mm (hours 00 to 23, minutes 00 to 59; -00:00 is UTC). It is applied, so the result is the instant in UTC. The fraction of the second is one or more digits after a ., and digits beyond the ninth are cut off, not rounded. A leap second (:60) is refused, because a Timestamp is POSIX time, which has none. A text outside the range of the nanoseconds in an Int64, 1677-09-21T00:12:43.145224192Z to 2262-04-11T23:47:16.854775807Z, fails with a message that names the range; the years outside it are what a Date is for.

const issued = Timestamp.parse "1996-12-19T16:39:57-08:00"
print issued
print Timestamp.parse("2026-09-27T08:15:00").isError()
print Timestamp.parse("1990-12-31T23:59:60Z").isError()

httpDate() writes the HTTP date of RFC 9110 section 5.6.7, the IMF-fixdate of a Date field, and Timestamp.parseHttpDate reads it: Sun, 06 Nov 1994 08:49:37 GMT. The day and month names are English and fixed, never localized, the zone is always GMT, and the fraction of the second is dropped, rounding towards negative infinity. parseHttpDate also reads the two obsolete forms the RFC requires a recipient to accept, RFC 850 (Sunday, 06-Nov-94 08:49:37 GMT) and asctime (Sun Nov 6 08:49:37 1994, the day padded with a space), and all three name the same instant. The text is read as the RFC writes it: capitalized English names, GMT (UTC is refused), seconds 00 to 59, a leap second refused, and the weekday has to be the one the date falls on. The two digit year of RFC 850 follows POSIX %y, 70 to 99 are 1970 to 1999 and 00 to 69 are 2000 to 2069, so reading needs no clock. A date outside the range of a Timestamp fails.

const written = Timestamp.fromUnixNanoseconds 784_111_777_500_000_000
print written.httpDate()
print Timestamp.parseHttpDate("Sunday, 06-Nov-94 08:49:37 GMT")
print Timestamp.parseHttpDate("Sun Nov  6 08:49:37 1994")
print Timestamp.parseHttpDate("Mon, 06 Nov 1994 08:49:37 GMT").isError()
print Timestamp.parseHttpDate("Sun, 06 Nov 1994 08:49:37 UTC").isError()

iso8601Basic() writes the compact ISO 8601 form that the signing of Amazon S3's Signature Version 4 uses, 20130524T000000Z, and Timestamp.parseIso8601Basic reads it. It is UTC only: the T and the Z are capitals, and there is no other offset, no fraction (writing drops it, rounding towards negative infinity) and no separator. Date.basic() is the date alone, 20130524.

use Date from "std/time"

const signed = Timestamp.fromUnixNanoseconds 1_369_353_600_000_000_000
print signed.iso8601Basic()
print Timestamp.parseIso8601Basic("20130524T000000Z")
print Timestamp.parseIso8601Basic("2013-05-24T00:00:00Z").isError()
print Date.of(2013, 5, 24).expect("a day").basic()

date() is the UTC day of a Timestamp, and Date.startOfDay() the first instant of a day. at(offset) is the same instant on the wall clock of a fixed offset, an OffsetDateTime.

Date#

type Date with Compare, Equals, Hash, Show {
  static fn of(year: Int64, month: Int64, day: Int64): Result<Self, TimeParseError>
  static fn fromDaysSinceEpoch(days: Int64): Result<Self, TimeParseError>
  fn daysSinceEpoch(): Int64
  fn year(): Int64
  fn month(): Int64
  fn day(): Int64
  fn weekday(): Weekday
  fn isLeapYear(): Bool
  static fn daysInMonth(year: Int64, month: Int64): Int64?
  fn plusDays(days: Int64): Result<Self, TimeParseError>
  fn daysUntil(other: Self): Int64
  static fn parse(text: String): Result<Self, TimeParseError>
  fn startOfDay(): Result<Timestamp, TimeParseError>
  fn compare(other: Self): Ordering
  fn show(): String
  fn basic(): String
}

A day in the proleptic Gregorian calendar, from 0000-01-01 to 9999-12-31, with no time of day, offset or zone: the day a calendar shows, not a point in time. It holds the days since 1970-01-01 and nothing else, so every Date is a day that exists, and the ways to get one - Date.of, Date.fromDaysSinceEpoch, Date.parse, Timestamp.date - check. year(), month(), day() and weekday() read it back, plusDays and daysUntil count in whole days. There is no + with a Duration: adding a day and adding 24 hours are different questions, and a method named for the unit answers it. Date.parse reads YYYY-MM-DD and nothing else, and show() writes it.

use Date from "std/time"

const day = Date.of(2024, 12, 31).expect("a day")
print day
print day.weekday()
print day.plusDays(1).expect("a day")
print day.daysUntil(Date.of(2025, 3, 1).expect("a day"))
print Date.of(2023, 2, 29)

TimeOfDay#

type TimeOfDay with Compare, Equals, Hash, Show {
  static fn of(hour: Int64, minute: Int64, second: Int64, nanosecond: Int64 = 0): Result<Self, TimeParseError>
  static fn fromNanosecondsOfDay(nanoseconds: Int64): Result<Self, TimeParseError>
  fn hour(): Int64
  fn minute(): Int64
  fn second(): Int64
  fn nanosecond(): Int64
  fn nanosecondsOfDay(): Int64
  static fn parse(text: String): Result<Self, TimeParseError>
  fn compare(other: Self): Ordering
  fn show(): String
}

A time on the wall clock, 00:00:00 to 23:59:59.999999999, with no date, offset or zone: what a clock face shows. It holds the nanoseconds since midnight and nothing else, so every TimeOfDay is a time that exists; TimeOfDay.of(hour, minute, second, nanosecond = 0) and TimeOfDay.parse check, and a leap second (:60) is refused. It shows as 08:15:00, with the fraction of the second only where there is one (08:15:00.25), and parse reads exactly that.

use TimeOfDay from "std/time"

const opening = TimeOfDay.of(8, 15, 0, 250_000_000).expect("a time")
print opening
print opening.hour()
print TimeOfDay.parse("23:59:59.999999999")
print TimeOfDay.of(24, 0, 0).isError()

UtcOffset#

type UtcOffset with Compare, Equals, Hash, Show {
  static fn utc(): Self
  static fn ofSeconds(seconds: Int64): Result<Self, TimeParseError>
  static fn ofMinutes(minutes: Int64): Result<Self, TimeParseError>
  fn seconds(): Int64
  static fn parse(text: String): Result<Self, TimeParseError>
  fn compare(other: Self): Ordering
  fn show(): String
}

A fixed offset from UTC in whole seconds, -23:59:59 to +23:59:59. It is a number and not a place: it knows nothing of daylight saving, so one city has a different UtcOffset in summer and in winter. RFC 3339 writes minutes only, but databases send seconds, so seconds are kept. UtcOffset.utc(), ofMinutes and ofSeconds build one (the last two check the range), and parse reads Z, +hh:mm and -hh:mm, and a :ss behind the minutes. It shows as +02:00, -12:00, +05:45, with :ss only where there are seconds, and UTC as +00:00, never as Z.

use UtcOffset from "std/time"

const nepal = UtcOffset.ofMinutes(345).expect("an offset")
print nepal
print UtcOffset.utc()
print UtcOffset.parse("-12:00")
print UtcOffset.parse("+24:00").isError()

DateTime#

type DateTime with Compare, Equals, Hash, Show {
  static fn of(date: Date, timeOfDay: TimeOfDay): Self
  fn date(): Date
  fn timeOfDay(): TimeOfDay
  static fn parse(text: String): Result<Self, TimeParseError>
  fn plus(duration: Duration): Result<Self, TimeParseError>
  fn compare(other: Self): Ordering
  fn show(): String
}

A date and a time with no offset and no zone: the "local" date and time a calendar and a clock show together, 2026-09-27T08:15:00. It is not a point in time, because the same value is a different instant in every offset; it becomes one with an offset, as an OffsetDateTime. DateTime.of(date, timeOfDay) joins the two halves and date() and timeOfDay() give them back. parse reads YYYY-MM-DDThh:mm:ss with an optional fraction, separated by T, t or a space, and no offset. plus(duration) counts in exact seconds, 86 400 to a day, over month and year borders and leap days, and fails where the result leaves the years 0000 to 9999.

use DateTime from "std/time"

const start = DateTime.parse("2026-12-31T23:30:00").expect("a date and time")
print start.plus(2700.seconds())
print DateTime.parse("2026-09-27T08:15:00Z").isError()

OffsetDateTime#

type OffsetDateTime with Compare, Equals, Hash, Show {
  static fn of(dateTime: DateTime, offset: UtcOffset): Self
  fn dateTime(): DateTime
  fn date(): Date
  fn timeOfDay(): TimeOfDay
  fn offset(): UtcOffset
  fn instant(): Result<Timestamp, TimeParseError>
  static fn parse(text: String): Result<Self, TimeParseError>
  fn compare(other: Self): Ordering
  fn show(): String
}

A DateTime with the UtcOffset it was written in: the RFC 3339 date-time of most protocols and databases (timestamptz), 2026-09-27T10:15:00+02:00. Unlike a Timestamp it remembers the offset. parse follows Timestamp.parse (separator, mandatory offset, fraction, no leap second) but keeps the written offset, is not limited to the range of a Timestamp, and also reads :ss in the offset; show always writes the offset (+00:00 for UTC), so OffsetDateTime.parse(x.show()) is Ok(x). instant() is the Timestamp, and fails outside its range; Timestamp.at(offset) is the way there. == compares what was written, so the same instant in two offsets is not equal, and compare orders by the instant.

use OffsetDateTime, UtcOffset from "std/time"

const written = OffsetDateTime.parse("2026-09-27T10:15:00+02:00").expect("a date-time")
print written.instant()
print written.offset()
const issued = Timestamp.parse("2026-09-27T08:15:00Z").expect("a timestamp")
print issued.at(UtcOffset.ofMinutes(345).expect("an offset"))
print(written == issued.at(UtcOffset.ofMinutes(120).expect("an offset")))

There are no named zones and no daylight saving here: an offset is a number the text or the caller names.

Weekday#

type Weekday with Compare, Equals, Hash, Show {
  case Monday
  case Tuesday
  case Wednesday
  case Thursday
  case Friday
  case Saturday
  case Sunday
  fn number(): Int64
  fn compare(other: Self): Ordering
}

The day of the week, from Monday to Sunday: the order of ISO 8601, which is the order of Compare and of number() (Monday is 1, Sunday is 7). It shows as its name.

TimeParseError#

type TimeParseError with Show, Error {
  message: String
  fn show(): String
}

What Date, Timestamp, TimeOfDay, UtcOffset, DateTime and OffsetDateTime fail with: a message that says what is wrong, quoting the text where it read some. The name follows NumberParseError.

Clock#

native type Clock {
  static fn now(): Instant
  static fn timestamp(): Timestamp
  native static fn milliseconds(): Int64
}

The two clocks of the machine; reading either is the capability clock, and a sandboxed script needs the module std/time granted. now() is the monotonic clock, which never goes backwards: for measuring and for deadlines. timestamp() is the wall clock, the date a signature, an expiry or a log line is written with; it jumps where the clock of the machine is set, both ways, so the difference of two timestamps is no measurement. milliseconds() is monotonic milliseconds counted from the first reading - only differences between two readings are meaningful, as for an Instant - and is the form a tool that measures its own work wants (torb check --timings), where an Instant and a Duration would be two allocations per measurement.

use Clock from "std/time"

const issued = Clock.timestamp()
const expires = issued + 3600.seconds()
print "valid from {issued} until {expires}"

In the browser - the playground - the wall clock is the one of the page, Date.now() of JavaScript.

sleep#

native fn sleep(seconds: Float64): Task<Void>

Suspends the running task for this many seconds. Needs .await(), like every Task (see std/task).

Esc