Module time/datetime
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
Typed parse/construction errors (D1 style, mirrors PercentError).
Variants
| Variant | Fields | Description |
|---|---|---|
InvalidFormat | index: usize | The text does not match RFC 3339 |
OutOfRange | field: str | A parsed/constructed component is outside its calendar range; |
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) -> StringRender self under spec. An unrecognised spec degrades to the plain
to_string() rendering rather than failing.
Parameters
| Name | Type | Notes |
|---|---|---|
self | Self | |
spec | str |
Returns: String
Methods
to_string : (DateTimeError) fn(self : DateTimeError) -> Stringsource : (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
| Name | Type | Notes |
|---|---|---|
self | DateTimeError |
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
| Name | Type | Description |
|---|---|---|
year | i32 | Proleptic Gregorian year, unbounded and signed (negative years are
years BCE). |
month | u8 | Month, |
day | u8 | Day of month, |
hour | u8 | Hour, |
minute | u8 | Minute, |
second | u8 | Second, |
nanosecond | u32 | Nanoseconds within the second, |
utc_offset_secs | i32 | Seconds EAST of UTC for the civil fields above; |
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) -> StringRender self under spec. An unrecognised spec degrades to the plain
to_string() rendering rather than failing.
Parameters
| Name | Type | Notes |
|---|---|---|
self | Self | |
spec | str |
Returns: String
impl(DateTime, ...)
now_utc : (DateTime) fn() -> DateTimeThe 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() -> DateTimeCurrent 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) -> DateTimeThe 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
| Name | Type | Notes |
|---|---|---|
secs | i64 | |
nanos | i64 |
Returns: DateTime
to_unix : (DateTime) fn(self : DateTime) -> i64Unix 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
| Name | Type | Notes |
|---|---|---|
self | DateTime |
Returns: i64
is_leap_year : (DateTime) fn(self : DateTime) -> boolWhether 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
| Name | Type | Notes |
|---|---|---|
self | DateTime |
Returns: bool
day_of_week : (DateTime) fn(self : DateTime) -> u8Day 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
| Name | Type | Notes |
|---|---|---|
self | DateTime |
Returns: u8
day_of_year : (DateTime) fn(self : DateTime) -> u16Day 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
| Name | Type | Notes |
|---|---|---|
self | DateTime |
Returns: u16
impl(DateTime, ToString(...))
to_string : (DateTime) fn(self : DateTime) -> Stringimpl(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
| Name | Type | Notes | Description |
|---|---|---|---|
year | i32 | Proleptic Gregorian year, unbounded and signed (negative years are
years BCE). | |
month | u8 | Month, | |
day | u8 | Day of month, | |
hour | u8 | Hour, | |
minute | u8 | Minute, | |
second | u8 | Second, | |
nanosecond | u32 | default: 0 | Nanoseconds within the second, |
utc_offset_secs | i32 | default: 0 | Seconds EAST of UTC for the civil fields above; |
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
| Name | Type | Notes |
|---|---|---|
text | String |
Returns: Result(DateTime, DateTimeError)
to_unix_utc : (DateTime) fn(self : DateTime) -> i64Unix 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
| Name | Type | Notes |
|---|---|---|
self | DateTime |
Returns: i64
add : (DateTime) fn(self : DateTime, d : Duration) -> DateTimeimpl(DateTime, Eq(DateTime)(...))
impl(DateTime, Ord(DateTime)(...))
Methods
== : (DateTime) fn(lhs : DateTime, rhs : DateTime) -> bool!= : (DateTime) fn(lhs : DateTime, rhs : DateTime) -> bool< : (DateTime) fn(lhs : DateTime, rhs : DateTime) -> bool<= : (DateTime) fn(lhs : DateTime, rhs : DateTime) -> bool> : (DateTime) fn(lhs : DateTime, rhs : DateTime) -> bool>= : (DateTime) fn(lhs : DateTime, rhs : DateTime) -> bool