Chapter 14 Temporal Types
SourceDates, times, and anything that depends on a calendar sit inside a thicket of subtle questions: which calendar, which time zone, what counts as "today", how should this render under a given locale. Silo's temporal types address the thicket by making the relevant parameters type-level — a date's calendar is part of its type, not something that falls out of a format string — and by delegating the hard parts (calendar arithmetic, zone rules, locale rendering) to ICU4X.
Calendar-parameterised dates
A Date carries its calendar as a type parameter:
:fn today ( -> ( Date Gregorian ) ) + Temporal .. . :end
(Date Gregorian) is a Gregorian-calendar date. (Date Buddhist) is a Buddhist date. (Date Japanese) is a Japanese-era date. The type tracks the calendar, which means you can't accidentally mix two calendars in one computation — the compiler catches it.
Calendar conversion is a From impl:
my-date (( Date Buddhist) .from)
Every calendar that ICU4X supports has a calendar type here, and every pair of calendars has a From impl that goes through the Julian-day canonical intermediate.
Shortening DateTime in your own code
Writing (DateTime Gregorian) in every signature gets old fast when a project only cares about one calendar. The idiom is to add a local alias at the top of your own module:
:alias ( pub) DateTime ( DateTime Gregorian )
This shadows the stdlib's DateTime type-constructor with a zero-argument alias that means "Gregorian DateTime." Inside your module, bare DateTime now works in signatures, and the stdlib's generic (DateTime C) is still available whenever you need a different calendar. Projects that work with several calendars alias a differently-named type for each.
The standard library keeps the explicit parameterised form so calendar-sensitive code in the library itself — calendar conversion, era handling, locale-specific month-count logic — can't accidentally commit to one calendar.
Time, DateTime, TimeZone
Time is a time-of-day (no date, no zone) with fields .h .m .s .ms:
0 0 30 14 Time # ⌊Time{ h ↦ 14 m ↦ 30 s ↦ 0 ms ↦ 0 }⌉
Construction is ordinary record construction: the first-popped (top of stack) becomes the first-declared field, so values are pushed in reverse declaration order — ms, s, m, h — and the constructor pops them in .h .m .s .ms order. Reading right-to- left in the source you'd see "14 (hours) 30 (minutes) 0 (seconds) 0 (ms)", which is how a Silo person reads constructor arguments.
DateTime bundles a date and a time, parameterised by calendar:
:fn now ( -> ( DateTime Gregorian ) ) + Temporal .. . :end
A DateTime without a time zone is a "wall-clock" value — "14:30 local time on March 5th" — with no commitment to what UTC offset that maps to. Useful for storing human-facing dates that shouldn't drift when a zone changes (a birthday, a wedding date, a meeting title).
A DateTime with a time zone is a "point in time" — a value that maps to a specific UTC instant. The zone is a third type parameter:
:fn now-utc ( -> ( DateTime Gregorian TimeZone ) ) + Temporal .. . :end
Two-parameter DateTime is wall-clock; three-parameter is zoned. The compiler distinguishes them, and neither can be silently converted to the other — the conversion is explicit, because it depends on which zone you assume.
Locale-aware rendering
Temporal values render through locale-aware format traits — {Date}, {DateTime}, {Time} — which consult the CurrentLocale aspect that chapter 14 introduces in detail. The chapter-14 machinery is what you use here; nothing temporal adds to the locale story beyond participating in it:
now "{DateTime}" format
# ⌊"April 10, 2026, 9:30:12 AM"⌉ under an en-US locale
'de-DE ( Locale .try-from) .unwrap :with CurrentLocale
now "{DateTime}" format
# ⌊"10. April 2026, 09:30:12"⌉ date-format conventions follow de-DE
:end
Everything else about locales — what Locale actually is, how you build one, how the aspect defaults — lives in the localisation chapter. From here on we concentrate on temporal types themselves.
Durations and instants
Alongside the calendar types Silo ships:
Instant— a monotonic point on the UTC timeline. Good for "what time is it now" and "how much time elapsed between X and Y".UDuration— an unsigned duration. "Three minutes." Can't be negative, constructed via factory words like3 .from-mins.IDuration— a signed duration. The result of subtracting twoInstants or twoDateTimes. Can be positive, negative, or zero.
UDuration and IDuration are distinct types: the compiler makes you handle the difference between "how long" and "which direction". Subtracting two UDurations gives an IDuration; adding a UDuration to an Instant gives another Instant.
The +Temporal effect
Words that read the current time carry the +Temporal effect:
:fn now ( -> ( DateTime Gregorian ) ) + Temporal .. . :end
:fn today ( -> ( Date Gregorian ) ) + Temporal .. . : end
:fn sleep ( UDuration -> ) + Temporal .. . : end
This is just an ordinary effect (chapter 10). The host provides the +Temporal handler. A test host can install a fake clock; a replay host can feed pre-recorded timestamps. Pure computations on already-obtained temporal values don't need the effect.
ICU4X integration
Practically everything interesting in this chapter comes from ICU4X:
- Calendars (
Gregorian,Buddhist,Japanese,Chinese,Hebrew,Islamic, etc.) are ICU4X calendars. TimeZonerules come from ICU4X's time-zone database.Localeis an ICU4X locale value.- The
DateTimeformat trait goes through ICU4X'sDateTimeFormatter, which handles locale-specific skeletons, the Gregorian→Hijri switch, eras, and everything else you'd otherwise have to get wrong. - Calendar arithmetic (adding a month, finding the next Monday) uses ICU4X's calendar operations, not naive day-counting.
As with strings (chapter 13), the practical consequence is that correct behaviour for the 99% case comes for free, and the places where Silo's stdlib differs from ICU4X are just the thin layer that exposes everything as Silo types with the right trait impls.
Key points
Date,DateTime, andTimeZoneare parameterised by their calendar and (optionally) time zone. The type keeps mixed calendars out.- Two-parameter
DateTimeis wall-clock; three-parameter is zoned. The distinction is a type difference. - Locale-aware format traits (
{Date},{DateTime},{Time}) consult theCurrentLocaleaspect — same machinery as every other locale-aware piece of the standard library. - Calendar arithmetic, time-zone rules, and localised rendering all go through ICU4X. You don't reach for a second library.
- Reading the current time requires
+Temporal; pure computations on already-obtained temporal values don't. UDuration(unsigned) andIDuration(signed) are distinct types to keep "how long" and "which direction" from blurring.
Next: modules and imports — the :use system, visibility modes, and the no-orphan-rule discipline.