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(()) }