Module time/datetime

time/datetime
Stability: unstable — two things in the surface are unsettled, and one is a live bug. The `to_unix` / `to_unix_utc` pair is a naming trap: `to_unix` is the shorter, more obvious name and is the WRONG one for any value with an offset, which is how `DateTime.now()` returns every value. Renaming `to_unix` to say that it reads the fields as UTC (`to_unix_as_utc`, or making `to_unix` the offset-aware one) is a breaking change nobody has decided yet. The local-offset behaviour of `now()` is itself only one release old — until 2026-09-06 it returned UTC under a local name (`issues/fixed/datetime-now-returned-utc-and-called-it-local.md`). `day_of_week` is wrong for year 0 and returns a `u8` wrap for negative years, because Sakamoto's algorithm needs floored division and C truncates toward zero (`issues/stddoc-io-datetime-day-of-week-wrong-for-non-positive-years.md`). `_validate` never checks the year, so those values reach it through the validating `new`. Beyond that: whether a real time-zone database belongs in `std` (Rust leaves it to `chrono-tz`/`jiff`) decides whether `utc_offset_secs` stays the whole zone story, and the eight public fields are unvalidated where `new` validates, so a struct literal can build a 31st of February that every method here will happily compute on. Freezing follows those three decisions, not a release count. — stable modules only change additively; this one may still change.

Wall-clock date and time, either in UTC or at a FIXED offset from it.

A DateTime is a broken-down civil date plus a utc_offset_secs. It is not zone-aware: the offset is a number of seconds, not a zone identifier, so a value cannot answer "what will this clock read next July" or be converted from one zone to another. DateTime.now() asks the C library for the offset in force right now (DST included) and stores that; every other constructor leaves the offset at 0, i.e. UTC.

Two conversions look interchangeable and are not: to_unix reads the civil fields AS IF they were UTC, while to_unix_utc subtracts the offset and so names the actual instant. Reach for to_unix_utc unless you know the offset is 0.

Example

{ DateTime } :: import "std/time/datetime";

now := DateTime.now_utc();
println(now.to_string());   // "2026-02-26T17:41:24Z"

Stability

unstable — two things in the surface are unsettled, and one is a live bug.

The to_unix / to_unix_utc pair is a naming trap: to_unix is the shorter, more obvious name and is the WRONG one for any value with an offset, which is how DateTime.now() returns every value. Renaming to_unix to say that it reads the fields as UTC (to_unix_as_utc, or making to_unix the offset-aware one) is a breaking change nobody has decided yet. The local-offset behaviour of now() is itself only one release old — until 2026-09-06 it returned UTC under a local name (issues/fixed/datetime-now-returned-utc-and-called-it-local.md).

day_of_week is wrong for year 0 and returns a u8 wrap for negative years, because Sakamoto's algorithm needs floored division and C truncates toward zero (issues/stddoc-io-datetime-day-of-week-wrong-for-non-positive-years.md). _validate never checks the year, so those values reach it through the validating new.

Beyond that: whether a real time-zone database belongs in std (Rust leaves it to chrono-tz/jiff) decides whether utc_offset_secs stays the whole zone story, and the eight public fields are unvalidated where new validates, so a struct literal can build a 31st of February that every method here will happily compute on. Freezing follows those three decisions, not a release count.

Types

DateTimeError

Typed parse/construction errors (D1 style, mirrors PercentError).

Variants

VariantFieldsDescription
InvalidFormatindex: usize

The text does not match RFC 3339 date-time; index is the BYTE offset where matching failed.

OutOfRangefield: str

A parsed/constructed component is outside its calendar range; field names it ("month", "day", "hour", "minute", "second", "offset").

Trait Implementations

impl(generic(T : Type), where(T <: ToString), T : (ToString))
impl(generic(T : Type), where(T <: ToString), T : (ToString), Format)
format : fn(self : Self, spec : str) -> String

Render self under spec. An unrecognised spec degrades to the plain to_string() rendering rather than failing.

Parameters

NameTypeNotes
selfSelf
specstr

Returns: String

Methods
to_string : (DateTimeError) fn(self : DateTimeError) -> String

Parameters

NameTypeNotes
selfDateTimeError

Returns: String

source : (DateTimeError) fn(self : DateTimeError) -> Option(dyn( + ToString))

The error that caused this one, or .None at the root of the chain.

Rust's Error::source. Defaulted to .None, so an error with nothing underneath it implements the trait by saying only what it is; a wrapper overrides it to hand back what it wrapped. Walking the chain to the root cause is not expressible yet — the returned Dyn loses the Error trait on an erased receiver, so a caller can print one link but cannot follow it (#521, issues/self-trait-in-a-return-type-loses-the-trait-on-an-erased-receiver.md).

Parameters

NameTypeNotes
selfDateTimeError

Returns: Option(dyn( + ToString))

DateTime struct
DateTime

Calendar date and time with nanosecond precision. Values are in UTC unless utc_offset_secs is non-zero.

The fields are public and are NOT checked on assignment — DateTime.new and DateTime.parse are the validating entry points. A value built with a struct literal can hold an impossible date, and every method here will compute on it without complaint.

Fields

NameTypeDescription
yeari32

Proleptic Gregorian year, unbounded and signed (negative years are years BCE). _validate does not range-check it, and day_of_week is wrong for years at or below 0 — see ## Stability.

monthu8

Month, 1..=12.

dayu8

Day of month, 1 through the length of this month in this year (leap-aware).

houru8

Hour, 0..=23.

minuteu8

Minute, 0..=59.

secondu8

Second, 0..=60 — 60 is admitted so a leap second parses rather than being rejected as garbage.

nanosecondu32

Nanoseconds within the second, 0..=999_999_999.

utc_offset_secsi32

Seconds EAST of UTC for the civil fields above; 0 means the value is UTC. ±86_399 is the accepted range. This is a fixed offset, not a zone, so it records what the offset WAS at this instant and cannot be used to convert the value to another zone.

Trait Implementations

impl(generic(T : Type), where(T <: ToString), T : (ToString))
impl(generic(T : Type), where(T <: ToString), T : (ToString), Format)
format : fn(self : Self, spec : str) -> String

Render self under spec. An unrecognised spec degrades to the plain to_string() rendering rather than failing.

Parameters

NameTypeNotes
selfSelf
specstr

Returns: String

impl(DateTime, ...)
now_utc : (DateTime) fn() -> DateTime

The current UTC date and time, with utc_offset_secs left at 0 — use now() for the local wall clock.

Panics if clock_gettime(CLOCK_REALTIME) fails, rather than reading as 1970: a broken clock must not silently yield the epoch (plans/archive/STD_API_AUDIT.md C10).

Returns: DateTime

now : (DateTime) fn() -> DateTime

Current LOCAL date/time: the wall clock in the process's zone (TZ, else the system zone) with utc_offset_secs set to that zone's offset at this instant, DST included — so to_unix_utc is exact and to_string renders ±HH:MM. Until 2026-09-06 this returned now_utc() under a local name (issues/fixed/datetime-now-returned-utc-and-called-it-local.md). Where the C library has no zone information (wasm) the offset is 0.

Returns: DateTime

from_unix : (DateTime) fn(secs : i64, nanos : i64) -> DateTime

The UTC civil date and time of a Unix timestamp — utc_offset_secs is 0, so the result is UTC even if the caller meant a local instant.

Negative secs (before 1970) work: the day and the within-day remainder are both normalised toward negative infinity. nanos is stored as given, narrowed to u32 and NOT normalised or validated — pass a value already in [0, 1e9), which is what every OS timestamp field supplies. DateTime.new is the constructor that validates.

Parameters

NameTypeNotes
secsi64
nanosi64

Returns: DateTime

to_unix : (DateTime) fn(self : DateTime) -> i64

Unix seconds obtained by reading the civil fields AS IF they were UTC.

This IGNORES utc_offset_secs, so for any value carrying an offset — everything now() returns — it is not the instant the value names. to_unix_utc is the offset-aware form and is the one to reach for; this one is useful when you want the wall clock's own epoch reading, which is how _local_utc_offset recovers a zone offset. The sub-second part is dropped: whole seconds only.

Exact for negative years too — it is the era-based inverse of from_unix, not a loop.

Parameters

NameTypeNotes
selfDateTime

Returns: i64

is_leap_year : (DateTime) fn(self : DateTime) -> bool

Whether year is a leap year under the proleptic Gregorian rule (divisible by 4, except centuries not divisible by 400) — so year 0 and year 2000 are leap years and 1900 is not.

Parameters

NameTypeNotes
selfDateTime

Returns: bool

day_of_week : (DateTime) fn(self : DateTime) -> u8

Day of the week as ISO 8601 numbers the days: 0 = Monday through 6 = Sunday. Rust's chrono spells this weekday().

Reads the CIVIL fields, so it is the weekday of the wall clock rather than of the UTC instant — the two differ near midnight for a value with an offset.

Only valid for years 1 and later. It is Sakamoto's algorithm, which needs floored division; C truncates toward zero, so year 0 is off by one and a negative year returns a u8 wrap outside 0..=6 (issues/stddoc-io-datetime-day-of-week-wrong-for-non-positive-years.md).

Parameters

NameTypeNotes
selfDateTime

Returns: u8

day_of_year : (DateTime) fn(self : DateTime) -> u16

Day of the year, 1-based: 1 on 1 January and 365 (366 in a leap year) on 31 December. Leap-aware, and correct for negative years, unlike day_of_week — it only sums month lengths.

Parameters

NameTypeNotes
selfDateTime

Returns: u16

impl(DateTime, ToString(...))
to_string : (DateTime) fn(self : DateTime) -> String

Render self as the text a USER should read — Rust's Display::fmt, not its Debug. Hand-written (or generated by derive(Error) from a per-variant format string) whenever the structural form would be wrong.

Parameters

NameTypeNotes
selfDateTime

Returns: String

impl(DateTime, ...)
new : (DateTime) fn(year : i32, month : u8, day : u8, hour : u8, minute : u8, second : u8, nanosecond : u32, utc_offset_secs : i32) -> Result(DateTime, DateTimeError)

Component constructor with calendar validation (leap-aware days, leap-second 60 admitted, offset within a day).

Parameters

NameTypeNotesDescription
yeari32

Proleptic Gregorian year, unbounded and signed (negative years are years BCE). _validate does not range-check it, and day_of_week is wrong for years at or below 0 — see ## Stability.

monthu8

Month, 1..=12.

dayu8

Day of month, 1 through the length of this month in this year (leap-aware).

houru8

Hour, 0..=23.

minuteu8

Minute, 0..=59.

secondu8

Second, 0..=60 — 60 is admitted so a leap second parses rather than being rejected as garbage.

nanosecondu32default: 0

Nanoseconds within the second, 0..=999_999_999.

utc_offset_secsi32default: 0

Seconds EAST of UTC for the civil fields above; 0 means the value is UTC. ±86_399 is the accepted range. This is a fixed offset, not a zone, so it records what the offset WAS at this instant and cannot be used to convert the value to another zone.

Returns: Result(DateTime, DateTimeError)

parse : (DateTime) fn(text : String) -> Result(DateTime, DateTimeError)

Parse an RFC 3339 date-time: YYYY-MM-DDTHH:MM:SS[.f...][Z|±HH:MM]. Lowercase t/z are accepted (the RFC permits them); fractional seconds keep nanosecond precision (digits beyond 9 are truncated); calendar ranges are validated leap-aware.

Parameters

NameTypeNotes
textString

Returns: Result(DateTime, DateTimeError)

to_unix_utc : (DateTime) fn(self : DateTime) -> i64

Unix seconds of the INSTANT this value names — unlike to_unix, which reads the wall-clock fields as if they were UTC, this subtracts the UTC offset. Equal instants at different offsets agree here.

Parameters

NameTypeNotes
selfDateTime

Returns: i64

add : (DateTime) fn(self : DateTime, d : Duration) -> DateTime

This value shifted forward by d (offset preserved; nanoseconds carry).

Parameters

NameTypeNotes
selfDateTime
dDuration

Returns: DateTime

sub : (DateTime) fn(self : DateTime, d : Duration) -> DateTime

This value shifted backward by d.

Parameters

NameTypeNotes
selfDateTime
dDuration

Returns: DateTime

impl(DateTime, Eq(DateTime)(...))
impl(DateTime, Ord(DateTime)(...))
Methods
== : (DateTime) fn(lhs : DateTime, rhs : DateTime) -> bool

Parameters

NameTypeNotes
lhsDateTime
rhsDateTime

Returns: bool

!= : (DateTime) fn(lhs : DateTime, rhs : DateTime) -> bool

Parameters

NameTypeNotes
lhsDateTime
rhsDateTime

Returns: bool

< : (DateTime) fn(lhs : DateTime, rhs : DateTime) -> bool

Parameters

NameTypeNotes
lhsDateTime
rhsDateTime

Returns: bool

<= : (DateTime) fn(lhs : DateTime, rhs : DateTime) -> bool

Parameters

NameTypeNotes
lhsDateTime
rhsDateTime

Returns: bool

> : (DateTime) fn(lhs : DateTime, rhs : DateTime) -> bool

Parameters

NameTypeNotes
lhsDateTime
rhsDateTime

Returns: bool

>= : (DateTime) fn(lhs : DateTime, rhs : DateTime) -> bool

Parameters

NameTypeNotes
lhsDateTime
rhsDateTime

Returns: bool

cmp : (DateTime) fn(lhs : DateTime, rhs : DateTime) -> Ordering

Parameters

NameTypeNotes
lhsDateTime
rhsDateTime

Returns: Ordering