time 0.3.47

Date and time library. Fully interoperable with the standard library. Mostly compatible with #![no_std].
Documentation
use core::num::NonZero;

use rstest::rstest;
use time::format_description::modifier::{
    Day, End, Ignore, Month, MonthRepr, OffsetMinute, OffsetSecond, Ordinal, Padding, Period,
    TrailingInput, UnixTimestamp, UnixTimestampPrecision, WeekNumber, WeekNumberRepr, Weekday,
    WeekdayRepr, Year, YearRepr,
};
use time::format_description::{BorrowedFormatItem, Component};
use time::macros::{date, format_description, time};
use time::{Date, Time};

#[rstest]
fn nontrivial_string() {
    assert!(format_description!(r"").is_empty());
    assert!(format_description!(r###""###).is_empty());
    assert!(format_description!(b"").is_empty());
    assert!(format_description!(br"").is_empty());
    assert!(format_description!(br###""###).is_empty());
    #[rustfmt::skip]
    assert_eq!(
        format_description!("foo\
        bar\n\r\t\\\"\'\0\x20\x4E\x4e\u{20}\u{4E}\u{4_e}"),
        &[BorrowedFormatItem::Literal(b"foobar\n\r\t\\\"'\0 NN NN")]
    );
    #[rustfmt::skip]
    assert_eq!(
        format_description!(b"foo\
        bar\n\r\t\\\"\'\0\x20\x4E\x4e"),
        &[BorrowedFormatItem::Literal(b"foobar\n\r\t\\\"'\0 NN")]
    );
}

#[rstest]
fn format_description_version() {
    assert_eq!(
        format_description!(version = 1, "[["),
        &[BorrowedFormatItem::Literal(b"[")]
    );
    assert_eq!(
        format_description!(version = 1, r"\\"),
        &[BorrowedFormatItem::Literal(br"\\")]
    );
    assert_eq!(
        format_description!(version = 2, r"\\"),
        &[BorrowedFormatItem::Literal(br"\")]
    );
}

#[rstest]
fn nested_v1() {
    assert_eq!(
        format_description!(version = 1, "[optional [[[]]"),
        &[BorrowedFormatItem::Optional(&BorrowedFormatItem::Literal(
            b"["
        ))]
    );
    assert_eq!(
        format_description!(version = 1, "[optional [ [[ ]]"),
        &[BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(
            &[
                BorrowedFormatItem::Literal(b" "),
                BorrowedFormatItem::Literal(b"["),
                BorrowedFormatItem::Literal(b" "),
            ]
        ))]
    );
    assert_eq!(
        format_description!(version = 1, "[first [a][[[]]"),
        &[BorrowedFormatItem::First(&[
            BorrowedFormatItem::Literal(b"a"),
            BorrowedFormatItem::Literal(b"[")
        ])]
    );
}

#[rstest]
fn optional() {
    assert_eq!(
        format_description!(version = 2, "[optional [:[year]]]"),
        &[BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(
            &[
                BorrowedFormatItem::Literal(b":"),
                BorrowedFormatItem::Component(Component::Year(Default::default()))
            ]
        ))]
    );
    assert_eq!(
        format_description!(version = 2, "[optional [[year]]]"),
        &[BorrowedFormatItem::Optional(
            &BorrowedFormatItem::Component(Component::Year(Default::default()))
        )]
    );
    assert_eq!(
        format_description!(version = 2, r"[optional [\[]]"),
        &[BorrowedFormatItem::Optional(&BorrowedFormatItem::Literal(
            b"["
        ))]
    );
    assert_eq!(
        format_description!(version = 2, r"[optional [ \[ ]]"),
        &[BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(
            &[
                BorrowedFormatItem::Literal(b" "),
                BorrowedFormatItem::Literal(b"["),
                BorrowedFormatItem::Literal(b" "),
            ]
        ))]
    );
}

#[rstest]
fn first() {
    assert_eq!(
        format_description!(version = 2, "[first [a]]"),
        &[BorrowedFormatItem::First(&[BorrowedFormatItem::Literal(
            b"a"
        )])]
    );
    assert_eq!(
        format_description!(version = 2, "[first [a] [b]]"),
        &[BorrowedFormatItem::First(&[
            BorrowedFormatItem::Literal(b"a"),
            BorrowedFormatItem::Literal(b"b"),
        ])]
    );
    assert_eq!(
        format_description!(version = 2, "[first [a][b]]"),
        &[BorrowedFormatItem::First(&[
            BorrowedFormatItem::Literal(b"a"),
            BorrowedFormatItem::Literal(b"b"),
        ])]
    );
    assert_eq!(
        format_description!(version = 2, r"[first [a][\[]]"),
        &[BorrowedFormatItem::First(&[
            BorrowedFormatItem::Literal(b"a"),
            BorrowedFormatItem::Literal(b"["),
        ])]
    );
    assert_eq!(
        format_description!(version = 2, r"[first [a][\[\[]]"),
        &[BorrowedFormatItem::First(&[
            BorrowedFormatItem::Literal(b"a"),
            BorrowedFormatItem::Compound(&[
                BorrowedFormatItem::Literal(b"["),
                BorrowedFormatItem::Literal(b"["),
            ])
        ])]
    );
    assert_eq!(
        format_description!(
            version = 2,
            "[first [[period case:upper]] [[period case:lower]] ]"
        ),
        &[BorrowedFormatItem::First(&[
            BorrowedFormatItem::Component(Component::Period(
                Period::default()
                    .with_is_uppercase(true)
                    .with_case_sensitive(true)
            )),
            BorrowedFormatItem::Component(Component::Period(
                Period::default()
                    .with_is_uppercase(false)
                    .with_case_sensitive(true)
            )),
        ])]
    );
}

#[rstest]
fn backslash_escape() {
    assert_eq!(
        format_description!(version = 2, r"[optional [\]]]"),
        &[BorrowedFormatItem::Optional(&BorrowedFormatItem::Literal(
            b"]"
        ))]
    );
    assert_eq!(
        format_description!(version = 2, r"[optional [\[]]"),
        &[BorrowedFormatItem::Optional(&BorrowedFormatItem::Literal(
            b"["
        ))]
    );
    assert_eq!(
        format_description!(version = 2, r"[optional [\\]]"),
        &[BorrowedFormatItem::Optional(&BorrowedFormatItem::Literal(
            br"\"
        ))]
    );
    assert_eq!(
        format_description!(version = 2, r"\\"),
        &[BorrowedFormatItem::Literal(br"\")]
    );
    assert_eq!(
        format_description!(version = 2, r"\["),
        &[BorrowedFormatItem::Literal(br"[")]
    );
    assert_eq!(
        format_description!(version = 2, r"\]"),
        &[BorrowedFormatItem::Literal(br"]")]
    );
    assert_eq!(
        format_description!(version = 2, r"foo\\"),
        &[
            BorrowedFormatItem::Literal(b"foo"),
            BorrowedFormatItem::Literal(br"\"),
        ]
    );
    assert_eq!(
        format_description!(version = 2, r"\\"),
        &[BorrowedFormatItem::Literal(br"\")]
    );
    assert_eq!(
        format_description!(version = 2, r"\["),
        &[BorrowedFormatItem::Literal(br"[")]
    );
    assert_eq!(
        format_description!(version = 2, r"\]"),
        &[BorrowedFormatItem::Literal(br"]")]
    );
    assert_eq!(
        format_description!(version = 2, r"foo\\"),
        &[
            BorrowedFormatItem::Literal(b"foo"),
            BorrowedFormatItem::Literal(br"\"),
        ]
    );
}

#[rstest]
fn format_description_coverage() {
    assert_eq!(
        format_description!("[day padding:space][day padding:zero][day padding:none]"),
        &[
            BorrowedFormatItem::Component(Component::Day(
                Day::default().with_padding(Padding::Space)
            )),
            BorrowedFormatItem::Component(Component::Day(
                Day::default().with_padding(Padding::Zero)
            )),
            BorrowedFormatItem::Component(Component::Day(
                Day::default().with_padding(Padding::None)
            ))
        ]
    );
    assert_eq!(
        format_description!(
            "[offset_minute padding:space][offset_minute padding:zero][offset_minute padding:none]"
        ),
        &[
            BorrowedFormatItem::Component(Component::OffsetMinute(
                OffsetMinute::default().with_padding(Padding::Space)
            )),
            BorrowedFormatItem::Component(Component::OffsetMinute(
                OffsetMinute::default().with_padding(Padding::Zero)
            )),
            BorrowedFormatItem::Component(Component::OffsetMinute(
                OffsetMinute::default().with_padding(Padding::None)
            ))
        ]
    );
    assert_eq!(
        format_description!(
            "[offset_second padding:space][offset_second padding:zero][offset_second padding:none]"
        ),
        &[
            BorrowedFormatItem::Component(Component::OffsetSecond(
                OffsetSecond::default().with_padding(Padding::Space)
            )),
            BorrowedFormatItem::Component(Component::OffsetSecond(
                OffsetSecond::default().with_padding(Padding::Zero)
            )),
            BorrowedFormatItem::Component(Component::OffsetSecond(
                OffsetSecond::default().with_padding(Padding::None)
            )),
        ]
    );
    assert_eq!(
        format_description!("[ordinal padding:space][ordinal padding:zero][ordinal padding:none]"),
        &[
            BorrowedFormatItem::Component(Component::Ordinal(
                Ordinal::default().with_padding(Padding::Space)
            )),
            BorrowedFormatItem::Component(Component::Ordinal(
                Ordinal::default().with_padding(Padding::Zero)
            )),
            BorrowedFormatItem::Component(Component::Ordinal(
                Ordinal::default().with_padding(Padding::None)
            )),
        ]
    );
    assert_eq!(
        format_description!("[month repr:numerical]"),
        &[BorrowedFormatItem::Component(Component::Month(
            Month::default()
                .with_repr(MonthRepr::Numerical)
                .with_padding(Padding::Zero)
        ))]
    );
    assert_eq!(
        format_description!("[week_number repr:iso ]"),
        &[BorrowedFormatItem::Component(Component::WeekNumber(
            WeekNumber::default()
                .with_padding(Padding::Zero)
                .with_repr(WeekNumberRepr::Iso)
        ))]
    );
    assert_eq!(
        format_description!("[weekday repr:long one_indexed:true]"),
        &[BorrowedFormatItem::Component(Component::Weekday(
            Weekday::default()
                .with_repr(WeekdayRepr::Long)
                .with_one_indexed(true)
        ))]
    );
    assert_eq!(
        format_description!("[year repr:full base:calendar]"),
        &[BorrowedFormatItem::Component(Component::Year(
            Year::default()
                .with_repr(YearRepr::Full)
                .with_iso_week_based(false)
                .with_padding(Padding::Zero)
                .with_sign_is_mandatory(false)
        ))]
    );
    assert_eq!(
        format_description!("[[ "),
        &[
            BorrowedFormatItem::Literal(b"["),
            BorrowedFormatItem::Literal(b" ")
        ]
    );
    assert_eq!(
        format_description!("[ignore count:2]"),
        &[BorrowedFormatItem::Component(Component::Ignore(
            Ignore::count(const { NonZero::new(2).unwrap() })
        ))]
    );
    assert_eq!(
        format_description!("[unix_timestamp precision:nanosecond sign:mandatory]"),
        &[BorrowedFormatItem::Component(Component::UnixTimestamp(
            UnixTimestamp::default()
                .with_precision(UnixTimestampPrecision::Nanosecond)
                .with_sign_is_mandatory(true)
        ))]
    );
    assert_eq!(
        format_description!("[end]"),
        &[BorrowedFormatItem::Component(
            Component::End(End::default())
        )]
    );
    assert_eq!(
        format_description!("[end trailing_input:prohibit]"),
        &[BorrowedFormatItem::Component(Component::End(
            End::default().with_trailing_input(TrailingInput::Prohibit)
        ))]
    );
    assert_eq!(
        format_description!("[end trailing_input:discard]"),
        &[BorrowedFormatItem::Component(Component::End(
            End::default().with_trailing_input(TrailingInput::Discard)
        ))]
    );
}

#[rstest]
fn date_coverage() {
    assert_eq!(Ok(date!(2000-001)), Date::from_ordinal_date(2000, 1));
    assert_eq!(Ok(date!(2019-W01-1)), Date::from_ordinal_date(2018, 365));
    assert_eq!(Ok(date!(2021-W52-6)), Date::from_ordinal_date(2022, 1));
    assert_eq!(Ok(date!(2021-W34-5)), Date::from_ordinal_date(2021, 239));
}

#[rstest]
fn time_coverage() {
    assert_eq!(time!(12 AM), Time::MIDNIGHT);
    assert_eq!(Ok(time!(12 PM)), Time::from_hms(12, 0, 0));
}

mod demo {
    #[expect(dead_code)]
    type Result<T> = core::result::Result<T, ()>;
    #[expect(dead_code)]
    type Option = core::option::Option<()>;

    time::serde::format_description!(
        seconds,
        OffsetDateTime,
        "[year]-[month]-[day]T[hour]:[minute]:[second]Z"
    );
}