core:temporal
Minimal date and time types, modelled on TC39 Temporal.
This is the MVP described in docs/wep-2026-06-05-core-temporal.md: it
provides only the two value types every temporal API is built around, an
exact point on the timeline (Instant) and that instant paired with a
time-zone interpretation (ZonedDateTime). They carry ISO 8601 / RFC 3339
formatting (Display), civil field accessors, RFC 3339 parsing, epoch
constructors, and serde Serialize/Deserialize (an RFC 3339 string under
CBOR's date/time tag 0, a bare string in JSON; an epoch-seconds number is
also accepted on decode); arithmetic, now(), and
Duration are deferred to follow-up work. The types exist so a serde format
such as core:json or core:cbor has a concrete Wado type for timestamps.
Relationship to wasi:clocks: wasi:clocks exposes its own Instant
record ({ seconds, nanoseconds }) for the system clock. That type is a
Component Model binding — pinned to a specific WASI version and regenerated
by wado-from-idl — whereas core:temporal's Instant is a plain,
version-independent Wado type that grows methods. They share a name and
field layout deliberately: it is "the same concept, a different type". To
bridge them without making either depend on the other's representation,
core:temporal provides From impls both ways (a field-for-field copy), so
a clock reading converts with Instant::from(SystemClock::now()) and back
with ClockInstant::from. Merely naming the wasi:clocks record in these
impls is not a runtime use of the clock, so it pulls in no WASI import for a
component that never calls one.
Both core:temporal types are ISO 8601 only and so store no calendar.
Synopsis
let epoch = Instant::from_epoch_seconds(0);
assert `{epoch}` == "1970-01-01T00:00:00Z";
assert epoch < Instant::from_epoch_seconds(1_000_000_000);
assert ZonedDateTime::parse_rfc3339("2023-11-14T22:13:20+09:00") matches { Ok(zoned) && zoned.hour() == 22
&& `{zoned}` == "2023-11-14T22:13:20+09:00" };
Structs
pub struct Instant
An exact point on the timeline, as the offset from the Unix epoch (1970-01-01T00:00:00Z). Time-zone- and calendar-independent.
Corresponds to Temporal.Instant and the wasi:clocks instant record.
Where Temporal stores epochNanoseconds as a BigInt, this uses an i64
second count (≈ ±292 billion years, far beyond Temporal's ±273,790-year
range) plus a u32 sub-second part for full nanosecond resolution — the
exact shape of wasi:clocks instant.
Ord is auto-derived and orders chronologically: seconds is compared
first, then nanoseconds.
seconds: i64
Whole seconds since the Unix epoch. Negative values are before it.
nanoseconds: u32
Sub-second component, always in 0..1_000_000_000. Incrementing
nanoseconds always moves forward in time, even when seconds < 0
(e.g. one nanosecond before the epoch is
Instant { seconds: -1, nanoseconds: 999_999_999 }). Use Instant::new
to normalize an arbitrary count into this range; the accessors assume it.
pub fn new(seconds: i64, nanoseconds: i64) -> Instant
Construct an instant nanoseconds after seconds since the Unix epoch,
normalizing so the stored sub-second part lands in 0..1_000_000_000
even when nanoseconds is negative or >= 1_000_000_000. This is the
canonical funnel that upholds the field invariant the accessors rely on.
pub fn from_epoch_seconds(seconds: i64) -> Instant
Construct an instant from whole seconds since the Unix epoch (CBOR tag 1 integer form).
pub fn from_epoch_milliseconds(milliseconds: i64) -> Instant
Construct an instant from milliseconds since the Unix epoch. The inverse
of epoch_milliseconds.
pub fn from_epoch_nanoseconds(nanoseconds: i64) -> Instant
Construct an instant from nanoseconds since the Unix epoch. The inverse
of epoch_nanoseconds, and like it limited to the i64 range
(≈ ±292 years).
pub fn from_unix_seconds(seconds: f64) -> Instant
Construct an instant from fractional seconds since the Unix epoch (CBOR
tag 1 floating-point form). The fraction is rounded to the nearest
nanosecond; f64 precision limits sub-microsecond accuracy near the
present.
pub fn epoch_milliseconds(&self) -> i64
Milliseconds since the Unix epoch (Temporal.Instant.epochMilliseconds).
pub fn epoch_nanoseconds(&self) -> i64
Nanoseconds since the Unix epoch (Temporal.Instant.epochNanoseconds).
Temporal uses a BigInt; this returns an i64 and so is only valid within
≈ ±292 years of the epoch (1678..=2262) — outside that range the
multiplication traps. Use the seconds/nanoseconds fields directly for
the full range.
pub fn to_rfc3339(&self) -> String
RFC 3339 / ISO 8601 string in UTC (…Z), using the least sub-second
precision (0, 3, 6, or 9 fraction digits) that represents the instant
exactly. This is the serde wire form.
impl Display for Instant
fn fmt(&self, f: &mut Formatter)
impl DisplayAlt for Instant
fn fmt_alt(&self, f: &mut Formatter)
impl Serialize for Instant
fn serialize<S: Serializer>(&self, s: &mut S) -> Result<(), SerializeError>
impl Deserialize for Instant
fn deserialize<D: Deserializer>(d: &mut D) -> Result<Instant, DeserializeError>
impl From<ClockInstant> for Instant
fn from(value: ClockInstant) -> Instant
pub struct ZonedDateTime
An exact instant together with the time zone it is interpreted in.
Corresponds to Temporal.ZonedDateTime — the only "complete" temporal
value (an instant plus a wall-clock interpretation). The calendar is always
ISO 8601 and therefore not stored. The broken-down wall-clock fields (year,
month, day, hour, …) are a function of instant + time_zone, computed on
demand by accessors rather than stored state.
Ord is auto-derived and orders by instant first, then time_zone
lexically. That is not a meaningful chronological order across different
zones beyond the instant; ordering distinct zones is the caller's concern.
instant: Instant
The exact instant.
time_zone: String
IANA time-zone identifier (e.g. "America/New_York", "UTC") or a
fixed UTC offset (e.g. "+09:00"). Mirrors the Temporal time-zone slot,
which is also a string after the removal of Temporal.TimeZone. Only
"UTC"/"Z" and fixed ±HH:MM offsets are interpretable today; every
operation traps on an IANA name until a tz database is bundled.
pub fn parse_rfc3339(text: String) -> Result<ZonedDateTime, DeserializeError>
Parse an RFC 3339 / ISO 8601 timestamp such as
"2023-11-14T22:13:20.5+09:00" or "1970-01-01T00:00:00Z", preserving
the offset as a fixed-offset zone (so it round-trips through to_rfc3339
/ Display in canonical form).
The grammar is [±]Y…Y-MM-DDThh:mm:ss[.fraction](Z|±hh:mm): the year is
four or more digits with an optional sign (ISO 8601 extended years), the
date/time separator may be T, t, or a space, the fraction is
truncated to nanosecond precision, and the offset is mandatory. "Z" and
"+00:00" both normalize to a "Z" zone. Field values are range-checked
against the ISO 8601 calendar (so e.g. "2023-02-29" and "…T24:00:00Z"
are rejected); malformed or out-of-range input returns a
DeserializeError carrying the byte offset of the problem.
pub fn to_rfc3339(&self) -> String
RFC 3339 / ISO 8601 string in this zone's local time, with the offset
suffix (or Z) and the least sub-second precision that is exact. This is
the serde wire form. Traps on an IANA zone name (offset resolution is
future work).
pub fn year(&self) -> i32
ISO year in the zone's local time. May be negative or beyond four digits.
pub fn month(&self) -> i32
Month of the year, 1..=12.
pub fn day(&self) -> i32
Day of the month, 1..=31.
pub fn hour(&self) -> i32
Hour of the day, 0..=23.
pub fn minute(&self) -> i32
Minute of the hour, 0..=59.
pub fn second(&self) -> i32
Second of the minute, 0..=59.
pub fn millisecond(&self) -> i32
Millisecond of the second, 0..=999.
pub fn microsecond(&self) -> i32
Microsecond of the millisecond, 0..=999.
pub fn nanosecond(&self) -> i32
Nanosecond of the microsecond, 0..=999.
pub fn day_of_week(&self) -> i32
Day of the week, 1 (Monday) through 7 (Sunday), per ISO 8601.
pub fn day_of_year(&self) -> i32
Day of the year, 1..=366.
pub fn days_in_month(&self) -> i32
Number of days in this month, 28..=31.
pub fn days_in_year(&self) -> i32
Number of days in this year, 365 or 366.
pub fn in_leap_year(&self) -> bool
Whether this year is a leap year in the ISO 8601 calendar.
pub fn months_in_year(&self) -> i32
Number of months in this year — always 12 for ISO 8601.
