std/time/lib

std/time/src/lib.trb

Time: points in time (Instant), the spans between them (Duration), points on the wall clock (Timestamp), and Clock, which reads both clocks of the machine. The calendar is Date, a day of the proleptic Gregorian years 0000 to 9999, and Weekday; Timestamp.parse and Date.parse read RFC 3339 text and fail with a TimeParseError. The wall clock without a zone database is TimeOfDay, DateTime (a date and a time, no offset), UtcOffset (a fixed offset in whole seconds) and OffsetDateTime (a DateTime with its offset, so an instant that remembers how it was written); named zones and daylight saving are not here.

The three values are a count of nanoseconds in an Int64 and nothing else, so they cost what the number costs: comparing and subtracting them is integer arithmetic, and only reading a clock goes to the operating system. They are in the prelude and reach nothing; Clock and sleep are the capability, and an import of them is what says a file reads the time. Date, Weekday and TimeParseError read nothing either, and are an import because a name like Date in every file would clash with the types of the program.

type Instant

type Instant with Compare, Subtract<Instant, Duration>, Add<Duration, Instant>

A point in time. Only differences between two Instants are meaningful, never the value on its own: it is a monotonic reading whose origin is chosen by the process.

Examples

const start = Clock.now()
const later = start + 2.seconds()
print(later - start)

Related

  • Clock.now - where an Instant comes from.
  • Duration - what the difference of two of them is.

fn compare

fn compare(other: Self): Ordering

Which of the two readings came first.

fn subtract

fn subtract(other: Self): Duration

The span from other to this reading: negative where other is the later one.

fn add

fn add(other: Duration): Self

The point in time other after this one: a deadline, Clock.now() + 2.seconds().

type Duration

type Duration with Compare, Show, Add, Subtract

The span between two Instants, or a length of time asked for on its own (2.seconds()). It is signed: the difference of two readings taken the wrong way round is negative.

Examples

const wait = 2.seconds() + 500.milliseconds()
print wait
print(wait > 2.seconds())

Related

fn seconds

fn seconds(): Float64

As a fractional number of seconds: (Clock.now() - start).seconds().

fn nanoseconds

fn nanoseconds(): Int64

As a whole number of nanoseconds, which is exactly what it holds.

fn compare

fn compare(other: Self): Ordering

Which of the two spans is the shorter one.

fn add

fn add(other: Self): Self

The two spans one after the other. Panics where the sum does not fit, as every Int64 addition does.

fn subtract

fn subtract(other: Self): Self

How much longer this span is than other: negative where it is shorter.

fn show

fn show(): String

The seconds with an s behind them: 2.5s.

type Timestamp

type Timestamp with Compare, Equals, Hash, Show, Subtract<Timestamp, Duration>, Add<Duration, Timestamp>

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 - where an Instant only means something against another reading; and it can jump, because the clock of a machine is set.

It is a value and reads nothing, so a signature may carry one without the file reading the clock: it is in the prelude, and Clock.timestamp is where the current one comes from.

Examples

const written = Timestamp.fromUnixNanoseconds 1_790_000_000_500_000_000
print written
print(written + 2.seconds())
// prints 2026-09-21T14:13:20.5Z
// prints 2026-09-21T14:13:22.5Z

Related

fn fromUnixNanoseconds

static fn fromUnixNanoseconds(nanoseconds: Int64): Self

The point in time nanoseconds after 1970-01-01 00:00:00 UTC - before it, where the number is negative.

fn unixNanoseconds

fn unixNanoseconds(): Int64

Nanoseconds since 1970-01-01 00:00:00 UTC, which is exactly what it holds.

fn parse

static fn parse(text: String): Result<Self, TimeParseError>

Reads an RFC 3339 date-time, the text Timestamp.show writes and the form most protocols and databases use: 2026-09-27T08:15:00Z, 1996-12-19T16:39:57-08:00, 1985-04-12T23:20:50.52Z.

The date and the time are separated by T, t or one space. The offset is mandatory - a text without one is not an instant - and is Z, z, or +hh:mm or -hh:mm with hours 00 to 23 and 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 .; digits beyond the ninth are cut off, not rounded. A leap second (:60) fails, because a Timestamp counts POSIX time, which has none. The text has to fit the range of the nanoseconds in an Int64, from 1677-09-21T00:12:43.145224192Z to 2262-04-11T23:47:16.854775807Z; a Date covers the years outside it.

Examples

const issued = Timestamp.parse "1996-12-19T16:39:57-08:00"
print issued
print Timestamp.parse("2026-09-27T08:15:00").isError()
// prints Ok(1996-12-20T00:39:57Z)
// prints true

Errors

Fails with a TimeParseError naming the problem for text that is not an RFC 3339 date-time, a date or time that does not exist, a leap second, and an instant outside the range above.

Related

  • Timestamp.show - writes the text this reads: Timestamp.parse(t.show()) is Ok(t) for every t.
  • Date.parse - the date alone.

fn date

fn date(): Date

The day in UTC this point in time falls on.

fn compare

fn compare(other: Self): Ordering

Which of the two points in time came first.

fn subtract

fn subtract(other: Self): Duration

The span from other to this one: negative where other is the later one.

fn add

fn add(other: Duration): Self

The point in time other after this one.

fn show

fn show(): String

RFC 3339 in UTC: 2026-09-27T08:15:00Z, with as many digits of the second's fraction as it has and none where it has none (08:15:00.25Z).

fn httpDate

fn httpDate(): String

The HTTP date of RFC 9110 section 5.6.7, the IMF-fixdate that the Date field of a message carries: Sun, 06 Nov 1994 08:49:37 GMT. The day and month names are English and fixed, not localized, and the zone is always GMT. The fraction of the second is dropped, rounding towards negative infinity.

Examples

const written = Timestamp.fromUnixNanoseconds 784_111_777_500_000_000
print written.httpDate()
// prints Sun, 06 Nov 1994 08:49:37 GMT

Related

fn parseHttpDate

static fn parseHttpDate(text: String): Result<Self, TimeParseError>

Reads an HTTP date: the IMF-fixdate Timestamp.httpDate writes, and the two obsolete forms RFC 9110 section 5.6.7 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). All three name the same instant in the examples of the RFC.

The text is read as the RFC writes it, with nothing to spare: the weekday and month names are English and capitalized, the zone is GMT (UTC is refused) and the seconds run from 00 to 59. The weekday has to be the one the date falls on. The two digit year of RFC 850 is read as POSIX %y does: 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.

Examples

print Timestamp.parseHttpDate("Sun, 06 Nov 1994 08:49:37 GMT")
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()
// prints Ok(1994-11-06T08:49:37Z)
// prints Ok(1994-11-06T08:49:37Z)
// prints Ok(1994-11-06T08:49:37Z)
// prints true

Errors

Fails with a TimeParseError naming the problem for text that is none of the three forms, a name that is not a weekday or a month, a weekday that does not match the date, a date or time that does not exist, a leap second, a zone other than GMT, and an instant outside the range of a Timestamp.

fn iso8601Basic

fn iso8601Basic(): String

The compact ISO 8601 form of an instant, in UTC: 20130524T000000Z, the date format of the signing of Amazon S3's Signature Version 4. The fraction of the second is dropped, rounding towards negative infinity.

Examples

const signed = Timestamp.fromUnixNanoseconds 1_369_353_600_000_000_000
print signed.iso8601Basic()
// prints 20130524T000000Z

Related

fn parseIso8601Basic

static fn parseIso8601Basic(text: String): Result<Self, TimeParseError>

Reads the compact ISO 8601 form Timestamp.iso8601Basic writes, YYYYMMDDThhmmssZ: 20130524T000000Z. It is UTC only, so the T and the Z are capitals and there is no other offset, no fraction of the second and no separator; a leap second (:60) fails, because a Timestamp counts POSIX time, which has none.

Examples

print Timestamp.parseIso8601Basic("20130524T000000Z")
print Timestamp.parseIso8601Basic("2013-05-24T00:00:00Z").isError()
// prints Ok(2013-05-24T00:00:00Z)
// prints true

Errors

Fails with a TimeParseError naming the problem for text of any other form, a date or time that does not exist, a leap second, and an instant outside the range of a Timestamp.

fn at

fn at(offset: UtcOffset): OffsetDateTime

The same point in time on the wall clock of offset: 2026-09-27T08:15:00Z at +02:00 is 2026-09-27T10:15:00+02:00. It is the way from an instant to the date and time a person in that offset reads, and OffsetDateTime.instant is the way back.

Examples

const issued = Timestamp.parse("2026-09-27T08:15:00Z").expect("a timestamp")
const offset = UtcOffset.ofMinutes(120).expect("an offset")
print issued.at(offset)
// prints 2026-09-27T10:15:00+02:00

type TimeParseError

type TimeParseError with Show, Error

What reading a Date or a Timestamp from text fails with, and building a Date from numbers that are no day: a sentence that says what is wrong and, where it reads text, quotes it.

Examples

match Date.parse("2023-02-29") {
  Ok(day) => print day
  Fail(problem) => print problem
}
// prints 2023-02 has no day 29

field message

message: String

What is wrong, as a sentence for a person.

fn show

fn show(): String

The message, which is the whole report.

type Weekday

type Weekday with Compare, Equals, Hash, Show

A day of the week. The cases run from Monday to Sunday, which is the order of ISO 8601 and of Weekday.number: a Monday is less than a Sunday.

Examples

const day = Date.of(2000, 2, 29).expect("a day")
print day.weekday()
print day.weekday().number()
// prints Tuesday
// prints 2

Related

case Monday

case Monday

Monday, number 1: the first day of the ISO week.

case Tuesday

case Tuesday

Tuesday, number 2.

case Wednesday

case Wednesday

Wednesday, number 3.

case Thursday

case Thursday

Thursday, number 4.

case Friday

case Friday

Friday, number 5.

case Saturday

case Saturday

Saturday, number 6.

case Sunday

case Sunday

Sunday, number 7.

fn number

fn number(): Int64

The number of the day in ISO 8601: Monday is 1 and Sunday is 7.

fn compare

fn compare(other: Self): Ordering

Which of the two days comes first in the ISO week, which starts on Monday.

type Date

type Date with Compare, Equals, Hash, Show

A day in the proleptic Gregorian calendar, from 0000-01-01 to 9999-12-31: no time of day, no offset and no zone, so it is the day a calendar shows and not a point in time. Every Date is a day that exists, because the only ways to get one check it.

It is not in the prelude: use Date from "std/time". It reads no clock, so the import reaches no capability. There is no + with a Duration, because adding a day and adding 24 hours are not the same question; Date.plusDays says which one is meant.

Examples

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"))
// prints 2024-12-31
// prints Tuesday
// prints 2025-01-01
// prints 60

Related

fn of

static fn of(year: Int64, month: Int64, day: Int64): Result<Self, TimeParseError>

The day day of month month of year year.

Errors

Fails with a TimeParseError for a year outside 0 to 9999, a month outside 1 to 12 and a day the month does not have (Date.of(2023, 2, 29)).

fn fromDaysSinceEpoch

static fn fromDaysSinceEpoch(days: Int64): Result<Self, TimeParseError>

The day days after 1970-01-01, or before it where the number is negative; the way back from Date.daysSinceEpoch.

Errors

Fails with a TimeParseError for a day before 0000-01-01 or after 9999-12-31.

fn daysSinceEpoch

fn daysSinceEpoch(): Int64

The number of days from 1970-01-01 to this day, negative before it, which is exactly what it holds.

fn year

fn year(): Int64

The year, 0 to 9999.

fn month

fn month(): Int64

The month, 1 to 12.

fn day

fn day(): Int64

The day of the month, from 1 to the length of the month.

fn weekday

fn weekday(): Weekday

The day of the week this day falls on.

fn isLeapYear

fn isLeapYear(): Bool

Whether the year of this day has a 29th of February: every fourth year, except the centuries that 400 does not divide.

fn daysInMonth

static fn daysInMonth(year: Int64, month: Int64): Int64?

How many days month of year has, 28 to 31; None where the month is outside 1 to 12.

Examples

print Date.daysInMonth(2024, 2)
print Date.daysInMonth(1900, 2)
print Date.daysInMonth(2024, 13)
// prints Some(29)
// prints Some(28)
// prints None

fn plusDays

fn plusDays(days: Int64): Result<Self, TimeParseError>

The day days after this one, or before it where the number is negative.

Errors

Fails with a TimeParseError where the result is outside the years 0000 to 9999.

fn daysUntil

fn daysUntil(other: Self): Int64

How many days from this day to other: negative where other is the earlier one.

fn parse

static fn parse(text: String): Result<Self, TimeParseError>

Reads YYYY-MM-DD: a four digit year, two digits each for the month and the day, and nothing else.

Errors

Fails with a TimeParseError for any other text and for a day that does not exist.

fn startOfDay

fn startOfDay(): Result<Timestamp, TimeParseError>

The first instant of this day, 00:00:00 UTC.

Errors

Fails with a TimeParseError where that is outside the range of a Timestamp, 1677-09-21T00:12:43.145224192Z to 2262-04-11T23:47:16.854775807Z: from 1677-09-22 to 2262-04-11 it fits.

fn compare

fn compare(other: Self): Ordering

Which of the two days came first.

fn show

fn show(): String

YYYY-MM-DD, with the year to four digits and the month and day to two: 2024-12-31.

fn basic

fn basic(): String

The compact ISO 8601 form of the date, YYYYMMDD, that the signing of Amazon S3 uses: 20130524.

type TimeOfDay

type TimeOfDay with Compare, Equals, Hash, Show

A time on the wall clock, from 00:00:00 to 23:59:59.999999999: no date, no offset and no zone, so it is what a clock face shows and not a point in time. Every TimeOfDay is a time that exists, because the only ways to get one check it; a leap second (:60) is not one.

It is not in the prelude: use TimeOfDay from "std/time". It reads no clock, so the import reaches no capability.

Examples

const opening = TimeOfDay.of(8, 15, 0, 250_000_000).expect("a time")
print opening
print opening.hour()
print TimeOfDay.of(24, 0, 0).isError()
// prints 08:15:00.25
// prints 8
// prints true

Related

fn of

static fn of(hour: Int64, minute: Int64, second: Int64, nanosecond: Int64 = 0): Result<Self, TimeParseError>

The time hour:minute:second and nanosecond nanoseconds of it.

Errors

Fails with a TimeParseError for an hour outside 0 to 23, a minute or a second outside 0 to 59 and a nanosecond outside 0 to 999 999 999.

fn fromNanosecondsOfDay

static fn fromNanosecondsOfDay(nanoseconds: Int64): Result<Self, TimeParseError>

The time nanoseconds after midnight; the way back from TimeOfDay.nanosecondsOfDay.

Errors

Fails with a TimeParseError for a count outside 0 to 86 399 999 999 999, which is a day or more.

fn hour

fn hour(): Int64

The hour, 0 to 23.

fn minute

fn minute(): Int64

The minute of the hour, 0 to 59.

fn second

fn second(): Int64

The second of the minute, 0 to 59.

fn nanosecond

fn nanosecond(): Int64

The nanosecond of the second, 0 to 999 999 999.

fn nanosecondsOfDay

fn nanosecondsOfDay(): Int64

The nanoseconds since midnight, which is exactly what it holds.

fn parse

static fn parse(text: String): Result<Self, TimeParseError>

Reads hh:mm:ss with an optional fraction of the second (08:15:00, 23:59:59.999999999): two digits each, and nothing else. Digits of the fraction beyond the ninth are cut off, not rounded.

Errors

Fails with a TimeParseError for any other text, a time that does not exist and a leap second.

fn compare

fn compare(other: Self): Ordering

Which of the two times comes first in a day.

fn show

fn show(): String

hh:mm:ss, with as many digits of the fraction as it has and none where it has none: 08:15:00.25.

type UtcOffset

type UtcOffset with Compare, Equals, Hash, Show

A fixed offset from UTC in whole seconds, from -23:59:59 to +23:59:59: the +02:00 at the end of an RFC 3339 text. It is a number and not a place: it knows nothing of daylight saving or of the zone it was read in, so the same city has a different UtcOffset in summer and in winter. RFC 3339 writes minutes only, and databases send seconds, so seconds are kept.

It is not in the prelude: use UtcOffset from "std/time". It reads no clock, so the import reaches no capability.

Examples

const nepal = UtcOffset.ofMinutes(345).expect("an offset")
print nepal
print UtcOffset.utc()
print UtcOffset.parse("-12:00")
// prints +05:45
// prints +00:00
// prints Ok(-12:00)

Related

fn utc

static fn utc(): Self

No offset: UTC itself, Z in RFC 3339.

fn ofSeconds

static fn ofSeconds(seconds: Int64): Result<Self, TimeParseError>

The offset of seconds seconds east of UTC, west where the number is negative.

Errors

Fails with a TimeParseError for a number outside -86 399 to 86 399, which is a day or more.

fn ofMinutes

static fn ofMinutes(minutes: Int64): Result<Self, TimeParseError>

The offset of minutes minutes east of UTC, west where the number is negative: UtcOffset.ofMinutes(345) is +05:45.

Errors

Fails with a TimeParseError for a number outside -1439 to 1439, which is a day or more.

fn seconds

fn seconds(): Int64

The seconds east of UTC, negative to the west, which is exactly what it holds.

fn parse

static fn parse(text: String): Result<Self, TimeParseError>

Reads Z or z (UTC), or +hh:mm or -hh:mm with hours 00 to 23 and minutes 00 to 59; -00:00 is UTC. A :ss behind the minutes is read as well, which is what UtcOffset.show writes for an offset of whole seconds.

Errors

Fails with a TimeParseError for any other text and for an offset of a day or more.

fn compare

fn compare(other: Self): Ordering

Which of the two offsets is further west, which is the one with the smaller number of seconds.

fn show

fn show(): String

+hh:mm or -hh:mm, with :ss behind the minutes only where there are seconds: +02:00, -12:00, and +00:00 for UTC, which is never written as Z.

type DateTime

type DateTime with Compare, Equals, Hash, Show

A date and a time on the wall clock 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 - the same value is a different instant in every offset - which is why it cannot become a Timestamp without one: see OffsetDateTime.

It is not in the prelude: use DateTime from "std/time". It reads no clock, so the import reaches no capability.

Examples

const start = DateTime.parse("2026-12-31T23:30:00").expect("a date and time")
print start
print start.plus(2700.seconds())
// prints 2026-12-31T23:30:00
// prints Ok(2027-01-01T00:15:00)

Related

fn of

static fn of(date: Date, timeOfDay: TimeOfDay): Self

The date at timeOfDay.

fn date

fn date(): Date

The day of this date and time.

fn timeOfDay

fn timeOfDay(): TimeOfDay

The time of this date and time.

fn parse

static fn parse(text: String): Result<Self, TimeParseError>

Reads YYYY-MM-DDThh:mm:ss with an optional fraction of the second: the date and the time are separated by T, t or one space, and nothing follows, an offset least of all (that is OffsetDateTime.parse's).

Errors

Fails with a TimeParseError for any other text, a date or time that does not exist and a leap second.

fn plus

fn plus(duration: Duration): Result<Self, TimeParseError>

This date and time duration later, or earlier where the duration is negative. The wall clock is counted in exact seconds, 86 400 to a day: there is no daylight saving here to make a day longer or shorter.

Errors

Fails with a TimeParseError where the result is outside the years 0000 to 9999.

fn compare

fn compare(other: Self): Ordering

Which of the two came first: by day, and on the same day by time.

fn show

fn show(): String

YYYY-MM-DDThh:mm:ss, with the fraction of the second only where there is one: 2026-09-27T08:15:00.

type OffsetDateTime

type OffsetDateTime with Compare, Equals, Hash, Show

A DateTime with the UtcOffset it was written in: 2026-09-27T10:15:00+02:00, the RFC 3339 date-time of most protocols and databases. It is an instant, and it remembers the offset it was told in, which Timestamp does not. Two of them at the same instant but in different offsets are two values - == compares what was written - and compare orders them by the instant.

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

It is not in the prelude: use OffsetDateTime from "std/time". It reads no clock, so the import reaches no capability.

Examples

const written = OffsetDateTime.parse("2026-09-27T10:15:00+02:00").expect("a date-time")
print written.instant()
print written.offset()
print written
// prints Ok(2026-09-27T08:15:00Z)
// prints +02:00
// prints 2026-09-27T10:15:00+02:00

Related

fn of

static fn of(dateTime: DateTime, offset: UtcOffset): Self

The dateTime on the clock of offset: 2026-09-27T10:15:00 at +02:00 is 08:15:00 UTC.

fn dateTime

fn dateTime(): DateTime

The date and time as written, without the offset.

fn date

fn date(): Date

The day as written.

fn timeOfDay

fn timeOfDay(): TimeOfDay

The time as written.

fn offset

fn offset(): UtcOffset

The offset it was written in.

fn instant

fn instant(): Result<Timestamp, TimeParseError>

The point in time this names, in UTC.

Errors

Fails with a TimeParseError where the instant is outside the range of a Timestamp, 1677-09-21T00:12:43.145224192Z to 2262-04-11T23:47:16.854775807Z.

fn parse

static fn parse(text: String): Result<Self, TimeParseError>

Reads an RFC 3339 date-time and keeps the offset it was written with: 2026-09-27T10:15:00+02:00, 1996-12-19T16:39:57-08:00, 2026-09-27T08:15:00Z. The rules are Timestamp.parse's - the separator, the mandatory offset, the fraction, no leap second - except that the result is not limited to the range of a Timestamp, and that a :ss behind the minutes of the offset is read, which is what OffsetDateTime.show writes for an offset of whole seconds. Z and -00:00 are the offset +00:00.

Errors

Fails with a TimeParseError for text that is not an RFC 3339 date-time, and a date or time that does not exist.

fn compare

fn compare(other: Self): Ordering

Which of the two is the earlier instant, whatever the offset it was written in.

fn show

fn show(): String

RFC 3339 with the offset always written, +00:00 for UTC: 2026-09-27T10:15:00+02:00, which parse reads back.

extend Int64

extend Int64

The short way to write a Duration: 2.seconds() instead of a constructor.

fn seconds

fn seconds(): Duration

A Duration of this many seconds: 2.seconds(). Mainly for sandbox and task limits. More than about 292 years either way do not fit into the nanoseconds, and the Int64 multiplication under it panics then.

fn milliseconds

fn milliseconds(): Duration

A Duration of this many milliseconds: 250.milliseconds(). The Int64 multiplication under it panics where they do not fit into the nanoseconds.

type Clock

native type Clock

The two clocks of the machine: the monotonic one Clock.now measures with, and the wall clock Clock.timestamp tells the date by. Reading either is the capability clock, which is why Clock is an import and not in the prelude, and why a sandboxed script needs the module std/time granted.

Examples

const start = Clock.now()
print "started at {Clock.timestamp()}"
print(Clock.now() - start)

Related

fn now

static fn now(): Instant

The current point in time on the monotonic clock, which never goes backwards: for measuring and for deadlines.

fn timestamp

static fn timestamp(): Timestamp

The current point on 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 is no measurement - that is Clock.now's.

Examples

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

fn milliseconds

native static fn milliseconds(): Int64

Monotonic milliseconds, counted from the first reading. Only differences between two readings are meaningful, as for an Instant - this is the form a tool that measures its own work wants (torb check --timings), where a reading in milliseconds is the unit it prints.

fn sleep

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

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