# Time types

Store a moment as an `Instant`. The other types are for what an instant
isn't: a date with no time, a moment seen in a time zone, a length of
time, a zone, or a day of the week.

| Type | What it is | Example | On the wire |
|---|---|---|---|
| `Instant` | One point on the timeline, in UTC, to the millisecond | when a todo was created | `"2026-01-15T12:00:00Z"`, `"2026-01-15T12:00:00.250Z"` |
| `CalendarDate` | A date with no time or zone | a birthday, an invoice date | `"2026-10-25"` |
| `ZonedTime` | An `Instant` seen in a `Zone`, for showing or reasoning about local time | "09:30 in London" | `{ "instant": "2026-10-25T08:30:00Z", "zone": "Europe/London" }` |
| `Duration` | Elapsed time | a timeout, a retention period | `"PT5M"` |
| `Zone` | An IANA time zone, a built-in enum | `Zone.europe_london` | `"Europe/London"` |
| `Weekday` | A built-in enum | `Weekday.monday` | `"monday"` |

Use `CalendarDate` only for facts that really are dates. These types have
no time of day on its own and no date and time without a zone: a
`ZonedTime` holds an instant and the zone it is seen in. For what has no
zone, such as a shop's opening time, use `LocalTime` and `LocalDateTime`
([Time and calendar additions](time-and-calendar-additions.md)).
They are never a moment until a zone is chosen.

Time types never turn into each other or into `Text` by themselves. A
function's parameters and results use named types built on them, such as
`type DueAt = Instant {}`
([Signatures use named types](signatures-use-named-types.md)).

## Literals

- An `Instant` literal is ISO 8601 with a `Z` or an offset, to the
  millisecond: `"2026-10-25T08:30:00Z"`. The compiler checks it.
- A `CalendarDate` literal is a real date, `"2026-10-25"`, also checked by
  the compiler.
- A `Duration` is written only with a unit, `15m`. `min:` and `max:` on a
  `Duration` type are enforced.
- A `ZonedTime` has no literal.

Literals compare as times, whatever offset they are written with:

```jadpo
/// When an invoice falls due.
type DueAt = Instant {}

/// The day an invoice is dated.
type InvoiceDate = CalendarDate {}

test "time literals are checked while compiling and compare as time" {
    assert DueAt("2026-10-25T13:00:00+01:00") == DueAt("2026-10-25T12:00:00Z")
    assert InvoiceDate("2026-10-25") < InvoiceDate("2026-10-26")
}
```

## Precision and range

An `Instant` keeps milliseconds, the precision JavaScript clients, SQLite
and PostgreSQL all share. A literal with microseconds is refused, and writes
to PostgreSQL's microsecond column are always whole milliseconds.

An `Instant` holds the years 0000 to 9999, up to
`9999-12-30T22:00:00.999Z`, the last instant whose local date is in year
9999 in every zone. A `CalendarDate` holds the same years. A literal past
them is refused when the code compiles, and arithmetic that goes past them
is the fault `time_out_of_range`.

## `Zone`, `Weekday` and `Locale`

These are built-in enums. `Weekday` has a variant for each day of the
week, such as `Weekday.monday`. `Zone` has a variant for each zone in the
IANA time zone database's `zone.tab` (2026d), plus `utc`, named in
lower_snake_case: `Zone.america_argentina_buenos_aires`.

`Locale` has a variant for each locale Unicode CLDR 48.2.3 covers fully,
plus, for each language without a region, the language with its likely
region (`Locale.fr` and `Locale.fr_fr`, `Locale.en_gb`). That is 533 in
all, and `Locale.en_xa`, the pseudo-locale
([Tooling for translations](tooling-for-translations.md)). Each is named in
lower_snake_case from its BCP 47 tag. Both tables change only in a change
of their own.

## Comparing and storing a `ZonedTime`

Two `ZonedTime`s are equal when both their instants and their zones are.
They have no order of their own, since two in different zones have none.
Their instants do, through `temporal.instant(...)`.

A `ZonedTime` field of an entity is stored in two columns: its instant as
`timestamptz(3)` and its zone's IANA name as `text`.

## Where time can be read

`clock.now` in a function, a library function or an inline function is
`type.clock_context` ([The current time](the-current-time-clock-now.md)).
`clock.advance(...)` outside a test is `type.test_clock_context`
([Controlling time](controlling-time.md)). `config.<name>` is a
setting.
