Skip to main content

DateTime

Struct DateTime 

Source
pub struct DateTime { /* private fields */ }
Expand description

Container for temporal values for TOML format, based on RFC 3339.

General bounds are in forced during parsing but leniently, so things like exact leap second rules are not enforced, you should generally being converting these time values, to a more complete time library like jiff before use.

The DateTime type is essentially more compact version of:

use toml_spanner::{Date, Time, TimeOffset};
struct DateTime {
    date: Option<Date>,
    time: Option<Time>,
    offset: Option<TimeOffset>,
}

For more details on support formats inside TOML documents please reference the TOML v1.1.0 Specification.

Mapping DateTime to the TOML time kinds works like the following:

#[rustfmt::skip]
fn datetime_to_toml_kind(value: &toml_spanner::DateTime) -> &'static str {
    match (value.date(),value.time(),value.offset()) {
          (Some(_date), Some(_time), Some(_offset)) => "Offset Date-Time",
          (Some(_date), Some(_time), None         ) => "Local Date-Time",
          (Some(_date), None       , None         ) => "Local Date",
          (None       , Some(_time), None         ) => "Local Time",
        _ => unreachable!("for a DateTime produced from the toml-spanner::parse"),
    }
}

§Constructing a DateTime

Parsing from a TOML document is the usual path. For ad hoc values, FromStr accepts any RFC 3339 form:

use toml_spanner::{Date, DateTime, TimeOffset};
let value: DateTime = "2026-01-04T12:30:45Z".parse().unwrap();
assert_eq!(value.date(), Some(Date { year: 2026, month: 1, day: 4 }));
assert_eq!(value.offset(), Some(TimeOffset::Z));

To build one from parts, use Date::new and Time::new for the components then pick the constructor matching the TOML kind you want: DateTime::local_date, DateTime::local_time, DateTime::local_datetime, or DateTime::offset_datetime. The result serializes back to its RFC 3339 form via DateTime::format:

use std::mem::MaybeUninit;
use toml_spanner::{Date, DateTime, Time, TimeOffset};

let dt = DateTime::offset_datetime(
    Date::new(2026, 1, 4).unwrap(),
    Time::new(12, 30, 45, 0).unwrap(),
    TimeOffset::Z,
).unwrap();

let mut buf = MaybeUninit::uninit();
assert_eq!(dt.format(&mut buf), "2026-01-04T12:30:45Z");
Toggle Jiff Conversions Examples

This example is kept in sync with crates/third-party-integration-tests/src/main.rs. Edits here should be mirrored there.

use toml_spanner::{
    Arena, Date, DateTime, Error as TomlError, FromToml, Item, Key, Span as TomlSpan,
    Table, Time, TimeOffset, ToToml, ToTomlError,
};

fn extract_date(
    datetime: &toml_spanner::DateTime,
    span: TomlSpan,
) -> Result<jiff::civil::Date, TomlError> {
    let Some(date) = datetime.date() else {
        return Err(TomlError::custom("Missing date component", span));
    };
    match jiff::civil::Date::new(date.year as i16, date.month as i8, date.day as i8) {
        Ok(value) => Ok(value),
        Err(err) => Err(TomlError::custom(format!("Invalid date: {err}"), span)),
    }
}

fn extract_time(
    datetime: &toml_spanner::DateTime,
    span: TomlSpan,
) -> Result<jiff::civil::Time, TomlError> {
    let Some(time) = datetime.time() else {
        return Err(TomlError::custom("Missing time component", span));
    };
    match jiff::civil::Time::new(
        time.hour as i8,
        time.minute as i8,
        time.second as i8,
        time.nanosecond as i32,
    ) {
        Ok(value) => Ok(value),
        Err(err) => Err(TomlError::custom(format!("Invalid time: {err}"), span)),
    }
}

fn extract_timezone(
    datetime: &toml_spanner::DateTime,
    span: TomlSpan,
) -> Result<jiff::tz::TimeZone, TomlError> {
    let Some(offset) = datetime.offset() else {
        return Err(TomlError::custom("Missing offset component", span));
    };
    match offset {
        toml_spanner::TimeOffset::Z => Ok(jiff::tz::TimeZone::UTC),
        toml_spanner::TimeOffset::Custom { minutes } => {
            match jiff::tz::Offset::from_seconds(minutes as i32 * 60) {
                Ok(jiff_offset) => Ok(jiff::tz::TimeZone::fixed(jiff_offset)),
                Err(err) => Err(TomlError::custom(format!("Invalid offset: {err}"), span)),
            }
        }
    }
}

fn to_jiff_date(item: &toml_spanner::Item<'_>) -> Result<jiff::civil::Date, TomlError> {
    let Some(datetime) = item.as_datetime() else {
        return Err(item.expected(&"date"));
    };
    if datetime.time().is_some() {
        return Err(TomlError::custom(
            "Expected lone date but found time",
            item.span(),
        ));
    };
    extract_date(datetime, item.span())
}

fn to_jiff_datetime(item: &toml_spanner::Item<'_>) -> Result<jiff::civil::DateTime, TomlError> {
    let Some(datetime) = item.as_datetime() else {
        return Err(item.expected(&"civil datetime"));
    };
    if datetime.offset().is_some() {
        return Err(TomlError::custom(
            "Expected naive timestamp but found offset",
            item.span(),
        ));
    };
    Ok(jiff::civil::DateTime::from_parts(
        extract_date(datetime, item.span())?,
        extract_time(datetime, item.span())?,
    ))
}

fn to_jiff_timestamp(item: &toml_spanner::Item<'_>) -> Result<jiff::Timestamp, TomlError> {
    let Some(datetime) = item.as_datetime() else {
        return Err(item.expected(&"timestamp"));
    };
    let civil = jiff::civil::DateTime::from_parts(
        extract_date(datetime, item.span())?,
        extract_time(datetime, item.span())?,
    );
    let timezone = extract_timezone(datetime, item.span())?;
    match timezone.to_timestamp(civil) {
        Ok(value) => Ok(value),
        Err(err) => Err(TomlError::custom(
            format!("Invalid timestamp: {err}"),
            item.span(),
        )),
    }
}

fn from_jiff_date(date: jiff::civil::Date) -> Result<Date, ToTomlError> {
    let year = date.year();
    if year < 0 {
        return Err(ToTomlError::from("year out of TOML range (0..=9999)"));
    }
    Date::new(year as u16, date.month() as u8, date.day() as u8)
        .ok_or_else(|| ToTomlError::from("date out of TOML range"))
}

fn from_jiff_time(time: jiff::civil::Time) -> Result<Time, ToTomlError> {
    Time::new(
        time.hour() as u8,
        time.minute() as u8,
        time.second() as u8,
        time.subsec_nanosecond() as u32,
    )
    .ok_or_else(|| ToTomlError::from("time out of TOML range"))
}

fn from_jiff_civil_datetime(dt: jiff::civil::DateTime) -> Result<DateTime, ToTomlError> {
    Ok(DateTime::local_datetime(
        from_jiff_date(dt.date())?,
        from_jiff_time(dt.time())?,
    ))
}

fn from_jiff_timestamp(ts: jiff::Timestamp) -> Result<DateTime, ToTomlError> {
    let civil = ts.to_zoned(jiff::tz::TimeZone::UTC).datetime();
    Ok(DateTime::offset_datetime(
        from_jiff_date(civil.date())?,
        from_jiff_time(civil.time())?,
        TimeOffset::Z,
    )
    .expect("TimeOffset::Z is always valid"))
}

#[derive(Debug, PartialEq)]
pub struct TimeConfig {
    pub date: jiff::civil::Date,
    pub datetime: jiff::civil::DateTime,
    pub timestamp: jiff::Timestamp,
}

impl<'de> FromToml<'de> for TimeConfig {
    fn from_toml(
        ctx: &mut toml_spanner::Context<'de>,
        value: &toml_spanner::Item<'de>,
    ) -> Result<Self, toml_spanner::Failed> {
        let mut th = value.table_helper(ctx)?;
        let config = TimeConfig {
            date: th.required_mapped("date", to_jiff_date)?,
            datetime: th.required_mapped("datetime", to_jiff_datetime)?,
            timestamp: th.required_mapped("timestamp", to_jiff_timestamp)?,
        };
        Ok(config)
    }
}

impl ToToml for TimeConfig {
    fn to_toml<'a>(&'a self, arena: &'a Arena) -> Result<Item<'a>, ToTomlError> {
        let Some(mut table) = Table::try_with_capacity(3, arena) else {
            return Err(ToTomlError::from("table capacity exceeded"));
        };
        table.insert_unique(
            Key::new("date"),
            Item::from(DateTime::local_date(from_jiff_date(self.date)?)),
            arena,
        );
        table.insert_unique(
            Key::new("datetime"),
            Item::from(from_jiff_civil_datetime(self.datetime)?),
            arena,
        );
        table.insert_unique(
            Key::new("timestamp"),
            Item::from(from_jiff_timestamp(self.timestamp)?),
            arena,
        );
        Ok(table.into_item())
    }
}

fn main() {
    let arena = toml_spanner::Arena::new();

    let toml_doc = r#"
        date = 1997-02-28
        datetime = 2066-01-30T14:45:00
        timestamp = 3291-12-01T00:45:00Z
    "#;
    let mut doc = toml_spanner::parse(toml_doc, &arena).unwrap();
    let config: TimeConfig = doc.to().unwrap();

    let emitted = toml_spanner::to_string(&config).unwrap();

    let round_trip_arena = toml_spanner::Arena::new();
    let mut round_trip_doc = toml_spanner::parse(&emitted, &round_trip_arena).unwrap();
    let round_trip: TimeConfig = round_trip_doc.to().unwrap();
    assert_eq!(config, round_trip);
}

Implementations§

Source§

impl DateTime

Source

pub const MAX_FORMAT_LEN: usize = 40

Maximum number of bytes produced by DateTime::format.

Use this to size the MaybeUninit buffer passed to DateTime::format.

Source

pub fn local_date(date: Date) -> DateTime

Builds a DateTime holding only a calendar date.

Serialized as a TOML local date.

§Examples
use toml_spanner::{Date, DateTime};

let dt = DateTime::local_date(Date::new(2026, 3, 15).unwrap());
assert_eq!(dt.date().unwrap().month, 3);
assert!(dt.time().is_none());
Source

pub fn local_time(time: Time) -> DateTime

Builds a DateTime holding only a time of day.

Serialized as a TOML local time.

§Examples
use toml_spanner::{Time, DateTime};

let dt = DateTime::local_time(Time::new(14, 30, 5, 0).unwrap());
assert_eq!(dt.time().unwrap().hour, 14);
assert!(dt.date().is_none());
Source

pub fn local_datetime(date: Date, time: Time) -> DateTime

Builds a DateTime holding a date and time without a UTC offset.

Serialized as a TOML local date-time. Use this for wall-clock values whose timezone is implied by context rather than recorded in the data.

§Examples
use toml_spanner::{Date, Time, DateTime};

let dt = DateTime::local_datetime(
    Date::new(2026, 3, 15).unwrap(),
    Time::new(14, 30, 5, 0).unwrap(),
);
assert_eq!(dt.date().unwrap().day, 15);
assert!(dt.offset().is_none());
Source

pub fn offset_datetime( date: Date, time: Time, offset: TimeOffset, ) -> Option<DateTime>

Builds a DateTime holding a date, time, and UTC offset.

Serialized as a TOML offset date-time, the form to use when the value refers to an absolute moment in time.

Returns None when offset is a TimeOffset::Custom whose minutes fall outside ±23:59. TimeOffset::Z always succeeds.

§Examples
use toml_spanner::{Date, Time, DateTime, TimeOffset};

let dt = DateTime::offset_datetime(
    Date::new(2026, 3, 15).unwrap(),
    Time::new(14, 30, 5, 0).unwrap(),
    TimeOffset::Z,
).unwrap();
assert_eq!(dt.offset(), Some(TimeOffset::Z));

assert!(DateTime::offset_datetime(
    Date::new(2026, 3, 15).unwrap(),
    Time::new(14, 30, 5, 0).unwrap(),
    TimeOffset::Custom { minutes: 1440 },
).is_none());
Source

pub fn time(&self) -> Option<Time>

Returns the time component, or None for a local-date value.

Source

pub fn format<'a>(&self, buf: &'a mut MaybeUninit<[u8; 40]>) -> &'a str

Formats this datetime into the provided buffer and returns the result as a &str.

The output follows RFC 3339 formatting and matches the TOML serialization of the value. The caller must supply an uninitializebuffer of MAX_FORMAT_LEN bytes; the returned &str borrows from that buffer, starting from the beginning.

§Examples
use std::mem::MaybeUninit;
use toml_spanner::DateTime;

let dt: DateTime = "2026-01-04T12:30:45Z".parse().unwrap();
let mut buf = MaybeUninit::uninit();
assert_eq!(dt.format(&mut buf), "2026-01-04T12:30:45Z");
assert_eq!(size_of_val(&buf), DateTime::MAX_FORMAT_LEN);
Source

pub fn date(&self) -> Option<Date>

Returns the date component, or None for a local-time value.

Source

pub fn offset(&self) -> Option<TimeOffset>

Returns the UTC offset, or None for local date-times and local times.

Trait Implementations§

Source§

impl Clone for DateTime

Source§

fn clone(&self) -> DateTime

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for DateTime

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> From<DateTime> for Item<'de>

Source§

fn from(value: DateTime) -> Self

Converts to this type from the input type.
Source§

impl FromStr for DateTime

Source§

type Err = DateTimeError

The associated error which can be returned from parsing.
Source§

fn from_str(s: &str) -> Result<Self, Self::Err>

Parses a string s to return a value of this type. Read more
Source§

impl PartialEq for DateTime

Source§

fn eq(&self, other: &Self) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl ToToml for DateTime

Available on crate feature to-toml only.
Source§

fn to_toml<'a>(&'a self, _: &'a Arena) -> Result<Item<'a>, ToTomlError>

Produces a TOML Item representing this value. Read more
Source§

fn to_optional_toml<'a>( &'a self, arena: &'a Arena, ) -> Result<Option<Item<'a>>, ToTomlError>

Produces an optional TOML Item representing this value. Read more
Source§

impl Copy for DateTime

Source§

impl Eq for DateTime

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.