Revision control
Copy as Markdown
Other Tools
use std::num::NonZero;
use time::format_description::well_known::{Iso8601, Rfc2822, Rfc3339};
use time::format_description::{BorrowedFormatItem, Component, OwnedFormatItem, modifier};
use time::macros::{date, datetime, offset, time, utc_datetime};
use time::parsing::Parsed;
use time::{
Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcDateTime, UtcOffset, Weekday, error,
format_description as fd,
};
macro_rules! invalid_literal {
() => {
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidLiteral { .. },
))
};
}
macro_rules! invalid_component {
($name:literal) => {
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent($name),
))
};
}
#[test]
#[expect(clippy::cognitive_complexity, reason = "all test the same thing")]
fn rfc_2822() -> time::Result<()> {
assert_eq!(
OffsetDateTime::parse("Sat, 02 Jan 2021 03:04:05 GMT", &Rfc2822)?,
datetime!(2021-01-02 03:04:05 UTC),
);
assert_eq!(
OffsetDateTime::parse("Sat, 02 Jan 2021 03:04:05 UT", &Rfc2822)?,
datetime!(2021-01-02 03:04:05 UTC),
);
assert_eq!(
OffsetDateTime::parse("Sat, 02 Jan 2021 03:04:05 +0000", &Rfc2822)?,
datetime!(2021-01-02 03:04:05 UTC),
);
assert_eq!(
OffsetDateTime::parse("Sat, 02 Jan 2021 03:04:05 +0607", &Rfc2822)?,
datetime!(2021-01-02 03:04:05 +06:07),
);
assert_eq!(
OffsetDateTime::parse("Sat, 02 Jan 2021 03:04:05 -0607", &Rfc2822)?,
datetime!(2021-01-02 03:04:05 -06:07),
);
assert_eq!(
OffsetDateTime::parse("Fri, 31 Dec 2021 23:59:60 Z", &Rfc2822)?,
datetime!(2021-12-31 23:59:59.999_999_999 UTC),
);
assert_eq!(
OffsetDateTime::parse("Fri, 31 Dec 2021 23:59:60 z", &Rfc2822)?,
datetime!(2021-12-31 23:59:59.999_999_999 UTC),
);
assert_eq!(
OffsetDateTime::parse("Fri, 31 Dec 2021 23:59:60 a", &Rfc2822)?,
datetime!(2021-12-31 23:59:59.999_999_999 UTC),
);
assert_eq!(
OffsetDateTime::parse("Fri, 31 Dec 2021 23:59:60 A", &Rfc2822)?,
datetime!(2021-12-31 23:59:59.999_999_999 UTC),
);
assert_eq!(
OffsetDateTime::parse("Fri, 31 Dec 2021 17:52:60 -0607", &Rfc2822)?,
datetime!(2021-12-31 17:52:59.999_999_999 -06:07),
);
assert_eq!(
OffsetDateTime::parse("Sat, 01 Jan 2022 06:06:60 +0607", &Rfc2822)?,
datetime!(2022-01-01 06:06:59.999_999_999 +06:07),
);
assert_eq!(
UtcDateTime::parse("Sat, 02 Jan 2021 03:04:05 GMT", &Rfc2822)?,
utc_datetime!(2021-01-02 03:04:05),
);
assert_eq!(
UtcDateTime::parse("Sat, 02 Jan 2021 03:04:05 UT", &Rfc2822)?,
utc_datetime!(2021-01-02 03:04:05),
);
assert_eq!(
UtcDateTime::parse("Sat, 02 Jan 2021 03:04:05 +0000", &Rfc2822)?,
utc_datetime!(2021-01-02 03:04:05),
);
assert_eq!(
UtcDateTime::parse("Sat, 02 Jan 2021 03:04:05 +0607", &Rfc2822)?,
datetime!(2021-01-02 03:04:05 +06:07).to_utc(),
);
assert_eq!(
UtcDateTime::parse("Sat, 02 Jan 2021 03:04:05 -0607", &Rfc2822)?,
datetime!(2021-01-02 03:04:05 -06:07).to_utc(),
);
assert_eq!(
UtcDateTime::parse("Fri, 31 Dec 2021 23:59:60 Z", &Rfc2822)?,
utc_datetime!(2021-12-31 23:59:59.999_999_999),
);
assert_eq!(
UtcDateTime::parse("Fri, 31 Dec 2021 23:59:60 z", &Rfc2822)?,
utc_datetime!(2021-12-31 23:59:59.999_999_999),
);
assert_eq!(
UtcDateTime::parse("Fri, 31 Dec 2021 23:59:60 a", &Rfc2822)?,
utc_datetime!(2021-12-31 23:59:59.999_999_999),
);
assert_eq!(
UtcDateTime::parse("Fri, 31 Dec 2021 23:59:60 A", &Rfc2822)?,
utc_datetime!(2021-12-31 23:59:59.999_999_999),
);
assert_eq!(
UtcDateTime::parse("Fri, 31 Dec 2021 17:52:60 -0607", &Rfc2822)?,
datetime!(2021-12-31 17:52:59.999_999_999 -06:07).to_utc(),
);
assert_eq!(
UtcDateTime::parse("Sat, 01 Jan 2022 06:06:60 +0607", &Rfc2822)?,
datetime!(2022-01-01 06:06:59.999_999_999 +06:07).to_utc(),
);
assert_eq!(
Date::parse("Sat, 02 Jan 2021 03:04:05 GMT", &Rfc2822)?,
date!(2021-01-02)
);
assert_eq!(
Date::parse("Sat, 02 Jan 2021 03:04:05 +0607", &Rfc2822)?,
date!(2021-01-02)
);
assert_eq!(
Date::parse("Sat, 02 Jan 2021 03:04:05 -0607", &Rfc2822)?,
date!(2021-01-02)
);
assert_eq!(
Date::parse("Sat, 02 Jan 21 03:04:05 -0607", &Rfc2822)?,
date!(2021-01-02)
);
assert_eq!(
Date::parse("Sat, 02 Jan 71 03:04:05 -0607", &Rfc2822)?,
date!(1971-01-02)
);
assert_eq!(
OffsetDateTime::parse("Sat,(\\\u{a})02 Jan 2021 03:04:05 GMT", &Rfc2822)?,
datetime!(2021-01-02 03:04:05 UTC),
);
#[rustfmt::skip]
assert_eq!(
Time::parse(
" \t Sat,\r\n \
(\tfoo012FOO!)\
(\u{1}\u{b}\u{e}\u{7f})\
(\\\u{0})\
(\\\u{1}\\\u{9}\\\u{28}\\\u{29}\\\\u{5c}\\\u{7f})\
(\\\n\\\u{b})\
02 \r\n \r\n Jan 2021 03:04:05 GMT",
&Rfc2822
)?,
time!(03:04:05)
);
Ok(())
}
#[test]
fn issue_661() -> time::Result<()> {
assert_eq!(
OffsetDateTime::parse("02 Jan 2021 03:04:05 +0607", &Rfc2822)?,
datetime!(2021-01-02 03:04:05 +06:07),
);
assert_eq!(
Date::parse("02 Jan 2021 03:04:05 +0607", &Rfc2822)?,
date!(2021-01-02)
);
Ok(())
}
#[expect(clippy::cognitive_complexity, reason = "all test the same thing")]
#[test]
fn rfc_2822_err() {
// In the first test, the "weekday" component is invalid, we're actually testing the whitespace
// parser. The error is because the parser attempts and fails to parse the whitespace, but it's
// optional so it backtracks and attempts to parse the weekday (while still having leading
// whitespace). The weekday is also optional, so it backtracks and attempts to parse the day.
// This component is required, so it fails at this point.
assert!(matches!(
OffsetDateTime::parse(" \r\nM", &Rfc2822),
invalid_component!("day")
));
assert!(matches!(
OffsetDateTime::parse("Mon:", &Rfc2822),
invalid_literal!()
));
assert!(matches!(
OffsetDateTime::parse("Mon, o2", &Rfc2822),
invalid_component!("day")
));
assert!(matches!(
OffsetDateTime::parse("Mon, 02_", &Rfc2822),
invalid_literal!()
));
assert!(matches!(
OffsetDateTime::parse("Mon, 02 jxn", &Rfc2822),
invalid_component!("month")
));
assert!(matches!(
OffsetDateTime::parse("Mon, 02 Jan_", &Rfc2822),
invalid_literal!()
));
assert!(matches!(
OffsetDateTime::parse("Mon, 02 Jan abcd", &Rfc2822),
invalid_component!("year")
));
assert!(matches!(
OffsetDateTime::parse("Mon, 02 Jan 1899", &Rfc2822),
invalid_component!("year")
));
assert!(matches!(
OffsetDateTime::parse("Mon, 02 Jan 2021_", &Rfc2822),
invalid_literal!()
));
assert!(matches!(
OffsetDateTime::parse("Mon, 02 Jan 21_", &Rfc2822),
invalid_literal!()
));
assert!(matches!(
OffsetDateTime::parse("Mon, 02 Jan 2021 ab", &Rfc2822),
invalid_component!("hour")
));
assert!(matches!(
OffsetDateTime::parse("Mon, 02 Jan 2021 03_", &Rfc2822),
invalid_literal!()
));
assert!(matches!(
OffsetDateTime::parse("Mon, 02 Jan 2021 03:ab", &Rfc2822),
invalid_component!("minute")
));
assert!(matches!(
OffsetDateTime::parse("Mon, 02 Jan 2021 03:04_", &Rfc2822),
invalid_literal!()
));
assert!(matches!(
OffsetDateTime::parse("Mon, 02 Jan 2021 03:04:ab", &Rfc2822),
invalid_component!("second")
));
assert!(matches!(
OffsetDateTime::parse("Mon, 02 Jan 2021 03:04:05_", &Rfc2822),
invalid_literal!()
));
assert!(matches!(
OffsetDateTime::parse("Mon, 02 Jan 2021 03:04 6", &Rfc2822),
invalid_component!("offset hour")
));
assert!(matches!(
OffsetDateTime::parse("Mon, 02 Jan 2021 03:04:05 -6", &Rfc2822),
invalid_component!("offset hour")
));
assert!(matches!(
OffsetDateTime::parse("Mon, 02 Jan 2021 03:04:05 -060", &Rfc2822),
invalid_component!("offset minute")
));
assert!(matches!(
OffsetDateTime::parse("Fri, 31 Dec 2021 23:59:61 Z", &Rfc2822),
Err(error::Parse::TryFromParsed(error::TryFromParsed::ComponentRange(component)))
if component.name() == "second" && !component.is_conditional()
));
assert!(matches!(
OffsetDateTime::parse("Fri, 31 Dec 2021 03:04:60 Z", &Rfc2822),
Err(error::Parse::TryFromParsed(error::TryFromParsed::ComponentRange(component)))
if component.name() == "second" && component.is_conditional()
));
assert!(matches!(
OffsetDateTime::parse("Fri, 30 Dec 2021 23:59:60 Z", &Rfc2822),
Err(error::Parse::TryFromParsed(error::TryFromParsed::ComponentRange(component)))
if component.name() == "second" && component.is_conditional()
));
}
#[test]
fn rfc_3339() -> time::Result<()> {
assert_eq!(
OffsetDateTime::parse("2021-01-02T03:04:05Z", &Rfc3339)?,
datetime!(2021-01-02 03:04:05 UTC),
);
assert_eq!(
OffsetDateTime::parse("2021-12-31T23:59:60Z", &Rfc3339)?,
datetime!(2021-12-31 23:59:59.999_999_999 UTC),
);
assert_eq!(
OffsetDateTime::parse("2015-07-01T00:59:60+01:00", &Rfc3339)?,
datetime!(2015-06-30 23:59:59.999_999_999 UTC),
);
assert_eq!(
OffsetDateTime::parse("2021-01-02T03:04:05.1Z", &Rfc3339)?,
datetime!(2021-01-02 03:04:05.1 UTC),
);
assert_eq!(
OffsetDateTime::parse("2021-01-02T03:04:05.12Z", &Rfc3339)?,
datetime!(2021-01-02 03:04:05.12 UTC),
);
assert_eq!(
OffsetDateTime::parse("2021-01-02T03:04:05.123Z", &Rfc3339)?,
datetime!(2021-01-02 03:04:05.123 UTC),
);
assert_eq!(
OffsetDateTime::parse("2021-01-02T03:04:05.1234Z", &Rfc3339)?,
datetime!(2021-01-02 03:04:05.123_4 UTC),
);
assert_eq!(
OffsetDateTime::parse("2021-01-02T03:04:05.12345Z", &Rfc3339)?,
datetime!(2021-01-02 03:04:05.123_45 UTC),
);
assert_eq!(
OffsetDateTime::parse("2021-01-02T03:04:05.123456Z", &Rfc3339)?,
datetime!(2021-01-02 03:04:05.123_456 UTC),
);
assert_eq!(
OffsetDateTime::parse("2021-01-02T03:04:05.1234567Z", &Rfc3339)?,
datetime!(2021-01-02 03:04:05.123_456_7 UTC),
);
assert_eq!(
OffsetDateTime::parse("2021-01-02T03:04:05.12345678Z", &Rfc3339)?,
datetime!(2021-01-02 03:04:05.123_456_78 UTC),
);
assert_eq!(
OffsetDateTime::parse("2021-01-02T03:04:05.123456789Z", &Rfc3339)?,
datetime!(2021-01-02 03:04:05.123_456_789 UTC),
);
assert_eq!(
OffsetDateTime::parse("2021-01-02T03:04:05.123456789-01:02", &Rfc3339)?,
datetime!(2021-01-02 03:04:05.123_456_789 -01:02),
);
assert_eq!(
OffsetDateTime::parse("2021-01-02T03:04:05.123456789+01:02", &Rfc3339)?,
datetime!(2021-01-02 03:04:05.123_456_789 +01:02),
);
assert_eq!(
OffsetDateTime::parse("2021-01-02T03:04:05.123-00:01", &Rfc3339)?,
datetime!(2021-01-02 03:04:05.123 -00:01),
);
assert_eq!(
Date::parse("2021-01-02T03:04:05Z", &Rfc3339)?,
date!(2021-01-02),
);
assert_eq!(
Date::parse("2021-01-02T03:04:05.123+01:02", &Rfc3339)?,
date!(2021-01-02),
);
assert_eq!(
Date::parse("2021-01-02T03:04:05.123-01:02", &Rfc3339)?,
date!(2021-01-02),
);
assert_eq!(
UtcOffset::parse("2021-01-02T03:04:05Z", &Rfc3339)?,
offset!(UTC),
);
assert_eq!(
UtcOffset::parse("2021-01-02T03:04:05.123+01:02", &Rfc3339)?,
offset!(+01:02),
);
assert_eq!(
UtcOffset::parse("2021-01-02T03:04:05.123-01:02", &Rfc3339)?,
offset!(-01:02),
);
assert_eq!(
UtcOffset::parse("2021-01-02T03:04:05.123-00:01", &Rfc3339)?,
offset!(-00:01),
);
// Any separator is allowed by RFC 3339, not just `T`.
assert_eq!(
OffsetDateTime::parse("2021-01-02 03:04:05Z", &Rfc3339)?,
datetime!(2021-01-02 03:04:05 UTC),
);
assert_eq!(
OffsetDateTime::parse("2021-01-02$03:04:05Z", &Rfc3339)?,
datetime!(2021-01-02 03:04:05 UTC),
);
Ok(())
}
#[expect(clippy::cognitive_complexity, reason = "all test the same thing")]
#[test]
fn rfc_3339_err() {
assert!(matches!(
PrimitiveDateTime::parse("x", &Rfc3339),
invalid_component!("year")
));
assert!(matches!(
PrimitiveDateTime::parse("2021x", &Rfc3339),
invalid_literal!()
));
assert!(matches!(
PrimitiveDateTime::parse("2021-x", &Rfc3339),
invalid_component!("month")
));
assert!(matches!(
PrimitiveDateTime::parse("2021-0", &Rfc3339),
invalid_component!("month")
));
assert!(matches!(
PrimitiveDateTime::parse("2021-01x", &Rfc3339),
invalid_literal!()
));
assert!(matches!(
PrimitiveDateTime::parse("2021-01-0", &Rfc3339),
invalid_component!("day")
));
assert!(matches!(
PrimitiveDateTime::parse("2021-01-01", &Rfc3339),
invalid_component!("separator")
));
assert!(matches!(
PrimitiveDateTime::parse("2021-01-01T0", &Rfc3339),
invalid_component!("hour")
));
assert!(matches!(
PrimitiveDateTime::parse("2021-01-01T00x", &Rfc3339),
invalid_literal!()
));
assert!(matches!(
PrimitiveDateTime::parse("2021-01-01T00:0", &Rfc3339),
invalid_component!("minute")
));
assert!(matches!(
PrimitiveDateTime::parse("2021-01-01T00:00x", &Rfc3339),
invalid_literal!()
));
assert!(matches!(
PrimitiveDateTime::parse("2021-01-01T00:00:0", &Rfc3339),
invalid_component!("second")
));
assert!(matches!(
PrimitiveDateTime::parse("2021-01-01T00:00:00.x", &Rfc3339),
invalid_component!("subsecond")
));
assert!(matches!(
PrimitiveDateTime::parse("2021-01-01T00:00:00x", &Rfc3339),
invalid_component!("offset hour")
));
assert!(matches!(
PrimitiveDateTime::parse("2021-01-01T00:00:00+0", &Rfc3339),
invalid_component!("offset hour")
));
assert!(matches!(
PrimitiveDateTime::parse("2021-01-01T00:00:00+00x", &Rfc3339),
invalid_literal!()
));
assert!(matches!(
PrimitiveDateTime::parse("2021-01-01T00:00:00+00:0", &Rfc3339),
invalid_component!("offset minute")
));
assert!(matches!(
PrimitiveDateTime::parse("2021-13-01T00:00:00Z", &Rfc3339),
invalid_component!("month")
));
assert!(matches!(
PrimitiveDateTime::parse("2021-01-02T03:04:60Z", &Rfc3339),
Err(error::Parse::TryFromParsed(error::TryFromParsed::ComponentRange(component))) if component.name() == "second"
));
// Conversions to offset-unaware types do not perform special treatment for leap seconds
// even if the input could refer to one.
assert!(matches!(
PrimitiveDateTime::parse("2022-01-01T00:59:60+01:00", &Rfc3339),
Err(error::Parse::TryFromParsed(error::TryFromParsed::ComponentRange(component))) if component.name() == "second"
));
assert!(matches!(
Time::parse("2021-12-31T23:04:60Z", &Rfc3339),
Err(error::Parse::TryFromParsed(error::TryFromParsed::ComponentRange(component))) if component.name() == "second"
));
assert!(matches!(
OffsetDateTime::parse("2021-01-02T03:04:05Z ", &Rfc3339),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
OffsetDateTime::parse("x", &Rfc3339),
invalid_component!("year")
));
assert!(matches!(
OffsetDateTime::parse("2021x", &Rfc3339),
invalid_literal!()
));
assert!(matches!(
OffsetDateTime::parse("2021-x", &Rfc3339),
invalid_component!("month")
));
assert!(matches!(
OffsetDateTime::parse("2021-0", &Rfc3339),
invalid_component!("month")
));
assert!(matches!(
OffsetDateTime::parse("2021-01x", &Rfc3339),
invalid_literal!()
));
assert!(matches!(
OffsetDateTime::parse("2021-01-0", &Rfc3339),
invalid_component!("day")
));
assert!(matches!(
OffsetDateTime::parse("2021-01-01", &Rfc3339),
invalid_component!("separator")
));
assert!(matches!(
OffsetDateTime::parse("2021-01-01T0", &Rfc3339),
invalid_component!("hour")
));
assert!(matches!(
OffsetDateTime::parse("2021-01-01T00x", &Rfc3339),
invalid_literal!()
));
assert!(matches!(
OffsetDateTime::parse("2021-01-01T00:0", &Rfc3339),
invalid_component!("minute")
));
assert!(matches!(
OffsetDateTime::parse("2021-01-01T00:00x", &Rfc3339),
invalid_literal!()
));
assert!(matches!(
OffsetDateTime::parse("2021-01-01T00:00:0", &Rfc3339),
invalid_component!("second")
));
assert!(matches!(
OffsetDateTime::parse("2021-01-01T00:00:00.x", &Rfc3339),
invalid_component!("subsecond")
));
assert!(matches!(
OffsetDateTime::parse("2021-01-01T00:00:00x", &Rfc3339),
invalid_component!("offset hour")
));
assert!(matches!(
OffsetDateTime::parse("2021-01-01T00:00:00+0", &Rfc3339),
invalid_component!("offset hour")
));
assert!(matches!(
OffsetDateTime::parse("2021-01-01T00:00:00+00x", &Rfc3339),
invalid_literal!()
));
assert!(matches!(
OffsetDateTime::parse("2021-01-01T00:00:00+00:0", &Rfc3339),
invalid_component!("offset minute")
));
assert!(matches!(
OffsetDateTime::parse("2021-01-01T00:00:00+24:00", &Rfc3339),
invalid_component!("offset hour")
));
assert!(matches!(
OffsetDateTime::parse("2021-01-01T00:00:00+00:60", &Rfc3339),
Err(error::Parse::TryFromParsed(error::TryFromParsed::ComponentRange(component))) if component.name() == "offset minute"
));
assert!(matches!(
OffsetDateTime::parse("2021-13-01T00:00:00Z", &Rfc3339),
Err(error::Parse::TryFromParsed(error::TryFromParsed::ComponentRange(component))) if component.name() == "month"
));
assert!(matches!(
OffsetDateTime::parse("2021-12-31T23:59:61Z", &Rfc3339),
Err(error::Parse::TryFromParsed(error::TryFromParsed::ComponentRange(component))) if component.name() == "second" && !component.is_conditional()
));
assert!(matches!(
OffsetDateTime::parse("2021-01-02T23:59:60Z", &Rfc3339),
Err(error::Parse::TryFromParsed(error::TryFromParsed::ComponentRange(component))) if component.name() == "second" && component.is_conditional()
));
assert!(matches!(
OffsetDateTime::parse("2021-12-31T03:04:60Z", &Rfc3339),
Err(error::Parse::TryFromParsed(error::TryFromParsed::ComponentRange(component))) if component.name() == "second" && component.is_conditional()
));
assert!(matches!(
OffsetDateTime::parse("2021-12-31T23:59:60+01:00", &Rfc3339),
Err(error::Parse::TryFromParsed(error::TryFromParsed::ComponentRange(component))) if component.name() == "second" && component.is_conditional()
));
}
#[test]
fn iso_8601() {
assert_eq!(
OffsetDateTime::parse("2021-01-02T03:04:05Z", &Iso8601::DEFAULT),
Ok(datetime!(2021-01-02 03:04:05 UTC))
);
assert_eq!(
OffsetDateTime::parse("2021-002T03:04:05Z", &Iso8601::DEFAULT),
Ok(datetime!(2021-002 03:04:05 UTC))
);
assert_eq!(
OffsetDateTime::parse("2021-W01-2T03:04:05Z", &Iso8601::DEFAULT),
Ok(datetime!(2021-W01-2 03:04:05 UTC))
);
assert_eq!(
OffsetDateTime::parse("-002021-01-02T03:04:05+01:00", &Iso8601::DEFAULT),
Ok(datetime!(-002021-01-02 03:04:05 +01:00))
);
assert_eq!(
OffsetDateTime::parse("20210102T03.1Z", &Iso8601::DEFAULT),
Ok(datetime!(2021-01-02 03:06:00 UTC))
);
assert_eq!(
OffsetDateTime::parse("2021002T0304.1Z", &Iso8601::DEFAULT),
Ok(datetime!(2021-002 03:04:06 UTC))
);
assert_eq!(
OffsetDateTime::parse("2021W012T030405.1-0100", &Iso8601::DEFAULT),
Ok(datetime!(2021-W01-2 03:04:05.1 -01:00))
);
assert_eq!(
OffsetDateTime::parse("20210102T03Z", &Iso8601::DEFAULT),
Ok(datetime!(2021-01-02 03:00:00 UTC))
);
assert_eq!(
OffsetDateTime::parse("20210102T0304Z", &Iso8601::DEFAULT),
Ok(datetime!(2021-01-02 03:04:00 UTC))
);
assert_eq!(
UtcDateTime::parse("2021-01-02T03:04:05Z", &Iso8601::DEFAULT),
Ok(utc_datetime!(2021-01-02 03:04:05))
);
assert_eq!(
UtcDateTime::parse("2021-002T03:04:05Z", &Iso8601::DEFAULT),
Ok(utc_datetime!(2021-002 03:04:05))
);
assert_eq!(
UtcDateTime::parse("2021-W01-2T03:04:05Z", &Iso8601::DEFAULT),
Ok(utc_datetime!(2021-W01-2 03:04:05))
);
assert_eq!(
UtcDateTime::parse("-002021-01-02T03:04:05+01:00", &Iso8601::DEFAULT),
Ok(datetime!(-002021-01-02 03:04:05 +01:00).to_utc())
);
assert_eq!(
UtcDateTime::parse("20210102T03.1Z", &Iso8601::DEFAULT),
Ok(utc_datetime!(2021-01-02 03:06:00))
);
assert_eq!(
UtcDateTime::parse("2021002T0304.1Z", &Iso8601::DEFAULT),
Ok(utc_datetime!(2021-002 03:04:06))
);
assert_eq!(
UtcDateTime::parse("2021W012T030405.1-0100", &Iso8601::DEFAULT),
Ok(datetime!(2021-W01-2 03:04:05.1 -01:00).to_utc())
);
assert_eq!(
UtcDateTime::parse("20210102T03Z", &Iso8601::DEFAULT),
Ok(utc_datetime!(2021-01-02 03:00:00))
);
assert_eq!(
UtcDateTime::parse("20210102T0304Z", &Iso8601::DEFAULT),
Ok(utc_datetime!(2021-01-02 03:04:00))
);
assert_eq!(UtcOffset::parse("+07", &Iso8601::DEFAULT), Ok(offset!(+7)));
assert_eq!(
UtcOffset::parse("+0304", &Iso8601::DEFAULT),
Ok(offset!(+03:04))
);
assert_eq!(
PrimitiveDateTime::parse("2022-07-22T12:52:50.349409", &Iso8601::DEFAULT),
Ok(datetime!(2022-07-22 12:52:50.349409000))
);
}
#[expect(clippy::cognitive_complexity, reason = "all test the same thing")]
#[test]
fn iso_8601_error() {
assert!(matches!(
OffsetDateTime::parse("20210102T03:04Z", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
OffsetDateTime::parse("20210102T03.", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
OffsetDateTime::parse("2021-0102", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
OffsetDateTime::parse("2021-01-x", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
OffsetDateTime::parse("2021-Wx", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
OffsetDateTime::parse("2021-W012", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
OffsetDateTime::parse("2021-W01-x", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
OffsetDateTime::parse("2021-01-02T03:x", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
OffsetDateTime::parse("2021-01-02T03:04x", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
OffsetDateTime::parse("2021-01-02T03:04:", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert_eq!(
OffsetDateTime::parse("01:02", &Iso8601::DEFAULT),
Err(error::Parse::TryFromParsed(
error::TryFromParsed::InsufficientInformation
))
);
assert!(matches!(
UtcDateTime::parse("20210102T03:04Z", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
UtcDateTime::parse("20210102T03.", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
UtcDateTime::parse("2021-0102", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
UtcDateTime::parse("2021-01-x", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
UtcDateTime::parse("2021-Wx", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
UtcDateTime::parse("2021-W012", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
UtcDateTime::parse("2021-W01-x", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
UtcDateTime::parse("2021-01-02T03:x", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
UtcDateTime::parse("2021-01-02T03:04x", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
UtcDateTime::parse("2021-01-02T03:04:", &Iso8601::DEFAULT),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert_eq!(
UtcDateTime::parse("01:02", &Iso8601::DEFAULT),
Err(error::Parse::TryFromParsed(
error::TryFromParsed::InsufficientInformation
))
);
}
#[test]
fn parse_time() -> time::Result<()> {
let format_input_output = [
(fd::parse("[hour repr:12] [period]")?, "01 PM", time!(1 PM)),
(fd::parse("[hour]")?, "12", time!(12:00)),
(
fd::parse("[hour]:[minute]:[second]")?,
"13:02:03",
time!(13:02:03),
),
(
fd::parse("[hour repr:12]:[minute] [period]")?,
"01:02 PM",
time!(1:02 PM),
),
(fd::parse("[hour]:[minute]")?, "01:02", time!(1:02)),
(
fd::parse("[hour repr:12]:[minute] [period]")?,
"01:02 AM",
time!(1:02 AM),
),
(fd::parse("[hour]:[minute]")?, "01:02", time!(1:02)),
(fd::parse("[hour repr:12] [period]")?, "12 AM", time!(12 AM)),
(fd::parse("[hour repr:12] [period]")?, "12 PM", time!(12 PM)),
];
for (format_description, input, output) in &format_input_output {
assert_eq!(&Time::parse(input, format_description)?, output);
assert_eq!(
&Time::parse(input, &OwnedFormatItem::from(format_description))?,
output
);
assert_eq!(
&Time::parse(
input,
[OwnedFormatItem::from(format_description)].as_slice()
)?,
output
);
}
Ok(())
}
#[expect(clippy::cognitive_complexity, reason = "all test the same thing")]
#[test]
fn parse_time_err() -> time::Result<()> {
assert_eq!(
Time::try_from(Parsed::new()),
Err(error::TryFromParsed::InsufficientInformation)
);
assert_eq!(
Time::parse("", &fd::parse("")?),
Err(error::Parse::TryFromParsed(
error::TryFromParsed::InsufficientInformation
))
);
assert_eq!(
Time::parse("12:34", &fd::parse("[hour]:[second]")?),
Err(error::Parse::TryFromParsed(
error::TryFromParsed::InsufficientInformation
))
);
assert_eq!(
Time::parse("12:34", &fd::parse("[hour]:[subsecond]")?),
Err(error::Parse::TryFromParsed(
error::TryFromParsed::InsufficientInformation
))
);
assert!(matches!(
Time::parse("13 PM", &fd::parse("[hour repr:12] [period]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("hour")
))
));
assert!(matches!(
Time::parse(" ", &fd::parse("")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
Time::parse("a", &fd::parse("[subsecond digits:1]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("subsecond")
))
));
assert!(matches!(
Time::parse("1a", &fd::parse("[subsecond digits:2]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("subsecond")
))
));
assert!(matches!(
Time::parse("1a", &fd::parse_owned::<2>("[subsecond digits:2]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("subsecond")
))
));
assert!(matches!(
Time::parse(
"1a",
[fd::parse_owned::<2>("[subsecond digits:2]")?].as_slice()
),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("subsecond")
))
));
assert!(matches!(
Time::parse("12a", &fd::parse("[subsecond digits:3]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("subsecond")
))
));
assert!(matches!(
Time::parse("123a", &fd::parse("[subsecond digits:4]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("subsecond")
))
));
assert!(matches!(
Time::parse("1234a", &fd::parse("[subsecond digits:5]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("subsecond")
))
));
assert!(matches!(
Time::parse("12345a", &fd::parse("[subsecond digits:6]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("subsecond")
))
));
assert!(matches!(
Time::parse("123456a", &fd::parse("[subsecond digits:7]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("subsecond")
))
));
assert!(matches!(
Time::parse("1234567a", &fd::parse("[subsecond digits:8]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("subsecond")
))
));
assert!(matches!(
Time::parse("12345678a", &fd::parse("[subsecond digits:9]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("subsecond")
))
));
Ok(())
}
#[test]
fn parse_date() -> time::Result<()> {
let format_input_output = [
(
fd::parse("[year]-[month]-[day]")?,
"2021-01-02",
date!(2021-01-02),
),
(
fd::parse("[year repr:century range:standard][year repr:last_two]-[month]-[day]")?,
"2021-01-02",
date!(2021-01-02),
),
(fd::parse("[year]-[ordinal]")?, "2021-002", date!(2021-002)),
(
fd::parse("[year base:iso_week]-W[week_number]-[weekday repr:monday]")?,
"2020-W53-6",
date!(2021-01-02),
),
(
fd::parse("[year]-W[week_number repr:monday]-[weekday repr:monday]")?,
"2021-W00-6",
date!(2021-01-02),
),
(
fd::parse("[year]-W[week_number repr:sunday]-[weekday repr:sunday]")?,
"2021-W00-6",
date!(2021-01-02),
),
(
fd::parse("[year]-W[week_number repr:sunday]-[weekday repr:sunday]")?,
"2023-W01-1",
date!(2023-01-02),
),
(
fd::parse("[year]-W[week_number repr:sunday]-[weekday repr:sunday]")?,
"2022-W00-7",
date!(2022-01-02),
),
(
fd::parse("[year]-W[week_number repr:sunday]-[weekday repr:sunday]")?,
"2026-W00-5",
date!(2026-01-02),
),
(
fd::parse("[year]-W[week_number repr:sunday]-[weekday repr:sunday]")?,
"2025-W00-4",
date!(2025-01-02),
),
(
fd::parse("[year]-W[week_number repr:sunday]-[weekday repr:sunday]")?,
"2019-W00-3",
date!(2019-01-02),
),
(
fd::parse("[year]-W[week_number repr:sunday]-[weekday repr:sunday]")?,
"2018-W01-2",
date!(2018-01-02),
),
(
fd::parse(
"[year padding:space]-W[week_number repr:sunday padding:none]-[weekday \
repr:sunday]",
)?,
" 201-W01-2",
date!(201-01-06),
),
];
for (format_description, input, output) in &format_input_output {
assert_eq!(&Date::parse(input, format_description)?, output);
assert_eq!(
&Date::parse(input, &OwnedFormatItem::from(format_description))?,
output
);
}
Ok(())
}
#[expect(clippy::cognitive_complexity, reason = "all test the same thing")]
#[test]
fn parse_date_err() -> time::Result<()> {
assert_eq!(
Date::try_from(Parsed::new()),
Err(error::TryFromParsed::InsufficientInformation)
);
assert_eq!(
Date::parse("", &fd::parse("")?),
Err(error::Parse::TryFromParsed(
error::TryFromParsed::InsufficientInformation
))
);
assert!(matches!(
Date::parse("a", &fd::parse("[year]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("year")
))
));
assert!(matches!(
Date::parse("0001", &fd::parse("[year sign:mandatory]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("year")
))
));
assert!(matches!(
Date::parse("0a", &fd::parse("[year repr:last_two]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("year")
))
));
assert!(matches!(
Date::parse("2021-366", &fd::parse("[year]-[ordinal]")?),
Err(error::Parse::TryFromParsed(
error::TryFromParsed::ComponentRange(component)
)) if component.name() == "ordinal"
));
assert!(matches!(
Date::parse("2021-12-32", &fd::parse("[year]-[month]-[day]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("day")
))
));
assert!(matches!(
Date::parse("2021-02-30", &fd::parse("[year]-[month]-[day]")?),
Err(error::Parse::TryFromParsed(
error::TryFromParsed::ComponentRange(component)
)) if component.name() == "day"
));
assert!(matches!(
Date::parse("2019-W53-1", &fd::parse("[year base:iso_week]-W[week_number]-[weekday repr:monday]")?),
Err(error::Parse::TryFromParsed(
error::TryFromParsed::ComponentRange(component)
)) if component.name() == "week"
));
assert!(matches!(
Date::parse(
"2021-W54-1",
&fd::parse("[year base:iso_week]-W[week_number]-[weekday repr:monday]")?
),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("week number")
))
));
assert!(matches!(
Date::parse("2019-W53-1", &fd::parse("[year]-W[week_number repr:sunday]-[weekday repr:monday]")?),
Err(error::Parse::TryFromParsed(
error::TryFromParsed::ComponentRange(component)
)) if component.name() == "ordinal"
));
assert!(matches!(
Date::parse(
"2021-W54-1",
&fd::parse("[year]-W[week_number repr:sunday]-[weekday repr:monday]")?
),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("week number")
))
));
assert!(matches!(
Date::parse("2019-W53-1", &fd::parse("[year]-W[week_number repr:monday]-[weekday repr:monday]")?),
Err(error::Parse::TryFromParsed(
error::TryFromParsed::ComponentRange(component)
)) if component.name() == "ordinal"
));
assert!(matches!(
Date::parse(
"2021-W54-1",
&fd::parse("[year]-W[week_number repr:monday]-[weekday repr:monday]")?
),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("week number")
))
));
assert!(matches!(
Date::parse("Ja", &fd::parse("[month repr:short]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("month")
))
));
assert!(matches!(
Date::parse(" 2a21", &fd::parse("[year padding:space]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("year")
))
));
Ok(())
}
#[test]
fn parse_offset() -> time::Result<()> {
// Regression check for #522.
assert_eq!(
UtcOffset::parse(
"-00:01",
&fd::parse("[offset_hour sign:mandatory]:[offset_minute]")?,
),
Ok(offset!(-00:01)),
);
assert_eq!(
UtcOffset::parse(
"-00:00:01",
&fd::parse("[offset_hour sign:mandatory]:[offset_minute]:[offset_second]")?,
),
Ok(offset!(-00:00:01)),
);
Ok(())
}
#[test]
fn parse_offset_err() -> time::Result<()> {
assert_eq!(
UtcOffset::parse("", &fd::parse("")?),
Err(error::Parse::TryFromParsed(
error::TryFromParsed::InsufficientInformation
))
);
assert_eq!(
UtcOffset::parse("01", &fd::parse("[offset_hour sign:mandatory]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("offset hour")
))
);
assert!(matches!(
UtcOffset::parse("24", &fd::parse("[offset_hour]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("offset hour")
))
));
assert!(matches!(
UtcOffset::parse("00:60", &fd::parse("[offset_hour]:[offset_minute]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("offset minute")
))
));
assert!(matches!(
UtcOffset::parse(
"00:00:60",
&fd::parse("[offset_hour]:[offset_minute]:[offset_second]")?
),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("offset second")
))
));
Ok(())
}
#[test]
fn parse_primitive_date_time() -> time::Result<()> {
assert_eq!(
PrimitiveDateTime::parse("2023-07-27 23", &fd::parse("[year]-[month]-[day] [hour]")?),
Ok(datetime!(2023-07-27 23:00))
);
Ok(())
}
#[test]
fn parse_primitive_date_time_err() -> time::Result<()> {
assert_eq!(
PrimitiveDateTime::parse("", &fd::parse("")?),
Err(error::Parse::TryFromParsed(
error::TryFromParsed::InsufficientInformation
))
);
assert!(matches!(
PrimitiveDateTime::parse(
"2021-001 13 PM",
&fd::parse("[year]-[ordinal] [hour repr:12] [period]")?
),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("hour")
))
));
assert!(matches!(
PrimitiveDateTime::parse(
"2023-07-27 23:30",
&fd::parse("[year]-[month]-[day] [hour]")?
),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
Ok(())
}
#[test]
fn parse_offset_date_time_err() -> time::Result<()> {
assert_eq!(
OffsetDateTime::parse("", &fd::parse("")?),
Err(error::Parse::TryFromParsed(
error::TryFromParsed::InsufficientInformation
))
);
assert!(matches!(
OffsetDateTime::parse("x", &fd::parse("[year]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("year")
))
));
assert!(matches!(
OffsetDateTime::parse(
"2021-001 12 PM +25",
&fd::parse("[year]-[ordinal] [hour repr:12] [period] [offset_hour sign:mandatory]")?
),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("offset hour")
))
));
Ok(())
}
#[test]
fn parse_utc_date_time() -> time::Result<()> {
assert_eq!(
UtcDateTime::parse("2023-07-27 23", &fd::parse("[year]-[month]-[day] [hour]")?),
Ok(utc_datetime!(2023-07-27 23:00))
);
Ok(())
}
#[test]
fn parse_utc_date_time_err() -> time::Result<()> {
assert_eq!(
UtcDateTime::parse("", &fd::parse("")?),
Err(error::Parse::TryFromParsed(
error::TryFromParsed::InsufficientInformation
))
);
assert!(matches!(
UtcDateTime::parse(
"2021-001 13 PM",
&fd::parse("[year]-[ordinal] [hour repr:12] [period]")?
),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("hour")
))
));
assert!(matches!(
UtcDateTime::parse(
"2023-07-27 23:30",
&fd::parse("[year]-[month]-[day] [hour]")?
),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
UtcDateTime::parse("x", &fd::parse("[year]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("year")
))
));
assert!(matches!(
UtcDateTime::parse(
"2021-001 12 PM +25",
&fd::parse("[year]-[ordinal] [hour repr:12] [period] [offset_hour sign:mandatory]")?
),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("offset hour")
))
));
Ok(())
}
#[expect(clippy::cognitive_complexity, reason = "all test the same thing")]
#[test]
fn parse_components() -> time::Result<()> {
macro_rules! parse_component {
($component:expr, $input:expr, $(_. $property:ident() == $expected:expr);+ $(;)?) => {
let mut parsed = Parsed::new();
parsed.parse_component($input, $component)?;
$(assert_eq!(parsed.$property(), $expected);)+
};
}
parse_component!(
Component::Year(
modifier::Year::default()
.with_padding(modifier::Padding::Zero)
.with_repr(modifier::YearRepr::Full)
.with_range(modifier::YearRange::Extended)
.with_iso_week_based(false)
.with_sign_is_mandatory(false)
),
b"2021",
_.year() == Some(2021)
);
parse_component!(
Component::Year(modifier::Year::default()
.with_padding(modifier::Padding::Zero)
.with_repr(modifier::YearRepr::Century)
.with_range(modifier::YearRange::Extended)
.with_iso_week_based(false)
.with_sign_is_mandatory(false)
),
b"20",
_.year_century() == Some(20);
_.year_century_is_negative() == Some(false);
);
parse_component!(
Component::Year(
modifier::Year::default()
.with_padding(modifier::Padding::Zero)
.with_repr(modifier::YearRepr::LastTwo)
.with_range(modifier::YearRange::Extended)
.with_iso_week_based(false)
.with_sign_is_mandatory(false)
),
b"21",
_.year_last_two() == Some(21)
);
parse_component!(
Component::Year(
modifier::Year::default()
.with_padding(modifier::Padding::Zero)
.with_repr(modifier::YearRepr::Full)
.with_range(modifier::YearRange::Extended)
.with_iso_week_based(true)
.with_sign_is_mandatory(false)
),
b"2021",
_.iso_year() == Some(2021)
);
parse_component!(
Component::Year(modifier::Year::default()
.with_padding(modifier::Padding::Zero)
.with_repr(modifier::YearRepr::Century)
.with_range(modifier::YearRange::Extended)
.with_iso_week_based(true)
.with_sign_is_mandatory(false)
),
b"20",
_.iso_year_century() == Some(20);
_.iso_year_century_is_negative() == Some(false);
);
parse_component!(
Component::Year(
modifier::Year::default()
.with_padding(modifier::Padding::Zero)
.with_repr(modifier::YearRepr::LastTwo)
.with_range(modifier::YearRange::Extended)
.with_iso_week_based(true)
.with_sign_is_mandatory(false)
),
b"21",
_.iso_year_last_two() == Some(21)
);
parse_component!(
Component::Month(
modifier::Month::default()
.with_padding(modifier::Padding::Space)
.with_repr(modifier::MonthRepr::Numerical)
),
b" 1",
_.month() == Some(Month::January)
);
parse_component!(
Component::Month(
modifier::Month::default()
.with_padding(modifier::Padding::None)
.with_repr(modifier::MonthRepr::Short)
.with_case_sensitive(true)
),
b"Jan",
_.month() == Some(Month::January)
);
parse_component!(
Component::Month(
modifier::Month::default()
.with_padding(modifier::Padding::None)
.with_repr(modifier::MonthRepr::Short)
.with_case_sensitive(false)
),
b"jAn",
_.month() == Some(Month::January)
);
parse_component!(
Component::Month(
modifier::Month::default()
.with_padding(modifier::Padding::None)
.with_repr(modifier::MonthRepr::Long)
.with_case_sensitive(true)
),
b"January",
_.month() == Some(Month::January)
);
parse_component!(
Component::Month(
modifier::Month::default()
.with_padding(modifier::Padding::None)
.with_repr(modifier::MonthRepr::Long)
.with_case_sensitive(false)
),
b"jAnUaRy",
_.month() == Some(Month::January)
);
parse_component!(
Component::Ordinal(modifier::Ordinal::default().with_padding(modifier::Padding::Zero)),
b"012",
_.ordinal() == 12.try_into().ok()
);
parse_component!(
Component::Weekday(
modifier::Weekday::default()
.with_repr(modifier::WeekdayRepr::Short)
.with_one_indexed(false)
.with_case_sensitive(true)
),
b"Sun",
_.weekday() == Some(Weekday::Sunday)
);
parse_component!(
Component::Weekday(
modifier::Weekday::default()
.with_repr(modifier::WeekdayRepr::Short)
.with_one_indexed(false)
.with_case_sensitive(false)
),
b"sUn",
_.weekday() == Some(Weekday::Sunday)
);
parse_component!(
Component::Weekday(
modifier::Weekday::default()
.with_repr(modifier::WeekdayRepr::Long)
.with_one_indexed(false)
.with_case_sensitive(true)
),
b"Sunday",
_.weekday() == Some(Weekday::Sunday)
);
parse_component!(
Component::Weekday(
modifier::Weekday::default()
.with_repr(modifier::WeekdayRepr::Long)
.with_one_indexed(false)
.with_case_sensitive(false)
),
b"sUnDaY",
_.weekday() == Some(Weekday::Sunday)
);
parse_component!(
Component::Weekday(
modifier::Weekday::default()
.with_repr(modifier::WeekdayRepr::Sunday)
.with_one_indexed(false)
),
b"0",
_.weekday() == Some(Weekday::Sunday)
);
parse_component!(
Component::Weekday(
modifier::Weekday::default()
.with_repr(modifier::WeekdayRepr::Sunday)
.with_one_indexed(true)
),
b"1",
_.weekday() == Some(Weekday::Sunday)
);
parse_component!(
Component::Weekday(
modifier::Weekday::default()
.with_repr(modifier::WeekdayRepr::Monday)
.with_one_indexed(false)
),
b"6",
_.weekday() == Some(Weekday::Sunday)
);
parse_component!(
Component::Weekday(
modifier::Weekday::default()
.with_repr(modifier::WeekdayRepr::Monday)
.with_one_indexed(true)
),
b"7",
_.weekday() == Some(Weekday::Sunday)
);
parse_component!(
Component::WeekNumber(
modifier::WeekNumber::default()
.with_padding(modifier::Padding::None)
.with_repr(modifier::WeekNumberRepr::Sunday)
),
b"2",
_.sunday_week_number() == Some(2)
);
parse_component!(
Component::WeekNumber(
modifier::WeekNumber::default()
.with_padding(modifier::Padding::None)
.with_repr(modifier::WeekNumberRepr::Monday)
),
b"2",
_.monday_week_number() == Some(2)
);
parse_component!(
Component::WeekNumber(
modifier::WeekNumber::default()
.with_padding(modifier::Padding::None)
.with_repr(modifier::WeekNumberRepr::Iso)
),
b"2",
_.iso_week_number() == 2.try_into().ok()
);
parse_component!(
Component::Subsecond(
modifier::Subsecond::default().with_digits(modifier::SubsecondDigits::One)
),
b"1",
_.subsecond() == Some(100_000_000)
);
parse_component!(
Component::Subsecond(
modifier::Subsecond::default().with_digits(modifier::SubsecondDigits::Two)
),
b"12",
_.subsecond() == Some(120_000_000)
);
parse_component!(
Component::Subsecond(
modifier::Subsecond::default().with_digits(modifier::SubsecondDigits::Three)
),
b"123",
_.subsecond() == Some(123_000_000)
);
parse_component!(
Component::Subsecond(
modifier::Subsecond::default().with_digits(modifier::SubsecondDigits::Four)
),
b"1234",
_.subsecond() == Some(123_400_000)
);
parse_component!(
Component::Subsecond(
modifier::Subsecond::default().with_digits(modifier::SubsecondDigits::Five)
),
b"12345",
_.subsecond() == Some(123_450_000)
);
parse_component!(
Component::Subsecond(
modifier::Subsecond::default().with_digits(modifier::SubsecondDigits::Six)
),
b"123456",
_.subsecond() == Some(123_456_000)
);
parse_component!(
Component::Subsecond(
modifier::Subsecond::default().with_digits(modifier::SubsecondDigits::Seven)
),
b"1234567",
_.subsecond() == Some(123_456_700)
);
parse_component!(
Component::Subsecond(
modifier::Subsecond::default().with_digits(modifier::SubsecondDigits::Eight)
),
b"12345678",
_.subsecond() == Some(123_456_780)
);
parse_component!(
Component::Subsecond(
modifier::Subsecond::default().with_digits(modifier::SubsecondDigits::Nine)
),
b"123456789",
_.subsecond() == Some(123_456_789)
);
parse_component!(
Component::Subsecond(
modifier::Subsecond::default().with_digits(modifier::SubsecondDigits::OneOrMore)
),
b"123456789",
_.subsecond() == Some(123_456_789)
);
parse_component!(
Component::Period(
modifier::Period::default()
.with_is_uppercase(false)
.with_case_sensitive(true)
),
b"am",
_.hour_12_is_pm() == Some(false)
);
parse_component!(
Component::Period(
modifier::Period::default()
.with_is_uppercase(false)
.with_case_sensitive(false)
),
b"aM",
_.hour_12_is_pm() == Some(false)
);
let mut parsed = Parsed::new();
let result = parsed.parse_component(
b"abcdef",
Component::Ignore(modifier::Ignore::count(const { NonZero::new(3).unwrap() })),
)?;
assert_eq!(result, b"def");
let mut parsed = Parsed::new();
let result = parsed.parse_component(
b"abcdef",
Component::Ignore(modifier::Ignore::count(const { NonZero::new(7).unwrap() })),
);
assert!(matches!(
result,
Err(error::ParseFromDescription::InvalidComponent("ignore"))
));
parse_component!(
Component::UnixTimestamp(
modifier::UnixTimestamp::default()
.with_precision(modifier::UnixTimestampPrecision::Second)
.with_sign_is_mandatory(false)
),
b"1234567890",
_.unix_timestamp_nanos() == Some(1_234_567_890_000_000_000)
);
parse_component!(
Component::UnixTimestamp(
modifier::UnixTimestamp::default()
.with_precision(modifier::UnixTimestampPrecision::Millisecond)
.with_sign_is_mandatory(false)
),
b"1234567890123",
_.unix_timestamp_nanos() == Some(1_234_567_890_123_000_000)
);
parse_component!(
Component::UnixTimestamp(
modifier::UnixTimestamp::default()
.with_precision(modifier::UnixTimestampPrecision::Microsecond)
.with_sign_is_mandatory(false)
),
b"1234567890123456",
_.unix_timestamp_nanos() == Some(1_234_567_890_123_456_000)
);
parse_component!(
Component::UnixTimestamp(
modifier::UnixTimestamp::default()
.with_precision(modifier::UnixTimestampPrecision::Nanosecond)
.with_sign_is_mandatory(false)
),
b"1234567890123456789",
_.unix_timestamp_nanos() == Some(1_234_567_890_123_456_789)
);
parse_component!(
Component::UnixTimestamp(
modifier::UnixTimestamp::default()
.with_precision(modifier::UnixTimestampPrecision::Nanosecond)
.with_sign_is_mandatory(false)
),
b"-1234567890123456789",
_.unix_timestamp_nanos() == Some(-1_234_567_890_123_456_789)
);
Ok(())
}
#[test]
fn parse_optional() -> time::Result<()> {
// Ensure full parsing works as expected.
let mut parsed = Parsed::new();
let remaining_input = parsed.parse_item(
b"2021-01-02",
&BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(&fd::parse(
"[year]-[month]-[day]",
)?)),
)?;
assert!(remaining_input.is_empty());
assert_eq!(parsed.year(), Some(2021));
assert_eq!(parsed.month(), Some(Month::January));
assert_eq!(parsed.day().map(NonZero::get), Some(2));
let mut parsed = Parsed::new();
let remaining_input = parsed.parse_item(
b"2021-01-02",
&OwnedFormatItem::from(BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(
&fd::parse("[year]-[month]-[day]")?,
))),
)?;
assert!(remaining_input.is_empty());
assert_eq!(parsed.year(), Some(2021));
assert_eq!(parsed.month(), Some(Month::January));
assert_eq!(parsed.day().map(NonZero::get), Some(2));
// Ensure a successful partial parse *does not* mutate `parsed`.
let mut parsed = Parsed::new();
let remaining_input = parsed.parse_item(
b"2021-01",
&BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(&fd::parse(
"[year]-[month]-[day]",
)?)),
)?;
assert_eq!(remaining_input, b"2021-01");
assert!(parsed.year().is_none());
assert!(parsed.month().is_none());
assert!(parsed.day().is_none());
let mut parsed = Parsed::new();
let remaining_input = parsed.parse_item(
b"2021-01",
&OwnedFormatItem::from(BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(
&fd::parse("[year]-[month]-[day]")?,
))),
)?;
assert_eq!(remaining_input, b"2021-01");
assert!(parsed.year().is_none());
assert!(parsed.month().is_none());
assert!(parsed.day().is_none());
Ok(())
}
#[expect(clippy::cognitive_complexity, reason = "all test the same thing")]
#[test]
fn parse_first() -> time::Result<()> {
// Ensure the first item is parsed correctly.
let mut parsed = Parsed::new();
let remaining_input = parsed.parse_item(
b"2021-01-02",
&BorrowedFormatItem::First(&[BorrowedFormatItem::Compound(&fd::parse(
"[year]-[month]-[day]",
)?)]),
)?;
assert!(remaining_input.is_empty());
assert_eq!(parsed.year(), Some(2021));
assert_eq!(parsed.month(), Some(Month::January));
assert_eq!(parsed.day().map(NonZero::get), Some(2));
let mut parsed = Parsed::new();
let remaining_input = parsed.parse_item(
b"2021-01-02",
&OwnedFormatItem::from(BorrowedFormatItem::First(&[BorrowedFormatItem::Compound(
&fd::parse("[year]-[month]-[day]")?,
)])),
)?;
assert!(remaining_input.is_empty());
assert_eq!(parsed.year(), Some(2021));
assert_eq!(parsed.month(), Some(Month::January));
assert_eq!(parsed.day().map(NonZero::get), Some(2));
// Ensure an empty slice is a no-op success.
let mut parsed = Parsed::new();
let remaining_input = parsed.parse_item(b"2021-01-02", &BorrowedFormatItem::First(&[]))?;
assert_eq!(remaining_input, b"2021-01-02");
assert!(parsed.year().is_none());
assert!(parsed.month().is_none());
assert!(parsed.day().is_none());
let mut parsed = Parsed::new();
let remaining_input =
parsed.parse_item(b"2021-01-02", &OwnedFormatItem::First(Box::new([])))?;
assert_eq!(remaining_input, b"2021-01-02");
assert!(parsed.year().is_none());
assert!(parsed.month().is_none());
assert!(parsed.day().is_none());
// Ensure success when the first item fails.
let mut parsed = Parsed::new();
let remaining_input = parsed.parse_item(
b"2021-01-02",
&BorrowedFormatItem::First(&[
BorrowedFormatItem::Compound(&fd::parse("[period]")?),
BorrowedFormatItem::Compound(&fd::parse("x")?),
BorrowedFormatItem::Compound(&fd::parse("[year]-[month]-[day]")?),
]),
)?;
assert!(remaining_input.is_empty());
assert_eq!(parsed.year(), Some(2021));
assert_eq!(parsed.month(), Some(Month::January));
assert_eq!(parsed.day().map(NonZero::get), Some(2));
let mut parsed = Parsed::new();
let remaining_input = parsed.parse_item(
b"2021-01-02",
&OwnedFormatItem::from(BorrowedFormatItem::First(&[
BorrowedFormatItem::Compound(&fd::parse("[period]")?),
BorrowedFormatItem::Compound(&fd::parse("x")?),
BorrowedFormatItem::Compound(&fd::parse("[year]-[month]-[day]")?),
])),
)?;
assert!(remaining_input.is_empty());
assert_eq!(parsed.year(), Some(2021));
assert_eq!(parsed.month(), Some(Month::January));
assert_eq!(parsed.day().map(NonZero::get), Some(2));
// Ensure the first error is returned.
let mut parsed = Parsed::new();
let err = parsed
.parse_item(
b"2021-01-02",
&BorrowedFormatItem::First(&[
BorrowedFormatItem::Compound(&fd::parse("[period]")?),
BorrowedFormatItem::Compound(&fd::parse("x")?),
]),
)
.expect_err("parsing should fail");
assert_eq!(err, error::ParseFromDescription::InvalidComponent("period"));
let mut parsed = Parsed::new();
let err = parsed
.parse_item(
b"2021-01-02",
&OwnedFormatItem::from(BorrowedFormatItem::First(&[
BorrowedFormatItem::Compound(&fd::parse("[period]")?),
BorrowedFormatItem::Compound(&fd::parse("x")?),
])),
)
.expect_err("parsing should fail");
assert_eq!(err, error::ParseFromDescription::InvalidComponent("period"));
Ok(())
}
#[test]
fn parse_unix_timestamp() -> time::Result<()> {
assert_eq!(
OffsetDateTime::parse("1234567890", &fd::parse("[unix_timestamp]")?)?,
datetime!(2009-02-13 23:31:30 UTC)
);
assert_eq!(
OffsetDateTime::parse(
"1234567890123",
&fd::parse("[unix_timestamp precision:millisecond]")?
)?,
datetime!(2009-02-13 23:31:30.123 UTC)
);
assert_eq!(
OffsetDateTime::parse(
"1234567890123456",
&fd::parse("[unix_timestamp precision:microsecond]")?
)?,
datetime!(2009-02-13 23:31:30.123456 UTC)
);
assert_eq!(
OffsetDateTime::parse(
"1234567890123456789",
&fd::parse("[unix_timestamp precision:nanosecond]")?
)?,
datetime!(2009-02-13 23:31:30.123456789 UTC)
);
Ok(())
}
#[test]
fn parse_unix_timestamp_err() -> time::Result<()> {
assert_eq!(
OffsetDateTime::parse("1234567890", &fd::parse("[unix_timestamp sign:mandatory]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("unix_timestamp")
))
);
assert_eq!(
OffsetDateTime::parse("a", &fd::parse("[unix_timestamp precision:second]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("unix_timestamp")
))
);
assert_eq!(
OffsetDateTime::parse("a", &fd::parse("[unix_timestamp precision:millisecond]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("unix_timestamp")
))
);
assert_eq!(
OffsetDateTime::parse("a", &fd::parse("[unix_timestamp precision:microsecond]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("unix_timestamp")
))
);
assert_eq!(
OffsetDateTime::parse("a", &fd::parse("[unix_timestamp precision:nanosecond]")?),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidComponent("unix_timestamp")
))
);
Ok(())
}
#[test]
fn issue_601() {
let date = OffsetDateTime::parse(
"1234567890.123",
&fd::parse("[unix_timestamp].[subsecond digits:3]").expect("format description is valid"),
);
assert_eq!(date, Ok(datetime!(2009-02-13 23:31:30.123 +00:00:00)));
}
#[test]
fn end() -> time::Result<()> {
let mut parsed = Parsed::new();
let remaining_input = parsed.parse_item(
b"",
&BorrowedFormatItem::Component(Component::End(modifier::End::default())),
);
assert_eq!(remaining_input, Ok(b"".as_slice()));
assert_eq!(
Time::parse("00:00", &fd::parse("[hour]:[minute][end]")?),
Ok(time!(0:00))
);
assert_eq!(
Time::parse(
"00:00abcdef",
&fd::parse("[hour]:[minute][end trailing_input:discard]")?
),
Ok(time!(0:00))
);
assert_eq!(
Time::parse(
"00:00:00",
&fd::parse_owned::<2>("[hour]:[minute][optional [[end]]]:[second]")?
),
Ok(time!(0:00))
);
assert!(matches!(
Time::parse(
"00:00:00",
&fd::parse_owned::<2>("[hour]:[minute][end]:[second]")?
),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::UnexpectedTrailingCharacters { .. }
))
));
assert!(matches!(
Time::parse(
"00:00:00",
&fd::parse_owned::<2>("[hour]:[minute][end trailing_input:discard]:[second]")?
),
Err(error::Parse::ParseFromDescription(
error::ParseFromDescription::InvalidLiteral { .. }
))
));
Ok(())
}