From aa8a23785e2cc71352ab3e123bf043cb63c55c0d Mon Sep 17 00:00:00 2001 From: Dom Dwyer Date: Thu, 8 Jun 2023 15:27:23 +0200 Subject: [PATCH 01/68] fix: no panic when formatting with %#z This changes the "don't use me" panic into an error when using the parsing-only "%#z" formatter, consistent with other invalid formatters. --- src/format/mod.rs | 2 +- src/format/strftime.rs | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index c8bb1e3a33..7c9c3b7dd0 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -675,7 +675,7 @@ fn format_inner( off.map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::None)) } Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { - panic!("Do not try to write %#z it is undefined") + return Err(fmt::Error); } RFC2822 => // same as `%a, %d %b %Y %H:%M:%S %z` diff --git a/src/format/strftime.rs b/src/format/strftime.rs index b4275573b6..9dae6e7e09 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -718,4 +718,28 @@ mod tests { assert_eq!(nd.format_localized("%F", Locale::de_DE).to_string(), "2001-07-08"); assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Jul-2001"); } + + /// Ensure parsing a timestamp with the parse-only stftime formatter "%#z" does + /// not cause a panic. + /// + /// See . + #[test] + fn test_parse_only_timezone_offset_permissive_no_panic() { + use crate::NaiveDate; + use crate::{FixedOffset, TimeZone}; + use std::fmt::Write; + + let dt = FixedOffset::east_opt(34200) + .unwrap() + .from_local_datetime( + &NaiveDate::from_ymd_opt(2001, 7, 8) + .unwrap() + .and_hms_nano_opt(0, 34, 59, 1_026_490_708) + .unwrap(), + ) + .unwrap(); + + let mut buf = String::new(); + let _ = write!(buf, "{}", dt.format("%#z")).expect_err("parse-only formatter should fail"); + } } From 29e728ae9fea7da5db62b20c64f90ca44a670e52 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 28 May 2023 17:16:09 -0700 Subject: [PATCH 02/68] Add parse tests focused on timezone signage Add parse tests focused on timezone siganges "+", "-", and bad offsets --- src/format/parse.rs | 481 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 434 insertions(+), 47 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index ed3f91f6df..d34195f9d4 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -592,10 +592,31 @@ fn test_parse() { check!("", [lit!("a")]; TOO_SHORT); check!(" ", [lit!("a")]; INVALID); check!("a", [lit!("a")]; ); + check!("+", [lit!("+")]; ); + check!("-", [lit!("-")]; ); + check!(" ", [lit!(" ")]; ); check!("aa", [lit!("a")]; TOO_LONG); check!("A", [lit!("a")]; INVALID); check!("xy", [lit!("xy")]; ); check!("xy", [lit!("x"), lit!("y")]; ); + check!("1", [lit!("1")]; ); + check!("1234", [lit!("1234")]; ); + check!("+1234", [lit!("+1234")]; ); + check!("-1234", [lit!("-1234")]; ); + check!("PST", [lit!("PST")]; ); + check!("🀠", [lit!("🀠")]; ); + check!("🀠a", [lit!("🀠"), lit!("a")]; ); + check!("🀠a🀠", [lit!("🀠"), lit!("a🀠")]; ); + check!("a🀠b", [lit!("a"), lit!("🀠"), lit!("b")]; ); + // literals can be together + check!("xy", [lit!("xy")]; ); + check!("xyz", [lit!("xyz")]; ); + // or literals can be apart + check!("xy", [lit!("x"), lit!("y")]; ); + check!("xyz", [lit!("x"), lit!("yz")]; ); + check!("xyz", [lit!("xy"), lit!("z")]; ); + check!("xyz", [lit!("x"), lit!("y"), lit!("z")]; ); + // check!("x y", [lit!("x"), lit!("y")]; INVALID); check!("xy", [lit!("x"), sp!(""), lit!("y")]; ); check!("x y", [lit!("x"), sp!(""), lit!("y")]; ); @@ -771,48 +792,371 @@ fn test_parse() { check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID); // fixed: timezone offsets - check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); - check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); - check!("+00:30", [fix!(TimezoneOffset)]; offset: 30 * 60); - check!("-00:30", [fix!(TimezoneOffset)]; offset: -30 * 60); - check!("+04:56", [fix!(TimezoneOffset)]; offset: 296 * 60); - check!("-04:56", [fix!(TimezoneOffset)]; offset: -296 * 60); - check!("+24:00", [fix!(TimezoneOffset)]; offset: 24 * 60 * 60); - check!("-24:00", [fix!(TimezoneOffset)]; offset: -24 * 60 * 60); - check!("+99:59", [fix!(TimezoneOffset)]; offset: (100 * 60 - 1) * 60); - check!("-99:59", [fix!(TimezoneOffset)]; offset: -(100 * 60 - 1) * 60); - check!("+00:59", [fix!(TimezoneOffset)]; offset: 59 * 60); - check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("#12:34", [fix!(TimezoneOffset)]; INVALID); - check!("12:34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffset)]; offset: 754 * 60); - check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -754 * 60); - check!("", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffset)]; offset: 754 * 60); - check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 754 * 60, day: 5); - check!("Z", [fix!(TimezoneOffset)]; INVALID); - check!("z", [fix!(TimezoneOffset)]; INVALID); - check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60); - check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 754 * 60); - check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("+12:00", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60); - check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 12 * 60 * 60); - check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); + + // TimezoneOffset + check!("1", [fix!(TimezoneOffset)]; INVALID); + check!("12", [fix!(TimezoneOffset)]; INVALID); + check!("123", [fix!(TimezoneOffset)]; INVALID); + check!("1234", [fix!(TimezoneOffset)]; INVALID); + check!("12345", [fix!(TimezoneOffset)]; INVALID); + check!("123456", [fix!(TimezoneOffset)]; INVALID); + check!("1234567", [fix!(TimezoneOffset)]; INVALID); + check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("+12:34:", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:34:56:", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("12:34", [fix!(TimezoneOffset)]; INVALID); + check!("12:34:56", [fix!(TimezoneOffset)]; INVALID); + check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12: :34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12:::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12::::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:3456", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234:56", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234:567", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); + check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); + check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); + check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); + check!("+00:30", [fix!(TimezoneOffset)]; offset: 1_800); + check!("-00:30", [fix!(TimezoneOffset)]; offset: -1_800); + check!("+24:00", [fix!(TimezoneOffset)]; offset: 86_400); + check!("-24:00", [fix!(TimezoneOffset)]; offset: -86_400); + check!("+99:59", [fix!(TimezoneOffset)]; offset: 359_940); + check!("-99:59", [fix!(TimezoneOffset)]; offset: -359_940); + check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); + check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); + check!("#12:34", [fix!(TimezoneOffset)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12 34 ", [fix!(TimezoneOffset)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("12:34 ", [fix!(TimezoneOffset)]; INVALID); + check!(" 12:34", [fix!(TimezoneOffset)]; INVALID); + check!("", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffset), lit!(":")]; offset: 45_240); + check!("Z12:34", [fix!(TimezoneOffset)]; INVALID); + check!("X12:34", [fix!(TimezoneOffset)]; INVALID); + check!("Z+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("X+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("🀠+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("+12:34🀠", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:🀠34", [fix!(TimezoneOffset)]; INVALID); + check!("+1234🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: 45_240); + check!("-1234🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); + check!("+12:34🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: 45_240); + check!("-12:34🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); + check!("🀠+12:34", [lit!("🀠"), fix!(TimezoneOffset)]; offset: 45_240); + check!("Z", [fix!(TimezoneOffset)]; INVALID); + check!("A", [fix!(TimezoneOffset)]; INVALID); + check!("PST", [fix!(TimezoneOffset)]; INVALID); + check!("#Z", [fix!(TimezoneOffset)]; INVALID); + check!(":Z", [fix!(TimezoneOffset)]; INVALID); + check!("+Z", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffset)]; INVALID); + check!("+Z:", [fix!(TimezoneOffset)]; INVALID); + check!("z", [fix!(TimezoneOffset)]; INVALID); + check!(" :Z", [fix!(TimezoneOffset)]; INVALID); + check!(" Z", [fix!(TimezoneOffset)]; INVALID); + check!(" z", [fix!(TimezoneOffset)]; INVALID); + + // TimezoneOffsetColon + check!("1", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12", [fix!(TimezoneOffsetColon)]; INVALID); + check!("123", [fix!(TimezoneOffsetColon)]; INVALID); + check!("1234", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12345", [fix!(TimezoneOffsetColon)]; INVALID); + check!("123456", [fix!(TimezoneOffsetColon)]; INVALID); + check!("1234567", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12345678", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+1", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("-1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("+12345", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("1:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:3", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:5", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:56", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+1:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12:", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("+12:34:", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:7", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:78", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:3456", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+1234:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("-12 : 34", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12:::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("#1234", [fix!(TimezoneOffsetColon)]; INVALID); + check!("#12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("\t\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("12:34 ", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" 12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!(":", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffsetColon), lit!(":")]; offset: 45_240); + check!("Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("A", [fix!(TimezoneOffsetColon)]; INVALID); + check!("PST", [fix!(TimezoneOffsetColon)]; INVALID); + check!("#Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(":Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+Z", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+Z:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" :Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" z", [fix!(TimezoneOffsetColon)]; INVALID); + // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` + // and `TimezoneOffsetTripleColon` for function `parse_internal`. + // No need for separate tests for `TimezoneOffsetDoubleColon` and + // `TimezoneOffsetTripleColon`. + + // TimezoneOffsetZ + check!("1", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12", [fix!(TimezoneOffsetZ)]; INVALID); + check!("123", [fix!(TimezoneOffsetZ)]; INVALID); + check!("1234", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12345", [fix!(TimezoneOffsetZ)]; INVALID); + check!("123456", [fix!(TimezoneOffsetZ)]; INVALID); + check!("1234567", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12345678", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+1", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("-1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); + check!("+12345", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("1:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:3", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:5", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:56", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+1:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+12:", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); + check!("+12:34:", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:7", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:78", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12::34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12:3456", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+1234:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12: 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 :34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("12:34 ", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" 12:34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffsetZ), lit!(":")]; offset: 45_240); + check!("Z12:34", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("X12:34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!(" Z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!(" z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!("\u{0363}Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Z ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("A", [fix!(TimezoneOffsetZ)]; INVALID); + check!("PST", [fix!(TimezoneOffsetZ)]; INVALID); + check!("#Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(":Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(":z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("-Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+A", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+πŸ™ƒ", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+Z:", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" :Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" +Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!(" -Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); + check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); + check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); + check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); + // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` + // in function `parse_internal`. + // No need for separate tests for `TimezoneOffsetColonZ`. + + // TimezoneOffsetPermissive + check!("1", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("123", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("1234", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12345", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("123456", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("1234567", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12345678", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+1", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); + check!("+123", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("-1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("+12345", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+123456", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+1234567", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12345678", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:3", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); + check!("+12:3", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("-12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:7", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:78", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12::::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(" 12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!(" +12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!(" -12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("+12345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive), lit!(":")]; offset: 45_240); + check!("🀠+12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34🀠", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:🀠34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34🀠", [internal_fix!(TimezoneOffsetPermissive), lit!("🀠")]; offset: 45_240); + check!("🀠+12:34", [lit!("🀠"), internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!("A", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!(" Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!(" z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!("Z ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("#Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(":Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(":z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("-Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+A", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+πŸ™ƒ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+Z:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(" :Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(" +Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!(" -Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+:Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("Y", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + + // TimezoneName + check!("CEST", [fix!(TimezoneName)]; ); + check!("cest", [fix!(TimezoneName)]; ); // lowercase + check!("XXXXXXXX", [fix!(TimezoneName)]; ); // not a real timezone name + check!("!!!!", [fix!(TimezoneName)]; ); // not a real timezone name! + check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); + check!("CEST ", [fix!(TimezoneName)]; TOO_LONG); + check!(" CEST", [fix!(TimezoneName)]; TOO_LONG); + check!("CE ST", [fix!(TimezoneName)]; TOO_LONG); // some practical examples check!("2015-02-04T14:37:05+09:00", @@ -878,7 +1222,9 @@ fn test_rfc2822() { ("20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // no day of week ("20 JAN 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // upper case month ("Tue, 20 Jan 2015 17:35 -0800", Ok("Tue, 20 Jan 2015 17:35:00 -0800")), // no second + ("11 Sep 2001 09:45:00 +0000", Ok("Tue, 11 Sep 2001 09:45:00 +0000")), ("11 Sep 2001 09:45:00 EST", Ok("Tue, 11 Sep 2001 09:45:00 -0500")), + ("11 Sep 2001 09:45:00 GMT", Ok("Tue, 11 Sep 2001 09:45:00 +0000")), ("30 Feb 2015 17:35:20 -0800", Err(OUT_OF_RANGE)), // bad day of month ("Tue, 20 Jan 2015", Err(TOO_SHORT)), // omitted fields ("Tue, 20 Avr 2015 17:35:20 -0800", Err(INVALID)), // bad month name @@ -911,6 +1257,14 @@ fn test_rfc2822() { ("Tue, 20 Jan 2015 17:35:20 k", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), // named single-letter timezone "J" is specifically not valid ("Tue, 20 Jan 2015 17:35:20 J", Err(NOT_ENOUGH)), + ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset minutes + ("Tue, 20 Jan 2015 17:35:20Z", Err(INVALID)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 Zulu", Err(NOT_ENOUGH)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 ZULU", Err(NOT_ENOUGH)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 βˆ’0800", Err(INVALID)), // bad offset: timezone offset using MINUS SIGN (U+2212), not specified for RFC 2822 + ("Tue, 20 Jan 2015 17:35:20 0800", Err(INVALID)), // missing offset sign + ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named timezone + ("Tue, 20 Jan 2015😈17:35:20 -0800", Err(INVALID)), // bad character! ]; fn rfc2822_to_datetime(date: &str) -> ParseResult> { @@ -925,6 +1279,8 @@ fn test_rfc2822() { // Test against test data above for &(date, checkdate) in testdates.iter() { + eprintln!("Test input: {:?}", date); + eprintln!(" Expect: {:?}", checkdate); let d = rfc2822_to_datetime(date); // parse a date let dt = match d { // did we get a value? @@ -998,11 +1354,42 @@ fn test_rfc3339() { ("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")), ("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), ("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small - ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month - ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour - ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute - ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second - ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset + ("2015-01-20 17:35:20.001-08:00", Err(INVALID)), // missing separator 'T' + ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YMD + ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HMS + ("99999-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("-2000-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month value + ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour value + ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute value + ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second value + ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset value + ("15-01-20T17:35:20-08:00", Err(INVALID)), // bad year format + ("15-01-20T17:35:20-08:00:00", Err(INVALID)), // bad year format, bad offset format + ("2015-01-20T17:35:2008:00", Err(INVALID)), // missing offset sign + ("2015-01-20T17:35:20 08:00", Err(INVALID)), // missing offset sign + ("2015-01-20T17:35:20Zulu", Err(TOO_LONG)), // bad offset format + ("2015-01-20T17:35:20 Zulu", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20 GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20+GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20++08:00", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20--08:00", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20βˆ’βˆ’08:00", Err(INVALID)), // bad offset format with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20Β±08:00", Err(INVALID)), // bad offset sign + ("2015-01-20T17:35:20-08-00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08;00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-0800", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08:0", Err(TOO_SHORT)), // bad offset minutes + ("2015-01-20T17:35:20-08:AA", Err(INVALID)), // bad offset minutes + ("2015-01-20T17:35:20-08:ZZ", Err(INVALID)), // bad offset minutes + ("2015-01-20T17:35:20.001-08 : 00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08:00:00", Err(TOO_LONG)), // bad offset format + ("2015-01-20T17:35:20-08:", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T17:35:20-08", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T", Err(TOO_SHORT)), // missing HMS + ("2015-01-20T00:00:1", Err(TOO_SHORT)), // missing complete S + ("2015-01-20T00:00:1-08:00", Err(INVALID)), // missing complete S ]; fn rfc3339_to_datetime(date: &str) -> ParseResult> { From b9a0a251c5e19a17c2e5382aaa2b9c1613767d92 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 28 May 2023 17:20:17 -0700 Subject: [PATCH 03/68] Add tests for RFC 850 failures --- src/format/parse.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/format/parse.rs b/src/format/parse.rs index d34195f9d4..6cd23a2758 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -1336,6 +1336,25 @@ fn parse_rfc850() { for val in &testdates { assert_eq!(Ok(val.0), Utc.datetime_from_str(val.1, RFC850_FMT)); } + + let test_dates_fail = [ + "Saturday, 12-Nov-94 08:49:37", + "Saturday, 12-Nov-94 08:49:37 Z", + "Saturday, 12-Nov-94 08:49:37 GMTTTT", + "Saturday, 12-Nov-94 08:49:37 gmt", + "Saturday, 12-Nov-94 08:49:37 +08:00", + "Caturday, 12-Nov-94 08:49:37 GMT", + "Saturday, 99-Nov-94 08:49:37 GMT", + "Saturday, 12-Nov-2000 08:49:37 GMT", + "Saturday, 12-Mop-94 08:49:37 GMT", + "Saturday, 12-Nov-94 28:49:37 GMT", + "Saturday, 12-Nov-94 08:99:37 GMT", + "Saturday, 12-Nov-94 08:49:99 GMT", + ]; + + for val in &test_dates_fail { + assert!(Utc.datetime_from_str(val, RFC850_FMT).is_err()); + } } #[cfg(test)] From cf177ee8cae87f7024a81f707e73f4187a643333 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 28 May 2023 17:21:14 -0700 Subject: [PATCH 04/68] Replace test_tz_ascii_str with test_timezonename_new Replace test `test_tz_ascii_str` with more systemtatic test `test_timezonename_new`. --- src/offset/local/tz_info/timezone.rs | 66 ++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index d2de060237..550910d044 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -741,20 +741,58 @@ mod tests { } #[test] - fn test_tz_ascii_str() -> Result<(), Error> { - assert!(matches!(TimeZoneName::new(b""), Err(Error::LocalTimeType(_)))); - assert!(matches!(TimeZoneName::new(b"1"), Err(Error::LocalTimeType(_)))); - assert!(matches!(TimeZoneName::new(b"12"), Err(Error::LocalTimeType(_)))); - assert_eq!(TimeZoneName::new(b"123")?.as_bytes(), b"123"); - assert_eq!(TimeZoneName::new(b"1234")?.as_bytes(), b"1234"); - assert_eq!(TimeZoneName::new(b"12345")?.as_bytes(), b"12345"); - assert_eq!(TimeZoneName::new(b"123456")?.as_bytes(), b"123456"); - assert_eq!(TimeZoneName::new(b"1234567")?.as_bytes(), b"1234567"); - assert!(matches!(TimeZoneName::new(b"12345678"), Err(Error::LocalTimeType(_)))); - assert!(matches!(TimeZoneName::new(b"123456789"), Err(Error::LocalTimeType(_)))); - assert!(matches!(TimeZoneName::new(b"1234567890"), Err(Error::LocalTimeType(_)))); - - assert!(matches!(TimeZoneName::new(b"123\0\0\0"), Err(Error::LocalTimeType(_)))); + fn test_timezonename_new() -> Result<(), Error> { + // expect Error::LocalTimeType() + const INPUT_ERR: &[&str] = &[ + "", + "1", + "+", + "-", + "12", + "--", + "AB", + "ab", + "12345678", + "ABCDEFGH", + "123456789", + "1234567890", + "--------", + "123\0\0\0", + "\0\0\0", + "\x00123", + "123\0", + ]; + for input_ in INPUT_ERR.iter() { + eprintln!("TimeZoneName::new({:?}) (expect Error::LocalTimeType)", input_); + let input_ = input_.as_bytes(); + let err = TimeZoneName::new(input_); + eprintln!("err = {:?}", err); + assert!(matches!(err, Err(Error::LocalTimeType(_)))); + } + // expect Ok + const INPUT_OK_EXPECT: &[(&str, &str)] = &[ + ("123", "123"), + ("abc", "abc"), + ("ABC", "ABC"), + ("1234", "1234"), + ("12345", "12345"), + ("123456", "123456"), + ("1234567", "1234567"), + ("+1234", "+1234"), + ("+1234", "+1234"), + ("-1234", "-1234"), + // Ok nonsense + ("+++", "+++"), + ("-----", "-----"), + ]; + for (input_, expect) in INPUT_OK_EXPECT.iter() { + eprintln!("TimeZoneName::new({:?})", input_); + let output = TimeZoneName::new(input_.as_bytes()); + match output { + Ok(output) => assert_eq!(output.as_bytes(), expect.as_bytes()), + Err(error) => panic!("Failed: input {:?}, error {}", input_, error), + } + } Ok(()) } From ed24478e4eb73ea4b547fa8ebcc5da6cb14264fe Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 28 May 2023 17:24:25 -0700 Subject: [PATCH 05/68] timezone_offset_internal and TimeZoneName::new parse by chars Function `timezone_offset_internal` and `TimeZoneName::new` iterate over the input data by `chars()` instead of iterating `as_bytes()`. --- src/format/scan.rs | 15 +++++++++---- src/offset/local/tz_info/timezone.rs | 32 +++++++++++++++++----------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/format/scan.rs b/src/format/scan.rs index 2962ef162b..f2b807fb52 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -230,13 +230,20 @@ where Ok((b[0], b[1])) } } - let negative = match s.as_bytes().first() { - Some(&b'+') => false, - Some(&b'-') => true, + let negative = match s.chars().next() { + Some('+') => { + s = &s['+'.len_utf8()..]; + + false + } + Some('-') => { + s = &s['-'.len_utf8()..]; + + true + } Some(_) => return Err(INVALID), None => return Err(TOO_SHORT), }; - s = &s[1..]; // hours (00--99) let hours = match digits(s)? { diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 550910d044..81bcee73be 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -487,28 +487,36 @@ struct TimeZoneName { impl TimeZoneName { /// Construct a time zone name fn new(input: &[u8]) -> Result { - let len = input.len(); + let s = match str::from_utf8(input) { + Ok(s) => s, + Err(_err) => return Err(Error::LocalTimeType("invalid UTF-8")), + }; + let schars = s.chars().count(); - if !(3..=7).contains(&len) { + if !(3..=7).contains(&schars) { return Err(Error::LocalTimeType( "time zone name must have between 3 and 7 characters", )); } let mut bytes = [0; 8]; - bytes[0] = input.len() as u8; - - let mut i = 0; - while i < len { - let b = input[i]; - match b { - b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'+' | b'-' => {} + let mut copied = 0; + for (i, c) in s.chars().enumerate() { + match c { + '0'..='9' | 'A'..='Z' | 'a'..='z' + // ISO 8601 / RFC 3339 proscribes use of `+` (U+2B) PLUS SIGN + // in timezone + | '+' + // ISO 8601 / RFC 3339 allows use of `-` HYPHEN-MINUS (U+2D) + // in timezone + | '-' => { + bytes[i + 1] = c as u8; + } _ => return Err(Error::LocalTimeType("invalid characters in time zone name")), } - - bytes[i + 1] = b; - i += 1; + copied += 1; } + bytes[0] = copied as u8; Ok(Self { bytes }) } From 7a4da413cfaaf1060e74141f5f426baf93be5441 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 28 May 2023 17:32:16 -0700 Subject: [PATCH 06/68] timezone allows leading MINUS SIGN (U+2212) Timezone signage also allows MINUS SIGN (U+2212) as specified by ISO 8601 and RFC 3339. Not for RFC 2822 format or RFC 8536 transition string. Issue #835 --- src/format/parse.rs | 62 +++++++++++++++++++++++++--- src/format/scan.rs | 32 ++++++++++++-- src/offset/local/tz_info/timezone.rs | 23 +++++++++-- 3 files changed, 105 insertions(+), 12 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 6cd23a2758..c542bcb3b0 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -594,6 +594,8 @@ fn test_parse() { check!("a", [lit!("a")]; ); check!("+", [lit!("+")]; ); check!("-", [lit!("-")]; ); + check!("βˆ’", [lit!("βˆ’")]; ); // MINUS SIGN (U+2212) + // a Literal may contain whitespace and match whitespace, but this should not be done check!(" ", [lit!(" ")]; ); check!("aa", [lit!("a")]; TOO_LONG); check!("A", [lit!("a")]; INVALID); @@ -603,6 +605,7 @@ fn test_parse() { check!("1234", [lit!("1234")]; ); check!("+1234", [lit!("+1234")]; ); check!("-1234", [lit!("-1234")]; ); + check!("βˆ’1234", [lit!("βˆ’1234")]; ); // MINUS SIGN (U+2212) check!("PST", [lit!("PST")]; ); check!("🀠", [lit!("🀠")]; ); check!("🀠a", [lit!("🀠"), lit!("a")]; ); @@ -651,11 +654,17 @@ fn test_parse() { check!("-0042", [num!(Year)]; year: -42); check!("+0042", [num!(Year)]; year: 42); check!("-42195", [num!(Year)]; year: -42195); + check!("βˆ’42195", [num!(Year)]; INVALID); // MINUS SIGN (U+2212) check!("+42195", [num!(Year)]; year: 42195); check!(" -42195", [num!(Year)]; year: -42195); check!(" +42195", [num!(Year)]; year: 42195); check!(" - 42", [num!(Year)]; INVALID); check!(" + 42", [num!(Year)]; INVALID); + check!(" -42195", [sp!(" "), num!(Year)]; year: -42195); + check!(" βˆ’42195", [sp!(" "), num!(Year)]; INVALID); // MINUS SIGN (U+2212) + check!(" +42195", [sp!(" "), num!(Year)]; year: 42195); + check!(" - 42", [sp!(" "), num!(Year)]; INVALID); + check!(" + 42", [sp!(" "), num!(Year)]; INVALID); check!("-", [num!(Year)]; TOO_SHORT); check!("+", [num!(Year)]; TOO_SHORT); @@ -664,6 +673,13 @@ fn test_parse() { check!("+345", [num!(Ordinal)]; INVALID); check!("-345", [num!(Ordinal)]; INVALID); check!(" 345", [num!(Ordinal)]; ordinal: 345); + check!("βˆ’345", [num!(Ordinal)]; INVALID); // MINUS SIGN (U+2212) + check!("345 ", [num!(Ordinal)]; TOO_LONG); + check!(" 345", [sp!(" "), num!(Ordinal)]; ordinal: 345); + check!("345 ", [num!(Ordinal), sp!(" ")]; ordinal: 345); + check!("345🀠 ", [num!(Ordinal), lit!("🀠"), sp!(" ")]; ordinal: 345); + check!("345🀠", [num!(Ordinal)]; TOO_LONG); + check!("\u{0363}345", [num!(Ordinal)]; INVALID); check!(" +345", [num!(Ordinal)]; INVALID); check!(" -345", [num!(Ordinal)]; INVALID); @@ -813,6 +829,7 @@ fn test_parse() { check!("+12:3", [fix!(TimezoneOffset)]; TOO_SHORT); check!("+12:34", [fix!(TimezoneOffset)]; offset: 45_240); check!("-12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("βˆ’12:34", [fix!(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12:34:", [fix!(TimezoneOffset)]; TOO_LONG); check!("+12:34:5", [fix!(TimezoneOffset)]; TOO_LONG); check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); @@ -832,12 +849,14 @@ fn test_parse() { check!("+1234:567", [fix!(TimezoneOffset)]; TOO_LONG); check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); + check!("βˆ’00:00", [fix!(TimezoneOffset)]; offset: 0); // MINUS SIGN (U+2212) check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); check!("+00:30", [fix!(TimezoneOffset)]; offset: 1_800); check!("-00:30", [fix!(TimezoneOffset)]; offset: -1_800); check!("+24:00", [fix!(TimezoneOffset)]; offset: 86_400); check!("-24:00", [fix!(TimezoneOffset)]; offset: -86_400); + check!("βˆ’24:00", [fix!(TimezoneOffset)]; offset: -86_400); // MINUS SIGN (U+2212) check!("+99:59", [fix!(TimezoneOffset)]; offset: 359_940); check!("-99:59", [fix!(TimezoneOffset)]; offset: -359_940); check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); @@ -847,6 +866,8 @@ fn test_parse() { check!("+12 34 ", [fix!(TimezoneOffset)]; TOO_LONG); check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!(" βˆ’12:34", [fix!(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("12:34 ", [fix!(TimezoneOffset)]; INVALID); check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -45_240); @@ -858,7 +879,6 @@ fn test_parse() { check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("12:34 ", [fix!(TimezoneOffset)]; INVALID); check!(" 12:34", [fix!(TimezoneOffset)]; INVALID); check!("", [fix!(TimezoneOffset)]; TOO_SHORT); check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); @@ -869,13 +889,16 @@ fn test_parse() { check!("X12:34", [fix!(TimezoneOffset)]; INVALID); check!("Z+12:34", [fix!(TimezoneOffset)]; INVALID); check!("X+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("Xβˆ’12:34", [fix!(TimezoneOffset)]; INVALID); // MINUS SIGN (U+2212) check!("🀠+12:34", [fix!(TimezoneOffset)]; INVALID); check!("+12:34🀠", [fix!(TimezoneOffset)]; TOO_LONG); check!("+12:🀠34", [fix!(TimezoneOffset)]; INVALID); check!("+1234🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: 45_240); check!("-1234🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); + check!("βˆ’1234🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12:34🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: 45_240); check!("-12:34🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); + check!("βˆ’12:34🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); // MINUS SIGN (U+2212) check!("🀠+12:34", [lit!("🀠"), fix!(TimezoneOffset)]; offset: 45_240); check!("Z", [fix!(TimezoneOffset)]; INVALID); check!("A", [fix!(TimezoneOffset)]; INVALID); @@ -904,6 +927,7 @@ fn test_parse() { check!("+123", [fix!(TimezoneOffsetColon)]; TOO_SHORT); check!("+1234", [fix!(TimezoneOffsetColon)]; offset: 45_240); check!("-1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("βˆ’1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12345", [fix!(TimezoneOffsetColon)]; TOO_LONG); check!("+123456", [fix!(TimezoneOffsetColon)]; TOO_LONG); check!("+1234567", [fix!(TimezoneOffsetColon)]; TOO_LONG); @@ -920,6 +944,7 @@ fn test_parse() { check!("+12:3", [fix!(TimezoneOffsetColon)]; TOO_SHORT); check!("+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); check!("-12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("βˆ’12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12:34:", [fix!(TimezoneOffsetColon)]; TOO_LONG); check!("+12:34:5", [fix!(TimezoneOffsetColon)]; TOO_LONG); check!("+12:34:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); @@ -928,6 +953,8 @@ fn test_parse() { check!("+12:34:56:78", [fix!(TimezoneOffsetColon)]; TOO_LONG); check!("+12:3456", [fix!(TimezoneOffsetColon)]; TOO_LONG); check!("+1234:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("βˆ’12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("βˆ’12 : 34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); check!("+12 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); @@ -988,6 +1015,7 @@ fn test_parse() { check!("+123", [fix!(TimezoneOffsetZ)]; TOO_SHORT); check!("+1234", [fix!(TimezoneOffsetZ)]; offset: 45_240); check!("-1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); + check!("βˆ’1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12345", [fix!(TimezoneOffsetZ)]; TOO_LONG); check!("+123456", [fix!(TimezoneOffsetZ)]; TOO_LONG); check!("+1234567", [fix!(TimezoneOffsetZ)]; TOO_LONG); @@ -1004,6 +1032,7 @@ fn test_parse() { check!("+12:3", [fix!(TimezoneOffsetZ)]; TOO_SHORT); check!("+12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); check!("-12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); + check!("βˆ’12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12:34:", [fix!(TimezoneOffsetZ)]; TOO_LONG); check!("+12:34:5", [fix!(TimezoneOffsetZ)]; TOO_LONG); check!("+12:34:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); @@ -1074,6 +1103,7 @@ fn test_parse() { check!("+123", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); check!("+1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); check!("-1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("βˆ’1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12345", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); check!("+123456", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); check!("+1234567", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); @@ -1090,6 +1120,7 @@ fn test_parse() { check!("+12:3", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); check!("+12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); check!("-12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("βˆ’12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); check!("+12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); check!("+12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); @@ -1118,6 +1149,7 @@ fn test_parse() { check!("+12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); check!(" +12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); check!(" -12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!(" βˆ’12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) check!("+12345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); check!("+12:345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive), lit!(":")]; offset: 45_240); @@ -1164,6 +1196,16 @@ fn test_parse() { num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, second: 5, offset: 32400); + check!("2015-02-04T14:37:05-09:00", + [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), + num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, offset: -32400); + check!("2015-02-04T14:37:05βˆ’09:00", // timezone offset using MINUS SIGN (U+2212) + [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), + num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, offset: -32400); check!("20150204143705567", [num!(Year), num!(Month), num!(Day), num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)]; @@ -1367,15 +1409,21 @@ fn test_rfc3339() { // Test data - (input, Ok(expected result after parse and format) or Err(error code)) let testdates = [ ("2015-01-20T17:35:20-08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case + ("2015-01-20T17:35:20βˆ’08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case with MINUS SIGN (U+2212) ("1944-06-06T04:04:00Z", Ok("1944-06-06T04:04:00+00:00")), // D-day ("2001-09-11T09:45:00-08:00", Ok("2001-09-11T09:45:00-08:00")), ("2015-01-20T17:35:20.001-08:00", Ok("2015-01-20T17:35:20.001-08:00")), + ("2015-01-20T17:35:20.001βˆ’08:00", Ok("2015-01-20T17:35:20.001-08:00")), // with MINUS SIGN (U+2212) ("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")), ("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), + ("2015-01-20T17:35:20.000000004βˆ’08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), // with MINUS SIGN (U+2212) ("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small + ("2015-01-20T17:35:20.000000000452βˆ’08:00", Ok("2015-01-20T17:35:20-08:00")), // too small with MINUS SIGN (U+2212) ("2015-01-20 17:35:20.001-08:00", Err(INVALID)), // missing separator 'T' ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YMD ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HMS + ("-01-20T17:35:20-08:00", Err(INVALID)), // missing year + ("99-01-20T17:35:20-08:00", Err(INVALID)), // bad year format ("99999-01-20T17:35:20-08:00", Err(INVALID)), // bad year value ("-2000-01-20T17:35:20-08:00", Err(INVALID)), // bad year value ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month value @@ -1404,11 +1452,13 @@ fn test_rfc3339() { ("2015-01-20T17:35:20-08:ZZ", Err(INVALID)), // bad offset minutes ("2015-01-20T17:35:20.001-08 : 00", Err(INVALID)), // bad offset separator ("2015-01-20T17:35:20-08:00:00", Err(TOO_LONG)), // bad offset format - ("2015-01-20T17:35:20-08:", Err(TOO_SHORT)), // bad offset format - ("2015-01-20T17:35:20-08", Err(TOO_SHORT)), // bad offset format - ("2015-01-20T", Err(TOO_SHORT)), // missing HMS - ("2015-01-20T00:00:1", Err(TOO_SHORT)), // missing complete S - ("2015-01-20T00:00:1-08:00", Err(INVALID)), // missing complete S + ("2015-01-20T17:35:20+08:", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T17:35:20-08:", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T17:35:20βˆ’08:", Err(TOO_SHORT)), // bad offset format with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20-08", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T", Err(TOO_SHORT)), // missing HMS + ("2015-01-20T00:00:1", Err(TOO_SHORT)), // missing complete S + ("2015-01-20T00:00:1-08:00", Err(INVALID)), // missing complete S ]; fn rfc3339_to_datetime(date: &str) -> ParseResult> { diff --git a/src/format/scan.rs b/src/format/scan.rs index f2b807fb52..2f5498cc43 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -211,13 +211,28 @@ pub(super) fn timezone_offset(s: &str, consume_colon: F) -> ParseResult<(&str where F: FnMut(&str) -> ParseResult<&str>, { - timezone_offset_internal(s, consume_colon, false) + timezone_offset_internal(s, consume_colon, false, true) } +/// Parse a timezone from `s` and return the offset in seconds. +/// +/// The `consume_colon` function is used to parse a mandatory or optional `:` +/// separator between hours offset and minutes offset. +/// +/// The `allow_missing_minutes` flag allows the timezone minutes offset to be +/// missing from `s`. +/// +/// The `allow_tz_minus_sign` flag allows the timezone offset negative character +/// to also be `βˆ’` MINUS SIGN (U+2212) in addition to the typical +/// ASCII-compatible `-` HYPHEN-MINUS (U+2D). +/// This is part of [RFC 3339 & ISO 8601]. +/// +/// [RFC 3339 & ISO 8601]: https://en.wikipedia.org/w/index.php?title=ISO_8601&oldid=1114309368#Time_offsets_from_UTC fn timezone_offset_internal( mut s: &str, mut consume_colon: F, allow_missing_minutes: bool, + allow_tz_minus_sign: bool, ) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, @@ -232,15 +247,26 @@ where } let negative = match s.chars().next() { Some('+') => { + // PLUS SIGN (U+2B) s = &s['+'.len_utf8()..]; false } Some('-') => { + // HYPHEN-MINUS (U+2D) s = &s['-'.len_utf8()..]; true } + Some('βˆ’') => { + // MINUS SIGN (U+2212) + if !allow_tz_minus_sign { + return Err(INVALID); + } + s = &s['βˆ’'.len_utf8()..]; + + true + } Some(_) => return Err(INVALID), None => return Err(TOO_SHORT), }; @@ -309,7 +335,7 @@ where { match s.as_bytes().first() { Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)), - _ => timezone_offset_internal(s, colon, true), + _ => timezone_offset_internal(s, colon, true, true), } } @@ -347,7 +373,7 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> Ok((s, None)) } } else { - let (s_, offset) = timezone_offset(s, |s| Ok(s))?; + let (s_, offset) = timezone_offset_internal(s, |s| Ok(s), false, false)?; Ok((s_, Some(offset))) } } diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 81bcee73be..ac7c3f2616 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -486,14 +486,19 @@ struct TimeZoneName { impl TimeZoneName { /// Construct a time zone name + /// + /// Note: Converts `βˆ’` MINUS SIGN (U+2212) to `-` HYPHEN-MINUS (U+2D). + /// Multi-byte MINUS SIGN is allowed in [ISO 8601 / RFC 3339]. But + /// working with single-byte HYPHEN-MINUS is easier and more common. + /// + /// [ISO 8601 / RFC 3339]: https://en.wikipedia.org/w/index.php?title=ISO_8601&oldid=1114309368#Time_offsets_from_UTC fn new(input: &[u8]) -> Result { let s = match str::from_utf8(input) { Ok(s) => s, Err(_err) => return Err(Error::LocalTimeType("invalid UTF-8")), }; - let schars = s.chars().count(); - if !(3..=7).contains(&schars) { + if !(3..=7).contains(&s.chars().count()) { return Err(Error::LocalTimeType( "time zone name must have between 3 and 7 characters", )); @@ -504,7 +509,7 @@ impl TimeZoneName { for (i, c) in s.chars().enumerate() { match c { '0'..='9' | 'A'..='Z' | 'a'..='z' - // ISO 8601 / RFC 3339 proscribes use of `+` (U+2B) PLUS SIGN + // ISO 8601 / RFC 3339 proscribes use of `+` PLUS SIGN (U+2B) // in timezone | '+' // ISO 8601 / RFC 3339 allows use of `-` HYPHEN-MINUS (U+2D) @@ -512,6 +517,13 @@ impl TimeZoneName { | '-' => { bytes[i + 1] = c as u8; } + // ISO 8601 / RFC 3339 recommends the use of + // `βˆ’` MINUS SIGN (U+2212) in timezone. + // But replace with single-byte `-` HYPHEN-MINUS (U+2D) for + // easier byte <-> char conversions later on. + | 'βˆ’' => { + bytes[i + 1] = b'-'; + } _ => return Err(Error::LocalTimeType("invalid characters in time zone name")), } copied += 1; @@ -756,8 +768,10 @@ mod tests { "1", "+", "-", + "βˆ’", // MINUS SIGN (U+2212) "12", "--", + "βˆ’βˆ’", // MINUS SIGN (U+2212) "AB", "ab", "12345678", @@ -789,9 +803,12 @@ mod tests { ("+1234", "+1234"), ("+1234", "+1234"), ("-1234", "-1234"), + ("βˆ’1234", "-1234"), // MINUS SIGN (U+2212) to HYPHEN-MINUS (U+002D) // Ok nonsense ("+++", "+++"), ("-----", "-----"), + ("βˆ’βˆ’βˆ’", "---"), // MINUS SIGN (U+2212) to HYPHEN-MINUS (U+002D) + ("βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’", "-------"), // MINUS SIGN (U+2212) to HYPHEN-MINUS (U+002D) ]; for (input_, expect) in INPUT_OK_EXPECT.iter() { eprintln!("TimeZoneName::new({:?})", input_); From 8f2e94d9304ee9942f1a98850466248011477202 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 9 Jun 2023 13:54:49 +0200 Subject: [PATCH 07/68] Remove `#![allow(deprecated)]` --- src/format/parse.rs | 22 ++++++++++------------ src/format/scan.rs | 10 ++++------ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index c542bcb3b0..298622a580 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -4,8 +4,6 @@ //! Date and time parsing routines. -#![allow(deprecated)] - use core::borrow::Borrow; use core::str; use core::usize; @@ -100,7 +98,7 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st // since we do not directly go to a `DateTime` so one can recover // the offset information from `Parsed` anyway. - s = s.trim_left(); + s = s.trim_start(); if let Ok((s_, weekday)) = scan::short_weekday(s) { if !s_.starts_with(',') { @@ -110,7 +108,7 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st parsed.set_weekday(weekday)?; } - s = s.trim_left(); + s = s.trim_start(); parsed.set_day(try_consume!(scan::number(s, 1, 2)))?; s = scan::space(s)?; // mandatory parsed.set_month(1 + i64::from(try_consume!(scan::short_month0(s))))?; @@ -136,9 +134,9 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st s = scan::space(s)?; // mandatory parsed.set_hour(try_consume!(scan::number(s, 2, 2)))?; - s = scan::char(s.trim_left(), b':')?.trim_left(); // *S ":" *S + s = scan::char(s.trim_start(), b':')?.trim_start(); // *S ":" *S parsed.set_minute(try_consume!(scan::number(s, 2, 2)))?; - if let Ok(s_) = scan::char(s.trim_left(), b':') { + if let Ok(s_) = scan::char(s.trim_start(), b':') { // [ ":" *S 2DIGIT ] parsed.set_second(try_consume!(scan::number(s_, 2, 2)))?; } @@ -323,12 +321,12 @@ where } Item::Space(_) => { - s = s.trim_left(); + s = s.trim_start(); } #[cfg(any(feature = "alloc", feature = "std", test))] Item::OwnedSpace(_) => { - s = s.trim_left(); + s = s.trim_start(); } Item::Numeric(ref spec, ref _pad) => { @@ -361,7 +359,7 @@ where Internal(ref int) => match int._dummy {}, }; - s = s.trim_left(); + s = s.trim_start(); let v = if signed { if s.starts_with('-') { let v = try_consume!(scan::number(&s[1..], 1, usize::MAX)); @@ -455,7 +453,7 @@ where | &TimezoneOffsetTripleColon | &TimezoneOffset => { let offset = try_consume!(scan::timezone_offset( - s.trim_left(), + s.trim_start(), scan::colon_or_space )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; @@ -463,7 +461,7 @@ where &TimezoneOffsetColonZ | &TimezoneOffsetZ => { let offset = try_consume!(scan::timezone_offset_zulu( - s.trim_left(), + s.trim_start(), scan::colon_or_space )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; @@ -472,7 +470,7 @@ where val: InternalInternal::TimezoneOffsetPermissive, }) => { let offset = try_consume!(scan::timezone_offset_permissive( - s.trim_left(), + s.trim_start(), scan::colon_or_space )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; diff --git a/src/format/scan.rs b/src/format/scan.rs index 2f5498cc43..fc67484317 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -5,8 +5,6 @@ * Various scanning routines for the parser. */ -#![allow(deprecated)] - use super::{ParseResult, INVALID, OUT_OF_RANGE, TOO_SHORT}; use crate::Weekday; @@ -79,7 +77,7 @@ pub(super) fn nanosecond(s: &str) -> ParseResult<(&str, i64)> { let v = v.checked_mul(SCALE[consumed]).ok_or(OUT_OF_RANGE)?; // if there are more than 9 digits, skip next digits. - let s = s.trim_left_matches(|c: char| c.is_ascii_digit()); + let s = s.trim_start_matches(|c: char| c.is_ascii_digit()); Ok((s, v)) } @@ -188,7 +186,7 @@ pub(super) fn char(s: &str, c1: u8) -> ParseResult<&str> { /// Tries to consume one or more whitespace. pub(super) fn space(s: &str) -> ParseResult<&str> { - let s_ = s.trim_left(); + let s_ = s.trim_start(); if s_.len() < s.len() { Ok(s_) } else if s.is_empty() { @@ -200,7 +198,7 @@ pub(super) fn space(s: &str) -> ParseResult<&str> { /// Consumes any number (including zero) of colon or spaces. pub(super) fn colon_or_space(s: &str) -> ParseResult<&str> { - Ok(s.trim_left_matches(|c: char| c == ':' || c.is_whitespace())) + Ok(s.trim_start_matches(|c: char| c == ':' || c.is_whitespace())) } /// Tries to parse `[-+]\d\d` continued by `\d\d`. Return an offset in seconds if possible. @@ -381,7 +379,7 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> /// Tries to consume everything until next whitespace-like symbol. /// Does not provide any offset information from the consumed data. pub(super) fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> { - Ok((s.trim_left_matches(|c: char| !c.is_whitespace()), ())) + Ok((s.trim_start_matches(|c: char| !c.is_whitespace()), ())) } /// Tries to consume an RFC2822 comment including preceding ` `. From 67c59fd08e8cd5d4f0c8748753840f20483eddac Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 9 Jun 2023 13:55:10 +0200 Subject: [PATCH 08/68] Move test in `format::scan` to `test` module --- src/format/scan.rs | 67 +++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/src/format/scan.rs b/src/format/scan.rs index fc67484317..2273dec7d6 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -413,36 +413,41 @@ enum CommentState { } #[cfg(test)] -#[test] -fn test_rfc2822_comments() { - let testdata = [ - ("", Err(TOO_SHORT)), - (" ", Err(TOO_SHORT)), - ("x", Err(INVALID)), - ("(", Err(TOO_SHORT)), - ("()", Ok("")), - (" \r\n\t()", Ok("")), - ("() ", Ok(" ")), - ("()z", Ok("z")), - ("(x)", Ok("")), - ("(())", Ok("")), - ("((()))", Ok("")), - ("(x(x(x)x)x)", Ok("")), - ("( x ( x ( x ) x ) x )", Ok("")), - (r"(\)", Err(TOO_SHORT)), - (r"(\()", Ok("")), - (r"(\))", Ok("")), - (r"(\\)", Ok("")), - ("(()())", Ok("")), - ("( x ( x ) x ( x ) x )", Ok("")), - ]; - - for (test_in, expected) in testdata.iter() { - let actual = comment_2822(test_in).map(|(s, _)| s); - assert_eq!( - *expected, actual, - "{:?} expected to produce {:?}, but produced {:?}.", - test_in, expected, actual - ); +mod tests { + use super::comment_2822; + use crate::format::{INVALID, TOO_SHORT}; + + #[test] + fn test_rfc2822_comments() { + let testdata = [ + ("", Err(TOO_SHORT)), + (" ", Err(TOO_SHORT)), + ("x", Err(INVALID)), + ("(", Err(TOO_SHORT)), + ("()", Ok("")), + (" \r\n\t()", Ok("")), + ("() ", Ok(" ")), + ("()z", Ok("z")), + ("(x)", Ok("")), + ("(())", Ok("")), + ("((()))", Ok("")), + ("(x(x(x)x)x)", Ok("")), + ("( x ( x ( x ) x ) x )", Ok("")), + (r"(\)", Err(TOO_SHORT)), + (r"(\()", Ok("")), + (r"(\))", Ok("")), + (r"(\\)", Ok("")), + ("(()())", Ok("")), + ("( x ( x ) x ( x ) x )", Ok("")), + ]; + + for (test_in, expected) in testdata.iter() { + let actual = comment_2822(test_in).map(|(s, _)| s); + assert_eq!( + *expected, actual, + "{:?} expected to produce {:?}, but produced {:?}.", + test_in, expected, actual + ); + } } } From b2cb0fdf7bf0b145e85e0877de14ff846cafc3f0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 12 Jun 2023 11:39:59 +0200 Subject: [PATCH 09/68] Move test in `format::parse` to `test` module --- src/format/parse.rs | 1849 ++++++++++++++++++++++--------------------- 1 file changed, 925 insertions(+), 924 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 298622a580..f1737d7dd5 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -551,946 +551,947 @@ impl str::FromStr for DateTime { } #[cfg(test)] -#[test] -fn test_parse() { - use super::IMPOSSIBLE; - use super::*; +mod tests { + use crate::format::*; + use crate::{DateTime, FixedOffset, TimeZone, Utc}; + + #[test] + fn test_parse() { + // workaround for Rust issue #22255 + fn parse_all(s: &str, items: &[Item]) -> ParseResult { + let mut parsed = Parsed::new(); + parse(&mut parsed, s, items.iter())?; + Ok(parsed) + } - // workaround for Rust issue #22255 - fn parse_all(s: &str, items: &[Item]) -> ParseResult { - let mut parsed = Parsed::new(); - parse(&mut parsed, s, items.iter())?; - Ok(parsed) - } + macro_rules! check { + ($fmt:expr, $items:expr; $err:tt) => ( + assert_eq!(parse_all($fmt, &$items), Err($err)) + ); + ($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] { + let mut expected = Parsed::new(); + $(expected.$k = Some($v);)* + assert_eq!(parse_all($fmt, &$items), Ok(expected)) + }); + } - macro_rules! check { - ($fmt:expr, $items:expr; $err:tt) => ( - assert_eq!(parse_all($fmt, &$items), Err($err)) - ); - ($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] { - let mut expected = Parsed::new(); - $(expected.$k = Some($v);)* - assert_eq!(parse_all($fmt, &$items), Ok(expected)) - }); + // empty string + check!("", []; ); + check!(" ", []; TOO_LONG); + check!("a", []; TOO_LONG); + + // whitespaces + check!("", [sp!("")]; ); + check!(" ", [sp!("")]; ); + check!("\t", [sp!("")]; ); + check!(" \n\r \n", [sp!("")]; ); + check!("a", [sp!("")]; TOO_LONG); + + // literal + check!("", [lit!("a")]; TOO_SHORT); + check!(" ", [lit!("a")]; INVALID); + check!("a", [lit!("a")]; ); + check!("+", [lit!("+")]; ); + check!("-", [lit!("-")]; ); + check!("βˆ’", [lit!("βˆ’")]; ); // MINUS SIGN (U+2212) + // a Literal may contain whitespace and match whitespace, but this should not be done + check!(" ", [lit!(" ")]; ); + check!("aa", [lit!("a")]; TOO_LONG); + check!("A", [lit!("a")]; INVALID); + check!("xy", [lit!("xy")]; ); + check!("xy", [lit!("x"), lit!("y")]; ); + check!("1", [lit!("1")]; ); + check!("1234", [lit!("1234")]; ); + check!("+1234", [lit!("+1234")]; ); + check!("-1234", [lit!("-1234")]; ); + check!("βˆ’1234", [lit!("βˆ’1234")]; ); // MINUS SIGN (U+2212) + check!("PST", [lit!("PST")]; ); + check!("🀠", [lit!("🀠")]; ); + check!("🀠a", [lit!("🀠"), lit!("a")]; ); + check!("🀠a🀠", [lit!("🀠"), lit!("a🀠")]; ); + check!("a🀠b", [lit!("a"), lit!("🀠"), lit!("b")]; ); + // literals can be together + check!("xy", [lit!("xy")]; ); + check!("xyz", [lit!("xyz")]; ); + // or literals can be apart + check!("xy", [lit!("x"), lit!("y")]; ); + check!("xyz", [lit!("x"), lit!("yz")]; ); + check!("xyz", [lit!("xy"), lit!("z")]; ); + check!("xyz", [lit!("x"), lit!("y"), lit!("z")]; ); + // + check!("x y", [lit!("x"), lit!("y")]; INVALID); + check!("xy", [lit!("x"), sp!(""), lit!("y")]; ); + check!("x y", [lit!("x"), sp!(""), lit!("y")]; ); + + // numeric + check!("1987", [num!(Year)]; year: 1987); + check!("1987 ", [num!(Year)]; TOO_LONG); + check!("0x12", [num!(Year)]; TOO_LONG); // `0` is parsed + check!("x123", [num!(Year)]; INVALID); + check!("2015", [num!(Year)]; year: 2015); + check!("0000", [num!(Year)]; year: 0); + check!("9999", [num!(Year)]; year: 9999); + check!(" \t987", [num!(Year)]; year: 987); + check!("5", [num!(Year)]; year: 5); + check!("5\0", [num!(Year)]; TOO_LONG); + check!("\x005", [num!(Year)]; INVALID); + check!("", [num!(Year)]; TOO_SHORT); + check!("12345", [num!(Year), lit!("5")]; year: 1234); + check!("12345", [nums!(Year), lit!("5")]; year: 1234); + check!("12345", [num0!(Year), lit!("5")]; year: 1234); + check!("12341234", [num!(Year), num!(Year)]; year: 1234); + check!("1234 1234", [num!(Year), num!(Year)]; year: 1234); + check!("1234 1235", [num!(Year), num!(Year)]; IMPOSSIBLE); + check!("1234 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); + check!("1234x1234", [num!(Year), lit!("x"), num!(Year)]; year: 1234); + check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); + check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); + + // signed numeric + check!("-42", [num!(Year)]; year: -42); + check!("+42", [num!(Year)]; year: 42); + check!("-0042", [num!(Year)]; year: -42); + check!("+0042", [num!(Year)]; year: 42); + check!("-42195", [num!(Year)]; year: -42195); + check!("βˆ’42195", [num!(Year)]; INVALID); // MINUS SIGN (U+2212) + check!("+42195", [num!(Year)]; year: 42195); + check!(" -42195", [num!(Year)]; year: -42195); + check!(" +42195", [num!(Year)]; year: 42195); + check!(" - 42", [num!(Year)]; INVALID); + check!(" + 42", [num!(Year)]; INVALID); + check!(" -42195", [sp!(" "), num!(Year)]; year: -42195); + check!(" βˆ’42195", [sp!(" "), num!(Year)]; INVALID); // MINUS SIGN (U+2212) + check!(" +42195", [sp!(" "), num!(Year)]; year: 42195); + check!(" - 42", [sp!(" "), num!(Year)]; INVALID); + check!(" + 42", [sp!(" "), num!(Year)]; INVALID); + check!("-", [num!(Year)]; TOO_SHORT); + check!("+", [num!(Year)]; TOO_SHORT); + + // unsigned numeric + check!("345", [num!(Ordinal)]; ordinal: 345); + check!("+345", [num!(Ordinal)]; INVALID); + check!("-345", [num!(Ordinal)]; INVALID); + check!(" 345", [num!(Ordinal)]; ordinal: 345); + check!("βˆ’345", [num!(Ordinal)]; INVALID); // MINUS SIGN (U+2212) + check!("345 ", [num!(Ordinal)]; TOO_LONG); + check!(" 345", [sp!(" "), num!(Ordinal)]; ordinal: 345); + check!("345 ", [num!(Ordinal), sp!(" ")]; ordinal: 345); + check!("345🀠 ", [num!(Ordinal), lit!("🀠"), sp!(" ")]; ordinal: 345); + check!("345🀠", [num!(Ordinal)]; TOO_LONG); + check!("\u{0363}345", [num!(Ordinal)]; INVALID); + check!(" +345", [num!(Ordinal)]; INVALID); + check!(" -345", [num!(Ordinal)]; INVALID); + + // various numeric fields + check!("1234 5678", + [num!(Year), num!(IsoYear)]; + year: 1234, isoyear: 5678); + check!("12 34 56 78", + [num!(YearDiv100), num!(YearMod100), num!(IsoYearDiv100), num!(IsoYearMod100)]; + year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78); + check!("1 2 3 4 5 6", + [num!(Month), num!(Day), num!(WeekFromSun), num!(WeekFromMon), num!(IsoWeek), + num!(NumDaysFromSun)]; + month: 1, day: 2, week_from_sun: 3, week_from_mon: 4, isoweek: 5, weekday: Weekday::Sat); + check!("7 89 01", + [num!(WeekdayFromMon), num!(Ordinal), num!(Hour12)]; + weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1); + check!("23 45 6 78901234 567890123", + [num!(Hour), num!(Minute), num!(Second), num!(Nanosecond), num!(Timestamp)]; + hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, + timestamp: 567_890_123); + + // fixed: month and weekday names + check!("apr", [fix!(ShortMonthName)]; month: 4); + check!("Apr", [fix!(ShortMonthName)]; month: 4); + check!("APR", [fix!(ShortMonthName)]; month: 4); + check!("ApR", [fix!(ShortMonthName)]; month: 4); + check!("April", [fix!(ShortMonthName)]; TOO_LONG); // `Apr` is parsed + check!("A", [fix!(ShortMonthName)]; TOO_SHORT); + check!("Sol", [fix!(ShortMonthName)]; INVALID); + check!("Apr", [fix!(LongMonthName)]; month: 4); + check!("Apri", [fix!(LongMonthName)]; TOO_LONG); // `Apr` is parsed + check!("April", [fix!(LongMonthName)]; month: 4); + check!("Aprill", [fix!(LongMonthName)]; TOO_LONG); + check!("Aprill", [fix!(LongMonthName), lit!("l")]; month: 4); + check!("Aprl", [fix!(LongMonthName), lit!("l")]; month: 4); + check!("April", [fix!(LongMonthName), lit!("il")]; TOO_SHORT); // do not backtrack + check!("thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); + check!("Thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); + check!("THU", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); + check!("tHu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); + check!("Thursday", [fix!(ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed + check!("T", [fix!(ShortWeekdayName)]; TOO_SHORT); + check!("The", [fix!(ShortWeekdayName)]; INVALID); + check!("Nop", [fix!(ShortWeekdayName)]; INVALID); + check!("Thu", [fix!(LongWeekdayName)]; weekday: Weekday::Thu); + check!("Thur", [fix!(LongWeekdayName)]; TOO_LONG); // `Thu` is parsed + check!("Thurs", [fix!(LongWeekdayName)]; TOO_LONG); // ditto + check!("Thursday", [fix!(LongWeekdayName)]; weekday: Weekday::Thu); + check!("Thursdays", [fix!(LongWeekdayName)]; TOO_LONG); + check!("Thursdays", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu); + check!("Thus", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu); + check!("Thursday", [fix!(LongWeekdayName), lit!("rsday")]; TOO_SHORT); // do not backtrack + + // fixed: am/pm + check!("am", [fix!(LowerAmPm)]; hour_div_12: 0); + check!("pm", [fix!(LowerAmPm)]; hour_div_12: 1); + check!("AM", [fix!(LowerAmPm)]; hour_div_12: 0); + check!("PM", [fix!(LowerAmPm)]; hour_div_12: 1); + check!("am", [fix!(UpperAmPm)]; hour_div_12: 0); + check!("pm", [fix!(UpperAmPm)]; hour_div_12: 1); + check!("AM", [fix!(UpperAmPm)]; hour_div_12: 0); + check!("PM", [fix!(UpperAmPm)]; hour_div_12: 1); + check!("Am", [fix!(LowerAmPm)]; hour_div_12: 0); + check!(" Am", [fix!(LowerAmPm)]; INVALID); + check!("ame", [fix!(LowerAmPm)]; TOO_LONG); // `am` is parsed + check!("a", [fix!(LowerAmPm)]; TOO_SHORT); + check!("p", [fix!(LowerAmPm)]; TOO_SHORT); + check!("x", [fix!(LowerAmPm)]; TOO_SHORT); + check!("xx", [fix!(LowerAmPm)]; INVALID); + check!("", [fix!(LowerAmPm)]; TOO_SHORT); + + // fixed: dot plus nanoseconds + check!("", [fix!(Nanosecond)]; ); // no field set, but not an error + check!(".", [fix!(Nanosecond)]; TOO_SHORT); + check!("4", [fix!(Nanosecond)]; TOO_LONG); // never consumes `4` + check!("4", [fix!(Nanosecond), num!(Second)]; second: 4); + check!(".0", [fix!(Nanosecond)]; nanosecond: 0); + check!(".4", [fix!(Nanosecond)]; nanosecond: 400_000_000); + check!(".42", [fix!(Nanosecond)]; nanosecond: 420_000_000); + check!(".421", [fix!(Nanosecond)]; nanosecond: 421_000_000); + check!(".42195", [fix!(Nanosecond)]; nanosecond: 421_950_000); + check!(".421950803", [fix!(Nanosecond)]; nanosecond: 421_950_803); + check!(".421950803547", [fix!(Nanosecond)]; nanosecond: 421_950_803); + check!(".000000003547", [fix!(Nanosecond)]; nanosecond: 3); + check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0); + check!(".", [fix!(Nanosecond)]; TOO_SHORT); + check!(".4x", [fix!(Nanosecond)]; TOO_LONG); + check!(". 4", [fix!(Nanosecond)]; INVALID); + check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming + + // fixed: nanoseconds without the dot + check!("", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!(".", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!("0", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!("421", [internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); + check!("42143", [internal_fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43); + check!("42195", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); + check!("4x", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!(" 4", [internal_fix!(Nanosecond3NoDot)]; INVALID); + check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID); + + check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!(".", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!("42195", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!("421950", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); + check!("000003", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 3000); + check!("000000", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 0); + check!("4x", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!(" 4", [internal_fix!(Nanosecond6NoDot)]; INVALID); + check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID); + + check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); + check!(".", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); + check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); + check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); + check!("000000003", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 3); + check!("42195080354", [internal_fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 + check!("421950803547", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG); + check!("000000000", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 0); + check!("00000000x", [internal_fix!(Nanosecond9NoDot)]; INVALID); + check!(" 4", [internal_fix!(Nanosecond9NoDot)]; INVALID); + check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID); + + // fixed: timezone offsets + + // TimezoneOffset + check!("1", [fix!(TimezoneOffset)]; INVALID); + check!("12", [fix!(TimezoneOffset)]; INVALID); + check!("123", [fix!(TimezoneOffset)]; INVALID); + check!("1234", [fix!(TimezoneOffset)]; INVALID); + check!("12345", [fix!(TimezoneOffset)]; INVALID); + check!("123456", [fix!(TimezoneOffset)]; INVALID); + check!("1234567", [fix!(TimezoneOffset)]; INVALID); + check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("βˆ’12:34", [fix!(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:34:56:", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("12:34", [fix!(TimezoneOffset)]; INVALID); + check!("12:34:56", [fix!(TimezoneOffset)]; INVALID); + check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12: :34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12:::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12::::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); + check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:3456", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234:56", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+1234:567", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); + check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); + check!("βˆ’00:00", [fix!(TimezoneOffset)]; offset: 0); // MINUS SIGN (U+2212) + check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); + check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); + check!("+00:30", [fix!(TimezoneOffset)]; offset: 1_800); + check!("-00:30", [fix!(TimezoneOffset)]; offset: -1_800); + check!("+24:00", [fix!(TimezoneOffset)]; offset: 86_400); + check!("-24:00", [fix!(TimezoneOffset)]; offset: -86_400); + check!("βˆ’24:00", [fix!(TimezoneOffset)]; offset: -86_400); // MINUS SIGN (U+2212) + check!("+99:59", [fix!(TimezoneOffset)]; offset: 359_940); + check!("-99:59", [fix!(TimezoneOffset)]; offset: -359_940); + check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); + check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); + check!("#12:34", [fix!(TimezoneOffset)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12 34 ", [fix!(TimezoneOffset)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!(" βˆ’12:34", [fix!(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("12:34 ", [fix!(TimezoneOffset)]; INVALID); + check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); + check!(" 12:34", [fix!(TimezoneOffset)]; INVALID); + check!("", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffset), lit!(":")]; offset: 45_240); + check!("Z12:34", [fix!(TimezoneOffset)]; INVALID); + check!("X12:34", [fix!(TimezoneOffset)]; INVALID); + check!("Z+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("X+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("Xβˆ’12:34", [fix!(TimezoneOffset)]; INVALID); // MINUS SIGN (U+2212) + check!("🀠+12:34", [fix!(TimezoneOffset)]; INVALID); + check!("+12:34🀠", [fix!(TimezoneOffset)]; TOO_LONG); + check!("+12:🀠34", [fix!(TimezoneOffset)]; INVALID); + check!("+1234🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: 45_240); + check!("-1234🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); + check!("βˆ’1234🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: 45_240); + check!("-12:34🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); + check!("βˆ’12:34🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); // MINUS SIGN (U+2212) + check!("🀠+12:34", [lit!("🀠"), fix!(TimezoneOffset)]; offset: 45_240); + check!("Z", [fix!(TimezoneOffset)]; INVALID); + check!("A", [fix!(TimezoneOffset)]; INVALID); + check!("PST", [fix!(TimezoneOffset)]; INVALID); + check!("#Z", [fix!(TimezoneOffset)]; INVALID); + check!(":Z", [fix!(TimezoneOffset)]; INVALID); + check!("+Z", [fix!(TimezoneOffset)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffset)]; INVALID); + check!("+Z:", [fix!(TimezoneOffset)]; INVALID); + check!("z", [fix!(TimezoneOffset)]; INVALID); + check!(" :Z", [fix!(TimezoneOffset)]; INVALID); + check!(" Z", [fix!(TimezoneOffset)]; INVALID); + check!(" z", [fix!(TimezoneOffset)]; INVALID); + + // TimezoneOffsetColon + check!("1", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12", [fix!(TimezoneOffsetColon)]; INVALID); + check!("123", [fix!(TimezoneOffsetColon)]; INVALID); + check!("1234", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12345", [fix!(TimezoneOffsetColon)]; INVALID); + check!("123456", [fix!(TimezoneOffsetColon)]; INVALID); + check!("1234567", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12345678", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+1", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("-1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("βˆ’1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("1:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:3", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:5", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34:56", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+1:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12:", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("βˆ’12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:7", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:78", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:3456", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+1234:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("βˆ’12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("βˆ’12 : 34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("-12 : 34", [fix!(TimezoneOffsetColon)]; offset: -45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12:::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("#1234", [fix!(TimezoneOffsetColon)]; INVALID); + check!("#12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("\t\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); + check!("12:34 ", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" 12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!(":", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffsetColon), lit!(":")]; offset: 45_240); + check!("Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("A", [fix!(TimezoneOffsetColon)]; INVALID); + check!("PST", [fix!(TimezoneOffsetColon)]; INVALID); + check!("#Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(":Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+Z", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+Z:", [fix!(TimezoneOffsetColon)]; INVALID); + check!("z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" :Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" Z", [fix!(TimezoneOffsetColon)]; INVALID); + check!(" z", [fix!(TimezoneOffsetColon)]; INVALID); + // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` + // and `TimezoneOffsetTripleColon` for function `parse_internal`. + // No need for separate tests for `TimezoneOffsetDoubleColon` and + // `TimezoneOffsetTripleColon`. + + // TimezoneOffsetZ + check!("1", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12", [fix!(TimezoneOffsetZ)]; INVALID); + check!("123", [fix!(TimezoneOffsetZ)]; INVALID); + check!("1234", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12345", [fix!(TimezoneOffsetZ)]; INVALID); + check!("123456", [fix!(TimezoneOffsetZ)]; INVALID); + check!("1234567", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12345678", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+1", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+123", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+1234", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("-1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); + check!("βˆ’1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+123456", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+1234567", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12345678", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("1:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:3", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:5", [fix!(TimezoneOffsetZ)]; INVALID); + check!("12:34:56", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+1:", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+12:", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:3", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("-12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); + check!("βˆ’12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:5", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:7", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:78", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12::34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12:3456", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+1234:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12: 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 :34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("12:34 ", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" 12:34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!(" +12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); + check!("+12345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fix!(TimezoneOffsetZ), lit!(":")]; offset: 45_240); + check!("Z12:34", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("X12:34", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!(" Z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!(" z", [fix!(TimezoneOffsetZ)]; offset: 0); + check!("\u{0363}Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Z ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!("A", [fix!(TimezoneOffsetZ)]; INVALID); + check!("PST", [fix!(TimezoneOffsetZ)]; INVALID); + check!("#Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(":Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(":z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("-Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+A", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+πŸ™ƒ", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+Z:", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" :Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" +Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!(" -Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); + check!("+:Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); + check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); + check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); + check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); + check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); + // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` + // in function `parse_internal`. + // No need for separate tests for `TimezoneOffsetColonZ`. + + // TimezoneOffsetPermissive + check!("1", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("123", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("1234", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12345", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("123456", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("1234567", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12345678", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+1", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); + check!("+123", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("-1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("βˆ’1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+123456", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+1234567", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12345678", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:3", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); + check!("+12:3", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("-12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!("βˆ’12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:7", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:78", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12::::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(" 12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!(" +12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!(" -12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); + check!(" βˆ’12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); + check!("+12:345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive), lit!(":")]; offset: 45_240); + check!("🀠+12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34🀠", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:🀠34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34🀠", [internal_fix!(TimezoneOffsetPermissive), lit!("🀠")]; offset: 45_240); + check!("🀠+12:34", [lit!("🀠"), internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); + check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!("A", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!(" Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!(" z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); + check!("Z ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); + check!("#Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(":Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(":z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("-Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+A", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+πŸ™ƒ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("+Z:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(" :Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!(" +Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!(" -Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+:Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("Y", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + + // TimezoneName + check!("CEST", [fix!(TimezoneName)]; ); + check!("cest", [fix!(TimezoneName)]; ); // lowercase + check!("XXXXXXXX", [fix!(TimezoneName)]; ); // not a real timezone name + check!("!!!!", [fix!(TimezoneName)]; ); // not a real timezone name! + check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); + check!("CEST ", [fix!(TimezoneName)]; TOO_LONG); + check!(" CEST", [fix!(TimezoneName)]; TOO_LONG); + check!("CE ST", [fix!(TimezoneName)]; TOO_LONG); + + // some practical examples + check!("2015-02-04T14:37:05+09:00", + [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), + num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, offset: 32400); + check!("2015-02-04T14:37:05-09:00", + [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), + num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, offset: -32400); + check!("2015-02-04T14:37:05βˆ’09:00", // timezone offset using MINUS SIGN (U+2212) + [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), + num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, offset: -32400); + check!("20150204143705567", + [num!(Year), num!(Month), num!(Day), + num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, nanosecond: 567000000); + check!("Mon, 10 Jun 2013 09:32:37 GMT", + [fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "), + fix!(ShortMonthName), sp!(" "), num!(Year), sp!(" "), num!(Hour), lit!(":"), + num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT")]; + year: 2013, month: 6, day: 10, weekday: Weekday::Mon, + hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); + check!("Sun Aug 02 13:39:15 CEST 2020", + [fix!(ShortWeekdayName), sp!(" "), fix!(ShortMonthName), sp!(" "), + num!(Day), sp!(" "), num!(Hour), lit!(":"), num!(Minute), lit!(":"), + num!(Second), sp!(" "), fix!(TimezoneName), sp!(" "), num!(Year)]; + year: 2020, month: 8, day: 2, weekday: Weekday::Sun, + hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15); + check!("20060102150405", + [num!(Year), num!(Month), num!(Day), num!(Hour), num!(Minute), num!(Second)]; + year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5); + check!("3:14PM", + [num!(Hour12), lit!(":"), num!(Minute), fix!(LowerAmPm)]; + hour_div_12: 1, hour_mod_12: 3, minute: 14); + check!("12345678901234.56789", + [num!(Timestamp), lit!("."), num!(Nanosecond)]; + nanosecond: 56_789, timestamp: 12_345_678_901_234); + check!("12345678901234.56789", + [num!(Timestamp), fix!(Nanosecond)]; + nanosecond: 567_890_000, timestamp: 12_345_678_901_234); } - // empty string - check!("", []; ); - check!(" ", []; TOO_LONG); - check!("a", []; TOO_LONG); - - // whitespaces - check!("", [sp!("")]; ); - check!(" ", [sp!("")]; ); - check!("\t", [sp!("")]; ); - check!(" \n\r \n", [sp!("")]; ); - check!("a", [sp!("")]; TOO_LONG); - - // literal - check!("", [lit!("a")]; TOO_SHORT); - check!(" ", [lit!("a")]; INVALID); - check!("a", [lit!("a")]; ); - check!("+", [lit!("+")]; ); - check!("-", [lit!("-")]; ); - check!("βˆ’", [lit!("βˆ’")]; ); // MINUS SIGN (U+2212) - // a Literal may contain whitespace and match whitespace, but this should not be done - check!(" ", [lit!(" ")]; ); - check!("aa", [lit!("a")]; TOO_LONG); - check!("A", [lit!("a")]; INVALID); - check!("xy", [lit!("xy")]; ); - check!("xy", [lit!("x"), lit!("y")]; ); - check!("1", [lit!("1")]; ); - check!("1234", [lit!("1234")]; ); - check!("+1234", [lit!("+1234")]; ); - check!("-1234", [lit!("-1234")]; ); - check!("βˆ’1234", [lit!("βˆ’1234")]; ); // MINUS SIGN (U+2212) - check!("PST", [lit!("PST")]; ); - check!("🀠", [lit!("🀠")]; ); - check!("🀠a", [lit!("🀠"), lit!("a")]; ); - check!("🀠a🀠", [lit!("🀠"), lit!("a🀠")]; ); - check!("a🀠b", [lit!("a"), lit!("🀠"), lit!("b")]; ); - // literals can be together - check!("xy", [lit!("xy")]; ); - check!("xyz", [lit!("xyz")]; ); - // or literals can be apart - check!("xy", [lit!("x"), lit!("y")]; ); - check!("xyz", [lit!("x"), lit!("yz")]; ); - check!("xyz", [lit!("xy"), lit!("z")]; ); - check!("xyz", [lit!("x"), lit!("y"), lit!("z")]; ); - // - check!("x y", [lit!("x"), lit!("y")]; INVALID); - check!("xy", [lit!("x"), sp!(""), lit!("y")]; ); - check!("x y", [lit!("x"), sp!(""), lit!("y")]; ); - - // numeric - check!("1987", [num!(Year)]; year: 1987); - check!("1987 ", [num!(Year)]; TOO_LONG); - check!("0x12", [num!(Year)]; TOO_LONG); // `0` is parsed - check!("x123", [num!(Year)]; INVALID); - check!("2015", [num!(Year)]; year: 2015); - check!("0000", [num!(Year)]; year: 0); - check!("9999", [num!(Year)]; year: 9999); - check!(" \t987", [num!(Year)]; year: 987); - check!("5", [num!(Year)]; year: 5); - check!("5\0", [num!(Year)]; TOO_LONG); - check!("\x005", [num!(Year)]; INVALID); - check!("", [num!(Year)]; TOO_SHORT); - check!("12345", [num!(Year), lit!("5")]; year: 1234); - check!("12345", [nums!(Year), lit!("5")]; year: 1234); - check!("12345", [num0!(Year), lit!("5")]; year: 1234); - check!("12341234", [num!(Year), num!(Year)]; year: 1234); - check!("1234 1234", [num!(Year), num!(Year)]; year: 1234); - check!("1234 1235", [num!(Year), num!(Year)]; IMPOSSIBLE); - check!("1234 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); - check!("1234x1234", [num!(Year), lit!("x"), num!(Year)]; year: 1234); - check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); - check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); - - // signed numeric - check!("-42", [num!(Year)]; year: -42); - check!("+42", [num!(Year)]; year: 42); - check!("-0042", [num!(Year)]; year: -42); - check!("+0042", [num!(Year)]; year: 42); - check!("-42195", [num!(Year)]; year: -42195); - check!("βˆ’42195", [num!(Year)]; INVALID); // MINUS SIGN (U+2212) - check!("+42195", [num!(Year)]; year: 42195); - check!(" -42195", [num!(Year)]; year: -42195); - check!(" +42195", [num!(Year)]; year: 42195); - check!(" - 42", [num!(Year)]; INVALID); - check!(" + 42", [num!(Year)]; INVALID); - check!(" -42195", [sp!(" "), num!(Year)]; year: -42195); - check!(" βˆ’42195", [sp!(" "), num!(Year)]; INVALID); // MINUS SIGN (U+2212) - check!(" +42195", [sp!(" "), num!(Year)]; year: 42195); - check!(" - 42", [sp!(" "), num!(Year)]; INVALID); - check!(" + 42", [sp!(" "), num!(Year)]; INVALID); - check!("-", [num!(Year)]; TOO_SHORT); - check!("+", [num!(Year)]; TOO_SHORT); - - // unsigned numeric - check!("345", [num!(Ordinal)]; ordinal: 345); - check!("+345", [num!(Ordinal)]; INVALID); - check!("-345", [num!(Ordinal)]; INVALID); - check!(" 345", [num!(Ordinal)]; ordinal: 345); - check!("βˆ’345", [num!(Ordinal)]; INVALID); // MINUS SIGN (U+2212) - check!("345 ", [num!(Ordinal)]; TOO_LONG); - check!(" 345", [sp!(" "), num!(Ordinal)]; ordinal: 345); - check!("345 ", [num!(Ordinal), sp!(" ")]; ordinal: 345); - check!("345🀠 ", [num!(Ordinal), lit!("🀠"), sp!(" ")]; ordinal: 345); - check!("345🀠", [num!(Ordinal)]; TOO_LONG); - check!("\u{0363}345", [num!(Ordinal)]; INVALID); - check!(" +345", [num!(Ordinal)]; INVALID); - check!(" -345", [num!(Ordinal)]; INVALID); - - // various numeric fields - check!("1234 5678", - [num!(Year), num!(IsoYear)]; - year: 1234, isoyear: 5678); - check!("12 34 56 78", - [num!(YearDiv100), num!(YearMod100), num!(IsoYearDiv100), num!(IsoYearMod100)]; - year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78); - check!("1 2 3 4 5 6", - [num!(Month), num!(Day), num!(WeekFromSun), num!(WeekFromMon), num!(IsoWeek), - num!(NumDaysFromSun)]; - month: 1, day: 2, week_from_sun: 3, week_from_mon: 4, isoweek: 5, weekday: Weekday::Sat); - check!("7 89 01", - [num!(WeekdayFromMon), num!(Ordinal), num!(Hour12)]; - weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1); - check!("23 45 6 78901234 567890123", - [num!(Hour), num!(Minute), num!(Second), num!(Nanosecond), num!(Timestamp)]; - hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, - timestamp: 567_890_123); - - // fixed: month and weekday names - check!("apr", [fix!(ShortMonthName)]; month: 4); - check!("Apr", [fix!(ShortMonthName)]; month: 4); - check!("APR", [fix!(ShortMonthName)]; month: 4); - check!("ApR", [fix!(ShortMonthName)]; month: 4); - check!("April", [fix!(ShortMonthName)]; TOO_LONG); // `Apr` is parsed - check!("A", [fix!(ShortMonthName)]; TOO_SHORT); - check!("Sol", [fix!(ShortMonthName)]; INVALID); - check!("Apr", [fix!(LongMonthName)]; month: 4); - check!("Apri", [fix!(LongMonthName)]; TOO_LONG); // `Apr` is parsed - check!("April", [fix!(LongMonthName)]; month: 4); - check!("Aprill", [fix!(LongMonthName)]; TOO_LONG); - check!("Aprill", [fix!(LongMonthName), lit!("l")]; month: 4); - check!("Aprl", [fix!(LongMonthName), lit!("l")]; month: 4); - check!("April", [fix!(LongMonthName), lit!("il")]; TOO_SHORT); // do not backtrack - check!("thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("THU", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("tHu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thursday", [fix!(ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("T", [fix!(ShortWeekdayName)]; TOO_SHORT); - check!("The", [fix!(ShortWeekdayName)]; INVALID); - check!("Nop", [fix!(ShortWeekdayName)]; INVALID); - check!("Thu", [fix!(LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thur", [fix!(LongWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("Thurs", [fix!(LongWeekdayName)]; TOO_LONG); // ditto - check!("Thursday", [fix!(LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thursdays", [fix!(LongWeekdayName)]; TOO_LONG); - check!("Thursdays", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu); - check!("Thus", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu); - check!("Thursday", [fix!(LongWeekdayName), lit!("rsday")]; TOO_SHORT); // do not backtrack - - // fixed: am/pm - check!("am", [fix!(LowerAmPm)]; hour_div_12: 0); - check!("pm", [fix!(LowerAmPm)]; hour_div_12: 1); - check!("AM", [fix!(LowerAmPm)]; hour_div_12: 0); - check!("PM", [fix!(LowerAmPm)]; hour_div_12: 1); - check!("am", [fix!(UpperAmPm)]; hour_div_12: 0); - check!("pm", [fix!(UpperAmPm)]; hour_div_12: 1); - check!("AM", [fix!(UpperAmPm)]; hour_div_12: 0); - check!("PM", [fix!(UpperAmPm)]; hour_div_12: 1); - check!("Am", [fix!(LowerAmPm)]; hour_div_12: 0); - check!(" Am", [fix!(LowerAmPm)]; INVALID); - check!("ame", [fix!(LowerAmPm)]; TOO_LONG); // `am` is parsed - check!("a", [fix!(LowerAmPm)]; TOO_SHORT); - check!("p", [fix!(LowerAmPm)]; TOO_SHORT); - check!("x", [fix!(LowerAmPm)]; TOO_SHORT); - check!("xx", [fix!(LowerAmPm)]; INVALID); - check!("", [fix!(LowerAmPm)]; TOO_SHORT); - - // fixed: dot plus nanoseconds - check!("", [fix!(Nanosecond)]; ); // no field set, but not an error - check!(".", [fix!(Nanosecond)]; TOO_SHORT); - check!("4", [fix!(Nanosecond)]; TOO_LONG); // never consumes `4` - check!("4", [fix!(Nanosecond), num!(Second)]; second: 4); - check!(".0", [fix!(Nanosecond)]; nanosecond: 0); - check!(".4", [fix!(Nanosecond)]; nanosecond: 400_000_000); - check!(".42", [fix!(Nanosecond)]; nanosecond: 420_000_000); - check!(".421", [fix!(Nanosecond)]; nanosecond: 421_000_000); - check!(".42195", [fix!(Nanosecond)]; nanosecond: 421_950_000); - check!(".421950803", [fix!(Nanosecond)]; nanosecond: 421_950_803); - check!(".421950803547", [fix!(Nanosecond)]; nanosecond: 421_950_803); - check!(".000000003547", [fix!(Nanosecond)]; nanosecond: 3); - check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0); - check!(".", [fix!(Nanosecond)]; TOO_SHORT); - check!(".4x", [fix!(Nanosecond)]; TOO_LONG); - check!(". 4", [fix!(Nanosecond)]; INVALID); - check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming - - // fixed: nanoseconds without the dot - check!("", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!(".", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("0", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("421", [internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); - check!("42143", [internal_fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43); - check!("42195", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); - check!("4x", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!(" 4", [internal_fix!(Nanosecond3NoDot)]; INVALID); - check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID); - - check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!(".", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("42195", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("421950", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); - check!("000003", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 3000); - check!("000000", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 0); - check!("4x", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!(" 4", [internal_fix!(Nanosecond6NoDot)]; INVALID); - check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID); - - check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!(".", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); - check!("000000003", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 3); - check!("42195080354", [internal_fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 - check!("421950803547", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG); - check!("000000000", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 0); - check!("00000000x", [internal_fix!(Nanosecond9NoDot)]; INVALID); - check!(" 4", [internal_fix!(Nanosecond9NoDot)]; INVALID); - check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID); - - // fixed: timezone offsets - - // TimezoneOffset - check!("1", [fix!(TimezoneOffset)]; INVALID); - check!("12", [fix!(TimezoneOffset)]; INVALID); - check!("123", [fix!(TimezoneOffset)]; INVALID); - check!("1234", [fix!(TimezoneOffset)]; INVALID); - check!("12345", [fix!(TimezoneOffset)]; INVALID); - check!("123456", [fix!(TimezoneOffset)]; INVALID); - check!("1234567", [fix!(TimezoneOffset)]; INVALID); - check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+123456", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+1234567", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12345678", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12:3", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12:34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("-12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("βˆ’12:34", [fix!(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:34:5", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:34:56:", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("12:34", [fix!(TimezoneOffset)]; INVALID); - check!("12:34:56", [fix!(TimezoneOffset)]; INVALID); - check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12: :34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12:::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12::::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:3456", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+1234:56", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+1234:567", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("βˆ’00:00", [fix!(TimezoneOffset)]; offset: 0); // MINUS SIGN (U+2212) - check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); - check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); - check!("+00:30", [fix!(TimezoneOffset)]; offset: 1_800); - check!("-00:30", [fix!(TimezoneOffset)]; offset: -1_800); - check!("+24:00", [fix!(TimezoneOffset)]; offset: 86_400); - check!("-24:00", [fix!(TimezoneOffset)]; offset: -86_400); - check!("βˆ’24:00", [fix!(TimezoneOffset)]; offset: -86_400); // MINUS SIGN (U+2212) - check!("+99:59", [fix!(TimezoneOffset)]; offset: 359_940); - check!("-99:59", [fix!(TimezoneOffset)]; offset: -359_940); - check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("#12:34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12 34 ", [fix!(TimezoneOffset)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!(" βˆ’12:34", [fix!(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("12:34 ", [fix!(TimezoneOffset)]; INVALID); - check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!(" 12:34", [fix!(TimezoneOffset)]; INVALID); - check!("", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fix!(TimezoneOffset), lit!(":")]; offset: 45_240); - check!("Z12:34", [fix!(TimezoneOffset)]; INVALID); - check!("X12:34", [fix!(TimezoneOffset)]; INVALID); - check!("Z+12:34", [fix!(TimezoneOffset)]; INVALID); - check!("X+12:34", [fix!(TimezoneOffset)]; INVALID); - check!("Xβˆ’12:34", [fix!(TimezoneOffset)]; INVALID); // MINUS SIGN (U+2212) - check!("🀠+12:34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34🀠", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:🀠34", [fix!(TimezoneOffset)]; INVALID); - check!("+1234🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: 45_240); - check!("-1234🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); - check!("βˆ’1234🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: 45_240); - check!("-12:34🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); - check!("βˆ’12:34🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); // MINUS SIGN (U+2212) - check!("🀠+12:34", [lit!("🀠"), fix!(TimezoneOffset)]; offset: 45_240); - check!("Z", [fix!(TimezoneOffset)]; INVALID); - check!("A", [fix!(TimezoneOffset)]; INVALID); - check!("PST", [fix!(TimezoneOffset)]; INVALID); - check!("#Z", [fix!(TimezoneOffset)]; INVALID); - check!(":Z", [fix!(TimezoneOffset)]; INVALID); - check!("+Z", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+:Z", [fix!(TimezoneOffset)]; INVALID); - check!("+Z:", [fix!(TimezoneOffset)]; INVALID); - check!("z", [fix!(TimezoneOffset)]; INVALID); - check!(" :Z", [fix!(TimezoneOffset)]; INVALID); - check!(" Z", [fix!(TimezoneOffset)]; INVALID); - check!(" z", [fix!(TimezoneOffset)]; INVALID); - - // TimezoneOffsetColon - check!("1", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12", [fix!(TimezoneOffsetColon)]; INVALID); - check!("123", [fix!(TimezoneOffsetColon)]; INVALID); - check!("1234", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12345", [fix!(TimezoneOffsetColon)]; INVALID); - check!("123456", [fix!(TimezoneOffsetColon)]; INVALID); - check!("1234567", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12345678", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+1", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("-1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); - check!("βˆ’1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+123456", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+1234567", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12345678", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("1:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:3", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34:5", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34:56", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+1:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12:", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:3", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("-12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); - check!("βˆ’12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:5", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:7", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:78", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:3456", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+1234:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("βˆ’12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("βˆ’12 : 34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("-12 : 34", [fix!(TimezoneOffsetColon)]; offset: -45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12:::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("#1234", [fix!(TimezoneOffsetColon)]; INVALID); - check!("#12:34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("\t\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("12:34 ", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" 12:34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!(":", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fix!(TimezoneOffsetColon), lit!(":")]; offset: 45_240); - check!("Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!("A", [fix!(TimezoneOffsetColon)]; INVALID); - check!("PST", [fix!(TimezoneOffsetColon)]; INVALID); - check!("#Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(":Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+Z", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+:Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+Z:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" :Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" z", [fix!(TimezoneOffsetColon)]; INVALID); - // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` - // and `TimezoneOffsetTripleColon` for function `parse_internal`. - // No need for separate tests for `TimezoneOffsetDoubleColon` and - // `TimezoneOffsetTripleColon`. - - // TimezoneOffsetZ - check!("1", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12", [fix!(TimezoneOffsetZ)]; INVALID); - check!("123", [fix!(TimezoneOffsetZ)]; INVALID); - check!("1234", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12345", [fix!(TimezoneOffsetZ)]; INVALID); - check!("123456", [fix!(TimezoneOffsetZ)]; INVALID); - check!("1234567", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12345678", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+1", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("-1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); - check!("βˆ’1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+123456", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+1234567", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12345678", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("1:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:3", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34:5", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34:56", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+1:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12:", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:3", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("-12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); - check!("βˆ’12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:5", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:7", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:78", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12::34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12:3456", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+1234:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12: 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 :34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("12:34 ", [fix!(TimezoneOffsetZ)]; INVALID); - check!(" 12:34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fix!(TimezoneOffsetZ), lit!(":")]; offset: 45_240); - check!("Z12:34", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("X12:34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!(" Z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!(" z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("\u{0363}Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Z ", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("A", [fix!(TimezoneOffsetZ)]; INVALID); - check!("PST", [fix!(TimezoneOffsetZ)]; INVALID); - check!("#Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!(":Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!(":z", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("-Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+A", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+πŸ™ƒ", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+Z:", [fix!(TimezoneOffsetZ)]; INVALID); - check!(" :Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!(" +Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!(" -Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+:Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); - check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); - // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` - // in function `parse_internal`. - // No need for separate tests for `TimezoneOffsetColonZ`. - - // TimezoneOffsetPermissive - check!("1", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("123", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("1234", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12345", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("123456", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("1234567", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12345678", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+1", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); - check!("+123", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("-1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); - check!("βˆ’1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+123456", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+1234567", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12345678", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:3", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); - check!("+12:3", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("-12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); - check!("βˆ’12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:7", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:78", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12::::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(" 12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!(" +12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!(" -12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); - check!(" βˆ’12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive), lit!(":")]; offset: 45_240); - check!("🀠+12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34🀠", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:🀠34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34🀠", [internal_fix!(TimezoneOffsetPermissive), lit!("🀠")]; offset: 45_240); - check!("🀠+12:34", [lit!("🀠"), internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("A", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!(" Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!(" z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("Z ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("#Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(":Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(":z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("-Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+A", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+πŸ™ƒ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+Z:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(" :Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(" +Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!(" -Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+:Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("Y", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - - // TimezoneName - check!("CEST", [fix!(TimezoneName)]; ); - check!("cest", [fix!(TimezoneName)]; ); // lowercase - check!("XXXXXXXX", [fix!(TimezoneName)]; ); // not a real timezone name - check!("!!!!", [fix!(TimezoneName)]; ); // not a real timezone name! - check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); - check!("CEST ", [fix!(TimezoneName)]; TOO_LONG); - check!(" CEST", [fix!(TimezoneName)]; TOO_LONG); - check!("CE ST", [fix!(TimezoneName)]; TOO_LONG); - - // some practical examples - check!("2015-02-04T14:37:05+09:00", - [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), - num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, offset: 32400); - check!("2015-02-04T14:37:05-09:00", - [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), - num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, offset: -32400); - check!("2015-02-04T14:37:05βˆ’09:00", // timezone offset using MINUS SIGN (U+2212) - [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), - num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, offset: -32400); - check!("20150204143705567", - [num!(Year), num!(Month), num!(Day), - num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, nanosecond: 567000000); - check!("Mon, 10 Jun 2013 09:32:37 GMT", - [fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "), - fix!(ShortMonthName), sp!(" "), num!(Year), sp!(" "), num!(Hour), lit!(":"), - num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT")]; - year: 2013, month: 6, day: 10, weekday: Weekday::Mon, - hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); - check!("Sun Aug 02 13:39:15 CEST 2020", - [fix!(ShortWeekdayName), sp!(" "), fix!(ShortMonthName), sp!(" "), - num!(Day), sp!(" "), num!(Hour), lit!(":"), num!(Minute), lit!(":"), - num!(Second), sp!(" "), fix!(TimezoneName), sp!(" "), num!(Year)]; - year: 2020, month: 8, day: 2, weekday: Weekday::Sun, - hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15); - check!("20060102150405", - [num!(Year), num!(Month), num!(Day), num!(Hour), num!(Minute), num!(Second)]; - year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5); - check!("3:14PM", - [num!(Hour12), lit!(":"), num!(Minute), fix!(LowerAmPm)]; - hour_div_12: 1, hour_mod_12: 3, minute: 14); - check!("12345678901234.56789", - [num!(Timestamp), lit!("."), num!(Nanosecond)]; - nanosecond: 56_789, timestamp: 12_345_678_901_234); - check!("12345678901234.56789", - [num!(Timestamp), fix!(Nanosecond)]; - nanosecond: 567_890_000, timestamp: 12_345_678_901_234); -} + #[test] + fn test_rfc2822() { + // Test data - (input, Ok(expected result after parse and format) or Err(error code)) + let testdates = [ + ("Tue, 20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // normal case + ("Fri, 2 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // folding whitespace + ("Fri, 02 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // leading zero + ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // trailing comment + ( + r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))", + Ok("Tue, 20 Jan 2015 17:35:20 -0800"), + ), // complex trailing comment + (r"Tue, 20 Jan 2015 17:35:20 -0800 (UTC\)", Err(TOO_LONG)), // incorrect comment, not enough closing parentheses + ( + "Tue, 20 Jan 2015 17:35:20 -0800 (UTC)\t \r\n(Anothercomment)", + Ok("Tue, 20 Jan 2015 17:35:20 -0800"), + ), // multiple comments + ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC) ", Err(TOO_LONG)), // trailing whitespace after comment + ("20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // no day of week + ("20 JAN 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // upper case month + ("Tue, 20 Jan 2015 17:35 -0800", Ok("Tue, 20 Jan 2015 17:35:00 -0800")), // no second + ("11 Sep 2001 09:45:00 +0000", Ok("Tue, 11 Sep 2001 09:45:00 +0000")), + ("11 Sep 2001 09:45:00 EST", Ok("Tue, 11 Sep 2001 09:45:00 -0500")), + ("11 Sep 2001 09:45:00 GMT", Ok("Tue, 11 Sep 2001 09:45:00 +0000")), + ("30 Feb 2015 17:35:20 -0800", Err(OUT_OF_RANGE)), // bad day of month + ("Tue, 20 Jan 2015", Err(TOO_SHORT)), // omitted fields + ("Tue, 20 Avr 2015 17:35:20 -0800", Err(INVALID)), // bad month name + ("Tue, 20 Jan 2015 25:35:20 -0800", Err(OUT_OF_RANGE)), // bad hour + ("Tue, 20 Jan 2015 7:35:20 -0800", Err(INVALID)), // bad # of digits in hour + ("Tue, 20 Jan 2015 17:65:20 -0800", Err(OUT_OF_RANGE)), // bad minute + ("Tue, 20 Jan 2015 17:35:90 -0800", Err(OUT_OF_RANGE)), // bad second + ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset + ("6 Jun 1944 04:00:00Z", Err(INVALID)), // bad offset (zulu not allowed) + ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named time zone + // named timezones that have specific timezone offsets + // see https://www.rfc-editor.org/rfc/rfc2822#section-4.3 + ("Tue, 20 Jan 2015 17:35:20 GMT", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 UT", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 ut", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 EDT", Ok("Tue, 20 Jan 2015 17:35:20 -0400")), + ("Tue, 20 Jan 2015 17:35:20 EST", Ok("Tue, 20 Jan 2015 17:35:20 -0500")), + ("Tue, 20 Jan 2015 17:35:20 CDT", Ok("Tue, 20 Jan 2015 17:35:20 -0500")), + ("Tue, 20 Jan 2015 17:35:20 CST", Ok("Tue, 20 Jan 2015 17:35:20 -0600")), + ("Tue, 20 Jan 2015 17:35:20 MDT", Ok("Tue, 20 Jan 2015 17:35:20 -0600")), + ("Tue, 20 Jan 2015 17:35:20 MST", Ok("Tue, 20 Jan 2015 17:35:20 -0700")), + ("Tue, 20 Jan 2015 17:35:20 PDT", Ok("Tue, 20 Jan 2015 17:35:20 -0700")), + ("Tue, 20 Jan 2015 17:35:20 PST", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), + ("Tue, 20 Jan 2015 17:35:20 pst", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), + // named single-letter military timezones must fallback to +0000 + ("Tue, 20 Jan 2015 17:35:20 Z", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 A", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 a", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 K", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 k", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + // named single-letter timezone "J" is specifically not valid + ("Tue, 20 Jan 2015 17:35:20 J", Err(NOT_ENOUGH)), + ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset minutes + ("Tue, 20 Jan 2015 17:35:20Z", Err(INVALID)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 Zulu", Err(NOT_ENOUGH)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 ZULU", Err(NOT_ENOUGH)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 βˆ’0800", Err(INVALID)), // bad offset: timezone offset using MINUS SIGN (U+2212), not specified for RFC 2822 + ("Tue, 20 Jan 2015 17:35:20 0800", Err(INVALID)), // missing offset sign + ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named timezone + ("Tue, 20 Jan 2015😈17:35:20 -0800", Err(INVALID)), // bad character! + ]; -#[cfg(test)] -#[test] -fn test_rfc2822() { - use super::NOT_ENOUGH; - use super::*; - use crate::offset::FixedOffset; - use crate::DateTime; - - // Test data - (input, Ok(expected result after parse and format) or Err(error code)) - let testdates = [ - ("Tue, 20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // normal case - ("Fri, 2 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // folding whitespace - ("Fri, 02 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // leading zero - ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // trailing comment - ( - r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))", - Ok("Tue, 20 Jan 2015 17:35:20 -0800"), - ), // complex trailing comment - (r"Tue, 20 Jan 2015 17:35:20 -0800 (UTC\)", Err(TOO_LONG)), // incorrect comment, not enough closing parentheses - ( - "Tue, 20 Jan 2015 17:35:20 -0800 (UTC)\t \r\n(Anothercomment)", - Ok("Tue, 20 Jan 2015 17:35:20 -0800"), - ), // multiple comments - ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC) ", Err(TOO_LONG)), // trailing whitespace after comment - ("20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // no day of week - ("20 JAN 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // upper case month - ("Tue, 20 Jan 2015 17:35 -0800", Ok("Tue, 20 Jan 2015 17:35:00 -0800")), // no second - ("11 Sep 2001 09:45:00 +0000", Ok("Tue, 11 Sep 2001 09:45:00 +0000")), - ("11 Sep 2001 09:45:00 EST", Ok("Tue, 11 Sep 2001 09:45:00 -0500")), - ("11 Sep 2001 09:45:00 GMT", Ok("Tue, 11 Sep 2001 09:45:00 +0000")), - ("30 Feb 2015 17:35:20 -0800", Err(OUT_OF_RANGE)), // bad day of month - ("Tue, 20 Jan 2015", Err(TOO_SHORT)), // omitted fields - ("Tue, 20 Avr 2015 17:35:20 -0800", Err(INVALID)), // bad month name - ("Tue, 20 Jan 2015 25:35:20 -0800", Err(OUT_OF_RANGE)), // bad hour - ("Tue, 20 Jan 2015 7:35:20 -0800", Err(INVALID)), // bad # of digits in hour - ("Tue, 20 Jan 2015 17:65:20 -0800", Err(OUT_OF_RANGE)), // bad minute - ("Tue, 20 Jan 2015 17:35:90 -0800", Err(OUT_OF_RANGE)), // bad second - ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset - ("6 Jun 1944 04:00:00Z", Err(INVALID)), // bad offset (zulu not allowed) - ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named time zone - // named timezones that have specific timezone offsets - // see https://www.rfc-editor.org/rfc/rfc2822#section-4.3 - ("Tue, 20 Jan 2015 17:35:20 GMT", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 UT", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 ut", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 EDT", Ok("Tue, 20 Jan 2015 17:35:20 -0400")), - ("Tue, 20 Jan 2015 17:35:20 EST", Ok("Tue, 20 Jan 2015 17:35:20 -0500")), - ("Tue, 20 Jan 2015 17:35:20 CDT", Ok("Tue, 20 Jan 2015 17:35:20 -0500")), - ("Tue, 20 Jan 2015 17:35:20 CST", Ok("Tue, 20 Jan 2015 17:35:20 -0600")), - ("Tue, 20 Jan 2015 17:35:20 MDT", Ok("Tue, 20 Jan 2015 17:35:20 -0600")), - ("Tue, 20 Jan 2015 17:35:20 MST", Ok("Tue, 20 Jan 2015 17:35:20 -0700")), - ("Tue, 20 Jan 2015 17:35:20 PDT", Ok("Tue, 20 Jan 2015 17:35:20 -0700")), - ("Tue, 20 Jan 2015 17:35:20 PST", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), - ("Tue, 20 Jan 2015 17:35:20 pst", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), - // named single-letter military timezones must fallback to +0000 - ("Tue, 20 Jan 2015 17:35:20 Z", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 A", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 a", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 K", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 k", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - // named single-letter timezone "J" is specifically not valid - ("Tue, 20 Jan 2015 17:35:20 J", Err(NOT_ENOUGH)), - ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset minutes - ("Tue, 20 Jan 2015 17:35:20Z", Err(INVALID)), // bad offset: zulu not allowed - ("Tue, 20 Jan 2015 17:35:20 Zulu", Err(NOT_ENOUGH)), // bad offset: zulu not allowed - ("Tue, 20 Jan 2015 17:35:20 ZULU", Err(NOT_ENOUGH)), // bad offset: zulu not allowed - ("Tue, 20 Jan 2015 17:35:20 βˆ’0800", Err(INVALID)), // bad offset: timezone offset using MINUS SIGN (U+2212), not specified for RFC 2822 - ("Tue, 20 Jan 2015 17:35:20 0800", Err(INVALID)), // missing offset sign - ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named timezone - ("Tue, 20 Jan 2015😈17:35:20 -0800", Err(INVALID)), // bad character! - ]; - - fn rfc2822_to_datetime(date: &str) -> ParseResult> { - let mut parsed = Parsed::new(); - parse(&mut parsed, date, [Item::Fixed(Fixed::RFC2822)].iter())?; - parsed.to_datetime() - } + fn rfc2822_to_datetime(date: &str) -> ParseResult> { + let mut parsed = Parsed::new(); + parse(&mut parsed, date, [Item::Fixed(Fixed::RFC2822)].iter())?; + parsed.to_datetime() + } - fn fmt_rfc2822_datetime(dt: DateTime) -> String { - dt.format_with_items([Item::Fixed(Fixed::RFC2822)].iter()).to_string() - } + fn fmt_rfc2822_datetime(dt: DateTime) -> String { + dt.format_with_items([Item::Fixed(Fixed::RFC2822)].iter()).to_string() + } - // Test against test data above - for &(date, checkdate) in testdates.iter() { - eprintln!("Test input: {:?}", date); - eprintln!(" Expect: {:?}", checkdate); - let d = rfc2822_to_datetime(date); // parse a date - let dt = match d { - // did we get a value? - Ok(dt) => Ok(fmt_rfc2822_datetime(dt)), // yes, go on - Err(e) => Err(e), // otherwise keep an error for the comparison - }; - if dt != checkdate.map(|s| s.to_string()) { - // check for expected result - panic!( - "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", - date, dt, checkdate - ); + // Test against test data above + for &(date, checkdate) in testdates.iter() { + eprintln!("Test input: {:?}", date); + eprintln!(" Expect: {:?}", checkdate); + let d = rfc2822_to_datetime(date); // parse a date + let dt = match d { + // did we get a value? + Ok(dt) => Ok(fmt_rfc2822_datetime(dt)), // yes, go on + Err(e) => Err(e), // otherwise keep an error for the comparison + }; + if dt != checkdate.map(|s| s.to_string()) { + // check for expected result + panic!( + "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", + date, dt, checkdate + ); + } } } -} -#[cfg(test)] -#[test] -fn parse_rfc850() { - use crate::{TimeZone, Utc}; - - static RFC850_FMT: &str = "%A, %d-%b-%y %T GMT"; - - let dt_str = "Sunday, 06-Nov-94 08:49:37 GMT"; - let dt = Utc.with_ymd_and_hms(1994, 11, 6, 8, 49, 37).unwrap(); - - // Check that the format is what we expect - assert_eq!(dt.format(RFC850_FMT).to_string(), dt_str); - - // Check that it parses correctly - assert_eq!(Ok(dt), Utc.datetime_from_str("Sunday, 06-Nov-94 08:49:37 GMT", RFC850_FMT)); - - // Check that the rest of the weekdays parse correctly (this test originally failed because - // Sunday parsed incorrectly). - let testdates = [ - (Utc.with_ymd_and_hms(1994, 11, 7, 8, 49, 37).unwrap(), "Monday, 07-Nov-94 08:49:37 GMT"), - (Utc.with_ymd_and_hms(1994, 11, 8, 8, 49, 37).unwrap(), "Tuesday, 08-Nov-94 08:49:37 GMT"), - ( - Utc.with_ymd_and_hms(1994, 11, 9, 8, 49, 37).unwrap(), - "Wednesday, 09-Nov-94 08:49:37 GMT", - ), - ( - Utc.with_ymd_and_hms(1994, 11, 10, 8, 49, 37).unwrap(), - "Thursday, 10-Nov-94 08:49:37 GMT", - ), - (Utc.with_ymd_and_hms(1994, 11, 11, 8, 49, 37).unwrap(), "Friday, 11-Nov-94 08:49:37 GMT"), - ( - Utc.with_ymd_and_hms(1994, 11, 12, 8, 49, 37).unwrap(), - "Saturday, 12-Nov-94 08:49:37 GMT", - ), - ]; - - for val in &testdates { - assert_eq!(Ok(val.0), Utc.datetime_from_str(val.1, RFC850_FMT)); - } + #[test] + fn parse_rfc850() { + static RFC850_FMT: &str = "%A, %d-%b-%y %T GMT"; + + let dt_str = "Sunday, 06-Nov-94 08:49:37 GMT"; + let dt = Utc.with_ymd_and_hms(1994, 11, 6, 8, 49, 37).unwrap(); + + // Check that the format is what we expect + assert_eq!(dt.format(RFC850_FMT).to_string(), dt_str); + + // Check that it parses correctly + assert_eq!(Ok(dt), Utc.datetime_from_str("Sunday, 06-Nov-94 08:49:37 GMT", RFC850_FMT)); + + // Check that the rest of the weekdays parse correctly (this test originally failed because + // Sunday parsed incorrectly). + let testdates = [ + ( + Utc.with_ymd_and_hms(1994, 11, 7, 8, 49, 37).unwrap(), + "Monday, 07-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 8, 8, 49, 37).unwrap(), + "Tuesday, 08-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 9, 8, 49, 37).unwrap(), + "Wednesday, 09-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 10, 8, 49, 37).unwrap(), + "Thursday, 10-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 11, 8, 49, 37).unwrap(), + "Friday, 11-Nov-94 08:49:37 GMT", + ), + ( + Utc.with_ymd_and_hms(1994, 11, 12, 8, 49, 37).unwrap(), + "Saturday, 12-Nov-94 08:49:37 GMT", + ), + ]; - let test_dates_fail = [ - "Saturday, 12-Nov-94 08:49:37", - "Saturday, 12-Nov-94 08:49:37 Z", - "Saturday, 12-Nov-94 08:49:37 GMTTTT", - "Saturday, 12-Nov-94 08:49:37 gmt", - "Saturday, 12-Nov-94 08:49:37 +08:00", - "Caturday, 12-Nov-94 08:49:37 GMT", - "Saturday, 99-Nov-94 08:49:37 GMT", - "Saturday, 12-Nov-2000 08:49:37 GMT", - "Saturday, 12-Mop-94 08:49:37 GMT", - "Saturday, 12-Nov-94 28:49:37 GMT", - "Saturday, 12-Nov-94 08:99:37 GMT", - "Saturday, 12-Nov-94 08:49:99 GMT", - ]; - - for val in &test_dates_fail { - assert!(Utc.datetime_from_str(val, RFC850_FMT).is_err()); - } -} + for val in &testdates { + assert_eq!(Ok(val.0), Utc.datetime_from_str(val.1, RFC850_FMT)); + } -#[cfg(test)] -#[test] -fn test_rfc3339() { - use super::*; - use crate::offset::FixedOffset; - use crate::DateTime; - - // Test data - (input, Ok(expected result after parse and format) or Err(error code)) - let testdates = [ - ("2015-01-20T17:35:20-08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case - ("2015-01-20T17:35:20βˆ’08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case with MINUS SIGN (U+2212) - ("1944-06-06T04:04:00Z", Ok("1944-06-06T04:04:00+00:00")), // D-day - ("2001-09-11T09:45:00-08:00", Ok("2001-09-11T09:45:00-08:00")), - ("2015-01-20T17:35:20.001-08:00", Ok("2015-01-20T17:35:20.001-08:00")), - ("2015-01-20T17:35:20.001βˆ’08:00", Ok("2015-01-20T17:35:20.001-08:00")), // with MINUS SIGN (U+2212) - ("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")), - ("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), - ("2015-01-20T17:35:20.000000004βˆ’08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), // with MINUS SIGN (U+2212) - ("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small - ("2015-01-20T17:35:20.000000000452βˆ’08:00", Ok("2015-01-20T17:35:20-08:00")), // too small with MINUS SIGN (U+2212) - ("2015-01-20 17:35:20.001-08:00", Err(INVALID)), // missing separator 'T' - ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YMD - ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HMS - ("-01-20T17:35:20-08:00", Err(INVALID)), // missing year - ("99-01-20T17:35:20-08:00", Err(INVALID)), // bad year format - ("99999-01-20T17:35:20-08:00", Err(INVALID)), // bad year value - ("-2000-01-20T17:35:20-08:00", Err(INVALID)), // bad year value - ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month value - ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour value - ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute value - ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second value - ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset value - ("15-01-20T17:35:20-08:00", Err(INVALID)), // bad year format - ("15-01-20T17:35:20-08:00:00", Err(INVALID)), // bad year format, bad offset format - ("2015-01-20T17:35:2008:00", Err(INVALID)), // missing offset sign - ("2015-01-20T17:35:20 08:00", Err(INVALID)), // missing offset sign - ("2015-01-20T17:35:20Zulu", Err(TOO_LONG)), // bad offset format - ("2015-01-20T17:35:20 Zulu", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20GMT", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20 GMT", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20+GMT", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20++08:00", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20--08:00", Err(INVALID)), // bad offset format - ("2015-01-20T17:35:20βˆ’βˆ’08:00", Err(INVALID)), // bad offset format with MINUS SIGN (U+2212) - ("2015-01-20T17:35:20Β±08:00", Err(INVALID)), // bad offset sign - ("2015-01-20T17:35:20-08-00", Err(INVALID)), // bad offset separator - ("2015-01-20T17:35:20-08;00", Err(INVALID)), // bad offset separator - ("2015-01-20T17:35:20-0800", Err(INVALID)), // bad offset separator - ("2015-01-20T17:35:20-08:0", Err(TOO_SHORT)), // bad offset minutes - ("2015-01-20T17:35:20-08:AA", Err(INVALID)), // bad offset minutes - ("2015-01-20T17:35:20-08:ZZ", Err(INVALID)), // bad offset minutes - ("2015-01-20T17:35:20.001-08 : 00", Err(INVALID)), // bad offset separator - ("2015-01-20T17:35:20-08:00:00", Err(TOO_LONG)), // bad offset format - ("2015-01-20T17:35:20+08:", Err(TOO_SHORT)), // bad offset format - ("2015-01-20T17:35:20-08:", Err(TOO_SHORT)), // bad offset format - ("2015-01-20T17:35:20βˆ’08:", Err(TOO_SHORT)), // bad offset format with MINUS SIGN (U+2212) - ("2015-01-20T17:35:20-08", Err(TOO_SHORT)), // bad offset format - ("2015-01-20T", Err(TOO_SHORT)), // missing HMS - ("2015-01-20T00:00:1", Err(TOO_SHORT)), // missing complete S - ("2015-01-20T00:00:1-08:00", Err(INVALID)), // missing complete S - ]; - - fn rfc3339_to_datetime(date: &str) -> ParseResult> { - let mut parsed = Parsed::new(); - parse(&mut parsed, date, [Item::Fixed(Fixed::RFC3339)].iter())?; - parsed.to_datetime() - } + let test_dates_fail = [ + "Saturday, 12-Nov-94 08:49:37", + "Saturday, 12-Nov-94 08:49:37 Z", + "Saturday, 12-Nov-94 08:49:37 GMTTTT", + "Saturday, 12-Nov-94 08:49:37 gmt", + "Saturday, 12-Nov-94 08:49:37 +08:00", + "Caturday, 12-Nov-94 08:49:37 GMT", + "Saturday, 99-Nov-94 08:49:37 GMT", + "Saturday, 12-Nov-2000 08:49:37 GMT", + "Saturday, 12-Mop-94 08:49:37 GMT", + "Saturday, 12-Nov-94 28:49:37 GMT", + "Saturday, 12-Nov-94 08:99:37 GMT", + "Saturday, 12-Nov-94 08:49:99 GMT", + ]; - fn fmt_rfc3339_datetime(dt: DateTime) -> String { - dt.format_with_items([Item::Fixed(Fixed::RFC3339)].iter()).to_string() + for val in &test_dates_fail { + assert!(Utc.datetime_from_str(val, RFC850_FMT).is_err()); + } } - // Test against test data above - for &(date, checkdate) in testdates.iter() { - let d = rfc3339_to_datetime(date); // parse a date - let dt = match d { - // did we get a value? - Ok(dt) => Ok(fmt_rfc3339_datetime(dt)), // yes, go on - Err(e) => Err(e), // otherwise keep an error for the comparison - }; - if dt != checkdate.map(|s| s.to_string()) { - // check for expected result - panic!( - "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", - date, dt, checkdate - ); + #[cfg(test)] + #[test] + fn test_rfc3339() { + use super::*; + use crate::offset::FixedOffset; + use crate::DateTime; + + // Test data - (input, Ok(expected result after parse and format) or Err(error code)) + let testdates = [ + ("2015-01-20T17:35:20-08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case + ("2015-01-20T17:35:20βˆ’08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case with MINUS SIGN (U+2212) + ("1944-06-06T04:04:00Z", Ok("1944-06-06T04:04:00+00:00")), // D-day + ("2001-09-11T09:45:00-08:00", Ok("2001-09-11T09:45:00-08:00")), + ("2015-01-20T17:35:20.001-08:00", Ok("2015-01-20T17:35:20.001-08:00")), + ("2015-01-20T17:35:20.001βˆ’08:00", Ok("2015-01-20T17:35:20.001-08:00")), // with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")), + ("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), + ("2015-01-20T17:35:20.000000004βˆ’08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), // with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small + ("2015-01-20T17:35:20.000000000452βˆ’08:00", Ok("2015-01-20T17:35:20-08:00")), // too small with MINUS SIGN (U+2212) + ("2015-01-20 17:35:20.001-08:00", Err(INVALID)), // missing separator 'T' + ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YMD + ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HMS + ("-01-20T17:35:20-08:00", Err(INVALID)), // missing year + ("99-01-20T17:35:20-08:00", Err(INVALID)), // bad year format + ("99999-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("-2000-01-20T17:35:20-08:00", Err(INVALID)), // bad year value + ("2015-02-30T17:35:20-08:00", Err(OUT_OF_RANGE)), // bad day of month value + ("2015-01-20T25:35:20-08:00", Err(OUT_OF_RANGE)), // bad hour value + ("2015-01-20T17:65:20-08:00", Err(OUT_OF_RANGE)), // bad minute value + ("2015-01-20T17:35:90-08:00", Err(OUT_OF_RANGE)), // bad second value + ("2015-01-20T17:35:20-24:00", Err(OUT_OF_RANGE)), // bad offset value + ("15-01-20T17:35:20-08:00", Err(INVALID)), // bad year format + ("15-01-20T17:35:20-08:00:00", Err(INVALID)), // bad year format, bad offset format + ("2015-01-20T17:35:2008:00", Err(INVALID)), // missing offset sign + ("2015-01-20T17:35:20 08:00", Err(INVALID)), // missing offset sign + ("2015-01-20T17:35:20Zulu", Err(TOO_LONG)), // bad offset format + ("2015-01-20T17:35:20 Zulu", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20 GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20+GMT", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20++08:00", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20--08:00", Err(INVALID)), // bad offset format + ("2015-01-20T17:35:20βˆ’βˆ’08:00", Err(INVALID)), // bad offset format with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20Β±08:00", Err(INVALID)), // bad offset sign + ("2015-01-20T17:35:20-08-00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08;00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-0800", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08:0", Err(TOO_SHORT)), // bad offset minutes + ("2015-01-20T17:35:20-08:AA", Err(INVALID)), // bad offset minutes + ("2015-01-20T17:35:20-08:ZZ", Err(INVALID)), // bad offset minutes + ("2015-01-20T17:35:20.001-08 : 00", Err(INVALID)), // bad offset separator + ("2015-01-20T17:35:20-08:00:00", Err(TOO_LONG)), // bad offset format + ("2015-01-20T17:35:20+08:", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T17:35:20-08:", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T17:35:20βˆ’08:", Err(TOO_SHORT)), // bad offset format with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20-08", Err(TOO_SHORT)), // bad offset format + ("2015-01-20T", Err(TOO_SHORT)), // missing HMS + ("2015-01-20T00:00:1", Err(TOO_SHORT)), // missing complete S + ("2015-01-20T00:00:1-08:00", Err(INVALID)), // missing complete S + ]; + + fn rfc3339_to_datetime(date: &str) -> ParseResult> { + let mut parsed = Parsed::new(); + parse(&mut parsed, date, [Item::Fixed(Fixed::RFC3339)].iter())?; + parsed.to_datetime() + } + + fn fmt_rfc3339_datetime(dt: DateTime) -> String { + dt.format_with_items([Item::Fixed(Fixed::RFC3339)].iter()).to_string() + } + + // Test against test data above + for &(date, checkdate) in testdates.iter() { + let d = rfc3339_to_datetime(date); // parse a date + let dt = match d { + // did we get a value? + Ok(dt) => Ok(fmt_rfc3339_datetime(dt)), // yes, go on + Err(e) => Err(e), // otherwise keep an error for the comparison + }; + if dt != checkdate.map(|s| s.to_string()) { + // check for expected result + panic!( + "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", + date, dt, checkdate + ); + } } } -} -#[cfg(test)] -#[test] -fn test_issue_1010() { - let dt = crate::NaiveDateTime::parse_from_str("\u{c}SUN\u{e}\u{3000}\0m@J\u{3000}\0\u{3000}\0m\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}\0SU\u{c}\u{c}", - "\u{c}\u{c}%A\u{c}\u{b}\0SUN\u{c}\u{c}\u{c}SUNN\u{c}\u{c}\u{c}SUN\u{c}\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}%a"); - assert_eq!(dt, Err(ParseError(ParseErrorKind::Invalid))); + #[test] + fn test_issue_1010() { + let dt = crate::NaiveDateTime::parse_from_str("\u{c}SUN\u{e}\u{3000}\0m@J\u{3000}\0\u{3000}\0m\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}\0SU\u{c}\u{c}", + "\u{c}\u{c}%A\u{c}\u{b}\0SUN\u{c}\u{c}\u{c}SUNN\u{c}\u{c}\u{c}SUN\u{c}\u{c}!\u{c}\u{b}\u{c}\u{c}\u{c}\u{c}%A\u{c}\u{b}%a"); + assert_eq!(dt, Err(ParseError(ParseErrorKind::Invalid))); + } } From fb220c8ab9f0a627fd3230700ac2fbc50538d2d7 Mon Sep 17 00:00:00 2001 From: zachs18 <8355914+zachs18@users.noreply.github.com> Date: Wed, 14 Jun 2023 18:47:12 -0500 Subject: [PATCH 10/68] Fix NaiveTime doc typo `3:00:59 + 60s = 3:01:59`, not `3:02:00` --- src/naive/time/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 328f2cbf76..bff4543f7f 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -100,7 +100,8 @@ mod tests; /// (short for [`NaiveTime::overflowing_add_signed`](#method.overflowing_add_signed)): /// /// - `03:00:00 + 1s = 03:00:01`. -/// - `03:00:59 + 60s = 03:02:00`. +/// - `03:00:59 + 60s = 03:01:59`. +/// - `03:00:59 + 61s = 03:02:00`. /// - `03:00:59 + 1s = 03:01:00`. /// - `03:00:60 + 1s = 03:01:00`. /// Note that the sum is identical to the previous. From edbdfc1e817e6c19159a0924573d04b190f68f0e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 22 Jun 2023 08:03:52 +0200 Subject: [PATCH 11/68] Add test for formatting type sizes --- src/format/strftime.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 9dae6e7e09..0a61a859e9 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -742,4 +742,21 @@ mod tests { let mut buf = String::new(); let _ = write!(buf, "{}", dt.format("%#z")).expect_err("parse-only formatter should fail"); } + + #[test] + #[cfg(not(feature = "unstable-locales"))] + fn test_type_sizes() { + use core::mem::size_of; + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 80); + } + + #[test] + #[cfg(feature = "unstable-locales")] + fn test_type_sizes() { + use core::mem::size_of; + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 112); + assert_eq!(size_of::(), 2); + } } From f6b453ea60d78103f2f244589453b79655c0fd69 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 23 Jun 2023 13:48:10 +0200 Subject: [PATCH 12/68] Add format string parsing benchmarks --- benches/chrono.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/benches/chrono.rs b/benches/chrono.rs index 216616b824..a097ce6ea5 100644 --- a/benches/chrono.rs +++ b/benches/chrono.rs @@ -3,7 +3,10 @@ use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use chrono::format::StrftimeItems; use chrono::prelude::*; +#[cfg(feature = "unstable-locales")] +use chrono::Locale; use chrono::{DateTime, FixedOffset, Local, Utc, __BenchYearFlags}; fn bench_datetime_parse_from_rfc2822(c: &mut Criterion) { @@ -122,6 +125,27 @@ fn bench_num_days_from_ce(c: &mut Criterion) { } } +fn bench_parse_strftime(c: &mut Criterion) { + c.bench_function("bench_parse_strftime", |b| { + b.iter(|| { + let str = black_box("%a, %d %b %Y %H:%M:%S GMT"); + let items = StrftimeItems::new(str); + black_box(items.collect::>()); + }) + }); +} + +#[cfg(feature = "unstable-locales")] +fn bench_parse_strftime_localized(c: &mut Criterion) { + c.bench_function("bench_parse_strftime_localized", |b| { + b.iter(|| { + let str = black_box("%a, %d %b %Y %H:%M:%S GMT"); + let items = StrftimeItems::new_with_locale(str, Locale::nl_NL); + black_box(items.collect::>()); + }) + }); +} + criterion_group!( benches, bench_datetime_parse_from_rfc2822, @@ -132,6 +156,13 @@ criterion_group!( bench_year_flags_from_year, bench_num_days_from_ce, bench_get_local_time, + bench_parse_strftime, ); +#[cfg(feature = "unstable-locales")] +criterion_group!(unstable_locales, bench_parse_strftime_localized,); + +#[cfg(not(feature = "unstable-locales"))] criterion_main!(benches); +#[cfg(feature = "unstable-locales")] +criterion_main!(benches, unstable_locales); From 91b8db8c23206c2d782ad698a23c0a5d561ed05a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 23 Jun 2023 13:06:33 +0200 Subject: [PATCH 13/68] Rename `recons` to `queue` --- src/format/strftime.rs | 65 +++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 0a61a859e9..2569040632 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -220,9 +220,8 @@ pub struct StrftimeItems<'a> { /// Remaining portion of the string. remainder: &'a str, /// If the current specifier is composed of multiple formatting items (e.g. `%+`), - /// parser refers to the statically reconstructed slice of them. - /// If `recons` is not empty they have to be returned earlier than the `remainder`. - recons: Fmt<'a>, + /// `queue` stores a slice of `Item`s that have to be returned one by one. + queue: Fmt<'a>, /// Date format d_fmt: Fmt<'a>, /// Date and time format @@ -247,7 +246,7 @@ impl<'a> StrftimeItems<'a> { let d_t_fmt = StrftimeItems::new(locales::d_t_fmt(locale)).collect(); let t_fmt = StrftimeItems::new(locales::t_fmt(locale)).collect(); - StrftimeItems { remainder: s, recons: Vec::new(), d_fmt, d_t_fmt, t_fmt } + StrftimeItems { remainder: s, queue: Vec::new(), d_fmt, d_t_fmt, t_fmt } } #[cfg(not(feature = "unstable-locales"))] @@ -256,7 +255,7 @@ impl<'a> StrftimeItems<'a> { StrftimeItems { remainder: s, - recons: FMT_NONE, + queue: FMT_NONE, d_fmt: D_FMT, d_t_fmt: D_T_FMT, t_fmt: T_FMT, @@ -267,7 +266,7 @@ impl<'a> StrftimeItems<'a> { fn with_remainer(s: &'a str) -> StrftimeItems<'a> { StrftimeItems { remainder: s, - recons: Vec::new(), + queue: Vec::new(), d_fmt: D_FMT.to_vec(), d_t_fmt: D_T_FMT.to_vec(), t_fmt: T_FMT.to_vec(), @@ -281,17 +280,17 @@ impl<'a> Iterator for StrftimeItems<'a> { type Item = Item<'a>; fn next(&mut self) -> Option> { - // we have some reconstructed items to return - if !self.recons.is_empty() { + // We have items queued to return from a specifier composed of multiple formatting items. + if !self.queue.is_empty() { let item; #[cfg(feature = "unstable-locales")] { - item = self.recons.remove(0); + item = self.queue.remove(0); } #[cfg(not(feature = "unstable-locales"))] { - item = self.recons[0].clone(); - self.recons = &self.recons[1..]; + item = self.queue[0].clone(); + self.queue = &self.queue[1..]; } return Some(item); } @@ -329,32 +328,32 @@ impl<'a> Iterator for StrftimeItems<'a> { return Some(Item::Error); } - macro_rules! recons { + macro_rules! queue { [$head:expr, $($tail:expr),+ $(,)*] => ({ #[cfg(feature = "unstable-locales")] { - self.recons.clear(); - $(self.recons.push($tail);)+ + self.queue.clear(); + $(self.queue.push($tail);)+ } #[cfg(not(feature = "unstable-locales"))] { - const RECONS: &'static [Item<'static>] = &[$($tail),+]; - self.recons = RECONS; + const QUEUE: &'static [Item<'static>] = &[$($tail),+]; + self.queue = QUEUE; } $head }) } - macro_rules! recons_from_slice { + macro_rules! queue_from_slice { ($slice:expr) => {{ #[cfg(feature = "unstable-locales")] { - self.recons.clear(); - self.recons.extend_from_slice(&$slice[1..]); + self.queue.clear(); + self.queue.extend_from_slice(&$slice[1..]); } #[cfg(not(feature = "unstable-locales"))] { - self.recons = &$slice[1..]; + self.queue = &$slice[1..]; } $slice[0].clone() }}; @@ -365,26 +364,26 @@ impl<'a> Iterator for StrftimeItems<'a> { 'B' => fix!(LongMonthName), 'C' => num0!(YearDiv100), 'D' => { - recons![num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)] + queue![num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)] } - 'F' => recons![num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)], + 'F' => queue![num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)], 'G' => num0!(IsoYear), 'H' => num0!(Hour), 'I' => num0!(Hour12), 'M' => num0!(Minute), 'P' => fix!(LowerAmPm), - 'R' => recons![num0!(Hour), lit!(":"), num0!(Minute)], + 'R' => queue![num0!(Hour), lit!(":"), num0!(Minute)], 'S' => num0!(Second), - 'T' => recons![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)], + 'T' => queue![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)], 'U' => num0!(WeekFromSun), 'V' => num0!(IsoWeek), 'W' => num0!(WeekFromMon), - 'X' => recons_from_slice!(self.t_fmt), + 'X' => queue_from_slice!(self.t_fmt), 'Y' => num0!(Year), 'Z' => fix!(TimezoneName), 'a' => fix!(ShortWeekdayName), 'b' | 'h' => fix!(ShortMonthName), - 'c' => recons_from_slice!(self.d_t_fmt), + 'c' => queue_from_slice!(self.d_t_fmt), 'd' => num0!(Day), 'e' => nums!(Day), 'f' => num0!(Nanosecond), @@ -395,7 +394,7 @@ impl<'a> Iterator for StrftimeItems<'a> { 'm' => num0!(Month), 'n' => sp!("\n"), 'p' => fix!(UpperAmPm), - 'r' => recons![ + 'r' => queue![ num0!(Hour12), lit!(":"), num0!(Minute), @@ -408,10 +407,10 @@ impl<'a> Iterator for StrftimeItems<'a> { 't' => sp!("\t"), 'u' => num!(WeekdayFromMon), 'v' => { - recons![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"), num0!(Year)] + queue![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"), num0!(Year)] } 'w' => num!(NumDaysFromSun), - 'x' => recons_from_slice!(self.d_fmt), + 'x' => queue_from_slice!(self.d_fmt), 'y' => num0!(YearMod100), 'z' => { if is_alternate { @@ -467,13 +466,15 @@ impl<'a> Iterator for StrftimeItems<'a> { _ => Item::Error, // no such specifier }; - // adjust `item` if we have any padding modifier + // Adjust `item` if we have any padding modifier. + // Not allowed on non-numeric items or on specifiers composed out of multiple + // formatting items. if let Some(new_pad) = pad_override { match item { - Item::Numeric(ref kind, _pad) if self.recons.is_empty() => { + Item::Numeric(ref kind, _pad) if self.queue.is_empty() => { Some(Item::Numeric(kind.clone(), new_pad)) } - _ => Some(Item::Error), // no reconstructed or non-numeric item allowed + _ => Some(Item::Error), } } else { Some(item) From 38ce59114c508b1e992e71cd46139ea4fcc1868e Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 23 Jun 2023 13:18:27 +0200 Subject: [PATCH 14/68] Factor out `parse_next_item` --- src/format/strftime.rs | 62 +++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 2569040632..fce2ac416c 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -295,22 +295,31 @@ impl<'a> Iterator for StrftimeItems<'a> { return Some(item); } - match self.remainder.chars().next() { + // Normal: we are parsing the formatting string. + let (remainder, item) = self.parse_next_item(self.remainder)?; + self.remainder = remainder; + Some(item) + } +} + +impl<'a> StrftimeItems<'a> { + fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> { + match remainder.chars().next() { // we are done None => None, // the next item is a specifier Some('%') => { - self.remainder = &self.remainder[1..]; + remainder = &remainder[1..]; macro_rules! next { () => { - match self.remainder.chars().next() { + match remainder.chars().next() { Some(x) => { - self.remainder = &self.remainder[x.len_utf8()..]; + remainder = &remainder[x.len_utf8()..]; x } - None => return Some(Item::Error), // premature end of string + None => return Some((remainder, Item::Error)), // premature end of string } }; } @@ -325,7 +334,7 @@ impl<'a> Iterator for StrftimeItems<'a> { let is_alternate = spec == '#'; let spec = if pad_override.is_some() || is_alternate { next!() } else { spec }; if is_alternate && !HAVE_ALTERNATES.contains(spec) { - return Some(Item::Error); + return Some((remainder, Item::Error)); } macro_rules! queue { @@ -421,14 +430,14 @@ impl<'a> Iterator for StrftimeItems<'a> { } '+' => fix!(RFC3339), ':' => { - if self.remainder.starts_with("::z") { - self.remainder = &self.remainder[3..]; + if remainder.starts_with("::z") { + remainder = &remainder[3..]; fix!(TimezoneOffsetTripleColon) - } else if self.remainder.starts_with(":z") { - self.remainder = &self.remainder[2..]; + } else if remainder.starts_with(":z") { + remainder = &remainder[2..]; fix!(TimezoneOffsetDoubleColon) - } else if self.remainder.starts_with('z') { - self.remainder = &self.remainder[1..]; + } else if remainder.starts_with('z') { + remainder = &remainder[1..]; fix!(TimezoneOffsetColon) } else { Item::Error @@ -472,38 +481,35 @@ impl<'a> Iterator for StrftimeItems<'a> { if let Some(new_pad) = pad_override { match item { Item::Numeric(ref kind, _pad) if self.queue.is_empty() => { - Some(Item::Numeric(kind.clone(), new_pad)) + Some((remainder, Item::Numeric(kind.clone(), new_pad))) } - _ => Some(Item::Error), + _ => Some((remainder, Item::Error)), } } else { - Some(item) + Some((remainder, item)) } } // the next item is space Some(c) if c.is_whitespace() => { // `%` is not a whitespace, so `c != '%'` is redundant - let nextspec = self - .remainder - .find(|c: char| !c.is_whitespace()) - .unwrap_or(self.remainder.len()); + let nextspec = + remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len()); assert!(nextspec > 0); - let item = sp!(&self.remainder[..nextspec]); - self.remainder = &self.remainder[nextspec..]; - Some(item) + let item = sp!(&remainder[..nextspec]); + remainder = &remainder[nextspec..]; + Some((remainder, item)) } // the next item is literal _ => { - let nextspec = self - .remainder + let nextspec = remainder .find(|c: char| c.is_whitespace() || c == '%') - .unwrap_or(self.remainder.len()); + .unwrap_or(remainder.len()); assert!(nextspec > 0); - let item = lit!(&self.remainder[..nextspec]); - self.remainder = &self.remainder[nextspec..]; - Some(item) + let item = lit!(&remainder[..nextspec]); + remainder = &remainder[nextspec..]; + Some((remainder, item)) } } } From d8ec23def837c897d6e939466341237033b24f09 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 23 Jun 2023 13:24:48 +0200 Subject: [PATCH 15/68] Delay parsing of localized formatting string until use --- src/format/strftime.rs | 143 +++++++++++++++++------------------------ 1 file changed, 59 insertions(+), 84 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index fce2ac416c..51f4762da8 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -180,21 +180,10 @@ Notes: China Daylight Time. */ -#[cfg(feature = "unstable-locales")] -extern crate alloc; - -#[cfg(feature = "unstable-locales")] -use alloc::vec::Vec; - #[cfg(feature = "unstable-locales")] use super::{locales, Locale}; use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad}; -#[cfg(feature = "unstable-locales")] -type Fmt<'a> = Vec>; -#[cfg(not(feature = "unstable-locales"))] -type Fmt<'a> = &'static [Item<'static>]; - static D_FMT: &[Item<'static>] = &[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]; static D_T_FMT: &[Item<'static>] = &[ @@ -221,20 +210,25 @@ pub struct StrftimeItems<'a> { remainder: &'a str, /// If the current specifier is composed of multiple formatting items (e.g. `%+`), /// `queue` stores a slice of `Item`s that have to be returned one by one. - queue: Fmt<'a>, - /// Date format - d_fmt: Fmt<'a>, - /// Date and time format - d_t_fmt: Fmt<'a>, - /// Time format - t_fmt: Fmt<'a>, + queue: &'static [Item<'static>], + #[cfg(feature = "unstable-locales")] + locale_str: &'a str, + #[cfg(feature = "unstable-locales")] + locale: Option, } impl<'a> StrftimeItems<'a> { /// Creates a new parsing iterator from the `strftime`-like format string. #[must_use] pub fn new(s: &'a str) -> StrftimeItems<'a> { - Self::with_remainer(s) + #[cfg(not(feature = "unstable-locales"))] + { + StrftimeItems { remainder: s, queue: &[] } + } + #[cfg(feature = "unstable-locales")] + { + StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: None } + } } /// Creates a new parsing iterator from the `strftime`-like format string. @@ -242,35 +236,7 @@ impl<'a> StrftimeItems<'a> { #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[must_use] pub fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { - let d_fmt = StrftimeItems::new(locales::d_fmt(locale)).collect(); - let d_t_fmt = StrftimeItems::new(locales::d_t_fmt(locale)).collect(); - let t_fmt = StrftimeItems::new(locales::t_fmt(locale)).collect(); - - StrftimeItems { remainder: s, queue: Vec::new(), d_fmt, d_t_fmt, t_fmt } - } - - #[cfg(not(feature = "unstable-locales"))] - fn with_remainer(s: &'a str) -> StrftimeItems<'a> { - static FMT_NONE: &[Item<'static>; 0] = &[]; - - StrftimeItems { - remainder: s, - queue: FMT_NONE, - d_fmt: D_FMT, - d_t_fmt: D_T_FMT, - t_fmt: T_FMT, - } - } - - #[cfg(feature = "unstable-locales")] - fn with_remainer(s: &'a str) -> StrftimeItems<'a> { - StrftimeItems { - remainder: s, - queue: Vec::new(), - d_fmt: D_FMT.to_vec(), - d_t_fmt: D_T_FMT.to_vec(), - t_fmt: T_FMT.to_vec(), - } + StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: Some(locale) } } } @@ -281,17 +247,16 @@ impl<'a> Iterator for StrftimeItems<'a> { fn next(&mut self) -> Option> { // We have items queued to return from a specifier composed of multiple formatting items. - if !self.queue.is_empty() { - let item; - #[cfg(feature = "unstable-locales")] - { - item = self.queue.remove(0); - } - #[cfg(not(feature = "unstable-locales"))] - { - item = self.queue[0].clone(); - self.queue = &self.queue[1..]; - } + if let Some((item, remainder)) = self.queue.split_first() { + self.queue = remainder; + return Some(item.clone()); + } + + // We are in the middle of parsing the localized formatting string of a specifier. + #[cfg(feature = "unstable-locales")] + if !self.locale_str.is_empty() { + let (remainder, item) = self.parse_next_item(self.locale_str)?; + self.locale_str = remainder; return Some(item); } @@ -339,31 +304,15 @@ impl<'a> StrftimeItems<'a> { macro_rules! queue { [$head:expr, $($tail:expr),+ $(,)*] => ({ - #[cfg(feature = "unstable-locales")] - { - self.queue.clear(); - $(self.queue.push($tail);)+ - } - #[cfg(not(feature = "unstable-locales"))] - { - const QUEUE: &'static [Item<'static>] = &[$($tail),+]; - self.queue = QUEUE; - } + const QUEUE: &'static [Item<'static>] = &[$($tail),+]; + self.queue = QUEUE; $head }) } - + #[cfg(not(feature = "unstable-locales"))] macro_rules! queue_from_slice { ($slice:expr) => {{ - #[cfg(feature = "unstable-locales")] - { - self.queue.clear(); - self.queue.extend_from_slice(&$slice[1..]); - } - #[cfg(not(feature = "unstable-locales"))] - { - self.queue = &$slice[1..]; - } + self.queue = &$slice[1..]; $slice[0].clone() }}; } @@ -387,12 +336,18 @@ impl<'a> StrftimeItems<'a> { 'U' => num0!(WeekFromSun), 'V' => num0!(IsoWeek), 'W' => num0!(WeekFromMon), - 'X' => queue_from_slice!(self.t_fmt), + #[cfg(not(feature = "unstable-locales"))] + 'X' => queue_from_slice!(T_FMT), + #[cfg(feature = "unstable-locales")] + 'X' => self.switch_to_locale_str(locales::t_fmt, T_FMT), 'Y' => num0!(Year), 'Z' => fix!(TimezoneName), 'a' => fix!(ShortWeekdayName), 'b' | 'h' => fix!(ShortMonthName), - 'c' => queue_from_slice!(self.d_t_fmt), + #[cfg(not(feature = "unstable-locales"))] + 'c' => queue_from_slice!(D_T_FMT), + #[cfg(feature = "unstable-locales")] + 'c' => self.switch_to_locale_str(locales::d_t_fmt, D_T_FMT), 'd' => num0!(Day), 'e' => nums!(Day), 'f' => num0!(Nanosecond), @@ -419,7 +374,10 @@ impl<'a> StrftimeItems<'a> { queue![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"), num0!(Year)] } 'w' => num!(NumDaysFromSun), - 'x' => queue_from_slice!(self.d_fmt), + #[cfg(not(feature = "unstable-locales"))] + 'x' => queue_from_slice!(D_FMT), + #[cfg(feature = "unstable-locales")] + 'x' => self.switch_to_locale_str(locales::d_fmt, D_FMT), 'y' => num0!(YearMod100), 'z' => { if is_alternate { @@ -513,6 +471,23 @@ impl<'a> StrftimeItems<'a> { } } } + + #[cfg(feature = "unstable-locales")] + fn switch_to_locale_str( + &mut self, + localized_fmt_str: impl Fn(Locale) -> &'static str, + fallback: &'static [Item<'static>], + ) -> Item<'a> { + if let Some(locale) = self.locale { + assert!(self.locale_str.is_empty()); + let (fmt_str, item) = self.parse_next_item(localized_fmt_str(locale)).unwrap(); + self.locale_str = fmt_str; + item + } else { + self.queue = &fallback[1..]; + fallback[0].clone() + } + } } #[cfg(test)] @@ -755,7 +730,7 @@ mod tests { fn test_type_sizes() { use core::mem::size_of; assert_eq!(size_of::(), 24); - assert_eq!(size_of::(), 80); + assert_eq!(size_of::(), 32); } #[test] @@ -763,7 +738,7 @@ mod tests { fn test_type_sizes() { use core::mem::size_of; assert_eq!(size_of::(), 24); - assert_eq!(size_of::(), 112); + assert_eq!(size_of::(), 56); assert_eq!(size_of::(), 2); } } From 13ad948ef9d0426d82a44a9be5fc1370edfea5a1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 23 Jun 2023 13:25:33 +0200 Subject: [PATCH 16/68] Make `StrftimeItems::new` const --- src/format/strftime.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 51f4762da8..94e315b5d1 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -220,7 +220,7 @@ pub struct StrftimeItems<'a> { impl<'a> StrftimeItems<'a> { /// Creates a new parsing iterator from the `strftime`-like format string. #[must_use] - pub fn new(s: &'a str) -> StrftimeItems<'a> { + pub const fn new(s: &'a str) -> StrftimeItems<'a> { #[cfg(not(feature = "unstable-locales"))] { StrftimeItems { remainder: s, queue: &[] } @@ -235,7 +235,7 @@ impl<'a> StrftimeItems<'a> { #[cfg(feature = "unstable-locales")] #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] #[must_use] - pub fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { + pub const fn new_with_locale(s: &'a str, locale: Locale) -> StrftimeItems<'a> { StrftimeItems { remainder: s, queue: &[], locale_str: "", locale: Some(locale) } } } From ea9398eb0d8fa55fe0064093af32f45443e5c0e0 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 26 Jun 2023 21:44:41 +0200 Subject: [PATCH 17/68] Add formatting benchmarks --- benches/chrono.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/benches/chrono.rs b/benches/chrono.rs index a097ce6ea5..d0babf3e19 100644 --- a/benches/chrono.rs +++ b/benches/chrono.rs @@ -146,6 +146,36 @@ fn bench_parse_strftime_localized(c: &mut Criterion) { }); } +fn bench_format(c: &mut Criterion) { + let dt = Local::now(); + c.bench_function("bench_format", |b| b.iter(|| format!("{}", dt.format("%Y-%m-%d %H-%M-%S")))); +} + +fn bench_format_with_items(c: &mut Criterion) { + let dt = Local::now(); + let items: Vec<_> = StrftimeItems::new("%Y-%m-%d %H-%M-%S").collect(); + c.bench_function("bench_format_with_items", |b| { + b.iter(|| format!("{}", dt.format_with_items(items.iter()))) + }); +} + +fn bench_format_manual(c: &mut Criterion) { + let dt = Local::now(); + c.bench_function("bench_format_manual", |b| { + b.iter(|| { + format!( + "{}-{:02}-{:02} {:02}:{:02}:{:02}", + dt.year(), + dt.month(), + dt.day(), + dt.hour(), + dt.minute(), + dt.second() + ) + }) + }); +} + criterion_group!( benches, bench_datetime_parse_from_rfc2822, @@ -157,6 +187,9 @@ criterion_group!( bench_num_days_from_ce, bench_get_local_time, bench_parse_strftime, + bench_format, + bench_format_with_items, + bench_format_manual, ); #[cfg(feature = "unstable-locales")] From 1a3c43a03e088cff247f86da126888726de52065 Mon Sep 17 00:00:00 2001 From: Mike Cronce Date: Thu, 29 Jun 2023 07:02:26 -0400 Subject: [PATCH 18/68] Add FromStr for FixedOffset (#1157) Co-authored-by: Yuxuan Shui --- src/format/mod.rs | 4 ++-- src/format/scan.rs | 4 ++-- src/offset/fixed.rs | 24 ++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index 7c9c3b7dd0..9591fa4d16 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -420,7 +420,7 @@ impl Error for ParseError { } // to be used in this module and submodules -const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange); +pub(crate) const OUT_OF_RANGE: ParseError = ParseError(ParseErrorKind::OutOfRange); const IMPOSSIBLE: ParseError = ParseError(ParseErrorKind::Impossible); const NOT_ENOUGH: ParseError = ParseError(ParseErrorKind::NotEnough); const INVALID: ParseError = ParseError(ParseErrorKind::Invalid); @@ -838,7 +838,7 @@ mod parsed; // due to the size of parsing routines, they are in separate modules. mod parse; -mod scan; +pub(crate) mod scan; pub mod strftime; diff --git a/src/format/scan.rs b/src/format/scan.rs index 2273dec7d6..00e06c7c13 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -197,7 +197,7 @@ pub(super) fn space(s: &str) -> ParseResult<&str> { } /// Consumes any number (including zero) of colon or spaces. -pub(super) fn colon_or_space(s: &str) -> ParseResult<&str> { +pub(crate) fn colon_or_space(s: &str) -> ParseResult<&str> { Ok(s.trim_start_matches(|c: char| c == ':' || c.is_whitespace())) } @@ -205,7 +205,7 @@ pub(super) fn colon_or_space(s: &str) -> ParseResult<&str> { /// /// The additional `colon` may be used to parse a mandatory or optional `:` /// between hours and minutes, and should return either a new suffix or `Err` when parsing fails. -pub(super) fn timezone_offset(s: &str, consume_colon: F) -> ParseResult<(&str, i32)> +pub(crate) fn timezone_offset(s: &str, consume_colon: F) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, { diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index 246d6666ea..fcf445fd97 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -5,14 +5,18 @@ use core::fmt; use core::ops::{Add, Sub}; +use core::str::FromStr; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; use super::{LocalResult, Offset, TimeZone}; +use crate::format::scan; +use crate::format::OUT_OF_RANGE; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::oldtime::Duration as OldDuration; use crate::DateTime; +use crate::ParseError; use crate::Timelike; /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59. @@ -113,6 +117,15 @@ impl FixedOffset { } } +/// Parsing a `str` into a `FixedOffset` uses the format [`%z`](crate::format::strftime). +impl FromStr for FixedOffset { + type Err = ParseError; + fn from_str(s: &str) -> Result { + let (_, offset) = scan::timezone_offset(s, scan::colon_or_space)?; + Self::east_opt(offset).ok_or(OUT_OF_RANGE) + } +} + impl TimeZone for FixedOffset { type Offset = FixedOffset; @@ -246,6 +259,7 @@ impl Sub for DateTime { mod tests { use super::FixedOffset; use crate::offset::TimeZone; + use std::str::FromStr; #[test] fn test_date_extreme_offset() { @@ -292,4 +306,14 @@ mod tests { "2012-03-04T05:06:07-23:59:59".to_string() ); } + + #[test] + fn test_parse_offset() { + let offset = FixedOffset::from_str("-0500").unwrap(); + assert_eq!(offset.local_minus_utc, -5 * 3600); + let offset = FixedOffset::from_str("-08:00").unwrap(); + assert_eq!(offset.local_minus_utc, -8 * 3600); + let offset = FixedOffset::from_str("+06:30").unwrap(); + assert_eq!(offset.local_minus_utc, (6 * 3600) + 1800); + } } From 224933ec512a0cf0d428152c8a730abcae9fb9e1 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 29 Jun 2023 13:13:06 +0200 Subject: [PATCH 19/68] Remove indirection for parsing timezone offset --- src/format/parse.rs | 4 +++- src/format/scan.rs | 19 ++++--------------- src/offset/fixed.rs | 2 +- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index f1737d7dd5..4c1c0b9eae 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -454,7 +454,9 @@ where | &TimezoneOffset => { let offset = try_consume!(scan::timezone_offset( s.trim_start(), - scan::colon_or_space + scan::colon_or_space, + false, + true, )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } diff --git a/src/format/scan.rs b/src/format/scan.rs index 00e06c7c13..35e39e7a85 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -201,17 +201,6 @@ pub(crate) fn colon_or_space(s: &str) -> ParseResult<&str> { Ok(s.trim_start_matches(|c: char| c == ':' || c.is_whitespace())) } -/// Tries to parse `[-+]\d\d` continued by `\d\d`. Return an offset in seconds if possible. -/// -/// The additional `colon` may be used to parse a mandatory or optional `:` -/// between hours and minutes, and should return either a new suffix or `Err` when parsing fails. -pub(crate) fn timezone_offset(s: &str, consume_colon: F) -> ParseResult<(&str, i32)> -where - F: FnMut(&str) -> ParseResult<&str>, -{ - timezone_offset_internal(s, consume_colon, false, true) -} - /// Parse a timezone from `s` and return the offset in seconds. /// /// The `consume_colon` function is used to parse a mandatory or optional `:` @@ -226,7 +215,7 @@ where /// This is part of [RFC 3339 & ISO 8601]. /// /// [RFC 3339 & ISO 8601]: https://en.wikipedia.org/w/index.php?title=ISO_8601&oldid=1114309368#Time_offsets_from_UTC -fn timezone_offset_internal( +pub(crate) fn timezone_offset( mut s: &str, mut consume_colon: F, allow_missing_minutes: bool, @@ -321,7 +310,7 @@ where Err(INVALID) } } - _ => timezone_offset(s, colon), + _ => timezone_offset(s, colon, false, true), } } @@ -333,7 +322,7 @@ where { match s.as_bytes().first() { Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)), - _ => timezone_offset_internal(s, colon, true, true), + _ => timezone_offset(s, colon, true, true), } } @@ -371,7 +360,7 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> Ok((s, None)) } } else { - let (s_, offset) = timezone_offset_internal(s, |s| Ok(s), false, false)?; + let (s_, offset) = timezone_offset(s, |s| Ok(s), false, false)?; Ok((s_, Some(offset))) } } diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index fcf445fd97..dbcc9ebb07 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -121,7 +121,7 @@ impl FixedOffset { impl FromStr for FixedOffset { type Err = ParseError; fn from_str(s: &str) -> Result { - let (_, offset) = scan::timezone_offset(s, scan::colon_or_space)?; + let (_, offset) = scan::timezone_offset(s, scan::colon_or_space, false, true)?; Self::east_opt(offset).ok_or(OUT_OF_RANGE) } } From 3330c186b2ab55470db67aabadb22367058cd942 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 29 Jun 2023 13:14:28 +0200 Subject: [PATCH 20/68] Group imports from the same module --- src/offset/fixed.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index dbcc9ebb07..df9f27680f 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -11,13 +11,10 @@ use core::str::FromStr; use rkyv::{Archive, Deserialize, Serialize}; use super::{LocalResult, Offset, TimeZone}; -use crate::format::scan; -use crate::format::OUT_OF_RANGE; +use crate::format::{scan, OUT_OF_RANGE}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::oldtime::Duration as OldDuration; -use crate::DateTime; -use crate::ParseError; -use crate::Timelike; +use crate::{DateTime, ParseError, Timelike}; /// The time zone with fixed offset, from UTC-23:59:59 to UTC+23:59:59. /// From 11a63a07d1980bc5cf8490e073d3e12518784725 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 29 Jun 2023 13:27:37 +0200 Subject: [PATCH 21/68] Inline trivial single-use function --- src/format/parse.rs | 2 +- src/format/scan.rs | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 4c1c0b9eae..b9f93b7d1a 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -445,7 +445,7 @@ where } &TimezoneName => { - try_consume!(scan::timezone_name_skip(s)); + try_consume!(Ok((s.trim_start_matches(|c: char| !c.is_whitespace()), ()))); } &TimezoneOffsetColon diff --git a/src/format/scan.rs b/src/format/scan.rs index 35e39e7a85..5e0b610cf0 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -365,12 +365,6 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> } } -/// Tries to consume everything until next whitespace-like symbol. -/// Does not provide any offset information from the consumed data. -pub(super) fn timezone_name_skip(s: &str) -> ParseResult<(&str, ())> { - Ok((s.trim_start_matches(|c: char| !c.is_whitespace()), ())) -} - /// Tries to consume an RFC2822 comment including preceding ` `. /// /// Returns the remaining string after the closing parenthesis. From cc6671ffeae4418ec7f7ffe3c4b178002a22e676 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 29 Jun 2023 13:43:33 +0200 Subject: [PATCH 22/68] Use std API for case-insensitive ASCII comparison --- src/format/scan.rs | 44 ++++++++++++++------------------------------ 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/src/format/scan.rs b/src/format/scan.rs index 5e0b610cf0..8bd31d8117 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -8,24 +8,6 @@ use super::{ParseResult, INVALID, OUT_OF_RANGE, TOO_SHORT}; use crate::Weekday; -/// Returns true when two slices are equal case-insensitively (in ASCII). -/// Assumes that the `pattern` is already converted to lower case. -fn equals(s: &[u8], pattern: &str) -> bool { - let mut xs = s.iter().map(|&c| match c { - b'A'..=b'Z' => c + 32, - _ => c, - }); - let mut ys = pattern.as_bytes().iter().cloned(); - loop { - match (xs.next(), ys.next()) { - (None, None) => return true, - (None, _) | (_, None) => return false, - (Some(x), Some(y)) if x != y => return false, - _ => (), - } - } -} - /// Tries to parse the non-negative number from `min` to `max` digits. /// /// The absence of digits at all is an unconditional error. @@ -143,14 +125,16 @@ pub(super) fn short_weekday(s: &str) -> ParseResult<(&str, Weekday)> { /// It prefers long month names to short month names when both are possible. pub(super) fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { // lowercased month names, minus first three chars - static LONG_MONTH_SUFFIXES: [&str; 12] = - ["uary", "ruary", "ch", "il", "", "e", "y", "ust", "tember", "ober", "ember", "ember"]; + static LONG_MONTH_SUFFIXES: [&[u8]; 12] = [ + b"uary", b"ruary", b"ch", b"il", b"", b"e", b"y", b"ust", b"tember", b"ober", b"ember", + b"ember", + ]; let (mut s, month0) = short_month0(s)?; // tries to consume the suffix if possible let suffix = LONG_MONTH_SUFFIXES[month0 as usize]; - if s.len() >= suffix.len() && equals(&s.as_bytes()[..suffix.len()], suffix) { + if s.len() >= suffix.len() && s.as_bytes()[..suffix.len()].eq_ignore_ascii_case(suffix) { s = &s[suffix.len()..]; } @@ -161,14 +145,14 @@ pub(super) fn short_or_long_month0(s: &str) -> ParseResult<(&str, u8)> { /// It prefers long weekday names to short weekday names when both are possible. pub(super) fn short_or_long_weekday(s: &str) -> ParseResult<(&str, Weekday)> { // lowercased weekday names, minus first three chars - static LONG_WEEKDAY_SUFFIXES: [&str; 7] = - ["day", "sday", "nesday", "rsday", "day", "urday", "day"]; + static LONG_WEEKDAY_SUFFIXES: [&[u8]; 7] = + [b"day", b"sday", b"nesday", b"rsday", b"day", b"urday", b"day"]; let (mut s, weekday) = short_weekday(s)?; // tries to consume the suffix if possible let suffix = LONG_WEEKDAY_SUFFIXES[weekday.num_days_from_monday() as usize]; - if s.len() >= suffix.len() && equals(&s.as_bytes()[..suffix.len()], suffix) { + if s.len() >= suffix.len() && s.as_bytes()[..suffix.len()].eq_ignore_ascii_case(suffix) { s = &s[suffix.len()..]; } @@ -338,17 +322,17 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> let name = &s.as_bytes()[..upto]; let s = &s[upto..]; let offset_hours = |o| Ok((s, Some(o * 3600))); - if equals(name, "gmt") || equals(name, "ut") { + if name.eq_ignore_ascii_case(b"gmt") || name.eq_ignore_ascii_case(b"ut") { offset_hours(0) - } else if equals(name, "edt") { + } else if name.eq_ignore_ascii_case(b"edt") { offset_hours(-4) - } else if equals(name, "est") || equals(name, "cdt") { + } else if name.eq_ignore_ascii_case(b"est") || name.eq_ignore_ascii_case(b"cdt") { offset_hours(-5) - } else if equals(name, "cst") || equals(name, "mdt") { + } else if name.eq_ignore_ascii_case(b"cst") || name.eq_ignore_ascii_case(b"mdt") { offset_hours(-6) - } else if equals(name, "mst") || equals(name, "pdt") { + } else if name.eq_ignore_ascii_case(b"mst") || name.eq_ignore_ascii_case(b"pdt") { offset_hours(-7) - } else if equals(name, "pst") { + } else if name.eq_ignore_ascii_case(b"pst") { offset_hours(-8) } else if name.len() == 1 { match name[0] { From 5293ed77467620a11008d0734c1a72939262b103 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 29 Jun 2023 13:48:58 +0200 Subject: [PATCH 23/68] Fold zulu parsing into timezone_offset() --- src/format/parse.rs | 10 ++++++--- src/format/scan.rs | 50 ++++++++++++++++++++------------------------- src/offset/fixed.rs | 2 +- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index b9f93b7d1a..77293ff614 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -213,7 +213,7 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st parsed.set_nanosecond(nanosecond)?; } - let offset = try_consume!(scan::timezone_offset_zulu(s, |s| scan::char(s, b':'))); + let offset = try_consume!(scan::timezone_offset(s, |s| scan::char(s, b':'), true, false, true)); if offset <= -86_400 || offset >= 86_400 { return Err(OUT_OF_RANGE); } @@ -456,15 +456,19 @@ where s.trim_start(), scan::colon_or_space, false, + false, true, )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } &TimezoneOffsetColonZ | &TimezoneOffsetZ => { - let offset = try_consume!(scan::timezone_offset_zulu( + let offset = try_consume!(scan::timezone_offset( s.trim_start(), - scan::colon_or_space + scan::colon_or_space, + true, + false, + true, )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } diff --git a/src/format/scan.rs b/src/format/scan.rs index 8bd31d8117..7479e26824 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -202,12 +202,32 @@ pub(crate) fn colon_or_space(s: &str) -> ParseResult<&str> { pub(crate) fn timezone_offset( mut s: &str, mut consume_colon: F, + allow_zulu: bool, allow_missing_minutes: bool, allow_tz_minus_sign: bool, ) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, { + if allow_zulu { + let bytes = s.as_bytes(); + match bytes.first() { + Some(&b'z') | Some(&b'Z') => return Ok((&s[1..], 0)), + Some(&b'u') | Some(&b'U') => { + if bytes.len() >= 3 { + let (b, c) = (bytes[1], bytes[2]); + match (b | 32, c | 32) { + (b't', b'c') => return Ok((&s[3..], 0)), + _ => return Err(INVALID), + } + } else { + return Err(INVALID); + } + } + _ => {} + } + } + const fn digits(s: &str) -> ParseResult<(u8, u8)> { let b = s.as_bytes(); if b.len() < 2 { @@ -275,39 +295,13 @@ where Ok((s, if negative { -seconds } else { seconds })) } -/// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as `+00:00`. -pub(super) fn timezone_offset_zulu(s: &str, colon: F) -> ParseResult<(&str, i32)> -where - F: FnMut(&str) -> ParseResult<&str>, -{ - let bytes = s.as_bytes(); - match bytes.first() { - Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)), - Some(&b'u') | Some(&b'U') => { - if bytes.len() >= 3 { - let (b, c) = (bytes[1], bytes[2]); - match (b | 32, c | 32) { - (b't', b'c') => Ok((&s[3..], 0)), - _ => Err(INVALID), - } - } else { - Err(INVALID) - } - } - _ => timezone_offset(s, colon, false, true), - } -} - /// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as /// `+00:00`, and allows missing minutes entirely. pub(super) fn timezone_offset_permissive(s: &str, colon: F) -> ParseResult<(&str, i32)> where F: FnMut(&str) -> ParseResult<&str>, { - match s.as_bytes().first() { - Some(&b'z') | Some(&b'Z') => Ok((&s[1..], 0)), - _ => timezone_offset(s, colon, true, true), - } + timezone_offset(s, colon, true, true, true) } /// Same as `timezone_offset` but also allows for RFC 2822 legacy timezones. @@ -344,7 +338,7 @@ pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> Ok((s, None)) } } else { - let (s_, offset) = timezone_offset(s, |s| Ok(s), false, false)?; + let (s_, offset) = timezone_offset(s, |s| Ok(s), false, false, false)?; Ok((s_, Some(offset))) } } diff --git a/src/offset/fixed.rs b/src/offset/fixed.rs index df9f27680f..f7f0425dde 100644 --- a/src/offset/fixed.rs +++ b/src/offset/fixed.rs @@ -118,7 +118,7 @@ impl FixedOffset { impl FromStr for FixedOffset { type Err = ParseError; fn from_str(s: &str) -> Result { - let (_, offset) = scan::timezone_offset(s, scan::colon_or_space, false, true)?; + let (_, offset) = scan::timezone_offset(s, scan::colon_or_space, false, false, true)?; Self::east_opt(offset).ok_or(OUT_OF_RANGE) } } From 69456af23cd420c98587c09817ff21f26d02ec6b Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 29 Jun 2023 13:49:58 +0200 Subject: [PATCH 24/68] Fold permissive parsing into timezone_offset() --- src/format/parse.rs | 7 +++++-- src/format/scan.rs | 9 --------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 77293ff614..619d0a9ef4 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -475,9 +475,12 @@ where &Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive, }) => { - let offset = try_consume!(scan::timezone_offset_permissive( + let offset = try_consume!(scan::timezone_offset( s.trim_start(), - scan::colon_or_space + scan::colon_or_space, + true, + true, + true, )); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; } diff --git a/src/format/scan.rs b/src/format/scan.rs index 7479e26824..8f11e195d9 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -295,15 +295,6 @@ where Ok((s, if negative { -seconds } else { seconds })) } -/// Same as `timezone_offset` but also allows for `z`/`Z` which is the same as -/// `+00:00`, and allows missing minutes entirely. -pub(super) fn timezone_offset_permissive(s: &str, colon: F) -> ParseResult<(&str, i32)> -where - F: FnMut(&str) -> ParseResult<&str>, -{ - timezone_offset(s, colon, true, true, true) -} - /// Same as `timezone_offset` but also allows for RFC 2822 legacy timezones. /// May return `None` which indicates an insufficient offset data (i.e. `-0000`). /// See [RFC 2822 Section 4.3]. From f489ee12919d21d11caee8ddb03e45aa6330b913 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 27 Jun 2023 12:12:57 +0200 Subject: [PATCH 25/68] Move formatting to `format/format.rs` --- src/format/formatting.rs | 590 +++++++++++++++++++++++++++++++++++++++ src/format/mod.rs | 584 +------------------------------------- 2 files changed, 598 insertions(+), 576 deletions(-) create mode 100644 src/format/formatting.rs diff --git a/src/format/formatting.rs b/src/format/formatting.rs new file mode 100644 index 0000000000..452d7b76b8 --- /dev/null +++ b/src/format/formatting.rs @@ -0,0 +1,590 @@ +// This is a part of Chrono. +// See README.md and LICENSE.txt for details. + +//! Date and time formatting routines. + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(feature = "alloc")] +use alloc::string::{String, ToString}; +#[cfg(any(feature = "alloc", feature = "std", test))] +use core::borrow::Borrow; +use core::fmt; +use core::fmt::Write; + +#[cfg(any(feature = "alloc", feature = "std", test))] +use crate::naive::{NaiveDate, NaiveTime}; +#[cfg(any(feature = "alloc", feature = "std", test))] +use crate::offset::{FixedOffset, Offset}; +#[cfg(any(feature = "alloc", feature = "std", test))] +use crate::{Datelike, Timelike, Weekday}; + +#[cfg(feature = "unstable-locales")] +use super::locales; +#[cfg(any(feature = "alloc", feature = "std", test))] +use super::{Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric, Pad}; + +#[cfg(any(feature = "alloc", feature = "std", test))] +struct Locales { + short_months: &'static [&'static str], + long_months: &'static [&'static str], + short_weekdays: &'static [&'static str], + long_weekdays: &'static [&'static str], + am_pm: &'static [&'static str], +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +impl Locales { + fn new(_locale: Option) -> Self { + #[cfg(feature = "unstable-locales")] + { + let locale = _locale.unwrap_or(Locale::POSIX); + Self { + short_months: locales::short_months(locale), + long_months: locales::long_months(locale), + short_weekdays: locales::short_weekdays(locale), + long_weekdays: locales::long_weekdays(locale), + am_pm: locales::am_pm(locale), + } + } + #[cfg(not(feature = "unstable-locales"))] + Self { + short_months: &[ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + ], + long_months: &[ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ], + short_weekdays: &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + long_weekdays: &[ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ], + am_pm: &["AM", "PM"], + } + } +} + +/// A *temporary* object which can be used as an argument to `format!` or others. +/// This is normally constructed via `format` methods of each date and time type. +#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] +#[derive(Debug)] +pub struct DelayedFormat { + /// The date view, if any. + date: Option, + /// The time view, if any. + time: Option, + /// The name and local-to-UTC difference for the offset (timezone), if any. + off: Option<(String, FixedOffset)>, + /// An iterator returning formatting items. + items: I, + /// Locale used for text. + // TODO: Only used with the locale feature. We should make this property + // only present when the feature is enabled. + #[cfg(feature = "unstable-locales")] + locale: Option, +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { + /// Makes a new `DelayedFormat` value out of local date and time. + #[must_use] + pub fn new(date: Option, time: Option, items: I) -> DelayedFormat { + DelayedFormat { + date, + time, + off: None, + items, + #[cfg(feature = "unstable-locales")] + locale: None, + } + } + + /// Makes a new `DelayedFormat` value out of local date and time and UTC offset. + #[must_use] + pub fn new_with_offset( + date: Option, + time: Option, + offset: &Off, + items: I, + ) -> DelayedFormat + where + Off: Offset + fmt::Display, + { + let name_and_diff = (offset.to_string(), offset.fix()); + DelayedFormat { + date, + time, + off: Some(name_and_diff), + items, + #[cfg(feature = "unstable-locales")] + locale: None, + } + } + + /// Makes a new `DelayedFormat` value out of local date and time and locale. + #[cfg(feature = "unstable-locales")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] + #[must_use] + pub fn new_with_locale( + date: Option, + time: Option, + items: I, + locale: Locale, + ) -> DelayedFormat { + DelayedFormat { date, time, off: None, items, locale: Some(locale) } + } + + /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale. + #[cfg(feature = "unstable-locales")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] + #[must_use] + pub fn new_with_offset_and_locale( + date: Option, + time: Option, + offset: &Off, + items: I, + locale: Locale, + ) -> DelayedFormat + where + Off: Offset + fmt::Display, + { + let name_and_diff = (offset.to_string(), offset.fix()); + DelayedFormat { date, time, off: Some(name_and_diff), items, locale: Some(locale) } + } +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +impl<'a, I: Iterator + Clone, B: Borrow>> fmt::Display for DelayedFormat { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + #[cfg(feature = "unstable-locales")] + { + if let Some(locale) = self.locale { + return format_localized( + f, + self.date.as_ref(), + self.time.as_ref(), + self.off.as_ref(), + self.items.clone(), + locale, + ); + } + } + + format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone()) + } +} + +/// Tries to format given arguments with given formatting items. +/// Internally used by `DelayedFormat`. +#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] +pub fn format<'a, I, B>( + w: &mut fmt::Formatter, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + items: I, +) -> fmt::Result +where + I: Iterator + Clone, + B: Borrow>, +{ + let mut result = String::new(); + for item in items { + format_inner(&mut result, date, time, off, item.borrow(), None)?; + } + w.pad(&result) +} +/// Formats single formatting item +#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] +pub fn format_item( + w: &mut fmt::Formatter, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + item: &Item<'_>, +) -> fmt::Result { + let mut result = String::new(); + format_inner(&mut result, date, time, off, item, None)?; + w.pad(&result) +} + +/// Tries to format given arguments with given formatting items. +/// Internally used by `DelayedFormat`. +#[cfg(feature = "unstable-locales")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] +pub fn format_localized<'a, I, B>( + w: &mut fmt::Formatter, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + items: I, + locale: Locale, +) -> fmt::Result +where + I: Iterator + Clone, + B: Borrow>, +{ + let mut result = String::new(); + for item in items { + format_inner(&mut result, date, time, off, item.borrow(), Some(locale))?; + } + w.pad(&result) +} + +/// Formats single formatting item +#[cfg(feature = "unstable-locales")] +#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] +pub fn format_item_localized( + w: &mut fmt::Formatter, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + item: &Item<'_>, + locale: Locale, +) -> fmt::Result { + let mut result = String::new(); + format_inner(&mut result, date, time, off, item, Some(locale))?; + w.pad(&result) +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +fn format_inner( + result: &mut String, + date: Option<&NaiveDate>, + time: Option<&NaiveTime>, + off: Option<&(String, FixedOffset)>, + item: &Item<'_>, + locale: Option, +) -> fmt::Result { + let locale = Locales::new(locale); + + match *item { + Item::Literal(s) | Item::Space(s) => result.push_str(s), + #[cfg(any(feature = "alloc", feature = "std", test))] + Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s), + + Item::Numeric(ref spec, ref pad) => { + use self::Numeric::*; + + let week_from_sun = |d: &NaiveDate| d.weeks_from(Weekday::Sun); + let week_from_mon = |d: &NaiveDate| d.weeks_from(Weekday::Mon); + + let (width, v) = match *spec { + Year => (4, date.map(|d| i64::from(d.year()))), + YearDiv100 => (2, date.map(|d| i64::from(d.year()).div_euclid(100))), + YearMod100 => (2, date.map(|d| i64::from(d.year()).rem_euclid(100))), + IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))), + IsoYearDiv100 => (2, date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))), + IsoYearMod100 => (2, date.map(|d| i64::from(d.iso_week().year()).rem_euclid(100))), + Month => (2, date.map(|d| i64::from(d.month()))), + Day => (2, date.map(|d| i64::from(d.day()))), + WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))), + WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))), + IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))), + NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday().num_days_from_sunday()))), + WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday().number_from_monday()))), + Ordinal => (3, date.map(|d| i64::from(d.ordinal()))), + Hour => (2, time.map(|t| i64::from(t.hour()))), + Hour12 => (2, time.map(|t| i64::from(t.hour12().1))), + Minute => (2, time.map(|t| i64::from(t.minute()))), + Second => (2, time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))), + Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))), + Timestamp => ( + 1, + match (date, time, off) { + (Some(d), Some(t), None) => Some(d.and_time(*t).timestamp()), + (Some(d), Some(t), Some(&(_, off))) => { + Some((d.and_time(*t) - off).timestamp()) + } + (_, _, _) => None, + }, + ), + + // for the future expansion + Internal(ref int) => match int._dummy {}, + }; + + if let Some(v) = v { + if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { + // non-four-digit years require an explicit sign as per ISO 8601 + match *pad { + Pad::None => write!(result, "{:+}", v), + Pad::Zero => write!(result, "{:+01$}", v, width + 1), + Pad::Space => write!(result, "{:+1$}", v, width + 1), + } + } else { + match *pad { + Pad::None => write!(result, "{}", v), + Pad::Zero => write!(result, "{:01$}", v, width), + Pad::Space => write!(result, "{:1$}", v, width), + } + }? + } else { + return Err(fmt::Error); // insufficient arguments for given format + } + } + + Item::Fixed(ref spec) => { + use self::Fixed::*; + + let ret = + match *spec { + ShortMonthName => date.map(|d| { + result.push_str(locale.short_months[d.month0() as usize]); + Ok(()) + }), + LongMonthName => date.map(|d| { + result.push_str(locale.long_months[d.month0() as usize]); + Ok(()) + }), + ShortWeekdayName => date.map(|d| { + result.push_str( + locale.short_weekdays[d.weekday().num_days_from_sunday() as usize], + ); + Ok(()) + }), + LongWeekdayName => date.map(|d| { + result.push_str( + locale.long_weekdays[d.weekday().num_days_from_sunday() as usize], + ); + Ok(()) + }), + LowerAmPm => time.map(|t| { + let ampm = if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] }; + for char in ampm.chars() { + result.extend(char.to_lowercase()) + } + Ok(()) + }), + UpperAmPm => time.map(|t| { + result.push_str(if t.hour12().0 { + locale.am_pm[1] + } else { + locale.am_pm[0] + }); + Ok(()) + }), + Nanosecond => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + if nano == 0 { + Ok(()) + } else if nano % 1_000_000 == 0 { + write!(result, ".{:03}", nano / 1_000_000) + } else if nano % 1_000 == 0 { + write!(result, ".{:06}", nano / 1_000) + } else { + write!(result, ".{:09}", nano) + } + }), + Nanosecond3 => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, ".{:03}", nano / 1_000_000) + }), + Nanosecond6 => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, ".{:06}", nano / 1_000) + }), + Nanosecond9 => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, ".{:09}", nano) + }), + Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time + .map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, "{:03}", nano / 1_000_000) + }), + Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time + .map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, "{:06}", nano / 1_000) + }), + Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time + .map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, "{:09}", nano) + }), + TimezoneName => off.map(|(name, _)| { + result.push_str(name); + Ok(()) + }), + TimezoneOffsetColon => off + .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Single)), + TimezoneOffsetDoubleColon => off + .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Double)), + TimezoneOffsetTripleColon => off + .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Triple)), + TimezoneOffsetColonZ => off + .map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::Single)), + TimezoneOffset => { + off.map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::None)) + } + TimezoneOffsetZ => { + off.map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::None)) + } + Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { + return Err(fmt::Error); + } + RFC2822 => + // same as `%a, %d %b %Y %H:%M:%S %z` + { + if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { + Some(write_rfc2822_inner(result, d, t, off, locale)) + } else { + None + } + } + RFC3339 => + // same as `%Y-%m-%dT%H:%M:%S%.f%:z` + { + if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { + Some(write_rfc3339(result, crate::NaiveDateTime::new(*d, *t), off)) + } else { + None + } + } + }; + + match ret { + Some(ret) => ret?, + None => return Err(fmt::Error), // insufficient arguments for given format + } + } + + Item::Error => return Err(fmt::Error), + } + Ok(()) +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +enum Colons { + None, + Single, + Double, + Triple, +} + +/// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`. +/// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true. +#[cfg(any(feature = "alloc", feature = "std", test))] +fn write_local_minus_utc( + result: &mut String, + off: FixedOffset, + allow_zulu: bool, + colon_type: Colons, +) -> fmt::Result { + let off = off.local_minus_utc(); + if allow_zulu && off == 0 { + result.push('Z'); + return Ok(()); + } + let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; + result.push(sign); + + write_hundreds(result, (off / 3600) as u8)?; + + match colon_type { + Colons::None => write_hundreds(result, (off / 60 % 60) as u8), + Colons::Single => { + result.push(':'); + write_hundreds(result, (off / 60 % 60) as u8) + } + Colons::Double => { + result.push(':'); + write_hundreds(result, (off / 60 % 60) as u8)?; + result.push(':'); + write_hundreds(result, (off % 60) as u8) + } + Colons::Triple => Ok(()), + } +} + +/// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` +#[cfg(any(feature = "alloc", feature = "std", test))] +pub(crate) fn write_rfc3339( + result: &mut String, + dt: crate::NaiveDateTime, + off: FixedOffset, +) -> fmt::Result { + // reuse `Debug` impls which already print ISO 8601 format. + // this is faster in this way. + write!(result, "{:?}", dt)?; + write_local_minus_utc(result, off, false, Colons::Single) +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` +pub(crate) fn write_rfc2822( + result: &mut String, + dt: crate::NaiveDateTime, + off: FixedOffset, +) -> fmt::Result { + write_rfc2822_inner(result, &dt.date(), &dt.time(), off, Locales::new(None)) +} + +#[cfg(any(feature = "alloc", feature = "std", test))] +/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` +fn write_rfc2822_inner( + result: &mut String, + d: &NaiveDate, + t: &NaiveTime, + off: FixedOffset, + locale: Locales, +) -> fmt::Result { + let year = d.year(); + // RFC2822 is only defined on years 0 through 9999 + if !(0..=9999).contains(&year) { + return Err(fmt::Error); + } + + result.push_str(locale.short_weekdays[d.weekday().num_days_from_sunday() as usize]); + result.push_str(", "); + write_hundreds(result, d.day() as u8)?; + result.push(' '); + result.push_str(locale.short_months[d.month0() as usize]); + result.push(' '); + write_hundreds(result, (year / 100) as u8)?; + write_hundreds(result, (year % 100) as u8)?; + result.push(' '); + write_hundreds(result, t.hour() as u8)?; + result.push(':'); + write_hundreds(result, t.minute() as u8)?; + result.push(':'); + let sec = t.second() + t.nanosecond() / 1_000_000_000; + write_hundreds(result, sec as u8)?; + result.push(' '); + write_local_minus_utc(result, off, false, Colons::None) +} + +/// Equivalent to `{:02}` formatting for n < 100. +pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result { + if n >= 100 { + return Err(fmt::Error); + } + + let tens = b'0' + n / 10; + let ones = b'0' + n % 10; + w.write_char(tens as char)?; + w.write_char(ones as char) +} diff --git a/src/format/mod.rs b/src/format/mod.rs index 9591fa4d16..d0d88d9b01 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -35,27 +35,23 @@ extern crate alloc; #[cfg(feature = "alloc")] use alloc::boxed::Box; -#[cfg(feature = "alloc")] -use alloc::string::{String, ToString}; -#[cfg(any(feature = "alloc", feature = "std", test))] -use core::borrow::Borrow; use core::fmt; -use core::fmt::Write; use core::str::FromStr; #[cfg(any(feature = "std", test))] use std::error::Error; -#[cfg(any(feature = "alloc", feature = "std", test))] -use crate::naive::{NaiveDate, NaiveTime}; -#[cfg(any(feature = "alloc", feature = "std", test))] -use crate::offset::{FixedOffset, Offset}; -#[cfg(any(feature = "alloc", feature = "std", test))] -use crate::{Datelike, Timelike}; use crate::{Month, ParseMonthError, ParseWeekdayError, Weekday}; #[cfg(feature = "unstable-locales")] pub(crate) mod locales; +pub(crate) use formatting::write_hundreds; +#[cfg(any(feature = "alloc", feature = "std", test))] +pub use formatting::{format, format_item, DelayedFormat}; +#[cfg(feature = "unstable-locales")] +pub use formatting::{format_item_localized, format_localized}; +#[cfg(any(feature = "alloc", feature = "std", test))] +pub(crate) use formatting::{write_rfc2822, write_rfc3339}; pub use parse::{parse, parse_and_remainder}; pub use parsed::Parsed; /// L10n locales. @@ -275,15 +271,6 @@ enum InternalInternal { Nanosecond9NoDot, } -#[cfg(any(feature = "alloc", feature = "std", test))] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -enum Colons { - None, - Single, - Double, - Triple, -} - /// A single formatting item. This is used for both formatting and parsing. #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Item<'a> { @@ -428,412 +415,8 @@ const TOO_SHORT: ParseError = ParseError(ParseErrorKind::TooShort); const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong); const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat); -#[cfg(any(feature = "alloc", feature = "std", test))] -struct Locales { - short_months: &'static [&'static str], - long_months: &'static [&'static str], - short_weekdays: &'static [&'static str], - long_weekdays: &'static [&'static str], - am_pm: &'static [&'static str], -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -impl Locales { - fn new(_locale: Option) -> Self { - #[cfg(feature = "unstable-locales")] - { - let locale = _locale.unwrap_or(Locale::POSIX); - Self { - short_months: locales::short_months(locale), - long_months: locales::long_months(locale), - short_weekdays: locales::short_weekdays(locale), - long_weekdays: locales::long_weekdays(locale), - am_pm: locales::am_pm(locale), - } - } - #[cfg(not(feature = "unstable-locales"))] - Self { - short_months: &[ - "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", - ], - long_months: &[ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ], - short_weekdays: &["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], - long_weekdays: &[ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - ], - am_pm: &["AM", "PM"], - } - } -} - -/// Formats single formatting item -#[cfg(any(feature = "alloc", feature = "std", test))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] -pub fn format_item( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - item: &Item<'_>, -) -> fmt::Result { - let mut result = String::new(); - format_inner(&mut result, date, time, off, item, None)?; - w.pad(&result) -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -fn format_inner( - result: &mut String, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - item: &Item<'_>, - locale: Option, -) -> fmt::Result { - let locale = Locales::new(locale); - - match *item { - Item::Literal(s) | Item::Space(s) => result.push_str(s), - #[cfg(any(feature = "alloc", feature = "std", test))] - Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s), - - Item::Numeric(ref spec, ref pad) => { - use self::Numeric::*; - - let week_from_sun = |d: &NaiveDate| d.weeks_from(Weekday::Sun); - let week_from_mon = |d: &NaiveDate| d.weeks_from(Weekday::Mon); - - let (width, v) = match *spec { - Year => (4, date.map(|d| i64::from(d.year()))), - YearDiv100 => (2, date.map(|d| i64::from(d.year()).div_euclid(100))), - YearMod100 => (2, date.map(|d| i64::from(d.year()).rem_euclid(100))), - IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))), - IsoYearDiv100 => (2, date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))), - IsoYearMod100 => (2, date.map(|d| i64::from(d.iso_week().year()).rem_euclid(100))), - Month => (2, date.map(|d| i64::from(d.month()))), - Day => (2, date.map(|d| i64::from(d.day()))), - WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))), - WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))), - IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))), - NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday().num_days_from_sunday()))), - WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday().number_from_monday()))), - Ordinal => (3, date.map(|d| i64::from(d.ordinal()))), - Hour => (2, time.map(|t| i64::from(t.hour()))), - Hour12 => (2, time.map(|t| i64::from(t.hour12().1))), - Minute => (2, time.map(|t| i64::from(t.minute()))), - Second => (2, time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))), - Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))), - Timestamp => ( - 1, - match (date, time, off) { - (Some(d), Some(t), None) => Some(d.and_time(*t).timestamp()), - (Some(d), Some(t), Some(&(_, off))) => { - Some((d.and_time(*t) - off).timestamp()) - } - (_, _, _) => None, - }, - ), - - // for the future expansion - Internal(ref int) => match int._dummy {}, - }; - - if let Some(v) = v { - if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { - // non-four-digit years require an explicit sign as per ISO 8601 - match *pad { - Pad::None => write!(result, "{:+}", v), - Pad::Zero => write!(result, "{:+01$}", v, width + 1), - Pad::Space => write!(result, "{:+1$}", v, width + 1), - } - } else { - match *pad { - Pad::None => write!(result, "{}", v), - Pad::Zero => write!(result, "{:01$}", v, width), - Pad::Space => write!(result, "{:1$}", v, width), - } - }? - } else { - return Err(fmt::Error); // insufficient arguments for given format - } - } - - Item::Fixed(ref spec) => { - use self::Fixed::*; - - let ret = - match *spec { - ShortMonthName => date.map(|d| { - result.push_str(locale.short_months[d.month0() as usize]); - Ok(()) - }), - LongMonthName => date.map(|d| { - result.push_str(locale.long_months[d.month0() as usize]); - Ok(()) - }), - ShortWeekdayName => date.map(|d| { - result.push_str( - locale.short_weekdays[d.weekday().num_days_from_sunday() as usize], - ); - Ok(()) - }), - LongWeekdayName => date.map(|d| { - result.push_str( - locale.long_weekdays[d.weekday().num_days_from_sunday() as usize], - ); - Ok(()) - }), - LowerAmPm => time.map(|t| { - let ampm = if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] }; - for char in ampm.chars() { - result.extend(char.to_lowercase()) - } - Ok(()) - }), - UpperAmPm => time.map(|t| { - result.push_str(if t.hour12().0 { - locale.am_pm[1] - } else { - locale.am_pm[0] - }); - Ok(()) - }), - Nanosecond => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - if nano == 0 { - Ok(()) - } else if nano % 1_000_000 == 0 { - write!(result, ".{:03}", nano / 1_000_000) - } else if nano % 1_000 == 0 { - write!(result, ".{:06}", nano / 1_000) - } else { - write!(result, ".{:09}", nano) - } - }), - Nanosecond3 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, ".{:03}", nano / 1_000_000) - }), - Nanosecond6 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, ".{:06}", nano / 1_000) - }), - Nanosecond9 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, ".{:09}", nano) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:03}", nano / 1_000_000) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:06}", nano / 1_000) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:09}", nano) - }), - TimezoneName => off.map(|(name, _)| { - result.push_str(name); - Ok(()) - }), - TimezoneOffsetColon => off - .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Single)), - TimezoneOffsetDoubleColon => off - .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Double)), - TimezoneOffsetTripleColon => off - .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Triple)), - TimezoneOffsetColonZ => off - .map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::Single)), - TimezoneOffset => { - off.map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::None)) - } - TimezoneOffsetZ => { - off.map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::None)) - } - Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { - return Err(fmt::Error); - } - RFC2822 => - // same as `%a, %d %b %Y %H:%M:%S %z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc2822_inner(result, d, t, off, locale)) - } else { - None - } - } - RFC3339 => - // same as `%Y-%m-%dT%H:%M:%S%.f%:z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc3339(result, crate::NaiveDateTime::new(*d, *t), off)) - } else { - None - } - } - }; - - match ret { - Some(ret) => ret?, - None => return Err(fmt::Error), // insufficient arguments for given format - } - } - - Item::Error => return Err(fmt::Error), - } - Ok(()) -} - -/// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`. -/// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true. -#[cfg(any(feature = "alloc", feature = "std", test))] -fn write_local_minus_utc( - result: &mut String, - off: FixedOffset, - allow_zulu: bool, - colon_type: Colons, -) -> fmt::Result { - let off = off.local_minus_utc(); - if allow_zulu && off == 0 { - result.push('Z'); - return Ok(()); - } - let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; - result.push(sign); - - write_hundreds(result, (off / 3600) as u8)?; - - match colon_type { - Colons::None => write_hundreds(result, (off / 60 % 60) as u8), - Colons::Single => { - result.push(':'); - write_hundreds(result, (off / 60 % 60) as u8) - } - Colons::Double => { - result.push(':'); - write_hundreds(result, (off / 60 % 60) as u8)?; - result.push(':'); - write_hundreds(result, (off % 60) as u8) - } - Colons::Triple => Ok(()), - } -} - -/// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` -#[cfg(any(feature = "alloc", feature = "std", test))] -pub(crate) fn write_rfc3339( - result: &mut String, - dt: crate::NaiveDateTime, - off: FixedOffset, -) -> fmt::Result { - // reuse `Debug` impls which already print ISO 8601 format. - // this is faster in this way. - write!(result, "{:?}", dt)?; - write_local_minus_utc(result, off, false, Colons::Single) -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` -pub(crate) fn write_rfc2822( - result: &mut String, - dt: crate::NaiveDateTime, - off: FixedOffset, -) -> fmt::Result { - write_rfc2822_inner(result, &dt.date(), &dt.time(), off, Locales::new(None)) -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` -fn write_rfc2822_inner( - result: &mut String, - d: &NaiveDate, - t: &NaiveTime, - off: FixedOffset, - locale: Locales, -) -> fmt::Result { - let year = d.year(); - // RFC2822 is only defined on years 0 through 9999 - if !(0..=9999).contains(&year) { - return Err(fmt::Error); - } - - result.push_str(locale.short_weekdays[d.weekday().num_days_from_sunday() as usize]); - result.push_str(", "); - write_hundreds(result, d.day() as u8)?; - result.push(' '); - result.push_str(locale.short_months[d.month0() as usize]); - result.push(' '); - write_hundreds(result, (year / 100) as u8)?; - write_hundreds(result, (year % 100) as u8)?; - result.push(' '); - write_hundreds(result, t.hour() as u8)?; - result.push(':'); - write_hundreds(result, t.minute() as u8)?; - result.push(':'); - let sec = t.second() + t.nanosecond() / 1_000_000_000; - write_hundreds(result, sec as u8)?; - result.push(' '); - write_local_minus_utc(result, off, false, Colons::None) -} - -/// Equivalent to `{:02}` formatting for n < 100. -pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result { - if n >= 100 { - return Err(fmt::Error); - } - - let tens = b'0' + n / 10; - let ones = b'0' + n % 10; - w.write_char(tens as char)?; - w.write_char(ones as char) -} - -/// Tries to format given arguments with given formatting items. -/// Internally used by `DelayedFormat`. -#[cfg(any(feature = "alloc", feature = "std", test))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] -pub fn format<'a, I, B>( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - items: I, -) -> fmt::Result -where - I: Iterator + Clone, - B: Borrow>, -{ - let mut result = String::new(); - for item in items { - format_inner(&mut result, date, time, off, item.borrow(), None)?; - } - w.pad(&result) -} +mod formatting; mod parsed; // due to the size of parsing routines, they are in separate modules. @@ -841,118 +424,6 @@ mod parse; pub(crate) mod scan; pub mod strftime; - -/// A *temporary* object which can be used as an argument to `format!` or others. -/// This is normally constructed via `format` methods of each date and time type. -#[cfg(any(feature = "alloc", feature = "std", test))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] -#[derive(Debug)] -pub struct DelayedFormat { - /// The date view, if any. - date: Option, - /// The time view, if any. - time: Option, - /// The name and local-to-UTC difference for the offset (timezone), if any. - off: Option<(String, FixedOffset)>, - /// An iterator returning formatting items. - items: I, - /// Locale used for text. - // TODO: Only used with the locale feature. We should make this property - // only present when the feature is enabled. - #[cfg(feature = "unstable-locales")] - locale: Option, -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { - /// Makes a new `DelayedFormat` value out of local date and time. - #[must_use] - pub fn new(date: Option, time: Option, items: I) -> DelayedFormat { - DelayedFormat { - date, - time, - off: None, - items, - #[cfg(feature = "unstable-locales")] - locale: None, - } - } - - /// Makes a new `DelayedFormat` value out of local date and time and UTC offset. - #[must_use] - pub fn new_with_offset( - date: Option, - time: Option, - offset: &Off, - items: I, - ) -> DelayedFormat - where - Off: Offset + fmt::Display, - { - let name_and_diff = (offset.to_string(), offset.fix()); - DelayedFormat { - date, - time, - off: Some(name_and_diff), - items, - #[cfg(feature = "unstable-locales")] - locale: None, - } - } - - /// Makes a new `DelayedFormat` value out of local date and time and locale. - #[cfg(feature = "unstable-locales")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] - #[must_use] - pub fn new_with_locale( - date: Option, - time: Option, - items: I, - locale: Locale, - ) -> DelayedFormat { - DelayedFormat { date, time, off: None, items, locale: Some(locale) } - } - - /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale. - #[cfg(feature = "unstable-locales")] - #[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] - #[must_use] - pub fn new_with_offset_and_locale( - date: Option, - time: Option, - offset: &Off, - items: I, - locale: Locale, - ) -> DelayedFormat - where - Off: Offset + fmt::Display, - { - let name_and_diff = (offset.to_string(), offset.fix()); - DelayedFormat { date, time, off: Some(name_and_diff), items, locale: Some(locale) } - } -} - -#[cfg(any(feature = "alloc", feature = "std", test))] -impl<'a, I: Iterator + Clone, B: Borrow>> fmt::Display for DelayedFormat { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - #[cfg(feature = "unstable-locales")] - { - if let Some(locale) = self.locale { - return format_localized( - f, - self.date.as_ref(), - self.time.as_ref(), - self.off.as_ref(), - self.items.clone(), - locale, - ); - } - } - - format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone()) - } -} - // this implementation is here only because we need some private code from `scan` /// Parsing a `str` into a `Weekday` uses the format [`%W`](./format/strftime/index.html). @@ -991,45 +462,6 @@ impl FromStr for Weekday { } } -/// Formats single formatting item -#[cfg(feature = "unstable-locales")] -#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] -pub fn format_item_localized( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - item: &Item<'_>, - locale: Locale, -) -> fmt::Result { - let mut result = String::new(); - format_inner(&mut result, date, time, off, item, Some(locale))?; - w.pad(&result) -} - -/// Tries to format given arguments with given formatting items. -/// Internally used by `DelayedFormat`. -#[cfg(feature = "unstable-locales")] -#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))] -pub fn format_localized<'a, I, B>( - w: &mut fmt::Formatter, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - items: I, - locale: Locale, -) -> fmt::Result -where - I: Iterator + Clone, - B: Borrow>, -{ - let mut result = String::new(); - for item in items { - format_inner(&mut result, date, time, off, item.borrow(), Some(locale))?; - } - w.pad(&result) -} - /// Parsing a `str` into a `Month` uses the format [`%W`](./format/strftime/index.html). /// /// # Example From d717526b4222fd4d69c8edaf28e69ada9aab6b30 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 27 Jun 2023 12:22:41 +0200 Subject: [PATCH 26/68] Move static formatting definitions into `parse_next_item` --- src/format/strftime.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 94e315b5d1..0c2edef7a1 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -184,25 +184,6 @@ Notes: use super::{locales, Locale}; use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad}; -static D_FMT: &[Item<'static>] = - &[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]; -static D_T_FMT: &[Item<'static>] = &[ - fix!(ShortWeekdayName), - sp!(" "), - fix!(ShortMonthName), - sp!(" "), - nums!(Day), - sp!(" "), - num0!(Hour), - lit!(":"), - num0!(Minute), - lit!(":"), - num0!(Second), - sp!(" "), - num0!(Year), -]; -static T_FMT: &[Item<'static>] = &[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)]; - /// Parsing iterator for `strftime`-like format strings. #[derive(Clone, Debug)] pub struct StrftimeItems<'a> { @@ -269,6 +250,25 @@ impl<'a> Iterator for StrftimeItems<'a> { impl<'a> StrftimeItems<'a> { fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> { + static D_FMT: &[Item<'static>] = + &[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]; + static D_T_FMT: &[Item<'static>] = &[ + fix!(ShortWeekdayName), + sp!(" "), + fix!(ShortMonthName), + sp!(" "), + nums!(Day), + sp!(" "), + num0!(Hour), + lit!(":"), + num0!(Minute), + lit!(":"), + num0!(Second), + sp!(" "), + num0!(Year), + ]; + static T_FMT: &[Item<'static>] = &[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)]; + match remainder.chars().next() { // we are done None => None, From 7afc6a2600fea734d134cb595c7249320a5c8695 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 11:06:16 +0200 Subject: [PATCH 27/68] Replace macros for formatting items with functions --- src/format/mod.rs | 44 +- src/format/parse.rs | 1152 ++++++++++++++++++++-------------------- src/format/strftime.rs | 213 ++++---- 3 files changed, 710 insertions(+), 699 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index d0d88d9b01..d9124fcd4c 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -295,40 +295,24 @@ pub enum Item<'a> { Error, } -macro_rules! lit { - ($x:expr) => { - Item::Literal($x) - }; +const fn num(numeric: Numeric) -> Item<'static> { + Item::Numeric(numeric, Pad::None) } -macro_rules! sp { - ($x:expr) => { - Item::Space($x) - }; -} -macro_rules! num { - ($x:ident) => { - Item::Numeric(Numeric::$x, Pad::None) - }; -} -macro_rules! num0 { - ($x:ident) => { - Item::Numeric(Numeric::$x, Pad::Zero) - }; + +const fn num0(numeric: Numeric) -> Item<'static> { + Item::Numeric(numeric, Pad::Zero) } -macro_rules! nums { - ($x:ident) => { - Item::Numeric(Numeric::$x, Pad::Space) - }; + +const fn nums(numeric: Numeric) -> Item<'static> { + Item::Numeric(numeric, Pad::Space) } -macro_rules! fix { - ($x:ident) => { - Item::Fixed(Fixed::$x) - }; + +const fn fixed(fixed: Fixed) -> Item<'static> { + Item::Fixed(fixed) } -macro_rules! internal_fix { - ($x:ident) => { - Item::Fixed(Fixed::Internal(InternalFixed { val: InternalInternal::$x })) - }; + +const fn internal_fixed(val: InternalInternal) -> Item<'static> { + Item::Fixed(Fixed::Internal(InternalFixed { val })) } /// An error from the `parse` function. diff --git a/src/format/parse.rs b/src/format/parse.rs index 619d0a9ef4..e390dd3ea3 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -566,6 +566,10 @@ mod tests { #[test] fn test_parse() { + use crate::format::InternalInternal::*; + use crate::format::Item::{Literal, Space}; + use crate::format::Numeric::*; + // workaround for Rust issue #22255 fn parse_all(s: &str, items: &[Item]) -> ParseResult { let mut parsed = Parsed::new(); @@ -590,658 +594,658 @@ mod tests { check!("a", []; TOO_LONG); // whitespaces - check!("", [sp!("")]; ); - check!(" ", [sp!("")]; ); - check!("\t", [sp!("")]; ); - check!(" \n\r \n", [sp!("")]; ); - check!("a", [sp!("")]; TOO_LONG); + check!("", [Space("")]; ); + check!(" ", [Space("")]; ); + check!("\t", [Space("")]; ); + check!(" \n\r \n", [Space("")]; ); + check!("a", [Space("")]; TOO_LONG); // literal - check!("", [lit!("a")]; TOO_SHORT); - check!(" ", [lit!("a")]; INVALID); - check!("a", [lit!("a")]; ); - check!("+", [lit!("+")]; ); - check!("-", [lit!("-")]; ); - check!("βˆ’", [lit!("βˆ’")]; ); // MINUS SIGN (U+2212) - // a Literal may contain whitespace and match whitespace, but this should not be done - check!(" ", [lit!(" ")]; ); - check!("aa", [lit!("a")]; TOO_LONG); - check!("A", [lit!("a")]; INVALID); - check!("xy", [lit!("xy")]; ); - check!("xy", [lit!("x"), lit!("y")]; ); - check!("1", [lit!("1")]; ); - check!("1234", [lit!("1234")]; ); - check!("+1234", [lit!("+1234")]; ); - check!("-1234", [lit!("-1234")]; ); - check!("βˆ’1234", [lit!("βˆ’1234")]; ); // MINUS SIGN (U+2212) - check!("PST", [lit!("PST")]; ); - check!("🀠", [lit!("🀠")]; ); - check!("🀠a", [lit!("🀠"), lit!("a")]; ); - check!("🀠a🀠", [lit!("🀠"), lit!("a🀠")]; ); - check!("a🀠b", [lit!("a"), lit!("🀠"), lit!("b")]; ); + check!("", [Literal("a")]; TOO_SHORT); + check!(" ", [Literal("a")]; INVALID); + check!("a", [Literal("a")]; ); + check!("+", [Literal("+")]; ); + check!("-", [Literal("-")]; ); + check!("βˆ’", [Literal("βˆ’")]; ); // MINUS SIGN (U+2212) + // a Literal may contain whitespace and match whitespace, but this should not be done + check!(" ", [Literal(" ")]; ); + check!("aa", [Literal("a")]; TOO_LONG); + check!("A", [Literal("a")]; INVALID); + check!("xy", [Literal("xy")]; ); + check!("xy", [Literal("x"), Literal("y")]; ); + check!("1", [Literal("1")]; ); + check!("1234", [Literal("1234")]; ); + check!("+1234", [Literal("+1234")]; ); + check!("-1234", [Literal("-1234")]; ); + check!("βˆ’1234", [Literal("βˆ’1234")]; ); // MINUS SIGN (U+2212) + check!("PST", [Literal("PST")]; ); + check!("🀠", [Literal("🀠")]; ); + check!("🀠a", [Literal("🀠"), Literal("a")]; ); + check!("🀠a🀠", [Literal("🀠"), Literal("a🀠")]; ); + check!("a🀠b", [Literal("a"), Literal("🀠"), Literal("b")]; ); // literals can be together - check!("xy", [lit!("xy")]; ); - check!("xyz", [lit!("xyz")]; ); + check!("xy", [Literal("xy")]; ); + check!("xyz", [Literal("xyz")]; ); // or literals can be apart - check!("xy", [lit!("x"), lit!("y")]; ); - check!("xyz", [lit!("x"), lit!("yz")]; ); - check!("xyz", [lit!("xy"), lit!("z")]; ); - check!("xyz", [lit!("x"), lit!("y"), lit!("z")]; ); + check!("xy", [Literal("x"), Literal("y")]; ); + check!("xyz", [Literal("x"), Literal("yz")]; ); + check!("xyz", [Literal("xy"), Literal("z")]; ); + check!("xyz", [Literal("x"), Literal("y"), Literal("z")]; ); // - check!("x y", [lit!("x"), lit!("y")]; INVALID); - check!("xy", [lit!("x"), sp!(""), lit!("y")]; ); - check!("x y", [lit!("x"), sp!(""), lit!("y")]; ); + check!("x y", [Literal("x"), Literal("y")]; INVALID); + check!("xy", [Literal("x"), Space(""), Literal("y")]; ); + check!("x y", [Literal("x"), Space(""), Literal("y")]; ); // numeric - check!("1987", [num!(Year)]; year: 1987); - check!("1987 ", [num!(Year)]; TOO_LONG); - check!("0x12", [num!(Year)]; TOO_LONG); // `0` is parsed - check!("x123", [num!(Year)]; INVALID); - check!("2015", [num!(Year)]; year: 2015); - check!("0000", [num!(Year)]; year: 0); - check!("9999", [num!(Year)]; year: 9999); - check!(" \t987", [num!(Year)]; year: 987); - check!("5", [num!(Year)]; year: 5); - check!("5\0", [num!(Year)]; TOO_LONG); - check!("\x005", [num!(Year)]; INVALID); - check!("", [num!(Year)]; TOO_SHORT); - check!("12345", [num!(Year), lit!("5")]; year: 1234); - check!("12345", [nums!(Year), lit!("5")]; year: 1234); - check!("12345", [num0!(Year), lit!("5")]; year: 1234); - check!("12341234", [num!(Year), num!(Year)]; year: 1234); - check!("1234 1234", [num!(Year), num!(Year)]; year: 1234); - check!("1234 1235", [num!(Year), num!(Year)]; IMPOSSIBLE); - check!("1234 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); - check!("1234x1234", [num!(Year), lit!("x"), num!(Year)]; year: 1234); - check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); - check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID); + check!("1987", [num(Year)]; year: 1987); + check!("1987 ", [num(Year)]; TOO_LONG); + check!("0x12", [num(Year)]; TOO_LONG); // `0` is parsed + check!("x123", [num(Year)]; INVALID); + check!("2015", [num(Year)]; year: 2015); + check!("0000", [num(Year)]; year: 0); + check!("9999", [num(Year)]; year: 9999); + check!(" \t987", [num(Year)]; year: 987); + check!("5", [num(Year)]; year: 5); + check!("5\0", [num(Year)]; TOO_LONG); + check!("\x005", [num(Year)]; INVALID); + check!("", [num(Year)]; TOO_SHORT); + check!("12345", [num(Year), Literal("5")]; year: 1234); + check!("12345", [nums(Year), Literal("5")]; year: 1234); + check!("12345", [num0(Year), Literal("5")]; year: 1234); + check!("12341234", [num(Year), num(Year)]; year: 1234); + check!("1234 1234", [num(Year), num(Year)]; year: 1234); + check!("1234 1235", [num(Year), num(Year)]; IMPOSSIBLE); + check!("1234 1234", [num(Year), Literal("x"), num(Year)]; INVALID); + check!("1234x1234", [num(Year), Literal("x"), num(Year)]; year: 1234); + check!("1234xx1234", [num(Year), Literal("x"), num(Year)]; INVALID); + check!("1234 x 1234", [num(Year), Literal("x"), num(Year)]; INVALID); // signed numeric - check!("-42", [num!(Year)]; year: -42); - check!("+42", [num!(Year)]; year: 42); - check!("-0042", [num!(Year)]; year: -42); - check!("+0042", [num!(Year)]; year: 42); - check!("-42195", [num!(Year)]; year: -42195); - check!("βˆ’42195", [num!(Year)]; INVALID); // MINUS SIGN (U+2212) - check!("+42195", [num!(Year)]; year: 42195); - check!(" -42195", [num!(Year)]; year: -42195); - check!(" +42195", [num!(Year)]; year: 42195); - check!(" - 42", [num!(Year)]; INVALID); - check!(" + 42", [num!(Year)]; INVALID); - check!(" -42195", [sp!(" "), num!(Year)]; year: -42195); - check!(" βˆ’42195", [sp!(" "), num!(Year)]; INVALID); // MINUS SIGN (U+2212) - check!(" +42195", [sp!(" "), num!(Year)]; year: 42195); - check!(" - 42", [sp!(" "), num!(Year)]; INVALID); - check!(" + 42", [sp!(" "), num!(Year)]; INVALID); - check!("-", [num!(Year)]; TOO_SHORT); - check!("+", [num!(Year)]; TOO_SHORT); + check!("-42", [num(Year)]; year: -42); + check!("+42", [num(Year)]; year: 42); + check!("-0042", [num(Year)]; year: -42); + check!("+0042", [num(Year)]; year: 42); + check!("-42195", [num(Year)]; year: -42195); + check!("βˆ’42195", [num(Year)]; INVALID); // MINUS SIGN (U+2212) + check!("+42195", [num(Year)]; year: 42195); + check!(" -42195", [num(Year)]; year: -42195); + check!(" +42195", [num(Year)]; year: 42195); + check!(" - 42", [num(Year)]; INVALID); + check!(" + 42", [num(Year)]; INVALID); + check!(" -42195", [Space(" "), num(Year)]; year: -42195); + check!(" βˆ’42195", [Space(" "), num(Year)]; INVALID); // MINUS SIGN (U+2212) + check!(" +42195", [Space(" "), num(Year)]; year: 42195); + check!(" - 42", [Space(" "), num(Year)]; INVALID); + check!(" + 42", [Space(" "), num(Year)]; INVALID); + check!("-", [num(Year)]; TOO_SHORT); + check!("+", [num(Year)]; TOO_SHORT); // unsigned numeric - check!("345", [num!(Ordinal)]; ordinal: 345); - check!("+345", [num!(Ordinal)]; INVALID); - check!("-345", [num!(Ordinal)]; INVALID); - check!(" 345", [num!(Ordinal)]; ordinal: 345); - check!("βˆ’345", [num!(Ordinal)]; INVALID); // MINUS SIGN (U+2212) - check!("345 ", [num!(Ordinal)]; TOO_LONG); - check!(" 345", [sp!(" "), num!(Ordinal)]; ordinal: 345); - check!("345 ", [num!(Ordinal), sp!(" ")]; ordinal: 345); - check!("345🀠 ", [num!(Ordinal), lit!("🀠"), sp!(" ")]; ordinal: 345); - check!("345🀠", [num!(Ordinal)]; TOO_LONG); - check!("\u{0363}345", [num!(Ordinal)]; INVALID); - check!(" +345", [num!(Ordinal)]; INVALID); - check!(" -345", [num!(Ordinal)]; INVALID); + check!("345", [num(Ordinal)]; ordinal: 345); + check!("+345", [num(Ordinal)]; INVALID); + check!("-345", [num(Ordinal)]; INVALID); + check!(" 345", [num(Ordinal)]; ordinal: 345); + check!("βˆ’345", [num(Ordinal)]; INVALID); // MINUS SIGN (U+2212) + check!("345 ", [num(Ordinal)]; TOO_LONG); + check!(" 345", [Space(" "), num(Ordinal)]; ordinal: 345); + check!("345 ", [num(Ordinal), Space(" ")]; ordinal: 345); + check!("345🀠 ", [num(Ordinal), Literal("🀠"), Space(" ")]; ordinal: 345); + check!("345🀠", [num(Ordinal)]; TOO_LONG); + check!("\u{0363}345", [num(Ordinal)]; INVALID); + check!(" +345", [num(Ordinal)]; INVALID); + check!(" -345", [num(Ordinal)]; INVALID); // various numeric fields check!("1234 5678", - [num!(Year), num!(IsoYear)]; + [num(Year), num(IsoYear)]; year: 1234, isoyear: 5678); check!("12 34 56 78", - [num!(YearDiv100), num!(YearMod100), num!(IsoYearDiv100), num!(IsoYearMod100)]; + [num(YearDiv100), num(YearMod100), num(IsoYearDiv100), num(IsoYearMod100)]; year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78); check!("1 2 3 4 5 6", - [num!(Month), num!(Day), num!(WeekFromSun), num!(WeekFromMon), num!(IsoWeek), - num!(NumDaysFromSun)]; + [num(Month), num(Day), num(WeekFromSun), num(WeekFromMon), num(IsoWeek), + num(NumDaysFromSun)]; month: 1, day: 2, week_from_sun: 3, week_from_mon: 4, isoweek: 5, weekday: Weekday::Sat); check!("7 89 01", - [num!(WeekdayFromMon), num!(Ordinal), num!(Hour12)]; + [num(WeekdayFromMon), num(Ordinal), num(Hour12)]; weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1); check!("23 45 6 78901234 567890123", - [num!(Hour), num!(Minute), num!(Second), num!(Nanosecond), num!(Timestamp)]; + [num(Hour), num(Minute), num(Second), num(Nanosecond), num(Timestamp)]; hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, timestamp: 567_890_123); // fixed: month and weekday names - check!("apr", [fix!(ShortMonthName)]; month: 4); - check!("Apr", [fix!(ShortMonthName)]; month: 4); - check!("APR", [fix!(ShortMonthName)]; month: 4); - check!("ApR", [fix!(ShortMonthName)]; month: 4); - check!("April", [fix!(ShortMonthName)]; TOO_LONG); // `Apr` is parsed - check!("A", [fix!(ShortMonthName)]; TOO_SHORT); - check!("Sol", [fix!(ShortMonthName)]; INVALID); - check!("Apr", [fix!(LongMonthName)]; month: 4); - check!("Apri", [fix!(LongMonthName)]; TOO_LONG); // `Apr` is parsed - check!("April", [fix!(LongMonthName)]; month: 4); - check!("Aprill", [fix!(LongMonthName)]; TOO_LONG); - check!("Aprill", [fix!(LongMonthName), lit!("l")]; month: 4); - check!("Aprl", [fix!(LongMonthName), lit!("l")]; month: 4); - check!("April", [fix!(LongMonthName), lit!("il")]; TOO_SHORT); // do not backtrack - check!("thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("THU", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("tHu", [fix!(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thursday", [fix!(ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("T", [fix!(ShortWeekdayName)]; TOO_SHORT); - check!("The", [fix!(ShortWeekdayName)]; INVALID); - check!("Nop", [fix!(ShortWeekdayName)]; INVALID); - check!("Thu", [fix!(LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thur", [fix!(LongWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("Thurs", [fix!(LongWeekdayName)]; TOO_LONG); // ditto - check!("Thursday", [fix!(LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thursdays", [fix!(LongWeekdayName)]; TOO_LONG); - check!("Thursdays", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu); - check!("Thus", [fix!(LongWeekdayName), lit!("s")]; weekday: Weekday::Thu); - check!("Thursday", [fix!(LongWeekdayName), lit!("rsday")]; TOO_SHORT); // do not backtrack + check!("apr", [fixed(Fixed::ShortMonthName)]; month: 4); + check!("Apr", [fixed(Fixed::ShortMonthName)]; month: 4); + check!("APR", [fixed(Fixed::ShortMonthName)]; month: 4); + check!("ApR", [fixed(Fixed::ShortMonthName)]; month: 4); + check!("April", [fixed(Fixed::ShortMonthName)]; TOO_LONG); // `Apr` is parsed + check!("A", [fixed(Fixed::ShortMonthName)]; TOO_SHORT); + check!("Sol", [fixed(Fixed::ShortMonthName)]; INVALID); + check!("Apr", [fixed(Fixed::LongMonthName)]; month: 4); + check!("Apri", [fixed(Fixed::LongMonthName)]; TOO_LONG); // `Apr` is parsed + check!("April", [fixed(Fixed::LongMonthName)]; month: 4); + check!("Aprill", [fixed(Fixed::LongMonthName)]; TOO_LONG); + check!("Aprill", [fixed(Fixed::LongMonthName), Literal("l")]; month: 4); + check!("Aprl", [fixed(Fixed::LongMonthName), Literal("l")]; month: 4); + check!("April", [fixed(Fixed::LongMonthName), Literal("il")]; TOO_SHORT); // do not backtrack + check!("thu", [fixed(Fixed::ShortWeekdayName)]; weekday: Weekday::Thu); + check!("Thu", [fixed(Fixed::ShortWeekdayName)]; weekday: Weekday::Thu); + check!("THU", [fixed(Fixed::ShortWeekdayName)]; weekday: Weekday::Thu); + check!("tHu", [fixed(Fixed::ShortWeekdayName)]; weekday: Weekday::Thu); + check!("Thursday", [fixed(Fixed::ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed + check!("T", [fixed(Fixed::ShortWeekdayName)]; TOO_SHORT); + check!("The", [fixed(Fixed::ShortWeekdayName)]; INVALID); + check!("Nop", [fixed(Fixed::ShortWeekdayName)]; INVALID); + check!("Thu", [fixed(Fixed::LongWeekdayName)]; weekday: Weekday::Thu); + check!("Thur", [fixed(Fixed::LongWeekdayName)]; TOO_LONG); // `Thu` is parsed + check!("Thurs", [fixed(Fixed::LongWeekdayName)]; TOO_LONG); // ditto + check!("Thursday", [fixed(Fixed::LongWeekdayName)]; weekday: Weekday::Thu); + check!("Thursdays", [fixed(Fixed::LongWeekdayName)]; TOO_LONG); + check!("Thursdays", [fixed(Fixed::LongWeekdayName), Literal("s")]; weekday: Weekday::Thu); + check!("Thus", [fixed(Fixed::LongWeekdayName), Literal("s")]; weekday: Weekday::Thu); + check!("Thursday", [fixed(Fixed::LongWeekdayName), Literal("rsday")]; TOO_SHORT); // do not backtrack // fixed: am/pm - check!("am", [fix!(LowerAmPm)]; hour_div_12: 0); - check!("pm", [fix!(LowerAmPm)]; hour_div_12: 1); - check!("AM", [fix!(LowerAmPm)]; hour_div_12: 0); - check!("PM", [fix!(LowerAmPm)]; hour_div_12: 1); - check!("am", [fix!(UpperAmPm)]; hour_div_12: 0); - check!("pm", [fix!(UpperAmPm)]; hour_div_12: 1); - check!("AM", [fix!(UpperAmPm)]; hour_div_12: 0); - check!("PM", [fix!(UpperAmPm)]; hour_div_12: 1); - check!("Am", [fix!(LowerAmPm)]; hour_div_12: 0); - check!(" Am", [fix!(LowerAmPm)]; INVALID); - check!("ame", [fix!(LowerAmPm)]; TOO_LONG); // `am` is parsed - check!("a", [fix!(LowerAmPm)]; TOO_SHORT); - check!("p", [fix!(LowerAmPm)]; TOO_SHORT); - check!("x", [fix!(LowerAmPm)]; TOO_SHORT); - check!("xx", [fix!(LowerAmPm)]; INVALID); - check!("", [fix!(LowerAmPm)]; TOO_SHORT); + check!("am", [fixed(Fixed::LowerAmPm)]; hour_div_12: 0); + check!("pm", [fixed(Fixed::LowerAmPm)]; hour_div_12: 1); + check!("AM", [fixed(Fixed::LowerAmPm)]; hour_div_12: 0); + check!("PM", [fixed(Fixed::LowerAmPm)]; hour_div_12: 1); + check!("am", [fixed(Fixed::UpperAmPm)]; hour_div_12: 0); + check!("pm", [fixed(Fixed::UpperAmPm)]; hour_div_12: 1); + check!("AM", [fixed(Fixed::UpperAmPm)]; hour_div_12: 0); + check!("PM", [fixed(Fixed::UpperAmPm)]; hour_div_12: 1); + check!("Am", [fixed(Fixed::LowerAmPm)]; hour_div_12: 0); + check!(" Am", [fixed(Fixed::LowerAmPm)]; INVALID); + check!("ame", [fixed(Fixed::LowerAmPm)]; TOO_LONG); // `am` is parsed + check!("a", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); + check!("p", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); + check!("x", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); + check!("xx", [fixed(Fixed::LowerAmPm)]; INVALID); + check!("", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); // fixed: dot plus nanoseconds - check!("", [fix!(Nanosecond)]; ); // no field set, but not an error - check!(".", [fix!(Nanosecond)]; TOO_SHORT); - check!("4", [fix!(Nanosecond)]; TOO_LONG); // never consumes `4` - check!("4", [fix!(Nanosecond), num!(Second)]; second: 4); - check!(".0", [fix!(Nanosecond)]; nanosecond: 0); - check!(".4", [fix!(Nanosecond)]; nanosecond: 400_000_000); - check!(".42", [fix!(Nanosecond)]; nanosecond: 420_000_000); - check!(".421", [fix!(Nanosecond)]; nanosecond: 421_000_000); - check!(".42195", [fix!(Nanosecond)]; nanosecond: 421_950_000); - check!(".421950803", [fix!(Nanosecond)]; nanosecond: 421_950_803); - check!(".421950803547", [fix!(Nanosecond)]; nanosecond: 421_950_803); - check!(".000000003547", [fix!(Nanosecond)]; nanosecond: 3); - check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0); - check!(".", [fix!(Nanosecond)]; TOO_SHORT); - check!(".4x", [fix!(Nanosecond)]; TOO_LONG); - check!(". 4", [fix!(Nanosecond)]; INVALID); - check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming + check!("", [fixed(Fixed::Nanosecond)]; ); // no field set, but not an error + check!(".", [fixed(Fixed::Nanosecond)]; TOO_SHORT); + check!("4", [fixed(Fixed::Nanosecond)]; TOO_LONG); // never consumes `4` + check!("4", [fixed(Fixed::Nanosecond), num(Second)]; second: 4); + check!(".0", [fixed(Fixed::Nanosecond)]; nanosecond: 0); + check!(".4", [fixed(Fixed::Nanosecond)]; nanosecond: 400_000_000); + check!(".42", [fixed(Fixed::Nanosecond)]; nanosecond: 420_000_000); + check!(".421", [fixed(Fixed::Nanosecond)]; nanosecond: 421_000_000); + check!(".42195", [fixed(Fixed::Nanosecond)]; nanosecond: 421_950_000); + check!(".421950803", [fixed(Fixed::Nanosecond)]; nanosecond: 421_950_803); + check!(".421950803547", [fixed(Fixed::Nanosecond)]; nanosecond: 421_950_803); + check!(".000000003547", [fixed(Fixed::Nanosecond)]; nanosecond: 3); + check!(".000000000547", [fixed(Fixed::Nanosecond)]; nanosecond: 0); + check!(".", [fixed(Fixed::Nanosecond)]; TOO_SHORT); + check!(".4x", [fixed(Fixed::Nanosecond)]; TOO_LONG); + check!(". 4", [fixed(Fixed::Nanosecond)]; INVALID); + check!(" .4", [fixed(Fixed::Nanosecond)]; TOO_LONG); // no automatic trimming // fixed: nanoseconds without the dot - check!("", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!(".", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("0", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("421", [internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); - check!("42143", [internal_fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43); - check!("42195", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); - check!("4x", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!(" 4", [internal_fix!(Nanosecond3NoDot)]; INVALID); - check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID); - - check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!(".", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("42195", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("421950", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); - check!("000003", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 3000); - check!("000000", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 0); - check!("4x", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!(" 4", [internal_fix!(Nanosecond6NoDot)]; INVALID); - check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID); - - check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!(".", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); - check!("000000003", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 3); - check!("42195080354", [internal_fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 - check!("421950803547", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG); - check!("000000000", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 0); - check!("00000000x", [internal_fix!(Nanosecond9NoDot)]; INVALID); - check!(" 4", [internal_fix!(Nanosecond9NoDot)]; INVALID); - check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID); + check!("", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); + check!(".", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); + check!("0", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); + check!("4", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); + check!("42", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); + check!("421", [internal_fixed(Nanosecond3NoDot)]; nanosecond: 421_000_000); + check!("42143", [internal_fixed(Nanosecond3NoDot), num(Second)]; nanosecond: 421_000_000, second: 43); + check!("42195", [internal_fixed(Nanosecond3NoDot)]; TOO_LONG); + check!("4x", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); + check!(" 4", [internal_fixed(Nanosecond3NoDot)]; INVALID); + check!(".421", [internal_fixed(Nanosecond3NoDot)]; INVALID); + + check!("", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); + check!(".", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); + check!("0", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); + check!("42195", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); + check!("421950", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 421_950_000); + check!("000003", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 3000); + check!("000000", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 0); + check!("4x", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); + check!(" 4", [internal_fixed(Nanosecond6NoDot)]; INVALID); + check!(".42100", [internal_fixed(Nanosecond6NoDot)]; INVALID); + + check!("", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); + check!(".", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); + check!("42195", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); + check!("421950803", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 421_950_803); + check!("000000003", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 3); + check!("42195080354", [internal_fixed(Nanosecond9NoDot), num(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 + check!("421950803547", [internal_fixed(Nanosecond9NoDot)]; TOO_LONG); + check!("000000000", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 0); + check!("00000000x", [internal_fixed(Nanosecond9NoDot)]; INVALID); + check!(" 4", [internal_fixed(Nanosecond9NoDot)]; INVALID); + check!(".42100000", [internal_fixed(Nanosecond9NoDot)]; INVALID); // fixed: timezone offsets // TimezoneOffset - check!("1", [fix!(TimezoneOffset)]; INVALID); - check!("12", [fix!(TimezoneOffset)]; INVALID); - check!("123", [fix!(TimezoneOffset)]; INVALID); - check!("1234", [fix!(TimezoneOffset)]; INVALID); - check!("12345", [fix!(TimezoneOffset)]; INVALID); - check!("123456", [fix!(TimezoneOffset)]; INVALID); - check!("1234567", [fix!(TimezoneOffset)]; INVALID); - check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12345", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+123456", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+1234567", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12345678", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12:3", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12:34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("-12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("βˆ’12:34", [fix!(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:34:5", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:34:56:", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("12:34", [fix!(TimezoneOffset)]; INVALID); - check!("12:34:56", [fix!(TimezoneOffset)]; INVALID); - check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12: :34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12:::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12::::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffset)]; offset: 45_240); - check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:3456", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+1234:56", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+1234:567", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); - check!("βˆ’00:00", [fix!(TimezoneOffset)]; offset: 0); // MINUS SIGN (U+2212) - check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); - check!("-00:01", [fix!(TimezoneOffset)]; offset: -60); - check!("+00:30", [fix!(TimezoneOffset)]; offset: 1_800); - check!("-00:30", [fix!(TimezoneOffset)]; offset: -1_800); - check!("+24:00", [fix!(TimezoneOffset)]; offset: 86_400); - check!("-24:00", [fix!(TimezoneOffset)]; offset: -86_400); - check!("βˆ’24:00", [fix!(TimezoneOffset)]; offset: -86_400); // MINUS SIGN (U+2212) - check!("+99:59", [fix!(TimezoneOffset)]; offset: 359_940); - check!("-99:59", [fix!(TimezoneOffset)]; offset: -359_940); - check!("+00:60", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE); - check!("#12:34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12 34 ", [fix!(TimezoneOffset)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!(" βˆ’12:34", [fix!(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("12:34 ", [fix!(TimezoneOffset)]; INVALID); - check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240); - check!(" 12:34", [fix!(TimezoneOffset)]; INVALID); - check!("", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+12345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fix!(TimezoneOffset), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fix!(TimezoneOffset), lit!(":")]; offset: 45_240); - check!("Z12:34", [fix!(TimezoneOffset)]; INVALID); - check!("X12:34", [fix!(TimezoneOffset)]; INVALID); - check!("Z+12:34", [fix!(TimezoneOffset)]; INVALID); - check!("X+12:34", [fix!(TimezoneOffset)]; INVALID); - check!("Xβˆ’12:34", [fix!(TimezoneOffset)]; INVALID); // MINUS SIGN (U+2212) - check!("🀠+12:34", [fix!(TimezoneOffset)]; INVALID); - check!("+12:34🀠", [fix!(TimezoneOffset)]; TOO_LONG); - check!("+12:🀠34", [fix!(TimezoneOffset)]; INVALID); - check!("+1234🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: 45_240); - check!("-1234🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); - check!("βˆ’1234🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: 45_240); - check!("-12:34🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); - check!("βˆ’12:34🀠", [fix!(TimezoneOffset), lit!("🀠")]; offset: -45_240); // MINUS SIGN (U+2212) - check!("🀠+12:34", [lit!("🀠"), fix!(TimezoneOffset)]; offset: 45_240); - check!("Z", [fix!(TimezoneOffset)]; INVALID); - check!("A", [fix!(TimezoneOffset)]; INVALID); - check!("PST", [fix!(TimezoneOffset)]; INVALID); - check!("#Z", [fix!(TimezoneOffset)]; INVALID); - check!(":Z", [fix!(TimezoneOffset)]; INVALID); - check!("+Z", [fix!(TimezoneOffset)]; TOO_SHORT); - check!("+:Z", [fix!(TimezoneOffset)]; INVALID); - check!("+Z:", [fix!(TimezoneOffset)]; INVALID); - check!("z", [fix!(TimezoneOffset)]; INVALID); - check!(" :Z", [fix!(TimezoneOffset)]; INVALID); - check!(" Z", [fix!(TimezoneOffset)]; INVALID); - check!(" z", [fix!(TimezoneOffset)]; INVALID); + check!("1", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("12", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("123", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("1234", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("12345", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("123456", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("1234567", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("+1", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); + check!("+12", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); + check!("+123", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); + check!("+1234", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("+12345", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+123456", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+1234567", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12345678", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12:", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); + check!("+12:3", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); + check!("+12:34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("-12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("βˆ’12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12:34:5", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12:34:56", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12:34:56:", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12 34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("+12 34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("12:34:56", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("+12::34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("+12: :34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("+12:::34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("+12::::34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("+12::34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("+12:34:56", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12:3456", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+1234:56", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+1234:567", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+00:00", [fixed(Fixed::TimezoneOffset)]; offset: 0); + check!("-00:00", [fixed(Fixed::TimezoneOffset)]; offset: 0); + check!("βˆ’00:00", [fixed(Fixed::TimezoneOffset)]; offset: 0); // MINUS SIGN (U+2212) + check!("+00:01", [fixed(Fixed::TimezoneOffset)]; offset: 60); + check!("-00:01", [fixed(Fixed::TimezoneOffset)]; offset: -60); + check!("+00:30", [fixed(Fixed::TimezoneOffset)]; offset: 1_800); + check!("-00:30", [fixed(Fixed::TimezoneOffset)]; offset: -1_800); + check!("+24:00", [fixed(Fixed::TimezoneOffset)]; offset: 86_400); + check!("-24:00", [fixed(Fixed::TimezoneOffset)]; offset: -86_400); + check!("βˆ’24:00", [fixed(Fixed::TimezoneOffset)]; offset: -86_400); // MINUS SIGN (U+2212) + check!("+99:59", [fixed(Fixed::TimezoneOffset)]; offset: 359_940); + check!("-99:59", [fixed(Fixed::TimezoneOffset)]; offset: -359_940); + check!("+00:60", [fixed(Fixed::TimezoneOffset)]; OUT_OF_RANGE); + check!("+00:99", [fixed(Fixed::TimezoneOffset)]; OUT_OF_RANGE); + check!("#12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("+12:34 ", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12 34 ", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!(" +12:34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!(" βˆ’12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("12:34 ", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!(" +12:34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("\t -12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); + check!(" 12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); + check!("+", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); + check!("+12345", [fixed(Fixed::TimezoneOffset), num(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fixed(Fixed::TimezoneOffset), num(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fixed(Fixed::TimezoneOffset), Literal(":")]; offset: 45_240); + check!("Z12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("X12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("Z+12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("X+12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("Xβˆ’12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); // MINUS SIGN (U+2212) + check!("🀠+12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("+12:34🀠", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); + check!("+12:🀠34", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("+1234🀠", [fixed(Fixed::TimezoneOffset), Literal("🀠")]; offset: 45_240); + check!("-1234🀠", [fixed(Fixed::TimezoneOffset), Literal("🀠")]; offset: -45_240); + check!("βˆ’1234🀠", [fixed(Fixed::TimezoneOffset), Literal("🀠")]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34🀠", [fixed(Fixed::TimezoneOffset), Literal("🀠")]; offset: 45_240); + check!("-12:34🀠", [fixed(Fixed::TimezoneOffset), Literal("🀠")]; offset: -45_240); + check!("βˆ’12:34🀠", [fixed(Fixed::TimezoneOffset), Literal("🀠")]; offset: -45_240); // MINUS SIGN (U+2212) + check!("🀠+12:34", [Literal("🀠"), fixed(Fixed::TimezoneOffset)]; offset: 45_240); + check!("Z", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("A", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("PST", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("#Z", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!(":Z", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("+Z", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); + check!("+:Z", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("+Z:", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("z", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!(" :Z", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!(" Z", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!(" z", [fixed(Fixed::TimezoneOffset)]; INVALID); // TimezoneOffsetColon - check!("1", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12", [fix!(TimezoneOffsetColon)]; INVALID); - check!("123", [fix!(TimezoneOffsetColon)]; INVALID); - check!("1234", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12345", [fix!(TimezoneOffsetColon)]; INVALID); - check!("123456", [fix!(TimezoneOffsetColon)]; INVALID); - check!("1234567", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12345678", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+1", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("-1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); - check!("βˆ’1234", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+123456", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+1234567", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12345678", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("1:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:3", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34:5", [fix!(TimezoneOffsetColon)]; INVALID); - check!("12:34:56", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+1:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12:", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:3", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("-12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); - check!("βˆ’12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:5", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:7", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:78", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:3456", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("+1234:56", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!("βˆ’12:34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("βˆ’12 : 34", [fix!(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("-12 : 34", [fix!(TimezoneOffsetColon)]; offset: -45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: :34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12:::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("#1234", [fix!(TimezoneOffsetColon)]; INVALID); - check!("#12:34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffsetColon)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("\t\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 45_240); - check!("12:34 ", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" 12:34", [fix!(TimezoneOffsetColon)]; INVALID); - check!("", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!(":", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+12345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fix!(TimezoneOffsetColon), lit!(":")]; offset: 45_240); - check!("Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!("A", [fix!(TimezoneOffsetColon)]; INVALID); - check!("PST", [fix!(TimezoneOffsetColon)]; INVALID); - check!("#Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(":Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+Z", [fix!(TimezoneOffsetColon)]; TOO_SHORT); - check!("+:Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!("+Z:", [fix!(TimezoneOffsetColon)]; INVALID); - check!("z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" :Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" Z", [fix!(TimezoneOffsetColon)]; INVALID); - check!(" z", [fix!(TimezoneOffsetColon)]; INVALID); + check!("1", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("123", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("1234", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12345", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("123456", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("1234567", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12345678", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("+1", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); + check!("+12", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); + check!("+123", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); + check!("+1234", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("-1234", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); + check!("βˆ’1234", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+123456", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+1234567", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+12345678", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("1:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12:3", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12:34", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12:34:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12:34:5", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("12:34:56", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("+1:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("+12:", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:3", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("-12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); + check!("βˆ’12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:5", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:7", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:78", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+12:3456", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("+1234:56", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!("βˆ’12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("βˆ’12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12 :34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12: 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12: 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12 :34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("-12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); + check!("+12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12: :34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12:::34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12::::34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("#1234", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("#12:34", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("+12:34 ", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); + check!(" +12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("\t+12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("\t\t+12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); + check!("12:34 ", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!(" 12:34", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); + check!("+", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); + check!(":", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("+12345", [fixed(Fixed::TimezoneOffsetColon), num(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fixed(Fixed::TimezoneOffsetColon), num(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fixed(Fixed::TimezoneOffsetColon), Literal(":")]; offset: 45_240); + check!("Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("A", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("PST", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("#Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!(":Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("+Z", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); + check!("+:Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("+Z:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!(" :Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!(" Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!(" z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` // and `TimezoneOffsetTripleColon` for function `parse_internal`. // No need for separate tests for `TimezoneOffsetDoubleColon` and // `TimezoneOffsetTripleColon`. // TimezoneOffsetZ - check!("1", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12", [fix!(TimezoneOffsetZ)]; INVALID); - check!("123", [fix!(TimezoneOffsetZ)]; INVALID); - check!("1234", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12345", [fix!(TimezoneOffsetZ)]; INVALID); - check!("123456", [fix!(TimezoneOffsetZ)]; INVALID); - check!("1234567", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12345678", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+1", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+123", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+1234", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("-1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); - check!("βˆ’1234", [fix!(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+123456", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+1234567", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12345678", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("1:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:3", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34:5", [fix!(TimezoneOffsetZ)]; INVALID); - check!("12:34:56", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+1:", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12:", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:3", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("-12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); - check!("βˆ’12:34", [fix!(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:5", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:7", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:78", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12::34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12:3456", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+1234:56", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12: 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 :34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("12:34 ", [fix!(TimezoneOffsetZ)]; INVALID); - check!(" 12:34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+12:34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!(" +12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240); - check!("+12345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fix!(TimezoneOffsetZ), lit!(":")]; offset: 45_240); - check!("Z12:34", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("X12:34", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!(" Z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!(" z", [fix!(TimezoneOffsetZ)]; offset: 0); - check!("\u{0363}Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Z ", [fix!(TimezoneOffsetZ)]; TOO_LONG); - check!("A", [fix!(TimezoneOffsetZ)]; INVALID); - check!("PST", [fix!(TimezoneOffsetZ)]; INVALID); - check!("#Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!(":Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!(":z", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("-Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+A", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+πŸ™ƒ", [fix!(TimezoneOffsetZ)]; INVALID); - check!("+Z:", [fix!(TimezoneOffsetZ)]; INVALID); - check!(" :Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!(" +Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!(" -Z", [fix!(TimezoneOffsetZ)]; TOO_SHORT); - check!("+:Z", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); - check!("Zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("zulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 0); - check!("+1234ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); - check!("+12:34ulu", [fix!(TimezoneOffsetZ), lit!("ulu")]; offset: 45_240); + check!("1", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("123", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("1234", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12345", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("123456", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("1234567", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12345678", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("+1", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("+12", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("+123", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("+1234", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("-1234", [fixed(Fixed::TimezoneOffsetZ)]; offset: -45_240); + check!("βˆ’1234", [fixed(Fixed::TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+123456", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+1234567", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12345678", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("1:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12:3", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12:34", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12:34:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12:34:5", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("12:34:56", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("+1:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("+12:", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:3", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("-12:34", [fixed(Fixed::TimezoneOffsetZ)]; offset: -45_240); + check!("βˆ’12:34", [fixed(Fixed::TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:5", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:7", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:78", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12::34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12:3456", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+1234:56", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12: 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12 :34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("12:34 ", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!(" 12:34", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("+12:34 ", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34 ", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!(" +12:34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); + check!("+12345", [fixed(Fixed::TimezoneOffsetZ), num(Day)]; offset: 45_240, day: 5); + check!("+12:345", [fixed(Fixed::TimezoneOffsetZ), num(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fixed(Fixed::TimezoneOffsetZ), Literal(":")]; offset: 45_240); + check!("Z12:34", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("X12:34", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("Z", [fixed(Fixed::TimezoneOffsetZ)]; offset: 0); + check!("z", [fixed(Fixed::TimezoneOffsetZ)]; offset: 0); + check!(" Z", [fixed(Fixed::TimezoneOffsetZ)]; offset: 0); + check!(" z", [fixed(Fixed::TimezoneOffsetZ)]; offset: 0); + check!("\u{0363}Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("Z ", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); + check!("A", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("PST", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("#Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!(":Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!(":z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("+Z", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("-Z", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("+A", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("+πŸ™ƒ", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("+Z:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!(" :Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!(" +Z", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!(" -Z", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); + check!("+:Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("Y", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); + check!("Zulu", [fixed(Fixed::TimezoneOffsetZ), Literal("ulu")]; offset: 0); + check!("zulu", [fixed(Fixed::TimezoneOffsetZ), Literal("ulu")]; offset: 0); + check!("+1234ulu", [fixed(Fixed::TimezoneOffsetZ), Literal("ulu")]; offset: 45_240); + check!("+12:34ulu", [fixed(Fixed::TimezoneOffsetZ), Literal("ulu")]; offset: 45_240); // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` // in function `parse_internal`. // No need for separate tests for `TimezoneOffsetColonZ`. // TimezoneOffsetPermissive - check!("1", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("123", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("1234", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12345", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("123456", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("1234567", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12345678", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+1", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+12", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); - check!("+123", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("-1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); - check!("βˆ’1234", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+123456", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+1234567", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12345678", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:3", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+1:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:", [internal_fix!(TimezoneOffsetPermissive)]; offset: 43_200); - check!("+12:3", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("-12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); - check!("βˆ’12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:5", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:7", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:78", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 ::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12::::34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(" 12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!(" +12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!(" -12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); - check!(" βˆ’12:34", [internal_fix!(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); - check!("+12:345", [internal_fix!(TimezoneOffsetPermissive), num!(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [internal_fix!(TimezoneOffsetPermissive), lit!(":")]; offset: 45_240); - check!("🀠+12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34🀠", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:🀠34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34🀠", [internal_fix!(TimezoneOffsetPermissive), lit!("🀠")]; offset: 45_240); - check!("🀠+12:34", [lit!("🀠"), internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240); - check!("Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("A", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!(" Z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!(" z", [internal_fix!(TimezoneOffsetPermissive)]; offset: 0); - check!("Z ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG); - check!("#Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(":Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(":z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("-Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+A", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+PST", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+πŸ™ƒ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("+Z:", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(" :Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!(" +Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!(" -Z", [internal_fix!(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+:Z", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); - check!("Y", [internal_fix!(TimezoneOffsetPermissive)]; INVALID); + check!("1", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("123", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("1234", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12345", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("123456", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("1234567", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12345678", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+1", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+12", [internal_fixed(TimezoneOffsetPermissive)]; offset: 43_200); + check!("+123", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+1234", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("-1234", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); + check!("βˆ’1234", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+123456", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+1234567", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12345678", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("1:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12:3", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12:34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:5", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("12:34:56", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+1:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+12:", [internal_fixed(TimezoneOffsetPermissive)]; offset: 43_200); + check!("+12:3", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("-12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); + check!("βˆ’12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:5", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:7", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:34:56:78", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 :34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 : 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 :34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 : 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 ::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: :34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:: 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12 ::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12: :34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:: 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12:::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("+12::::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("12:34 ", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!(" 12:34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34 ", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!(" +12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!(" -12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); + check!(" βˆ’12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [internal_fixed(TimezoneOffsetPermissive), num(Day)]; offset: 45_240, day: 5); + check!("+12:345", [internal_fixed(TimezoneOffsetPermissive), num(Day)]; offset: 45_240, day: 5); + check!("+12:34:", [internal_fixed(TimezoneOffsetPermissive), Literal(":")]; offset: 45_240); + check!("🀠+12:34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34🀠", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("+12:🀠34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+12:34🀠", [internal_fixed(TimezoneOffsetPermissive), Literal("🀠")]; offset: 45_240); + check!("🀠+12:34", [Literal("🀠"), internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); + check!("Z", [internal_fixed(TimezoneOffsetPermissive)]; offset: 0); + check!("A", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("PST", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("z", [internal_fixed(TimezoneOffsetPermissive)]; offset: 0); + check!(" Z", [internal_fixed(TimezoneOffsetPermissive)]; offset: 0); + check!(" z", [internal_fixed(TimezoneOffsetPermissive)]; offset: 0); + check!("Z ", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); + check!("#Z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!(":Z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!(":z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+Z", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("-Z", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+A", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+PST", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+πŸ™ƒ", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("+Z:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!(" :Z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!(" +Z", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); + check!(" -Z", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); + check!("+:Z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check!("Y", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); // TimezoneName - check!("CEST", [fix!(TimezoneName)]; ); - check!("cest", [fix!(TimezoneName)]; ); // lowercase - check!("XXXXXXXX", [fix!(TimezoneName)]; ); // not a real timezone name - check!("!!!!", [fix!(TimezoneName)]; ); // not a real timezone name! - check!("CEST 5", [fix!(TimezoneName), lit!(" "), num!(Day)]; day: 5); - check!("CEST ", [fix!(TimezoneName)]; TOO_LONG); - check!(" CEST", [fix!(TimezoneName)]; TOO_LONG); - check!("CE ST", [fix!(TimezoneName)]; TOO_LONG); + check!("CEST", [fixed(Fixed::TimezoneName)]; ); + check!("cest", [fixed(Fixed::TimezoneName)]; ); // lowercase + check!("XXXXXXXX", [fixed(Fixed::TimezoneName)]; ); // not a real timezone name + check!("!!!!", [fixed(Fixed::TimezoneName)]; ); // not a real timezone name! + check!("CEST 5", [fixed(Fixed::TimezoneName), Literal(" "), num(Day)]; day: 5); + check!("CEST ", [fixed(Fixed::TimezoneName)]; TOO_LONG); + check!(" CEST", [fixed(Fixed::TimezoneName)]; TOO_LONG); + check!("CE ST", [fixed(Fixed::TimezoneName)]; TOO_LONG); // some practical examples check!("2015-02-04T14:37:05+09:00", - [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), - num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; + [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), fixed(Fixed::TimezoneOffset)]; year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, second: 5, offset: 32400); check!("2015-02-04T14:37:05-09:00", - [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), - num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; + [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), fixed(Fixed::TimezoneOffset)]; year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, second: 5, offset: -32400); check!("2015-02-04T14:37:05βˆ’09:00", // timezone offset using MINUS SIGN (U+2212) - [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), - num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; + [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), fixed(Fixed::TimezoneOffset)]; year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, second: 5, offset: -32400); check!("20150204143705567", - [num!(Year), num!(Month), num!(Day), - num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)]; + [num(Year), num(Month), num(Day), + num(Hour), num(Minute), num(Second), internal_fixed(Nanosecond3NoDot)]; year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, second: 5, nanosecond: 567000000); check!("Mon, 10 Jun 2013 09:32:37 GMT", - [fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "), - fix!(ShortMonthName), sp!(" "), num!(Year), sp!(" "), num!(Hour), lit!(":"), - num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT")]; + [fixed(Fixed::ShortWeekdayName), Literal(","), Space(" "), num(Day), Space(" "), + fixed(Fixed::ShortMonthName), Space(" "), num(Year), Space(" "), num(Hour), Literal(":"), + num(Minute), Literal(":"), num(Second), Space(" "), Literal("GMT")]; year: 2013, month: 6, day: 10, weekday: Weekday::Mon, hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); check!("Sun Aug 02 13:39:15 CEST 2020", - [fix!(ShortWeekdayName), sp!(" "), fix!(ShortMonthName), sp!(" "), - num!(Day), sp!(" "), num!(Hour), lit!(":"), num!(Minute), lit!(":"), - num!(Second), sp!(" "), fix!(TimezoneName), sp!(" "), num!(Year)]; + [fixed(Fixed::ShortWeekdayName), Space(" "), fixed(Fixed::ShortMonthName), Space(" "), + num(Day), Space(" "), num(Hour), Literal(":"), num(Minute), Literal(":"), + num(Second), Space(" "), fixed(Fixed::TimezoneName), Space(" "), num(Year)]; year: 2020, month: 8, day: 2, weekday: Weekday::Sun, hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15); check!("20060102150405", - [num!(Year), num!(Month), num!(Day), num!(Hour), num!(Minute), num!(Second)]; + [num(Year), num(Month), num(Day), num(Hour), num(Minute), num(Second)]; year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5); check!("3:14PM", - [num!(Hour12), lit!(":"), num!(Minute), fix!(LowerAmPm)]; + [num(Hour12), Literal(":"), num(Minute), fixed(Fixed::LowerAmPm)]; hour_div_12: 1, hour_mod_12: 3, minute: 14); check!("12345678901234.56789", - [num!(Timestamp), lit!("."), num!(Nanosecond)]; + [num(Timestamp), Literal("."), num(Nanosecond)]; nanosecond: 56_789, timestamp: 12_345_678_901_234); check!("12345678901234.56789", - [num!(Timestamp), fix!(Nanosecond)]; + [num(Timestamp), fixed(Fixed::Nanosecond)]; nanosecond: 567_890_000, timestamp: 12_345_678_901_234); } diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 0c2edef7a1..cd57c90ed6 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -180,9 +180,10 @@ Notes: China Daylight Time. */ +use super::{fixed, internal_fixed, num, num0, nums}; #[cfg(feature = "unstable-locales")] use super::{locales, Locale}; -use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad}; +use super::{Fixed, InternalInternal, Item, Numeric, Pad}; /// Parsing iterator for `strftime`-like format strings. #[derive(Clone, Debug)] @@ -250,24 +251,29 @@ impl<'a> Iterator for StrftimeItems<'a> { impl<'a> StrftimeItems<'a> { fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> { + use InternalInternal::*; + use Item::{Literal, Space}; + use Numeric::*; + static D_FMT: &[Item<'static>] = - &[num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)]; + &[num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)]; static D_T_FMT: &[Item<'static>] = &[ - fix!(ShortWeekdayName), - sp!(" "), - fix!(ShortMonthName), - sp!(" "), - nums!(Day), - sp!(" "), - num0!(Hour), - lit!(":"), - num0!(Minute), - lit!(":"), - num0!(Second), - sp!(" "), - num0!(Year), + fixed(Fixed::ShortWeekdayName), + Space(" "), + fixed(Fixed::ShortMonthName), + Space(" "), + nums(Day), + Space(" "), + num0(Hour), + Literal(":"), + num0(Minute), + Literal(":"), + num0(Second), + Space(" "), + num0(Year), ]; - static T_FMT: &[Item<'static>] = &[num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)]; + static T_FMT: &[Item<'static>] = + &[num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)]; match remainder.chars().next() { // we are done @@ -318,118 +324,126 @@ impl<'a> StrftimeItems<'a> { } let item = match spec { - 'A' => fix!(LongWeekdayName), - 'B' => fix!(LongMonthName), - 'C' => num0!(YearDiv100), + 'A' => fixed(Fixed::LongWeekdayName), + 'B' => fixed(Fixed::LongMonthName), + 'C' => num0(YearDiv100), 'D' => { - queue![num0!(Month), lit!("/"), num0!(Day), lit!("/"), num0!(YearMod100)] + queue![num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)] + } + 'F' => queue![num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)], + 'G' => num0(IsoYear), + 'H' => num0(Hour), + 'I' => num0(Hour12), + 'M' => num0(Minute), + 'P' => fixed(Fixed::LowerAmPm), + 'R' => queue![num0(Hour), Literal(":"), num0(Minute)], + 'S' => num0(Second), + 'T' => { + queue![num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)] } - 'F' => queue![num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)], - 'G' => num0!(IsoYear), - 'H' => num0!(Hour), - 'I' => num0!(Hour12), - 'M' => num0!(Minute), - 'P' => fix!(LowerAmPm), - 'R' => queue![num0!(Hour), lit!(":"), num0!(Minute)], - 'S' => num0!(Second), - 'T' => queue![num0!(Hour), lit!(":"), num0!(Minute), lit!(":"), num0!(Second)], - 'U' => num0!(WeekFromSun), - 'V' => num0!(IsoWeek), - 'W' => num0!(WeekFromMon), + 'U' => num0(WeekFromSun), + 'V' => num0(IsoWeek), + 'W' => num0(WeekFromMon), #[cfg(not(feature = "unstable-locales"))] 'X' => queue_from_slice!(T_FMT), #[cfg(feature = "unstable-locales")] 'X' => self.switch_to_locale_str(locales::t_fmt, T_FMT), - 'Y' => num0!(Year), - 'Z' => fix!(TimezoneName), - 'a' => fix!(ShortWeekdayName), - 'b' | 'h' => fix!(ShortMonthName), + 'Y' => num0(Year), + 'Z' => fixed(Fixed::TimezoneName), + 'a' => fixed(Fixed::ShortWeekdayName), + 'b' | 'h' => fixed(Fixed::ShortMonthName), #[cfg(not(feature = "unstable-locales"))] 'c' => queue_from_slice!(D_T_FMT), #[cfg(feature = "unstable-locales")] 'c' => self.switch_to_locale_str(locales::d_t_fmt, D_T_FMT), - 'd' => num0!(Day), - 'e' => nums!(Day), - 'f' => num0!(Nanosecond), - 'g' => num0!(IsoYearMod100), - 'j' => num0!(Ordinal), - 'k' => nums!(Hour), - 'l' => nums!(Hour12), - 'm' => num0!(Month), - 'n' => sp!("\n"), - 'p' => fix!(UpperAmPm), + 'd' => num0(Day), + 'e' => nums(Day), + 'f' => num0(Nanosecond), + 'g' => num0(IsoYearMod100), + 'j' => num0(Ordinal), + 'k' => nums(Hour), + 'l' => nums(Hour12), + 'm' => num0(Month), + 'n' => Space("\n"), + 'p' => fixed(Fixed::UpperAmPm), 'r' => queue![ - num0!(Hour12), - lit!(":"), - num0!(Minute), - lit!(":"), - num0!(Second), - sp!(" "), - fix!(UpperAmPm) + num0(Hour12), + Literal(":"), + num0(Minute), + Literal(":"), + num0(Second), + Space(" "), + fixed(Fixed::UpperAmPm) ], - 's' => num!(Timestamp), - 't' => sp!("\t"), - 'u' => num!(WeekdayFromMon), + 's' => num(Timestamp), + 't' => Space("\t"), + 'u' => num(WeekdayFromMon), 'v' => { - queue![nums!(Day), lit!("-"), fix!(ShortMonthName), lit!("-"), num0!(Year)] + queue![ + nums(Day), + Literal("-"), + fixed(Fixed::ShortMonthName), + Literal("-"), + num0(Year) + ] } - 'w' => num!(NumDaysFromSun), + 'w' => num(NumDaysFromSun), #[cfg(not(feature = "unstable-locales"))] 'x' => queue_from_slice!(D_FMT), #[cfg(feature = "unstable-locales")] 'x' => self.switch_to_locale_str(locales::d_fmt, D_FMT), - 'y' => num0!(YearMod100), + 'y' => num0(YearMod100), 'z' => { if is_alternate { - internal_fix!(TimezoneOffsetPermissive) + internal_fixed(TimezoneOffsetPermissive) } else { - fix!(TimezoneOffset) + fixed(Fixed::TimezoneOffset) } } - '+' => fix!(RFC3339), + '+' => fixed(Fixed::RFC3339), ':' => { if remainder.starts_with("::z") { remainder = &remainder[3..]; - fix!(TimezoneOffsetTripleColon) + fixed(Fixed::TimezoneOffsetTripleColon) } else if remainder.starts_with(":z") { remainder = &remainder[2..]; - fix!(TimezoneOffsetDoubleColon) + fixed(Fixed::TimezoneOffsetDoubleColon) } else if remainder.starts_with('z') { remainder = &remainder[1..]; - fix!(TimezoneOffsetColon) + fixed(Fixed::TimezoneOffsetColon) } else { Item::Error } } '.' => match next!() { '3' => match next!() { - 'f' => fix!(Nanosecond3), + 'f' => fixed(Fixed::Nanosecond3), _ => Item::Error, }, '6' => match next!() { - 'f' => fix!(Nanosecond6), + 'f' => fixed(Fixed::Nanosecond6), _ => Item::Error, }, '9' => match next!() { - 'f' => fix!(Nanosecond9), + 'f' => fixed(Fixed::Nanosecond9), _ => Item::Error, }, - 'f' => fix!(Nanosecond), + 'f' => fixed(Fixed::Nanosecond), _ => Item::Error, }, '3' => match next!() { - 'f' => internal_fix!(Nanosecond3NoDot), + 'f' => internal_fixed(Nanosecond3NoDot), _ => Item::Error, }, '6' => match next!() { - 'f' => internal_fix!(Nanosecond6NoDot), + 'f' => internal_fixed(Nanosecond6NoDot), _ => Item::Error, }, '9' => match next!() { - 'f' => internal_fix!(Nanosecond9NoDot), + 'f' => internal_fixed(Nanosecond9NoDot), _ => Item::Error, }, - '%' => lit!("%"), + '%' => Literal("%"), _ => Item::Error, // no such specifier }; @@ -454,7 +468,7 @@ impl<'a> StrftimeItems<'a> { let nextspec = remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len()); assert!(nextspec > 0); - let item = sp!(&remainder[..nextspec]); + let item = Space(&remainder[..nextspec]); remainder = &remainder[nextspec..]; Some((remainder, item)) } @@ -465,7 +479,7 @@ impl<'a> StrftimeItems<'a> { .find(|c: char| c.is_whitespace() || c == '%') .unwrap_or(remainder.len()); assert!(nextspec > 0); - let item = lit!(&remainder[..nextspec]); + let item = Literal(&remainder[..nextspec]); remainder = &remainder[nextspec..]; Some((remainder, item)) } @@ -492,9 +506,12 @@ impl<'a> StrftimeItems<'a> { #[cfg(test)] mod tests { + use super::StrftimeItems; + use crate::format::Item::{self, Literal, Space}; #[cfg(feature = "unstable-locales")] - use super::Locale; - use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, StrftimeItems}; + use crate::format::Locale; + use crate::format::{fixed, internal_fixed, num, num0, nums}; + use crate::format::{Fixed, InternalInternal, Numeric::*}; use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc}; #[test] @@ -507,25 +524,28 @@ mod tests { } assert_eq!(parse_and_collect(""), []); - assert_eq!(parse_and_collect(" \t\n\r "), [sp!(" \t\n\r ")]); - assert_eq!(parse_and_collect("hello?"), [lit!("hello?")]); + assert_eq!(parse_and_collect(" \t\n\r "), [Space(" \t\n\r ")]); + assert_eq!(parse_and_collect("hello?"), [Literal("hello?")]); assert_eq!( parse_and_collect("a b\t\nc"), - [lit!("a"), sp!(" "), lit!("b"), sp!("\t\n"), lit!("c")] + [Literal("a"), Space(" "), Literal("b"), Space("\t\n"), Literal("c")] + ); + assert_eq!(parse_and_collect("100%%"), [Literal("100"), Literal("%")]); + assert_eq!( + parse_and_collect("100%% ok"), + [Literal("100"), Literal("%"), Space(" "), Literal("ok")] ); - assert_eq!(parse_and_collect("100%%"), [lit!("100"), lit!("%")]); - assert_eq!(parse_and_collect("100%% ok"), [lit!("100"), lit!("%"), sp!(" "), lit!("ok")]); - assert_eq!(parse_and_collect("%%PDF-1.0"), [lit!("%"), lit!("PDF-1.0")]); + assert_eq!(parse_and_collect("%%PDF-1.0"), [Literal("%"), Literal("PDF-1.0")]); assert_eq!( parse_and_collect("%Y-%m-%d"), - [num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)] + [num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)] ); assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]")); - assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]); + assert_eq!(parse_and_collect("%m %d"), [num0(Month), Space(" "), num0(Day)]); assert_eq!(parse_and_collect("%"), [Item::Error]); - assert_eq!(parse_and_collect("%%"), [lit!("%")]); + assert_eq!(parse_and_collect("%%"), [Literal("%")]); assert_eq!(parse_and_collect("%%%"), [Item::Error]); - assert_eq!(parse_and_collect("%%%%"), [lit!("%"), lit!("%")]); + assert_eq!(parse_and_collect("%%%%"), [Literal("%"), Literal("%")]); assert_eq!(parse_and_collect("foo%?"), [Item::Error]); assert_eq!(parse_and_collect("bar%42"), [Item::Error]); assert_eq!(parse_and_collect("quux% +"), [Item::Error]); @@ -536,16 +556,19 @@ mod tests { assert_eq!(parse_and_collect("%_Z"), [Item::Error]); assert_eq!(parse_and_collect("%.j"), [Item::Error]); assert_eq!(parse_and_collect("%:j"), [Item::Error]); - assert_eq!(parse_and_collect("%-j"), [num!(Ordinal)]); - assert_eq!(parse_and_collect("%0j"), [num0!(Ordinal)]); - assert_eq!(parse_and_collect("%_j"), [nums!(Ordinal)]); + assert_eq!(parse_and_collect("%-j"), [num(Ordinal)]); + assert_eq!(parse_and_collect("%0j"), [num0(Ordinal)]); + assert_eq!(parse_and_collect("%_j"), [nums(Ordinal)]); assert_eq!(parse_and_collect("%.e"), [Item::Error]); assert_eq!(parse_and_collect("%:e"), [Item::Error]); - assert_eq!(parse_and_collect("%-e"), [num!(Day)]); - assert_eq!(parse_and_collect("%0e"), [num0!(Day)]); - assert_eq!(parse_and_collect("%_e"), [nums!(Day)]); - assert_eq!(parse_and_collect("%z"), [fix!(TimezoneOffset)]); - assert_eq!(parse_and_collect("%#z"), [internal_fix!(TimezoneOffsetPermissive)]); + assert_eq!(parse_and_collect("%-e"), [num(Day)]); + assert_eq!(parse_and_collect("%0e"), [num0(Day)]); + assert_eq!(parse_and_collect("%_e"), [nums(Day)]); + assert_eq!(parse_and_collect("%z"), [fixed(Fixed::TimezoneOffset)]); + assert_eq!( + parse_and_collect("%#z"), + [internal_fixed(InternalInternal::TimezoneOffsetPermissive)] + ); assert_eq!(parse_and_collect("%#m"), [Item::Error]); } From cc0e427ab22a58fbb5d05f0f812588b50053b735 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 27 Jun 2023 12:24:18 +0200 Subject: [PATCH 28/68] Move module declaration to top --- src/format/mod.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index d9124fcd4c..cffd938c0f 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -42,6 +42,15 @@ use std::error::Error; use crate::{Month, ParseMonthError, ParseWeekdayError, Weekday}; +mod formatting; +mod parsed; + +// due to the size of parsing routines, they are in separate modules. +mod parse; +pub(crate) mod scan; + +pub mod strftime; + #[cfg(feature = "unstable-locales")] pub(crate) mod locales; @@ -399,15 +408,6 @@ const TOO_SHORT: ParseError = ParseError(ParseErrorKind::TooShort); const TOO_LONG: ParseError = ParseError(ParseErrorKind::TooLong); const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat); - -mod formatting; -mod parsed; - -// due to the size of parsing routines, they are in separate modules. -mod parse; -pub(crate) mod scan; - -pub mod strftime; // this implementation is here only because we need some private code from `scan` /// Parsing a `str` into a `Weekday` uses the format [`%W`](./format/strftime/index.html). From 807a9fb05b5bbacb65549fa9f7d9f41f6d52c34a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 10:24:46 +0200 Subject: [PATCH 29/68] Only require `std` feature for `std::error::Error` impl --- src/format/mod.rs | 4 ++-- src/round.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index cffd938c0f..007a2df9ae 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -37,7 +37,7 @@ extern crate alloc; use alloc::boxed::Box; use core::fmt; use core::str::FromStr; -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] use std::error::Error; use crate::{Month, ParseMonthError, ParseWeekdayError, Weekday}; @@ -390,7 +390,7 @@ impl fmt::Display for ParseError { } } -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl Error for ParseError { #[allow(deprecated)] diff --git a/src/round.rs b/src/round.rs index f43ab595c3..b6e771e0f0 100644 --- a/src/round.rs +++ b/src/round.rs @@ -100,11 +100,11 @@ const fn span_for_digits(digits: u16) -> u32 { /// will also fail if the `Duration` is bigger than the timestamp. pub trait DurationRound: Sized { /// Error that can occur in rounding or truncating - #[cfg(any(feature = "std", test))] + #[cfg(any(feature = "std"))] type Err: std::error::Error; /// Error that can occur in rounding or truncating - #[cfg(not(any(feature = "std", test)))] + #[cfg(not(any(feature = "std")))] type Err: fmt::Debug + fmt::Display; /// Return a copy rounded by Duration. @@ -299,7 +299,7 @@ impl fmt::Display for RoundingError { } } -#[cfg(any(feature = "std", test))] +#[cfg(any(feature = "std"))] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for RoundingError { #[allow(deprecated)] From 456948a20a0c3fb0272749c23b0cff07135a7563 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 11:32:49 +0200 Subject: [PATCH 30/68] Deduplicate test --- src/datetime/tests.rs | 72 +------------------------------------------ 1 file changed, 1 insertion(+), 71 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 3aeed665c0..41f0b1e894 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -677,77 +677,7 @@ fn test_subsecond_part() { } #[test] -#[cfg(not(target_os = "windows"))] -fn test_from_system_time() { - use std::time::Duration; - - let epoch = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0).unwrap(); - let nanos = 999_999_999; - - // SystemTime -> DateTime - assert_eq!(DateTime::::from(UNIX_EPOCH), epoch); - assert_eq!( - DateTime::::from(UNIX_EPOCH + Duration::new(999_999_999, nanos)), - Utc.from_local_datetime( - &NaiveDate::from_ymd_opt(2001, 9, 9) - .unwrap() - .and_hms_nano_opt(1, 46, 39, nanos) - .unwrap() - ) - .unwrap() - ); - assert_eq!( - DateTime::::from(UNIX_EPOCH - Duration::new(999_999_999, nanos)), - Utc.from_local_datetime( - &NaiveDate::from_ymd_opt(1938, 4, 24).unwrap().and_hms_nano_opt(22, 13, 20, 1).unwrap() - ) - .unwrap() - ); - - // DateTime -> SystemTime - assert_eq!(SystemTime::from(epoch), UNIX_EPOCH); - assert_eq!( - SystemTime::from( - Utc.from_local_datetime( - &NaiveDate::from_ymd_opt(2001, 9, 9) - .unwrap() - .and_hms_nano_opt(1, 46, 39, nanos) - .unwrap() - ) - .unwrap() - ), - UNIX_EPOCH + Duration::new(999_999_999, nanos) - ); - assert_eq!( - SystemTime::from( - Utc.from_local_datetime( - &NaiveDate::from_ymd_opt(1938, 4, 24) - .unwrap() - .and_hms_nano_opt(22, 13, 20, 1) - .unwrap() - ) - .unwrap() - ), - UNIX_EPOCH - Duration::new(999_999_999, 999_999_999) - ); - - // DateTime -> SystemTime (via `with_timezone`) - #[cfg(feature = "clock")] - { - assert_eq!(SystemTime::from(epoch.with_timezone(&Local)), UNIX_EPOCH); - } - assert_eq!( - SystemTime::from(epoch.with_timezone(&FixedOffset::east_opt(32400).unwrap())), - UNIX_EPOCH - ); - assert_eq!( - SystemTime::from(epoch.with_timezone(&FixedOffset::west_opt(28800).unwrap())), - UNIX_EPOCH - ); -} - -#[test] -#[cfg(target_os = "windows")] +#[cfg(feature = "std")] fn test_from_system_time() { use std::time::Duration; From 59531e0cd354328598b40821d715abfe26887065 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 09:37:03 +0200 Subject: [PATCH 31/68] Require `std` feature for `From` --- src/datetime/mod.rs | 6 +++--- src/datetime/tests.rs | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index bf9c16ad5d..b0c002b687 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -15,7 +15,7 @@ use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, hash, str}; #[cfg(feature = "std")] use std::string::ToString; -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] use std::time::{SystemTime, UNIX_EPOCH}; #[cfg(any(feature = "alloc", feature = "std", test))] @@ -1301,7 +1301,7 @@ impl str::FromStr for DateTime { } } -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl From for DateTime { fn from(t: SystemTime) -> DateTime { @@ -1330,7 +1330,7 @@ impl From for DateTime { } } -#[cfg(any(feature = "std", test))] +#[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl From> for SystemTime { fn from(dt: DateTime) -> SystemTime { diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 41f0b1e894..e770843977 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -1,5 +1,3 @@ -use std::time::{SystemTime, UNIX_EPOCH}; - use super::DateTime; use crate::naive::{NaiveDate, NaiveTime}; use crate::offset::{FixedOffset, TimeZone, Utc}; @@ -679,7 +677,7 @@ fn test_subsecond_part() { #[test] #[cfg(feature = "std")] fn test_from_system_time() { - use std::time::Duration; + use std::time::{Duration, SystemTime, UNIX_EPOCH}; let nanos = 999_999_000; From e15b897b9bcb3320235d06d2bcdf46925227efbc Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 09:05:54 +0200 Subject: [PATCH 32/68] Make `test_rfc3339` work without formatting --- src/format/parse.rs | 55 +++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index e390dd3ea3..cd5250d666 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -1411,26 +1411,36 @@ mod tests { } } - #[cfg(test)] #[test] fn test_rfc3339() { - use super::*; - use crate::offset::FixedOffset; - use crate::DateTime; + let ymd_hmsn = |y, m, d, h, n, s, nano, off| { + FixedOffset::east_opt(off * 60 * 60) + .unwrap() + .with_ymd_and_hms(y, m, d, h, n, s) + .unwrap() + .with_nanosecond(nano) + .unwrap() + }; - // Test data - (input, Ok(expected result after parse and format) or Err(error code)) + // Test data - (input, Ok(expected result) or Err(error code)) let testdates = [ - ("2015-01-20T17:35:20-08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case - ("2015-01-20T17:35:20βˆ’08:00", Ok("2015-01-20T17:35:20-08:00")), // normal case with MINUS SIGN (U+2212) - ("1944-06-06T04:04:00Z", Ok("1944-06-06T04:04:00+00:00")), // D-day - ("2001-09-11T09:45:00-08:00", Ok("2001-09-11T09:45:00-08:00")), - ("2015-01-20T17:35:20.001-08:00", Ok("2015-01-20T17:35:20.001-08:00")), - ("2015-01-20T17:35:20.001βˆ’08:00", Ok("2015-01-20T17:35:20.001-08:00")), // with MINUS SIGN (U+2212) - ("2015-01-20T17:35:20.000031-08:00", Ok("2015-01-20T17:35:20.000031-08:00")), - ("2015-01-20T17:35:20.000000004-08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), - ("2015-01-20T17:35:20.000000004βˆ’08:00", Ok("2015-01-20T17:35:20.000000004-08:00")), // with MINUS SIGN (U+2212) - ("2015-01-20T17:35:20.000000000452-08:00", Ok("2015-01-20T17:35:20-08:00")), // too small - ("2015-01-20T17:35:20.000000000452βˆ’08:00", Ok("2015-01-20T17:35:20-08:00")), // too small with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // normal case + ("2015-01-20T17:35:20βˆ’08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // normal case with MINUS SIGN (U+2212) + ("1944-06-06T04:04:00Z", Ok(ymd_hmsn(1944, 6, 6, 4, 4, 0, 0, 0))), // D-day + ("2001-09-11T09:45:00-08:00", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, -8))), + ("2015-01-20T17:35:20.001-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 1_000_000, -8))), + ("2015-01-20T17:35:20.001βˆ’08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 1_000_000, -8))), // with MINUS SIGN (U+2212) + ("2015-01-20T17:35:20.000031-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 31_000, -8))), + ("2015-01-20T17:35:20.000000004-08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 4, -8))), + ("2015-01-20T17:35:20.000000004βˆ’08:00", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 4, -8))), // with MINUS SIGN (U+2212) + ( + "2015-01-20T17:35:20.000000000452-08:00", + Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), + ), // too small + ( + "2015-01-20T17:35:20.000000000452βˆ’08:00", + Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), + ), // too small with MINUS SIGN (U+2212) ("2015-01-20 17:35:20.001-08:00", Err(INVALID)), // missing separator 'T' ("2015/01/20T17:35:20.001-08:00", Err(INVALID)), // wrong separator char YMD ("2015-01-20T17-35-20.001-08:00", Err(INVALID)), // wrong separator char HMS @@ -1479,19 +1489,10 @@ mod tests { parsed.to_datetime() } - fn fmt_rfc3339_datetime(dt: DateTime) -> String { - dt.format_with_items([Item::Fixed(Fixed::RFC3339)].iter()).to_string() - } - // Test against test data above for &(date, checkdate) in testdates.iter() { - let d = rfc3339_to_datetime(date); // parse a date - let dt = match d { - // did we get a value? - Ok(dt) => Ok(fmt_rfc3339_datetime(dt)), // yes, go on - Err(e) => Err(e), // otherwise keep an error for the comparison - }; - if dt != checkdate.map(|s| s.to_string()) { + let dt = rfc3339_to_datetime(date); // parse a date + if dt != checkdate { // check for expected result panic!( "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", From 41a1b99b9ecfb628ba08c7ebda25dcefa76e880f Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 10:08:35 +0200 Subject: [PATCH 33/68] Make `test_rfc2822` work without formatting --- src/format/parse.rs | 82 ++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index cd5250d666..f5bebb0b1a 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -1251,28 +1251,37 @@ mod tests { #[test] fn test_rfc2822() { - // Test data - (input, Ok(expected result after parse and format) or Err(error code)) + let ymd_hmsn = |y, m, d, h, n, s, nano, off| { + FixedOffset::east_opt(off * 60 * 60) + .unwrap() + .with_ymd_and_hms(y, m, d, h, n, s) + .unwrap() + .with_nanosecond(nano) + .unwrap() + }; + + // Test data - (input, Ok(expected result) or Err(error code)) let testdates = [ - ("Tue, 20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // normal case - ("Fri, 2 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // folding whitespace - ("Fri, 02 Jan 2015 17:35:20 -0800", Ok("Fri, 02 Jan 2015 17:35:20 -0800")), // leading zero - ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // trailing comment + ("Tue, 20 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // normal case + ("Fri, 2 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 2, 17, 35, 20, 0, -8))), // folding whitespace + ("Fri, 02 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 2, 17, 35, 20, 0, -8))), // leading zero + ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC)", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // trailing comment ( r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))", - Ok("Tue, 20 Jan 2015 17:35:20 -0800"), + Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), ), // complex trailing comment (r"Tue, 20 Jan 2015 17:35:20 -0800 (UTC\)", Err(TOO_LONG)), // incorrect comment, not enough closing parentheses ( "Tue, 20 Jan 2015 17:35:20 -0800 (UTC)\t \r\n(Anothercomment)", - Ok("Tue, 20 Jan 2015 17:35:20 -0800"), + Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8)), ), // multiple comments ("Tue, 20 Jan 2015 17:35:20 -0800 (UTC) ", Err(TOO_LONG)), // trailing whitespace after comment - ("20 Jan 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // no day of week - ("20 JAN 2015 17:35:20 -0800", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // upper case month - ("Tue, 20 Jan 2015 17:35 -0800", Ok("Tue, 20 Jan 2015 17:35:00 -0800")), // no second - ("11 Sep 2001 09:45:00 +0000", Ok("Tue, 11 Sep 2001 09:45:00 +0000")), - ("11 Sep 2001 09:45:00 EST", Ok("Tue, 11 Sep 2001 09:45:00 -0500")), - ("11 Sep 2001 09:45:00 GMT", Ok("Tue, 11 Sep 2001 09:45:00 +0000")), + ("20 Jan 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // no day of week + ("20 JAN 2015 17:35:20 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // upper case month + ("Tue, 20 Jan 2015 17:35 -0800", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 0, 0, -8))), // no second + ("11 Sep 2001 09:45:00 +0000", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, 0))), + ("11 Sep 2001 09:45:00 EST", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, -5))), + ("11 Sep 2001 09:45:00 GMT", Ok(ymd_hmsn(2001, 9, 11, 9, 45, 0, 0, 0))), ("30 Feb 2015 17:35:20 -0800", Err(OUT_OF_RANGE)), // bad day of month ("Tue, 20 Jan 2015", Err(TOO_SHORT)), // omitted fields ("Tue, 20 Avr 2015 17:35:20 -0800", Err(INVALID)), // bad month name @@ -1285,24 +1294,24 @@ mod tests { ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named time zone // named timezones that have specific timezone offsets // see https://www.rfc-editor.org/rfc/rfc2822#section-4.3 - ("Tue, 20 Jan 2015 17:35:20 GMT", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 UT", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 ut", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 EDT", Ok("Tue, 20 Jan 2015 17:35:20 -0400")), - ("Tue, 20 Jan 2015 17:35:20 EST", Ok("Tue, 20 Jan 2015 17:35:20 -0500")), - ("Tue, 20 Jan 2015 17:35:20 CDT", Ok("Tue, 20 Jan 2015 17:35:20 -0500")), - ("Tue, 20 Jan 2015 17:35:20 CST", Ok("Tue, 20 Jan 2015 17:35:20 -0600")), - ("Tue, 20 Jan 2015 17:35:20 MDT", Ok("Tue, 20 Jan 2015 17:35:20 -0600")), - ("Tue, 20 Jan 2015 17:35:20 MST", Ok("Tue, 20 Jan 2015 17:35:20 -0700")), - ("Tue, 20 Jan 2015 17:35:20 PDT", Ok("Tue, 20 Jan 2015 17:35:20 -0700")), - ("Tue, 20 Jan 2015 17:35:20 PST", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), - ("Tue, 20 Jan 2015 17:35:20 pst", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), + ("Tue, 20 Jan 2015 17:35:20 GMT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 UT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 ut", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 EDT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -4))), + ("Tue, 20 Jan 2015 17:35:20 EST", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -5))), + ("Tue, 20 Jan 2015 17:35:20 CDT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -5))), + ("Tue, 20 Jan 2015 17:35:20 CST", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -6))), + ("Tue, 20 Jan 2015 17:35:20 MDT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -6))), + ("Tue, 20 Jan 2015 17:35:20 MST", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -7))), + ("Tue, 20 Jan 2015 17:35:20 PDT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -7))), + ("Tue, 20 Jan 2015 17:35:20 PST", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), + ("Tue, 20 Jan 2015 17:35:20 pst", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, -8))), // named single-letter military timezones must fallback to +0000 - ("Tue, 20 Jan 2015 17:35:20 Z", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 A", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 a", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 K", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), - ("Tue, 20 Jan 2015 17:35:20 k", Ok("Tue, 20 Jan 2015 17:35:20 +0000")), + ("Tue, 20 Jan 2015 17:35:20 Z", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 A", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 a", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 K", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), + ("Tue, 20 Jan 2015 17:35:20 k", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), // named single-letter timezone "J" is specifically not valid ("Tue, 20 Jan 2015 17:35:20 J", Err(NOT_ENOUGH)), ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset minutes @@ -1321,21 +1330,12 @@ mod tests { parsed.to_datetime() } - fn fmt_rfc2822_datetime(dt: DateTime) -> String { - dt.format_with_items([Item::Fixed(Fixed::RFC2822)].iter()).to_string() - } - // Test against test data above for &(date, checkdate) in testdates.iter() { eprintln!("Test input: {:?}", date); eprintln!(" Expect: {:?}", checkdate); - let d = rfc2822_to_datetime(date); // parse a date - let dt = match d { - // did we get a value? - Ok(dt) => Ok(fmt_rfc2822_datetime(dt)), // yes, go on - Err(e) => Err(e), // otherwise keep an error for the comparison - }; - if dt != checkdate.map(|s| s.to_string()) { + let dt = rfc2822_to_datetime(date); // parse a date + if dt != checkdate { // check for expected result panic!( "Date conversion failed for {}\nReceived: {:?}\nExpected: {:?}", From eec5232a6985bb505abf4f4458488ff70cc82ed2 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 09:06:01 +0200 Subject: [PATCH 34/68] Make formatting tests require `alloc` or `std` feature --- src/datetime/tests.rs | 4 ++++ src/format/parse.rs | 3 ++- src/format/strftime.rs | 5 ++++- src/naive/date.rs | 1 + src/naive/datetime/tests.rs | 11 +++++++---- src/naive/isoweek.rs | 2 ++ src/naive/time/tests.rs | 1 + 7 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index e770843977..2d273f605d 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -356,6 +356,7 @@ fn test_datetime_with_timezone() { } #[test] +#[cfg(any(feature = "alloc", feature = "std"))] fn test_datetime_rfc2822_and_rfc3339() { let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); assert_eq!( @@ -449,6 +450,7 @@ fn test_datetime_rfc2822_and_rfc3339() { } #[test] +#[cfg(any(feature = "alloc", feature = "std"))] fn test_rfc3339_opts() { use crate::SecondsFormat::*; let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); @@ -479,6 +481,7 @@ fn test_rfc3339_opts() { #[test] #[should_panic] +#[cfg(any(feature = "alloc", feature = "std"))] fn test_rfc3339_opts_nonexhaustive() { use crate::SecondsFormat; let dt = Utc.with_ymd_and_hms(1999, 10, 9, 1, 2, 3).unwrap(); @@ -749,6 +752,7 @@ fn test_from_system_time() { } #[test] +#[cfg(any(feature = "alloc", feature = "std"))] fn test_datetime_format_alignment() { let datetime = Utc.with_ymd_and_hms(2007, 1, 2, 0, 0, 0).unwrap(); diff --git a/src/format/parse.rs b/src/format/parse.rs index f5bebb0b1a..f56771b7d1 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -562,7 +562,7 @@ impl str::FromStr for DateTime { #[cfg(test)] mod tests { use crate::format::*; - use crate::{DateTime, FixedOffset, TimeZone, Utc}; + use crate::{DateTime, FixedOffset, TimeZone, Timelike, Utc}; #[test] fn test_parse() { @@ -1353,6 +1353,7 @@ mod tests { let dt = Utc.with_ymd_and_hms(1994, 11, 6, 8, 49, 37).unwrap(); // Check that the format is what we expect + #[cfg(any(feature = "alloc", feature = "std"))] assert_eq!(dt.format(RFC850_FMT).to_string(), dt_str); // Check that it parses correctly diff --git a/src/format/strftime.rs b/src/format/strftime.rs index cd57c90ed6..ab3b5e16b2 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -512,6 +512,7 @@ mod tests { use crate::format::Locale; use crate::format::{fixed, internal_fixed, num, num0, nums}; use crate::format::{Fixed, InternalInternal, Numeric::*}; + #[cfg(any(feature = "alloc", feature = "std"))] use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc}; #[test] @@ -573,6 +574,7 @@ mod tests { } #[test] + #[cfg(any(feature = "alloc", feature = "std"))] fn test_strftime_docs() { let dt = FixedOffset::east_opt(34200) .unwrap() @@ -675,8 +677,8 @@ mod tests { assert_eq!(dt.format("%%").to_string(), "%"); } - #[cfg(feature = "unstable-locales")] #[test] + #[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] fn test_strftime_docs_localized() { let dt = FixedOffset::east_opt(34200) .unwrap() @@ -729,6 +731,7 @@ mod tests { /// /// See . #[test] + #[cfg(any(feature = "alloc", feature = "std"))] fn test_parse_only_timezone_offset_permissive_no_panic() { use crate::NaiveDate; use crate::{FixedOffset, TimeZone}; diff --git a/src/naive/date.rs b/src/naive/date.rs index f34d855a20..50fa4a210a 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -3040,6 +3040,7 @@ mod tests { } #[test] + #[cfg(any(feature = "alloc", feature = "std"))] fn test_date_format() { let d = NaiveDate::from_ymd_opt(2012, 3, 4).unwrap(); assert_eq!(d.format("%Y,%C,%y,%G,%g").to_string(), "2012,20,12,2012,12"); diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 53422c8c18..4656147d64 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -17,10 +17,11 @@ fn test_datetime_from_timestamp_millis() { (2034061609000, "2034-06-16 09:06:49.000000000"), ]; - for (timestamp_millis, formatted) in valid_map.iter().copied() { + for (timestamp_millis, _formatted) in valid_map.iter().copied() { let naive_datetime = NaiveDateTime::from_timestamp_millis(timestamp_millis); assert_eq!(timestamp_millis, naive_datetime.unwrap().timestamp_millis()); - assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), formatted); + #[cfg(any(feature = "alloc", feature = "std"))] + assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), _formatted); } let invalid = [i64::MAX, i64::MIN]; @@ -54,10 +55,11 @@ fn test_datetime_from_timestamp_micros() { (2034061609000000, "2034-06-16 09:06:49.000000000"), ]; - for (timestamp_micros, formatted) in valid_map.iter().copied() { + for (timestamp_micros, _formatted) in valid_map.iter().copied() { let naive_datetime = NaiveDateTime::from_timestamp_micros(timestamp_micros); assert_eq!(timestamp_micros, naive_datetime.unwrap().timestamp_micros()); - assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), formatted); + #[cfg(any(feature = "alloc", feature = "std"))] + assert_eq!(naive_datetime.unwrap().format("%F %T%.9f").to_string(), _formatted); } let invalid = [i64::MAX, i64::MIN]; @@ -287,6 +289,7 @@ fn test_datetime_parse_from_str() { } #[test] +#[cfg(any(feature = "alloc", feature = "std"))] fn test_datetime_format() { let dt = NaiveDate::from_ymd_opt(2010, 9, 8).unwrap().and_hms_milli_opt(7, 6, 54, 321).unwrap(); assert_eq!(dt.format("%c").to_string(), "Wed Sep 8 07:06:54 2010"); diff --git a/src/naive/isoweek.rs b/src/naive/isoweek.rs index df5556a120..8edf738ba0 100644 --- a/src/naive/isoweek.rs +++ b/src/naive/isoweek.rs @@ -158,11 +158,13 @@ mod tests { assert_eq!(minweek.year(), internals::MIN_YEAR); assert_eq!(minweek.week(), 1); assert_eq!(minweek.week0(), 0); + #[cfg(any(feature = "alloc", feature = "std"))] assert_eq!(format!("{:?}", minweek), NaiveDate::MIN.format("%G-W%V").to_string()); assert_eq!(maxweek.year(), internals::MAX_YEAR + 1); assert_eq!(maxweek.week(), 1); assert_eq!(maxweek.week0(), 0); + #[cfg(any(feature = "alloc", feature = "std"))] assert_eq!(format!("{:?}", maxweek), NaiveDate::MAX.format("%G-W%V").to_string()); } diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 62c46a247e..72491fbd2c 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -285,6 +285,7 @@ fn test_time_parse_from_str() { } #[test] +#[cfg(any(feature = "alloc", feature = "std"))] fn test_time_format() { let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap(); assert_eq!(t.format("%H,%k,%I,%l,%P,%p").to_string(), "03, 3,03, 3,am,AM"); From 26bfd6c6474d4d43b545692caf281115d4ec24be Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 10:25:07 +0200 Subject: [PATCH 35/68] Remove duplicate code --- src/naive/date.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index 50fa4a210a..428a1a20a1 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2290,15 +2290,6 @@ mod serde { formatter.write_str("a formatted date string") } - #[cfg(any(feature = "std", test))] - fn visit_str(self, value: &str) -> Result - where - E: de::Error, - { - value.parse().map_err(E::custom) - } - - #[cfg(not(any(feature = "std", test)))] fn visit_str(self, value: &str) -> Result where E: de::Error, From f3e556bd56c887f4f6521575ce09affbabd388a3 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 10:27:52 +0200 Subject: [PATCH 36/68] Fix typo --- src/datetime/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index b0c002b687..105681a4d7 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1495,7 +1495,7 @@ fn test_decodable_json( // we don't know the exact local offset but we can check that // the conversion didn't change the instant itself assert_eq!( - local_from_str(r#""2014-07-24T12:34:06Z""#).expect("local shouuld parse"), + local_from_str(r#""2014-07-24T12:34:06Z""#).expect("local should parse"), Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() ); assert_eq!( From 697f5d7d81d091afd85162b36fff007c7082d7ec Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 10:28:32 +0200 Subject: [PATCH 37/68] Require clock feature for `test_decodable_json` import --- src/datetime/serde.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 002ded0cca..a96f31a901 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -1130,7 +1130,9 @@ pub mod ts_seconds_option { #[cfg(test)] mod tests { - use crate::datetime::{test_decodable_json, test_encodable_json}; + #[cfg(feature = "clock")] + use crate::datetime::test_decodable_json; + use crate::datetime::test_encodable_json; use crate::{DateTime, TimeZone, Utc}; #[test] From 72e1fba0d24b386e41df30900ab7014c0fd523bd Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 11:49:36 +0200 Subject: [PATCH 38/68] Make formatting only available with `alloc` or `std` feature, not with `test` --- src/date.rs | 8 ++++---- src/datetime/mod.rs | 12 ++++++------ src/format/formatting.rs | 38 +++++++++++++++++++------------------- src/format/mod.rs | 8 ++++---- src/format/parse.rs | 7 +++---- src/naive/date.rs | 8 ++++---- src/naive/datetime/mod.rs | 8 ++++---- src/naive/time/mod.rs | 8 ++++---- 8 files changed, 48 insertions(+), 49 deletions(-) diff --git a/src/date.rs b/src/date.rs index d831745a43..15f3d436d1 100644 --- a/src/date.rs +++ b/src/date.rs @@ -4,7 +4,7 @@ //! ISO 8601 calendar date with time zone. #![allow(deprecated)] -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use core::borrow::Borrow; use core::cmp::Ordering; use core::ops::{Add, AddAssign, Sub, SubAssign}; @@ -15,7 +15,7 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg(feature = "unstable-locales")] use crate::format::Locale; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use crate::format::{DelayedFormat, Item, StrftimeItems}; use crate::naive::{IsoWeek, NaiveDate, NaiveTime}; use crate::offset::{TimeZone, Utc}; @@ -333,7 +333,7 @@ where Tz::Offset: fmt::Display, { /// Formats the date with the specified formatting items. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] @@ -348,7 +348,7 @@ where /// Formats the date with the specified format string. /// See the [`crate::format::strftime`] module /// on the supported escape sequences. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 105681a4d7..a7bd7a9fa0 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -18,7 +18,7 @@ use std::string::ToString; #[cfg(feature = "std")] use std::time::{SystemTime, UNIX_EPOCH}; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use crate::format::DelayedFormat; #[cfg(feature = "unstable-locales")] use crate::format::Locale; @@ -707,7 +707,7 @@ where /// /// Panics if the date can not be represented in this format: the year may not be negative and /// can not have more than 4 digits. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[must_use] pub fn to_rfc2822(&self) -> String { @@ -718,7 +718,7 @@ where } /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[must_use] pub fn to_rfc3339(&self) -> String { @@ -752,7 +752,7 @@ where /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), /// "2018-01-26T10:30:09+08:00"); /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[must_use] pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { @@ -798,7 +798,7 @@ where } /// Formats the combined date and time with the specified formatting items. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] @@ -823,7 +823,7 @@ where /// let formatted = format!("{}", date_time.format("%d/%m/%Y %H:%M")); /// assert_eq!(formatted, "02/04/2017 12:50"); /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 452d7b76b8..955d684248 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -8,24 +8,24 @@ extern crate alloc; #[cfg(feature = "alloc")] use alloc::string::{String, ToString}; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use core::borrow::Borrow; use core::fmt; use core::fmt::Write; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use crate::naive::{NaiveDate, NaiveTime}; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use crate::offset::{FixedOffset, Offset}; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use crate::{Datelike, Timelike, Weekday}; #[cfg(feature = "unstable-locales")] use super::locales; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use super::{Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric, Pad}; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] struct Locales { short_months: &'static [&'static str], long_months: &'static [&'static str], @@ -34,7 +34,7 @@ struct Locales { am_pm: &'static [&'static str], } -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] impl Locales { fn new(_locale: Option) -> Self { #[cfg(feature = "unstable-locales")] @@ -84,7 +84,7 @@ impl Locales { /// A *temporary* object which can be used as an argument to `format!` or others. /// This is normally constructed via `format` methods of each date and time type. -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[derive(Debug)] pub struct DelayedFormat { @@ -103,7 +103,7 @@ pub struct DelayedFormat { locale: Option, } -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { /// Makes a new `DelayedFormat` value out of local date and time. #[must_use] @@ -172,7 +172,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { } } -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] impl<'a, I: Iterator + Clone, B: Borrow>> fmt::Display for DelayedFormat { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { #[cfg(feature = "unstable-locales")] @@ -195,7 +195,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> fmt::Display for De /// Tries to format given arguments with given formatting items. /// Internally used by `DelayedFormat`. -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] pub fn format<'a, I, B>( w: &mut fmt::Formatter, @@ -215,7 +215,7 @@ where w.pad(&result) } /// Formats single formatting item -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] pub fn format_item( w: &mut fmt::Formatter, @@ -268,7 +268,7 @@ pub fn format_item_localized( w.pad(&result) } -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] fn format_inner( result: &mut String, date: Option<&NaiveDate>, @@ -281,7 +281,7 @@ fn format_inner( match *item { Item::Literal(s) | Item::Space(s) => result.push_str(s), - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s), Item::Numeric(ref spec, ref pad) => { @@ -476,7 +476,7 @@ fn format_inner( Ok(()) } -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] #[derive(Debug, Clone, PartialEq, Eq, Hash)] enum Colons { None, @@ -487,7 +487,7 @@ enum Colons { /// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`. /// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true. -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] fn write_local_minus_utc( result: &mut String, off: FixedOffset, @@ -521,7 +521,7 @@ fn write_local_minus_utc( } /// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] pub(crate) fn write_rfc3339( result: &mut String, dt: crate::NaiveDateTime, @@ -533,7 +533,7 @@ pub(crate) fn write_rfc3339( write_local_minus_utc(result, off, false, Colons::Single) } -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] /// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` pub(crate) fn write_rfc2822( result: &mut String, @@ -543,7 +543,7 @@ pub(crate) fn write_rfc2822( write_rfc2822_inner(result, &dt.date(), &dt.time(), off, Locales::new(None)) } -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] /// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` fn write_rfc2822_inner( result: &mut String, diff --git a/src/format/mod.rs b/src/format/mod.rs index 007a2df9ae..7d5dfb8702 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -55,11 +55,11 @@ pub mod strftime; pub(crate) mod locales; pub(crate) use formatting::write_hundreds; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] pub use formatting::{format, format_item, DelayedFormat}; #[cfg(feature = "unstable-locales")] pub use formatting::{format_item_localized, format_localized}; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] pub(crate) use formatting::{write_rfc2822, write_rfc3339}; pub use parse::{parse, parse_and_remainder}; pub use parsed::Parsed; @@ -286,13 +286,13 @@ pub enum Item<'a> { /// A literally printed and parsed text. Literal(&'a str), /// Same as `Literal` but with the string owned by the item. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] OwnedLiteral(Box), /// Whitespace. Prints literally but reads zero or more whitespace. Space(&'a str), /// Same as `Space` but with the string owned by the item. - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] OwnedSpace(Box), /// Numeric item. Can be optionally padded to the maximal length (if any) when formatting; diff --git a/src/format/parse.rs b/src/format/parse.rs index f56771b7d1..49742ce40c 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -309,7 +309,7 @@ where s = &s[prefix.len()..]; } - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] Item::OwnedLiteral(ref prefix) => { if s.len() < prefix.len() { return Err((s, TOO_SHORT)); @@ -324,7 +324,7 @@ where s = s.trim_start(); } - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] Item::OwnedSpace(_) => { s = s.trim_start(); } @@ -1349,12 +1349,11 @@ mod tests { fn parse_rfc850() { static RFC850_FMT: &str = "%A, %d-%b-%y %T GMT"; - let dt_str = "Sunday, 06-Nov-94 08:49:37 GMT"; let dt = Utc.with_ymd_and_hms(1994, 11, 6, 8, 49, 37).unwrap(); // Check that the format is what we expect #[cfg(any(feature = "alloc", feature = "std"))] - assert_eq!(dt.format(RFC850_FMT).to_string(), dt_str); + assert_eq!(dt.format(RFC850_FMT).to_string(), "Sunday, 06-Nov-94 08:49:37 GMT"); // Check that it parses correctly assert_eq!(Ok(dt), Utc.datetime_from_str("Sunday, 06-Nov-94 08:49:37 GMT", RFC850_FMT)); diff --git a/src/naive/date.rs b/src/naive/date.rs index 428a1a20a1..d4d6b662e2 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -3,7 +3,7 @@ //! ISO 8601 calendar date without timezone. -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use core::borrow::Borrow; use core::iter::FusedIterator; use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; @@ -16,7 +16,7 @@ use rkyv::{Archive, Deserialize, Serialize}; #[cfg(feature = "unstable-locales")] use pure_rust_locales::Locale; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use crate::format::DelayedFormat; use crate::format::{ parse, parse_and_remainder, write_hundreds, Item, Numeric, Pad, ParseError, ParseResult, @@ -1246,7 +1246,7 @@ impl NaiveDate { /// # let d = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap(); /// assert_eq!(format!("{}", d.format_with_items(fmt)), "2015-09-05"); /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] @@ -1290,7 +1290,7 @@ impl NaiveDate { /// assert_eq!(format!("{}", d.format("%Y-%m-%d")), "2015-09-05"); /// assert_eq!(format!("{}", d.format("%A, %-d %B, %C%y")), "Saturday, 5 September, 2015"); /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 1bd17d57be..6fa9bb31fb 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -3,7 +3,7 @@ //! ISO 8601 date and time without timezone. -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use core::borrow::Borrow; use core::fmt::Write; use core::ops::{Add, AddAssign, Sub, SubAssign}; @@ -12,7 +12,7 @@ use core::{fmt, str}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use crate::format::DelayedFormat; use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Fixed, Item, Numeric, Pad}; @@ -855,7 +855,7 @@ impl NaiveDateTime { /// # let dt = NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().and_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(format!("{}", dt.format_with_items(fmt)), "2015-09-05 23:56:04"); /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] @@ -899,7 +899,7 @@ impl NaiveDateTime { /// assert_eq!(format!("{}", dt.format("%Y-%m-%d %H:%M:%S")), "2015-09-05 23:56:04"); /// assert_eq!(format!("{}", dt.format("around %l %p on %b %-d")), "around 11 PM on Sep 5"); /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index bff4543f7f..b0f730feb6 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -3,7 +3,7 @@ //! ISO 8601 time without timezone. -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use core::borrow::Borrow; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, str}; @@ -11,7 +11,7 @@ use core::{fmt, str}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; -#[cfg(any(feature = "alloc", feature = "std", test))] +#[cfg(any(feature = "alloc", feature = "std"))] use crate::format::DelayedFormat; use crate::format::{ parse, parse_and_remainder, write_hundreds, Fixed, Item, Numeric, Pad, ParseError, ParseResult, @@ -746,7 +746,7 @@ impl NaiveTime { /// # let t = NaiveTime::from_hms_opt(23, 56, 4).unwrap(); /// assert_eq!(format!("{}", t.format_with_items(fmt)), "23:56:04"); /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] @@ -792,7 +792,7 @@ impl NaiveTime { /// assert_eq!(format!("{}", t.format("%H:%M:%S%.6f")), "23:56:04.012345"); /// assert_eq!(format!("{}", t.format("%-I:%M %p")), "11:56 PM"); /// ``` - #[cfg(any(feature = "alloc", feature = "std", test))] + #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[inline] #[must_use] From ea41eba87f430968418b64b88af9cd710a5fd2fb Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 20:48:28 +0200 Subject: [PATCH 39/68] Remove `Tz::Offset: Display` requirement from `DateTime::to_rfc*` --- src/datetime/mod.rs | 193 ++++++++++++++++++++++---------------------- 1 file changed, 97 insertions(+), 96 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index a7bd7a9fa0..8686340e16 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -488,6 +488,103 @@ impl DateTime { } } + /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. + /// + /// # Panics + /// + /// Panics if the date can not be represented in this format: the year may not be negative and + /// can not have more than 4 digits. + #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] + #[must_use] + pub fn to_rfc2822(&self) -> String { + let mut result = String::with_capacity(32); + crate::format::write_rfc2822(&mut result, self.naive_local(), self.offset.fix()) + .expect("writing rfc2822 datetime to string should never fail"); + result + } + + /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. + #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] + #[must_use] + pub fn to_rfc3339(&self) -> String { + let mut result = String::with_capacity(32); + crate::format::write_rfc3339(&mut result, self.naive_local(), self.offset.fix()) + .expect("writing rfc3339 datetime to string should never fail"); + result + } + + /// Return an RFC 3339 and ISO 8601 date and time string with subseconds + /// formatted as per `SecondsFormat`. + /// + /// If `use_z` is true and the timezone is UTC (offset 0), uses `Z` as + /// per [`Fixed::TimezoneOffsetColonZ`]. If `use_z` is false, uses + /// [`Fixed::TimezoneOffsetColon`] + /// + /// # Examples + /// + /// ```rust + /// # use chrono::{FixedOffset, SecondsFormat, TimeZone, Utc, NaiveDate}; + /// let dt = NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(18, 30, 9, 453_829).unwrap().and_local_timezone(Utc).unwrap(); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false), + /// "2018-01-26T18:30:09.453+00:00"); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, true), + /// "2018-01-26T18:30:09.453Z"); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), + /// "2018-01-26T18:30:09Z"); + /// + /// let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + /// let dt = pst.from_local_datetime(&NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(10, 30, 9, 453_829).unwrap()).unwrap(); + /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), + /// "2018-01-26T10:30:09+08:00"); + /// ``` + #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] + #[must_use] + pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { + use crate::format::Numeric::*; + use crate::format::Pad::Zero; + use crate::SecondsFormat::*; + + debug_assert!(secform != __NonExhaustive, "Do not use __NonExhaustive!"); + + const PREFIX: &[Item<'static>] = &[ + Item::Numeric(Year, Zero), + Item::Literal("-"), + Item::Numeric(Month, Zero), + Item::Literal("-"), + Item::Numeric(Day, Zero), + Item::Literal("T"), + Item::Numeric(Hour, Zero), + Item::Literal(":"), + Item::Numeric(Minute, Zero), + Item::Literal(":"), + Item::Numeric(Second, Zero), + ]; + + let ssitem = match secform { + Secs => None, + Millis => Some(Item::Fixed(Fixed::Nanosecond3)), + Micros => Some(Item::Fixed(Fixed::Nanosecond6)), + Nanos => Some(Item::Fixed(Fixed::Nanosecond9)), + AutoSi => Some(Item::Fixed(Fixed::Nanosecond)), + __NonExhaustive => unreachable!(), + }; + + let tzitem = Item::Fixed(if use_z { + Fixed::TimezoneOffsetColonZ + } else { + Fixed::TimezoneOffsetColon + }); + + let dt = self.fixed_offset(); + match ssitem { + None => dt.format_with_items(PREFIX.iter().chain([tzitem].iter())).to_string(), + Some(s) => dt.format_with_items(PREFIX.iter().chain([s, tzitem].iter())).to_string(), + } + } + /// The minimum possible `DateTime`. pub const MIN_UTC: DateTime = DateTime { datetime: NaiveDateTime::MIN, offset: Utc }; /// The maximum possible `DateTime`. @@ -701,102 +798,6 @@ impl DateTime where Tz::Offset: fmt::Display, { - /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. - /// - /// # Panics - /// - /// Panics if the date can not be represented in this format: the year may not be negative and - /// can not have more than 4 digits. - #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] - #[must_use] - pub fn to_rfc2822(&self) -> String { - let mut result = String::with_capacity(32); - crate::format::write_rfc2822(&mut result, self.naive_local(), self.offset.fix()) - .expect("writing rfc2822 datetime to string should never fail"); - result - } - - /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`. - #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] - #[must_use] - pub fn to_rfc3339(&self) -> String { - let mut result = String::with_capacity(32); - crate::format::write_rfc3339(&mut result, self.naive_local(), self.offset.fix()) - .expect("writing rfc3339 datetime to string should never fail"); - result - } - - /// Return an RFC 3339 and ISO 8601 date and time string with subseconds - /// formatted as per `SecondsFormat`. - /// - /// If `use_z` is true and the timezone is UTC (offset 0), uses `Z` as - /// per [`Fixed::TimezoneOffsetColonZ`]. If `use_z` is false, uses - /// [`Fixed::TimezoneOffsetColon`] - /// - /// # Examples - /// - /// ```rust - /// # use chrono::{FixedOffset, SecondsFormat, TimeZone, Utc, NaiveDate}; - /// let dt = NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(18, 30, 9, 453_829).unwrap().and_local_timezone(Utc).unwrap(); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, false), - /// "2018-01-26T18:30:09.453+00:00"); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Millis, true), - /// "2018-01-26T18:30:09.453Z"); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), - /// "2018-01-26T18:30:09Z"); - /// - /// let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); - /// let dt = pst.from_local_datetime(&NaiveDate::from_ymd_opt(2018, 1, 26).unwrap().and_hms_micro_opt(10, 30, 9, 453_829).unwrap()).unwrap(); - /// assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), - /// "2018-01-26T10:30:09+08:00"); - /// ``` - #[cfg(any(feature = "alloc", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] - #[must_use] - pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { - use crate::format::Numeric::*; - use crate::format::Pad::Zero; - use crate::SecondsFormat::*; - - debug_assert!(secform != __NonExhaustive, "Do not use __NonExhaustive!"); - - const PREFIX: &[Item<'static>] = &[ - Item::Numeric(Year, Zero), - Item::Literal("-"), - Item::Numeric(Month, Zero), - Item::Literal("-"), - Item::Numeric(Day, Zero), - Item::Literal("T"), - Item::Numeric(Hour, Zero), - Item::Literal(":"), - Item::Numeric(Minute, Zero), - Item::Literal(":"), - Item::Numeric(Second, Zero), - ]; - - let ssitem = match secform { - Secs => None, - Millis => Some(Item::Fixed(Fixed::Nanosecond3)), - Micros => Some(Item::Fixed(Fixed::Nanosecond6)), - Nanos => Some(Item::Fixed(Fixed::Nanosecond9)), - AutoSi => Some(Item::Fixed(Fixed::Nanosecond)), - __NonExhaustive => unreachable!(), - }; - - let tzitem = Item::Fixed(if use_z { - Fixed::TimezoneOffsetColonZ - } else { - Fixed::TimezoneOffsetColon - }); - - match ssitem { - None => self.format_with_items(PREFIX.iter().chain([tzitem].iter())).to_string(), - Some(s) => self.format_with_items(PREFIX.iter().chain([s, tzitem].iter())).to_string(), - } - } - /// Formats the combined date and time with the specified formatting items. #[cfg(any(feature = "alloc", feature = "std"))] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] From 0d09acfdad12458aa844b5474a1e34ed9dbc8497 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 22:04:35 +0200 Subject: [PATCH 40/68] Parallelize `try_verify_against_date_command` --- tests/dateutils.rs | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/tests/dateutils.rs b/tests/dateutils.rs index 36784ac1c6..de100e52fc 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -3,10 +3,10 @@ use chrono::offset::TimeZone; #[cfg(unix)] use chrono::Local; #[cfg(unix)] -use chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike}; +use chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; #[cfg(unix)] -use std::{path, process}; +use std::{path, process, thread}; #[cfg(unix)] fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { @@ -90,31 +90,26 @@ fn try_verify_against_date_command() { } assert_run_date_version(); - let mut date = NaiveDate::from_ymd_opt(1975, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); - eprintln!( - "Run command {:?} for every hour from {} to 2077, skipping some years...", + "Run command {:?} for every hour from 1975 to 2077, skipping some years...", DATE_PATH, - date.year() ); - let mut count: u64 = 0; - let mut year_at = date.year(); - while date.year() < 2078 { - if (1975..=1977).contains(&date.year()) - || (2020..=2022).contains(&date.year()) - || (2073..=2077).contains(&date.year()) - { - if date.year() != year_at { - eprintln!("at year {}...", date.year()); - year_at = date.year(); - } - verify_against_date_command_local(DATE_PATH, date); - count += 1; - } - date += chrono::Duration::hours(1); + let mut children = vec![]; + for year in [1975, 1976, 1977, 2020, 2021, 2022, 2073, 2074, 2075, 2076, 2077].iter() { + children.push(thread::spawn(|| { + let mut date = NaiveDate::from_ymd_opt(*year, 1, 1).unwrap().and_time(NaiveTime::MIN); + let end = NaiveDate::from_ymd_opt(*year + 1, 1, 1).unwrap().and_time(NaiveTime::MIN); + while date <= end { + verify_against_date_command_local(DATE_PATH, date); + date += chrono::Duration::hours(1); + } + })); + } + for child in children { + // Wait for the thread to finish. Returns a result. + let _ = child.join(); } - eprintln!("Command {:?} was run {} times", DATE_PATH, count); } #[cfg(target_os = "linux")] From 3dda28cffdda58d3062ada194a133237fedca142 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 20 May 2023 13:28:13 +0200 Subject: [PATCH 41/68] New offset formatter `OffsetFormat` --- src/format/formatting.rs | 181 +++++++++++++++++++++++++++------------ src/format/mod.rs | 45 ++++++++++ 2 files changed, 172 insertions(+), 54 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 955d684248..3c2b479005 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -23,7 +23,10 @@ use crate::{Datelike, Timelike, Weekday}; #[cfg(feature = "unstable-locales")] use super::locales; #[cfg(any(feature = "alloc", feature = "std"))] -use super::{Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric, Pad}; +use super::{ + Colons, Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric, OffsetFormat, + OffsetPrecision, Pad, +}; #[cfg(any(feature = "alloc", feature = "std"))] struct Locales { @@ -428,20 +431,42 @@ fn format_inner( result.push_str(name); Ok(()) }), - TimezoneOffsetColon => off - .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Single)), - TimezoneOffsetDoubleColon => off - .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Double)), - TimezoneOffsetTripleColon => off - .map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::Triple)), - TimezoneOffsetColonZ => off - .map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::Single)), - TimezoneOffset => { - off.map(|&(_, off)| write_local_minus_utc(result, off, false, Colons::None)) - } - TimezoneOffsetZ => { - off.map(|&(_, off)| write_local_minus_utc(result, off, true, Colons::None)) - } + TimezoneOffset | TimezoneOffsetZ => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Maybe, + allow_zulu: *spec == TimezoneOffsetZ, + padding: Pad::Zero, + } + .format(result, off) + }), + TimezoneOffsetColon | TimezoneOffsetColonZ => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Colon, + allow_zulu: *spec == TimezoneOffsetColonZ, + padding: Pad::Zero, + } + .format(result, off) + }), + TimezoneOffsetDoubleColon => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Seconds, + colons: Colons::Colon, + allow_zulu: false, + padding: Pad::Zero, + } + .format(result, off) + }), + TimezoneOffsetTripleColon => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Hours, + colons: Colons::None, + allow_zulu: false, + padding: Pad::Zero, + } + .format(result, off) + }), Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { return Err(fmt::Error); } @@ -477,46 +502,82 @@ fn format_inner( } #[cfg(any(feature = "alloc", feature = "std"))] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -enum Colons { - None, - Single, - Double, - Triple, -} - -/// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`. -/// `Z` instead of `+00[:]00` is allowed when `allow_zulu` is true. -#[cfg(any(feature = "alloc", feature = "std"))] -fn write_local_minus_utc( - result: &mut String, - off: FixedOffset, - allow_zulu: bool, - colon_type: Colons, -) -> fmt::Result { - let off = off.local_minus_utc(); - if allow_zulu && off == 0 { - result.push('Z'); - return Ok(()); - } - let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; - result.push(sign); - - write_hundreds(result, (off / 3600) as u8)?; +impl OffsetFormat { + /// Writes an offset from UTC to `result` with the format defined by `self`. + fn format(&self, result: &mut String, off: FixedOffset) -> fmt::Result { + let off = off.local_minus_utc(); + if self.allow_zulu && off == 0 { + result.push('Z'); + return Ok(()); + } + let (sign, off) = if off < 0 { ('-', -off) } else { ('+', off) }; + + let hours; + let mut mins = 0; + let mut secs = 0; + let precision = match self.precision { + OffsetPrecision::Hours => { + // Minutes and seconds are simply truncated + hours = (off / 3600) as u8; + OffsetPrecision::Hours + } + OffsetPrecision::Minutes | OffsetPrecision::OptionalMinutes => { + // Round seconds to the nearest minute. + let minutes = (off + 30) / 60; + mins = (minutes % 60) as u8; + hours = (minutes / 60) as u8; + if self.precision == OffsetPrecision::OptionalMinutes && mins == 0 { + OffsetPrecision::Hours + } else { + OffsetPrecision::Minutes + } + } + OffsetPrecision::Seconds + | OffsetPrecision::OptionalSeconds + | OffsetPrecision::OptionalMinutesAndSeconds => { + let minutes = off / 60; + secs = (off % 60) as u8; + mins = (minutes % 60) as u8; + hours = (minutes / 60) as u8; + if self.precision != OffsetPrecision::Seconds && secs == 0 { + if self.precision == OffsetPrecision::OptionalMinutesAndSeconds && mins == 0 { + OffsetPrecision::Hours + } else { + OffsetPrecision::Minutes + } + } else { + OffsetPrecision::Seconds + } + } + }; + let colons = self.colons == Colons::Colon; - match colon_type { - Colons::None => write_hundreds(result, (off / 60 % 60) as u8), - Colons::Single => { - result.push(':'); - write_hundreds(result, (off / 60 % 60) as u8) + if hours < 10 { + if self.padding == Pad::Space { + result.push(' '); + } + result.push(sign); + if self.padding == Pad::Zero { + result.push('0'); + } + result.push((b'0' + hours) as char); + } else { + result.push(sign); + write_hundreds(result, hours)?; + } + if let OffsetPrecision::Minutes | OffsetPrecision::Seconds = precision { + if colons { + result.push(':'); + } + write_hundreds(result, mins)?; } - Colons::Double => { - result.push(':'); - write_hundreds(result, (off / 60 % 60) as u8)?; - result.push(':'); - write_hundreds(result, (off % 60) as u8) + if let OffsetPrecision::Seconds = precision { + if colons { + result.push(':'); + } + write_hundreds(result, secs)?; } - Colons::Triple => Ok(()), + Ok(()) } } @@ -530,7 +591,13 @@ pub(crate) fn write_rfc3339( // reuse `Debug` impls which already print ISO 8601 format. // this is faster in this way. write!(result, "{:?}", dt)?; - write_local_minus_utc(result, off, false, Colons::Single) + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Colon, + allow_zulu: false, + padding: Pad::Zero, + } + .format(result, off) } #[cfg(any(feature = "alloc", feature = "std"))] @@ -574,7 +641,13 @@ fn write_rfc2822_inner( let sec = t.second() + t.nanosecond() / 1_000_000_000; write_hundreds(result, sec as u8)?; result.push(' '); - write_local_minus_utc(result, off, false, Colons::None) + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::None, + allow_zulu: false, + padding: Pad::Zero, + } + .format(result, off) } /// Equivalent to `{:02}` formatting for n < 100. diff --git a/src/format/mod.rs b/src/format/mod.rs index 7d5dfb8702..e31c8a8f4f 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -280,6 +280,51 @@ enum InternalInternal { Nanosecond9NoDot, } +/// Type for specifying the format of UTC offsets. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct OffsetFormat { + /// See `OffsetPrecision`. + pub precision: OffsetPrecision, + /// Separator between hours, minutes and seconds. + pub colons: Colons, + /// Represent `+00:00` as `Z`. + pub allow_zulu: bool, + /// Pad the hour value to two digits. + pub padding: Pad, +} + +/// The precision of an offset from UTC formatting item. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum OffsetPrecision { + /// Format offset from UTC as only hours. Not recommended, it is not uncommon for timezones to + /// have an offset of 30 minutes, 15 minutes, etc. + /// Any minutes and seconds get truncated. + Hours, + /// Format offset from UTC as hours and minutes. + /// Any seconds will be rounded to the nearest minute. + Minutes, + /// Format offset from UTC as hours, minutes and seconds. + Seconds, + /// Format offset from UTC as hours, and optionally with minutes. + /// Any seconds will be rounded to the nearest minute. + OptionalMinutes, + /// Format offset from UTC as hours and minutes, and optionally seconds. + OptionalSeconds, + /// Format offset from UTC as hours and optionally minutes and seconds. + OptionalMinutesAndSeconds, +} + +/// The separator between hours and minutes in an offset. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum Colons { + /// No separator + None, + /// Colon (`:`) as separator + Colon, + /// No separator when formatting, colon allowed when parsing. + Maybe, +} + /// A single formatting item. This is used for both formatting and parsing. #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub enum Item<'a> { From 29019fbf8b88d1921bdce0884541955983703329 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 12:31:32 +0200 Subject: [PATCH 42/68] Test offset formatter --- src/format/formatting.rs | 160 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 3c2b479005..298b3e9f30 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -661,3 +661,163 @@ pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result { w.write_char(tens as char)?; w.write_char(ones as char) } + +#[cfg(test)] +#[cfg(any(feature = "alloc", feature = "std"))] +mod tests { + use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; + use crate::FixedOffset; + + #[test] + fn test_offset_formatting() { + fn check_all(precision: OffsetPrecision, expected: [[&str; 7]; 12]) { + fn check( + precision: OffsetPrecision, + colons: Colons, + padding: Pad, + allow_zulu: bool, + offsets: [FixedOffset; 7], + expected: [&str; 7], + ) { + let offset_format = OffsetFormat { precision, colons, allow_zulu, padding }; + for (offset, expected) in offsets.iter().zip(expected.iter()) { + let mut output = String::new(); + offset_format.format(&mut output, *offset).unwrap(); + assert_eq!(&output, expected); + } + } + // +03:45, -03:30, +11:00, -11:00:22, +02:34:26, -12:34:30, +00:00 + let offsets = [ + FixedOffset::east_opt(13_500).unwrap(), + FixedOffset::east_opt(-12_600).unwrap(), + FixedOffset::east_opt(39_600).unwrap(), + FixedOffset::east_opt(-39_622).unwrap(), + FixedOffset::east_opt(9266).unwrap(), + FixedOffset::east_opt(-45270).unwrap(), + FixedOffset::east_opt(0).unwrap(), + ]; + check(precision, Colons::Colon, Pad::Zero, false, offsets, expected[0]); + check(precision, Colons::Colon, Pad::Zero, true, offsets, expected[1]); + check(precision, Colons::Colon, Pad::Space, false, offsets, expected[2]); + check(precision, Colons::Colon, Pad::Space, true, offsets, expected[3]); + check(precision, Colons::Colon, Pad::None, false, offsets, expected[4]); + check(precision, Colons::Colon, Pad::None, true, offsets, expected[5]); + check(precision, Colons::None, Pad::Zero, false, offsets, expected[6]); + check(precision, Colons::None, Pad::Zero, true, offsets, expected[7]); + check(precision, Colons::None, Pad::Space, false, offsets, expected[8]); + check(precision, Colons::None, Pad::Space, true, offsets, expected[9]); + check(precision, Colons::None, Pad::None, false, offsets, expected[10]); + check(precision, Colons::None, Pad::None, true, offsets, expected[11]); + // `Colons::Maybe` should format the same as `Colons::None` + check(precision, Colons::Maybe, Pad::Zero, false, offsets, expected[6]); + check(precision, Colons::Maybe, Pad::Zero, true, offsets, expected[7]); + check(precision, Colons::Maybe, Pad::Space, false, offsets, expected[8]); + check(precision, Colons::Maybe, Pad::Space, true, offsets, expected[9]); + check(precision, Colons::Maybe, Pad::None, false, offsets, expected[10]); + check(precision, Colons::Maybe, Pad::None, true, offsets, expected[11]); + } + check_all( + OffsetPrecision::Hours, + [ + ["+03", "-03", "+11", "-11", "+02", "-12", "+00"], + ["+03", "-03", "+11", "-11", "+02", "-12", "Z"], + [" +3", " -3", "+11", "-11", " +2", "-12", " +0"], + [" +3", " -3", "+11", "-11", " +2", "-12", "Z"], + ["+3", "-3", "+11", "-11", "+2", "-12", "+0"], + ["+3", "-3", "+11", "-11", "+2", "-12", "Z"], + ["+03", "-03", "+11", "-11", "+02", "-12", "+00"], + ["+03", "-03", "+11", "-11", "+02", "-12", "Z"], + [" +3", " -3", "+11", "-11", " +2", "-12", " +0"], + [" +3", " -3", "+11", "-11", " +2", "-12", "Z"], + ["+3", "-3", "+11", "-11", "+2", "-12", "+0"], + ["+3", "-3", "+11", "-11", "+2", "-12", "Z"], + ], + ); + check_all( + OffsetPrecision::Minutes, + [ + ["+03:45", "-03:30", "+11:00", "-11:00", "+02:34", "-12:35", "+00:00"], + ["+03:45", "-03:30", "+11:00", "-11:00", "+02:34", "-12:35", "Z"], + [" +3:45", " -3:30", "+11:00", "-11:00", " +2:34", "-12:35", " +0:00"], + [" +3:45", " -3:30", "+11:00", "-11:00", " +2:34", "-12:35", "Z"], + ["+3:45", "-3:30", "+11:00", "-11:00", "+2:34", "-12:35", "+0:00"], + ["+3:45", "-3:30", "+11:00", "-11:00", "+2:34", "-12:35", "Z"], + ["+0345", "-0330", "+1100", "-1100", "+0234", "-1235", "+0000"], + ["+0345", "-0330", "+1100", "-1100", "+0234", "-1235", "Z"], + [" +345", " -330", "+1100", "-1100", " +234", "-1235", " +000"], + [" +345", " -330", "+1100", "-1100", " +234", "-1235", "Z"], + ["+345", "-330", "+1100", "-1100", "+234", "-1235", "+000"], + ["+345", "-330", "+1100", "-1100", "+234", "-1235", "Z"], + ], + ); + #[rustfmt::skip] + check_all( + OffsetPrecision::Seconds, + [ + ["+03:45:00", "-03:30:00", "+11:00:00", "-11:00:22", "+02:34:26", "-12:34:30", "+00:00:00"], + ["+03:45:00", "-03:30:00", "+11:00:00", "-11:00:22", "+02:34:26", "-12:34:30", "Z"], + [" +3:45:00", " -3:30:00", "+11:00:00", "-11:00:22", " +2:34:26", "-12:34:30", " +0:00:00"], + [" +3:45:00", " -3:30:00", "+11:00:00", "-11:00:22", " +2:34:26", "-12:34:30", "Z"], + ["+3:45:00", "-3:30:00", "+11:00:00", "-11:00:22", "+2:34:26", "-12:34:30", "+0:00:00"], + ["+3:45:00", "-3:30:00", "+11:00:00", "-11:00:22", "+2:34:26", "-12:34:30", "Z"], + ["+034500", "-033000", "+110000", "-110022", "+023426", "-123430", "+000000"], + ["+034500", "-033000", "+110000", "-110022", "+023426", "-123430", "Z"], + [" +34500", " -33000", "+110000", "-110022", " +23426", "-123430", " +00000"], + [" +34500", " -33000", "+110000", "-110022", " +23426", "-123430", "Z"], + ["+34500", "-33000", "+110000", "-110022", "+23426", "-123430", "+00000"], + ["+34500", "-33000", "+110000", "-110022", "+23426", "-123430", "Z"], + ], + ); + check_all( + OffsetPrecision::OptionalMinutes, + [ + ["+03:45", "-03:30", "+11", "-11", "+02:34", "-12:35", "+00"], + ["+03:45", "-03:30", "+11", "-11", "+02:34", "-12:35", "Z"], + [" +3:45", " -3:30", "+11", "-11", " +2:34", "-12:35", " +0"], + [" +3:45", " -3:30", "+11", "-11", " +2:34", "-12:35", "Z"], + ["+3:45", "-3:30", "+11", "-11", "+2:34", "-12:35", "+0"], + ["+3:45", "-3:30", "+11", "-11", "+2:34", "-12:35", "Z"], + ["+0345", "-0330", "+11", "-11", "+0234", "-1235", "+00"], + ["+0345", "-0330", "+11", "-11", "+0234", "-1235", "Z"], + [" +345", " -330", "+11", "-11", " +234", "-1235", " +0"], + [" +345", " -330", "+11", "-11", " +234", "-1235", "Z"], + ["+345", "-330", "+11", "-11", "+234", "-1235", "+0"], + ["+345", "-330", "+11", "-11", "+234", "-1235", "Z"], + ], + ); + check_all( + OffsetPrecision::OptionalSeconds, + [ + ["+03:45", "-03:30", "+11:00", "-11:00:22", "+02:34:26", "-12:34:30", "+00:00"], + ["+03:45", "-03:30", "+11:00", "-11:00:22", "+02:34:26", "-12:34:30", "Z"], + [" +3:45", " -3:30", "+11:00", "-11:00:22", " +2:34:26", "-12:34:30", " +0:00"], + [" +3:45", " -3:30", "+11:00", "-11:00:22", " +2:34:26", "-12:34:30", "Z"], + ["+3:45", "-3:30", "+11:00", "-11:00:22", "+2:34:26", "-12:34:30", "+0:00"], + ["+3:45", "-3:30", "+11:00", "-11:00:22", "+2:34:26", "-12:34:30", "Z"], + ["+0345", "-0330", "+1100", "-110022", "+023426", "-123430", "+0000"], + ["+0345", "-0330", "+1100", "-110022", "+023426", "-123430", "Z"], + [" +345", " -330", "+1100", "-110022", " +23426", "-123430", " +000"], + [" +345", " -330", "+1100", "-110022", " +23426", "-123430", "Z"], + ["+345", "-330", "+1100", "-110022", "+23426", "-123430", "+000"], + ["+345", "-330", "+1100", "-110022", "+23426", "-123430", "Z"], + ], + ); + check_all( + OffsetPrecision::OptionalMinutesAndSeconds, + [ + ["+03:45", "-03:30", "+11", "-11:00:22", "+02:34:26", "-12:34:30", "+00"], + ["+03:45", "-03:30", "+11", "-11:00:22", "+02:34:26", "-12:34:30", "Z"], + [" +3:45", " -3:30", "+11", "-11:00:22", " +2:34:26", "-12:34:30", " +0"], + [" +3:45", " -3:30", "+11", "-11:00:22", " +2:34:26", "-12:34:30", "Z"], + ["+3:45", "-3:30", "+11", "-11:00:22", "+2:34:26", "-12:34:30", "+0"], + ["+3:45", "-3:30", "+11", "-11:00:22", "+2:34:26", "-12:34:30", "Z"], + ["+0345", "-0330", "+11", "-110022", "+023426", "-123430", "+00"], + ["+0345", "-0330", "+11", "-110022", "+023426", "-123430", "Z"], + [" +345", " -330", "+11", "-110022", " +23426", "-123430", " +0"], + [" +345", " -330", "+11", "-110022", " +23426", "-123430", "Z"], + ["+345", "-330", "+11", "-110022", "+23426", "-123430", "+0"], + ["+345", "-330", "+11", "-110022", "+23426", "-123430", "Z"], + ], + ); + } +} From edf413d07e5c1a972e382e8ff9f42cf8aefd4d8a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 30 Jun 2023 22:40:51 +0200 Subject: [PATCH 43/68] Rustfmt --- src/format/formatting.rs | 260 +++++++++++++++++++-------------------- 1 file changed, 129 insertions(+), 131 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 298b3e9f30..61cf4dc5e6 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -351,144 +351,142 @@ fn format_inner( Item::Fixed(ref spec) => { use self::Fixed::*; - let ret = - match *spec { - ShortMonthName => date.map(|d| { - result.push_str(locale.short_months[d.month0() as usize]); - Ok(()) - }), - LongMonthName => date.map(|d| { - result.push_str(locale.long_months[d.month0() as usize]); - Ok(()) - }), - ShortWeekdayName => date.map(|d| { - result.push_str( - locale.short_weekdays[d.weekday().num_days_from_sunday() as usize], - ); - Ok(()) - }), - LongWeekdayName => date.map(|d| { - result.push_str( - locale.long_weekdays[d.weekday().num_days_from_sunday() as usize], - ); - Ok(()) - }), - LowerAmPm => time.map(|t| { - let ampm = if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] }; - for char in ampm.chars() { - result.extend(char.to_lowercase()) - } - Ok(()) - }), - UpperAmPm => time.map(|t| { - result.push_str(if t.hour12().0 { - locale.am_pm[1] - } else { - locale.am_pm[0] - }); + let ret = match *spec { + ShortMonthName => date.map(|d| { + result.push_str(locale.short_months[d.month0() as usize]); + Ok(()) + }), + LongMonthName => date.map(|d| { + result.push_str(locale.long_months[d.month0() as usize]); + Ok(()) + }), + ShortWeekdayName => date.map(|d| { + result.push_str( + locale.short_weekdays[d.weekday().num_days_from_sunday() as usize], + ); + Ok(()) + }), + LongWeekdayName => date.map(|d| { + result.push_str( + locale.long_weekdays[d.weekday().num_days_from_sunday() as usize], + ); + Ok(()) + }), + LowerAmPm => time.map(|t| { + let ampm = if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] }; + for char in ampm.chars() { + result.extend(char.to_lowercase()) + } + Ok(()) + }), + UpperAmPm => time.map(|t| { + result.push_str(if t.hour12().0 { locale.am_pm[1] } else { locale.am_pm[0] }); + Ok(()) + }), + Nanosecond => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + if nano == 0 { Ok(()) - }), - Nanosecond => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - if nano == 0 { - Ok(()) - } else if nano % 1_000_000 == 0 { - write!(result, ".{:03}", nano / 1_000_000) - } else if nano % 1_000 == 0 { - write!(result, ".{:06}", nano / 1_000) - } else { - write!(result, ".{:09}", nano) - } - }), - Nanosecond3 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; + } else if nano % 1_000_000 == 0 { write!(result, ".{:03}", nano / 1_000_000) - }), - Nanosecond6 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; + } else if nano % 1_000 == 0 { write!(result, ".{:06}", nano / 1_000) - }), - Nanosecond9 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; + } else { write!(result, ".{:09}", nano) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:03}", nano / 1_000_000) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:06}", nano / 1_000) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time - .map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(result, "{:09}", nano) - }), - TimezoneName => off.map(|(name, _)| { - result.push_str(name); - Ok(()) - }), - TimezoneOffset | TimezoneOffsetZ => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Minutes, - colons: Colons::Maybe, - allow_zulu: *spec == TimezoneOffsetZ, - padding: Pad::Zero, - } - .format(result, off) - }), - TimezoneOffsetColon | TimezoneOffsetColonZ => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Minutes, - colons: Colons::Colon, - allow_zulu: *spec == TimezoneOffsetColonZ, - padding: Pad::Zero, - } - .format(result, off) - }), - TimezoneOffsetDoubleColon => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Seconds, - colons: Colons::Colon, - allow_zulu: false, - padding: Pad::Zero, - } - .format(result, off) - }), - TimezoneOffsetTripleColon => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Hours, - colons: Colons::None, - allow_zulu: false, - padding: Pad::Zero, - } - .format(result, off) - }), - Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { - return Err(fmt::Error); } - RFC2822 => - // same as `%a, %d %b %Y %H:%M:%S %z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc2822_inner(result, d, t, off, locale)) - } else { - None - } + }), + Nanosecond3 => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, ".{:03}", nano / 1_000_000) + }), + Nanosecond6 => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, ".{:06}", nano / 1_000) + }), + Nanosecond9 => time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, ".{:09}", nano) + }), + Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { + time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, "{:03}", nano / 1_000_000) + }) + } + Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { + time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, "{:06}", nano / 1_000) + }) + } + Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { + time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(result, "{:09}", nano) + }) + } + TimezoneName => off.map(|(name, _)| { + result.push_str(name); + Ok(()) + }), + TimezoneOffset | TimezoneOffsetZ => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Maybe, + allow_zulu: *spec == TimezoneOffsetZ, + padding: Pad::Zero, } - RFC3339 => - // same as `%Y-%m-%dT%H:%M:%S%.f%:z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc3339(result, crate::NaiveDateTime::new(*d, *t), off)) - } else { - None - } + .format(result, off) + }), + TimezoneOffsetColon | TimezoneOffsetColonZ => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Colon, + allow_zulu: *spec == TimezoneOffsetColonZ, + padding: Pad::Zero, + } + .format(result, off) + }), + TimezoneOffsetDoubleColon => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Seconds, + colons: Colons::Colon, + allow_zulu: false, + padding: Pad::Zero, } - }; + .format(result, off) + }), + TimezoneOffsetTripleColon => off.map(|&(_, off)| { + OffsetFormat { + precision: OffsetPrecision::Hours, + colons: Colons::None, + allow_zulu: false, + padding: Pad::Zero, + } + .format(result, off) + }), + Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { + return Err(fmt::Error); + } + RFC2822 => + // same as `%a, %d %b %Y %H:%M:%S %z` + { + if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { + Some(write_rfc2822_inner(result, d, t, off, locale)) + } else { + None + } + } + RFC3339 => + // same as `%Y-%m-%dT%H:%M:%S%.f%:z` + { + if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { + Some(write_rfc3339(result, crate::NaiveDateTime::new(*d, *t), off)) + } else { + None + } + } + }; match ret { Some(ret) => ret?, From 7cc0ca8c8b744620ef24c944b2a084655dad06b4 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 16:16:32 +0200 Subject: [PATCH 44/68] Require `clock` and `std` feature for `dateutils` tests --- tests/dateutils.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/tests/dateutils.rs b/tests/dateutils.rs index de100e52fc..52181e8316 100644 --- a/tests/dateutils.rs +++ b/tests/dateutils.rs @@ -1,14 +1,8 @@ -#[cfg(unix)] -use chrono::offset::TimeZone; -#[cfg(unix)] -use chrono::Local; -#[cfg(unix)] -use chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; - -#[cfg(unix)] +#![cfg(all(unix, feature = "clock", feature = "std"))] + +use chrono::{Datelike, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike}; use std::{path, process, thread}; -#[cfg(unix)] fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) { let output = process::Command::new(path) .arg("-d") @@ -64,7 +58,6 @@ const DATE_PATH: &str = "/usr/bin/date"; const DATE_PATH: &str = "/opt/freeware/bin/date"; #[cfg(test)] -#[cfg(unix)] /// test helper to sanity check the date command behaves as expected /// asserts the command succeeded fn assert_run_date_version() { @@ -82,7 +75,6 @@ fn assert_run_date_version() { } #[test] -#[cfg(unix)] fn try_verify_against_date_command() { if !path::Path::new(DATE_PATH).exists() { eprintln!("date command {:?} not found, skipping", DATE_PATH); From bd23f5b437c3edf387cd9451668b3bb736ed46ff Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 16:16:46 +0200 Subject: [PATCH 45/68] Require `clock` feature for `wasm` tests --- tests/wasm.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/wasm.rs b/tests/wasm.rs index 3cc3dc0390..b534f0a315 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -8,6 +8,7 @@ #![cfg(all( target_arch = "wasm32", feature = "wasmbind", + feature = "clock", not(any(target_os = "emscripten", target_os = "wasi")) ))] From 771169016187d9d5f58624dbf0abd5a63cdca384 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 3 Jul 2023 18:30:18 +0200 Subject: [PATCH 46/68] Also run integration tests with `no_std` on CI --- .github/workflows/test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a485fbe99c..3e8fe52aa1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -93,8 +93,7 @@ jobs: # run using `bash` on all platforms for consistent # line-continuation marks shell: bash - - run: cargo test --lib --no-default-features - - run: cargo test --doc --no-default-features + - run: cargo test --no-default-features no_std: strategy: From 3b49eb6e438718282b598bc6a1c9c4c784026068 Mon Sep 17 00:00:00 2001 From: scarf Date: Thu, 9 Mar 2023 21:24:15 +0900 Subject: [PATCH 47/68] test: locale's datetime and 12 hour clock time `%c`: did not take account of `%x` and `%X` `%r`: did not use locale's format and used `%I:%M:%S %p` --- src/format/strftime.rs | 57 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index ab3b5e16b2..51d4fc0869 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -752,11 +752,58 @@ mod tests { } #[test] - #[cfg(not(feature = "unstable-locales"))] - fn test_type_sizes() { - use core::mem::size_of; - assert_eq!(size_of::(), 24); - assert_eq!(size_of::(), 32); + #[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] + fn test_strftime_localized_korean() { + let dt = FixedOffset::east_opt(34200) + .unwrap() + .with_ymd_and_hms(2001, 7, 8, 0, 34, 59) + .unwrap() + .with_nanosecond(1_026_490_708) + .unwrap(); + + // date specifiers + assert_eq!(dt.format_localized("%b", Locale::ko_KR).to_string(), " 7μ›”"); + assert_eq!(dt.format_localized("%B", Locale::ko_KR).to_string(), "7μ›”"); + assert_eq!(dt.format_localized("%h", Locale::ko_KR).to_string(), " 7μ›”"); + assert_eq!(dt.format_localized("%a", Locale::ko_KR).to_string(), "일"); + assert_eq!(dt.format_localized("%A", Locale::ko_KR).to_string(), "μΌμš”μΌ"); + assert_eq!(dt.format_localized("%D", Locale::ko_KR).to_string(), "07/08/01"); + assert_eq!(dt.format_localized("%x", Locale::ko_KR).to_string(), "2001λ…„ 07μ›” 08일"); + assert_eq!(dt.format_localized("%F", Locale::ko_KR).to_string(), "2001-07-08"); + assert_eq!(dt.format_localized("%v", Locale::ko_KR).to_string(), " 8- 7μ›”-2001"); + assert_eq!(dt.format_localized("%r", Locale::ko_KR).to_string(), "μ˜€μ „ 12μ‹œ 34λΆ„ 60초"); + + // date & time specifiers + assert_eq!( + dt.format_localized("%c", Locale::ko_KR).to_string(), + "2001λ…„ 07μ›” 08일 (일) μ˜€μ „ 12μ‹œ 34λΆ„ 60초" + ); + } + + #[test] + #[cfg(all(feature = "unstable-locales", any(feature = "alloc", feature = "std")))] + fn test_strftime_localized_japanese() { + let dt = FixedOffset::east_opt(34200) + .unwrap() + .with_ymd_and_hms(2001, 7, 8, 0, 34, 59) + .unwrap() + .with_nanosecond(1_026_490_708) + .unwrap(); + + // date specifiers + assert_eq!(dt.format_localized("%b", Locale::ja_JP).to_string(), " 7月"); + assert_eq!(dt.format_localized("%B", Locale::ja_JP).to_string(), "7月"); + assert_eq!(dt.format_localized("%h", Locale::ja_JP).to_string(), " 7月"); + assert_eq!(dt.format_localized("%a", Locale::ja_JP).to_string(), "ζ—₯"); + assert_eq!(dt.format_localized("%A", Locale::ja_JP).to_string(), "ζ—₯ζ›œζ—₯"); + assert_eq!(dt.format_localized("%D", Locale::ja_JP).to_string(), "07/08/01"); + assert_eq!(dt.format_localized("%x", Locale::ja_JP).to_string(), "2001εΉ΄07月08ζ—₯"); + assert_eq!(dt.format_localized("%F", Locale::ja_JP).to_string(), "2001-07-08"); + assert_eq!(dt.format_localized("%v", Locale::ja_JP).to_string(), " 8- 7月-2001"); + assert_eq!(dt.format_localized("%r", Locale::ja_JP).to_string(), "εˆε‰12ζ™‚34εˆ†60η§’"); + + // date & time specifiers + assert_eq!(dt.format_localized("%c", Locale::ja_JP).to_string(), "2001εΉ΄07月08ζ—₯ 00ζ™‚34εˆ†60η§’"); } #[test] From 3c50b428a9cb5d03d0259d7d5acfdd6f3b03bf26 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 4 Jul 2023 18:23:49 +0200 Subject: [PATCH 48/68] Update `pure-rust-locales` to 0.6 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c5ba2eaabf..acc61634df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ time = { version = "0.1.43", optional = true } num-traits = { version = "0.2", default-features = false } rustc-serialize = { version = "0.3.20", optional = true } serde = { version = "1.0.99", default-features = false, optional = true } -pure-rust-locales = { version = "0.5.2", optional = true } +pure-rust-locales = { version = "0.6", optional = true } criterion = { version = "0.4.0", optional = true } rkyv = { version = "0.7", optional = true } arbitrary = { version = "1.0.0", features = ["derive"], optional = true } From dfd3cf5e509b45d13ee42a3ced23658495a70cfa Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 3 Jul 2023 14:44:17 +0200 Subject: [PATCH 49/68] Make `%r` use locale's 12 hour clock time --- src/format/locales.rs | 4 ++++ src/format/strftime.rs | 40 ++++++++++++++++++++++++++++------------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/format/locales.rs b/src/format/locales.rs index d6fecc978b..7311d661b1 100644 --- a/src/format/locales.rs +++ b/src/format/locales.rs @@ -31,3 +31,7 @@ pub(crate) const fn d_t_fmt(locale: Locale) -> &'static str { pub(crate) const fn t_fmt(locale: Locale) -> &'static str { locale_match!(locale => LC_TIME::T_FMT) } + +pub(crate) const fn t_fmt_ampm(locale: Locale) -> &'static str { + locale_match!(locale => LC_TIME::T_FMT_AMPM) +} diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 51d4fc0869..3a0d573710 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -65,7 +65,7 @@ The following specifiers are available both to formatting and parsing. | `%R` | `00:34` | Hour-minute format. Same as `%H:%M`. | | `%T` | `00:34:60` | Hour-minute-second format. Same as `%H:%M:%S`. | | `%X` | `00:34:60` | Locale's time representation (e.g., 23:13:48). | -| `%r` | `12:34:60 AM` | Hour-minute-second format in 12-hour clocks. Same as `%I:%M:%S %p`. | +| `%r` | `12:34:60 AM` | Locale's 12 hour clock time. (e.g., 11:11:04 PM). Falls back to `%X` if the locale does not have a 12 hour clock format. | | | | | | | | **TIME ZONE SPECIFIERS:** | | `%Z` | `ACST` | Local time zone name. Skips all non-whitespace characters during parsing. Identical to `%:z` when formatting. [^8] | @@ -274,6 +274,15 @@ impl<'a> StrftimeItems<'a> { ]; static T_FMT: &[Item<'static>] = &[num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)]; + static T_FMT_AMPM: &[Item<'static>] = &[ + num0(Hour12), + Literal(":"), + num0(Minute), + Literal(":"), + num0(Second), + Space(" "), + fixed(Fixed::UpperAmPm), + ]; match remainder.chars().next() { // we are done @@ -366,15 +375,19 @@ impl<'a> StrftimeItems<'a> { 'm' => num0(Month), 'n' => Space("\n"), 'p' => fixed(Fixed::UpperAmPm), - 'r' => queue![ - num0(Hour12), - Literal(":"), - num0(Minute), - Literal(":"), - num0(Second), - Space(" "), - fixed(Fixed::UpperAmPm) - ], + #[cfg(not(feature = "unstable-locales"))] + 'r' => queue_from_slice!(T_FMT_AMPM), + #[cfg(feature = "unstable-locales")] + 'r' => { + if self.locale.is_some() + && locales::t_fmt_ampm(self.locale.unwrap()).is_empty() + { + // 12-hour clock not supported by this locale. Switch to 24-hour format. + self.switch_to_locale_str(locales::t_fmt, T_FMT) + } else { + self.switch_to_locale_str(locales::t_fmt_ampm, T_FMT_AMPM) + } + } 's' => num(Timestamp), 't' => Space("\t"), 'u' => num(WeekdayFromMon), @@ -704,7 +717,7 @@ mod tests { assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34"); assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60"); assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60"); - assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "12:34:60 "); + assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "00:34:60"); // date & time specifiers assert_eq!( @@ -803,7 +816,10 @@ mod tests { assert_eq!(dt.format_localized("%r", Locale::ja_JP).to_string(), "εˆε‰12ζ™‚34εˆ†60η§’"); // date & time specifiers - assert_eq!(dt.format_localized("%c", Locale::ja_JP).to_string(), "2001εΉ΄07月08ζ—₯ 00ζ™‚34εˆ†60η§’"); + assert_eq!( + dt.format_localized("%c", Locale::ja_JP).to_string(), + "2001εΉ΄07月08ζ—₯ 00ζ™‚34εˆ†60η§’" + ); } #[test] From ac802520f7f3457b2f93198bc42b799c0721c135 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 5 Jul 2023 23:23:11 +0200 Subject: [PATCH 50/68] Clarify nanosecond formatting specifier doc --- src/format/strftime.rs | 50 +++++++++++------------------------------- 1 file changed, 13 insertions(+), 37 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 3a0d573710..5ca7507043 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -53,14 +53,14 @@ The following specifiers are available both to formatting and parsing. | | | | | `%M` | `34` | Minute number (00--59), zero-padded to 2 digits. | | `%S` | `60` | Second number (00--60), zero-padded to 2 digits. [^4] | -| `%f` | `026490000` | The fractional seconds (in nanoseconds) since last whole second. [^7] | -| `%.f` | `.026490`| Similar to `.%f` but left-aligned. These all consume the leading dot. [^7] | -| `%.3f`| `.026` | Similar to `.%f` but left-aligned but fixed to a length of 3. [^7] | -| `%.6f`| `.026490` | Similar to `.%f` but left-aligned but fixed to a length of 6. [^7] | -| `%.9f`| `.026490000` | Similar to `.%f` but left-aligned but fixed to a length of 9. [^7] | -| `%3f` | `026` | Similar to `%.3f` but without the leading dot. [^7] | -| `%6f` | `026490` | Similar to `%.6f` but without the leading dot. [^7] | -| `%9f` | `026490000` | Similar to `%.9f` but without the leading dot. [^7] | +| `%f` | `26490000` | Number of nanoseconds since last whole second. [^7] | +| `%.f` | `.026490`| Decimal fraction of a second. Consumes the leading dot. [^7] | +| `%.3f`| `.026` | Decimal fraction of a second with a fixed length of 3. | +| `%.6f`| `.026490` | Decimal fraction of a second with a fixed length of 6. | +| `%.9f`| `.026490000` | Decimal fraction of a second with a fixed length of 9. | +| `%3f` | `026` | Decimal fraction of a second like `%.3f` but without the leading dot. | +| `%6f` | `026490` | Decimal fraction of a second like `%.6f` but without the leading dot. | +| `%9f` | `026490000` | Decimal fraction of a second like `%.9f` but without the leading dot. | | | | | | `%R` | `00:34` | Hour-minute format. Same as `%H:%M`. | | `%T` | `00:34:60` | Hour-minute-second format. Same as `%H:%M:%S`. | @@ -132,36 +132,12 @@ Notes: For the purpose of Chrono, it only accounts for non-leap seconds so it slightly differs from ISO C `strftime` behavior. -[^7]: `%f`, `%.f`, `%.3f`, `%.6f`, `%.9f`, `%3f`, `%6f`, `%9f`: +[^7]: `%f`, `%.f`:
- The default `%f` is right-aligned and always zero-padded to 9 digits - for the compatibility with glibc and others, - so it always counts the number of nanoseconds since the last whole second. - E.g. 7ms after the last second will print `007000000`, - and parsing `7000000` will yield the same. -
-
- The variant `%.f` is left-aligned and print 0, 3, 6 or 9 fractional digits - according to the precision. - E.g. 70ms after the last second under `%.f` will print `.070` (note: not `.07`), - and parsing `.07`, `.070000` etc. will yield the same. - Note that they can print or read nothing if the fractional part is zero or - the next character is not `.`. -
-
- The variant `%.3f`, `%.6f` and `%.9f` are left-aligned and print 3, 6 or 9 fractional digits - according to the number preceding `f`. - E.g. 70ms after the last second under `%.3f` will print `.070` (note: not `.07`), - and parsing `.07`, `.070000` etc. will yield the same. - Note that they can read nothing if the fractional part is zero or - the next character is not `.` however will print with the specified length. -
-
- The variant `%3f`, `%6f` and `%9f` are left-aligned and print 3, 6 or 9 fractional digits - according to the number preceding `f`, but without the leading dot. - E.g. 70ms after the last second under `%3f` will print `070` (note: not `07`), - and parsing `07`, `070000` etc. will yield the same. - Note that they can read nothing if the fractional part is zero. + `%f` and `%.f` are notably different formatting specifiers.
+ `%f` counts the number of nanoseconds since the last whole second, while `%.f` is a fraction of a + second.
+ Example: 7ΞΌs is formatted as `7000` with `%f`, and formatted as `.000007` with `%.f`. [^8]: `%Z`: Since `chrono` is not aware of timezones beyond their offsets, this specifier From 26d03c9f50bf146ef29bf3ab950e64af5f70de0a Mon Sep 17 00:00:00 2001 From: Kevin Amado Date: Wed, 5 Jul 2023 14:21:20 -0600 Subject: [PATCH 51/68] const from_ymd_opt --- src/naive/date.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/naive/date.rs b/src/naive/date.rs index d4d6b662e2..3acbc1fe03 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -285,9 +285,14 @@ impl NaiveDate { /// assert!(from_ymd_opt(-400000, 1, 1).is_none()); /// ``` #[must_use] - pub fn from_ymd_opt(year: i32, month: u32, day: u32) -> Option { + pub const fn from_ymd_opt(year: i32, month: u32, day: u32) -> Option { let flags = YearFlags::from_year(year); - NaiveDate::from_mdf(year, Mdf::new(month, day, flags)?) + + if let Some(mdf) = Mdf::new(month, day, flags) { + NaiveDate::from_mdf(year, mdf) + } else { + None + } } /// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date) From ee3d61b17976aa852d0e09fbdcfe0b4160b662be Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 5 Jul 2023 14:32:37 +0200 Subject: [PATCH 52/68] Rust issue #22255 is no longer relevant --- src/format/parse.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 49742ce40c..4a1c4d6009 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -570,7 +570,6 @@ mod tests { use crate::format::Item::{Literal, Space}; use crate::format::Numeric::*; - // workaround for Rust issue #22255 fn parse_all(s: &str, items: &[Item]) -> ParseResult { let mut parsed = Parsed::new(); parse(&mut parsed, s, items.iter())?; From 33c69f8d8a6dae1fbb3a8ce57e61f112f6d6ef26 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 5 Jul 2023 14:45:59 +0200 Subject: [PATCH 53/68] Split up `test_parse` --- src/format/parse.rs | 72 ++++++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 4a1c4d6009..8dfd9740c8 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -564,28 +564,26 @@ mod tests { use crate::format::*; use crate::{DateTime, FixedOffset, TimeZone, Timelike, Utc}; - #[test] - fn test_parse() { - use crate::format::InternalInternal::*; - use crate::format::Item::{Literal, Space}; - use crate::format::Numeric::*; + fn parse_all(s: &str, items: &[Item]) -> ParseResult { + let mut parsed = Parsed::new(); + parse(&mut parsed, s, items.iter())?; + Ok(parsed) + } - fn parse_all(s: &str, items: &[Item]) -> ParseResult { - let mut parsed = Parsed::new(); - parse(&mut parsed, s, items.iter())?; - Ok(parsed) - } + macro_rules! check { + ($fmt:expr, $items:expr; $err:tt) => ( + assert_eq!(parse_all($fmt, &$items), Err($err)) + ); + ($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] { + let mut expected = Parsed::new(); + $(expected.$k = Some($v);)* + assert_eq!(parse_all($fmt, &$items), Ok(expected)) + }); + } - macro_rules! check { - ($fmt:expr, $items:expr; $err:tt) => ( - assert_eq!(parse_all($fmt, &$items), Err($err)) - ); - ($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] { - let mut expected = Parsed::new(); - $(expected.$k = Some($v);)* - assert_eq!(parse_all($fmt, &$items), Ok(expected)) - }); - } + #[test] + fn test_parse_whitespace_and_literal() { + use crate::format::Item::{Literal, Space}; // empty string check!("", []; ); @@ -634,6 +632,12 @@ mod tests { check!("x y", [Literal("x"), Literal("y")]; INVALID); check!("xy", [Literal("x"), Space(""), Literal("y")]; ); check!("x y", [Literal("x"), Space(""), Literal("y")]; ); + } + + #[test] + fn test_parse_numeric() { + use crate::format::Item::{Literal, Space}; + use crate::format::Numeric::*; // numeric check!("1987", [num(Year)]; year: 1987); @@ -712,6 +716,12 @@ mod tests { [num(Hour), num(Minute), num(Second), num(Nanosecond), num(Timestamp)]; hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, timestamp: 567_890_123); + } + + #[test] + fn test_parse_fixed() { + use crate::format::Fixed; + use crate::format::Item::Literal; // fixed: month and weekday names check!("apr", [fixed(Fixed::ShortMonthName)]; month: 4); @@ -762,6 +772,13 @@ mod tests { check!("x", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); check!("xx", [fixed(Fixed::LowerAmPm)]; INVALID); check!("", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); + } + + #[test] + fn test_parse_fixed_nanosecond() { + use crate::format::Fixed; + use crate::format::InternalInternal::*; + use crate::format::Numeric::*; // fixed: dot plus nanoseconds check!("", [fixed(Fixed::Nanosecond)]; ); // no field set, but not an error @@ -817,8 +834,14 @@ mod tests { check!("00000000x", [internal_fixed(Nanosecond9NoDot)]; INVALID); check!(" 4", [internal_fixed(Nanosecond9NoDot)]; INVALID); check!(".42100000", [internal_fixed(Nanosecond9NoDot)]; INVALID); + } - // fixed: timezone offsets + #[test] + fn test_parse_fixed_timezone_offset() { + use crate::format::Fixed; + use crate::format::InternalInternal::*; + use crate::format::Item::Literal; + use crate::format::Numeric::*; // TimezoneOffset check!("1", [fixed(Fixed::TimezoneOffset)]; INVALID); @@ -1200,6 +1223,13 @@ mod tests { check!("CEST ", [fixed(Fixed::TimezoneName)]; TOO_LONG); check!(" CEST", [fixed(Fixed::TimezoneName)]; TOO_LONG); check!("CE ST", [fixed(Fixed::TimezoneName)]; TOO_LONG); + } + + #[test] + fn test_parse_practical_examples() { + use crate::format::InternalInternal::*; + use crate::format::Item::{Literal, Space}; + use crate::format::Numeric::*; // some practical examples check!("2015-02-04T14:37:05+09:00", From 838ba6813ad85a825b8de88c277e33dda04d4111 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 5 Jul 2023 14:53:24 +0200 Subject: [PATCH 54/68] Import `Fixed` variants --- src/format/parse.rs | 689 ++++++++++++++++++++++---------------------- 1 file changed, 344 insertions(+), 345 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 8dfd9740c8..1b6773fa8f 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -720,84 +720,84 @@ mod tests { #[test] fn test_parse_fixed() { - use crate::format::Fixed; + use crate::format::Fixed::*; use crate::format::Item::Literal; // fixed: month and weekday names - check!("apr", [fixed(Fixed::ShortMonthName)]; month: 4); - check!("Apr", [fixed(Fixed::ShortMonthName)]; month: 4); - check!("APR", [fixed(Fixed::ShortMonthName)]; month: 4); - check!("ApR", [fixed(Fixed::ShortMonthName)]; month: 4); - check!("April", [fixed(Fixed::ShortMonthName)]; TOO_LONG); // `Apr` is parsed - check!("A", [fixed(Fixed::ShortMonthName)]; TOO_SHORT); - check!("Sol", [fixed(Fixed::ShortMonthName)]; INVALID); - check!("Apr", [fixed(Fixed::LongMonthName)]; month: 4); - check!("Apri", [fixed(Fixed::LongMonthName)]; TOO_LONG); // `Apr` is parsed - check!("April", [fixed(Fixed::LongMonthName)]; month: 4); - check!("Aprill", [fixed(Fixed::LongMonthName)]; TOO_LONG); - check!("Aprill", [fixed(Fixed::LongMonthName), Literal("l")]; month: 4); - check!("Aprl", [fixed(Fixed::LongMonthName), Literal("l")]; month: 4); - check!("April", [fixed(Fixed::LongMonthName), Literal("il")]; TOO_SHORT); // do not backtrack - check!("thu", [fixed(Fixed::ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thu", [fixed(Fixed::ShortWeekdayName)]; weekday: Weekday::Thu); - check!("THU", [fixed(Fixed::ShortWeekdayName)]; weekday: Weekday::Thu); - check!("tHu", [fixed(Fixed::ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thursday", [fixed(Fixed::ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("T", [fixed(Fixed::ShortWeekdayName)]; TOO_SHORT); - check!("The", [fixed(Fixed::ShortWeekdayName)]; INVALID); - check!("Nop", [fixed(Fixed::ShortWeekdayName)]; INVALID); - check!("Thu", [fixed(Fixed::LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thur", [fixed(Fixed::LongWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("Thurs", [fixed(Fixed::LongWeekdayName)]; TOO_LONG); // ditto - check!("Thursday", [fixed(Fixed::LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thursdays", [fixed(Fixed::LongWeekdayName)]; TOO_LONG); - check!("Thursdays", [fixed(Fixed::LongWeekdayName), Literal("s")]; weekday: Weekday::Thu); - check!("Thus", [fixed(Fixed::LongWeekdayName), Literal("s")]; weekday: Weekday::Thu); - check!("Thursday", [fixed(Fixed::LongWeekdayName), Literal("rsday")]; TOO_SHORT); // do not backtrack + check!("apr", [fixed(ShortMonthName)]; month: 4); + check!("Apr", [fixed(ShortMonthName)]; month: 4); + check!("APR", [fixed(ShortMonthName)]; month: 4); + check!("ApR", [fixed(ShortMonthName)]; month: 4); + check!("April", [fixed(ShortMonthName)]; TOO_LONG); // `Apr` is parsed + check!("A", [fixed(ShortMonthName)]; TOO_SHORT); + check!("Sol", [fixed(ShortMonthName)]; INVALID); + check!("Apr", [fixed(LongMonthName)]; month: 4); + check!("Apri", [fixed(LongMonthName)]; TOO_LONG); // `Apr` is parsed + check!("April", [fixed(LongMonthName)]; month: 4); + check!("Aprill", [fixed(LongMonthName)]; TOO_LONG); + check!("Aprill", [fixed(LongMonthName), Literal("l")]; month: 4); + check!("Aprl", [fixed(LongMonthName), Literal("l")]; month: 4); + check!("April", [fixed(LongMonthName), Literal("il")]; TOO_SHORT); // do not backtrack + check!("thu", [fixed(ShortWeekdayName)]; weekday: Weekday::Thu); + check!("Thu", [fixed(ShortWeekdayName)]; weekday: Weekday::Thu); + check!("THU", [fixed(ShortWeekdayName)]; weekday: Weekday::Thu); + check!("tHu", [fixed(ShortWeekdayName)]; weekday: Weekday::Thu); + check!("Thursday", [fixed(ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed + check!("T", [fixed(ShortWeekdayName)]; TOO_SHORT); + check!("The", [fixed(ShortWeekdayName)]; INVALID); + check!("Nop", [fixed(ShortWeekdayName)]; INVALID); + check!("Thu", [fixed(LongWeekdayName)]; weekday: Weekday::Thu); + check!("Thur", [fixed(LongWeekdayName)]; TOO_LONG); // `Thu` is parsed + check!("Thurs", [fixed(LongWeekdayName)]; TOO_LONG); // ditto + check!("Thursday", [fixed(LongWeekdayName)]; weekday: Weekday::Thu); + check!("Thursdays", [fixed(LongWeekdayName)]; TOO_LONG); + check!("Thursdays", [fixed(LongWeekdayName), Literal("s")]; weekday: Weekday::Thu); + check!("Thus", [fixed(LongWeekdayName), Literal("s")]; weekday: Weekday::Thu); + check!("Thursday", [fixed(LongWeekdayName), Literal("rsday")]; TOO_SHORT); // do not backtrack // fixed: am/pm - check!("am", [fixed(Fixed::LowerAmPm)]; hour_div_12: 0); - check!("pm", [fixed(Fixed::LowerAmPm)]; hour_div_12: 1); - check!("AM", [fixed(Fixed::LowerAmPm)]; hour_div_12: 0); - check!("PM", [fixed(Fixed::LowerAmPm)]; hour_div_12: 1); - check!("am", [fixed(Fixed::UpperAmPm)]; hour_div_12: 0); - check!("pm", [fixed(Fixed::UpperAmPm)]; hour_div_12: 1); - check!("AM", [fixed(Fixed::UpperAmPm)]; hour_div_12: 0); - check!("PM", [fixed(Fixed::UpperAmPm)]; hour_div_12: 1); - check!("Am", [fixed(Fixed::LowerAmPm)]; hour_div_12: 0); - check!(" Am", [fixed(Fixed::LowerAmPm)]; INVALID); - check!("ame", [fixed(Fixed::LowerAmPm)]; TOO_LONG); // `am` is parsed - check!("a", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); - check!("p", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); - check!("x", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); - check!("xx", [fixed(Fixed::LowerAmPm)]; INVALID); - check!("", [fixed(Fixed::LowerAmPm)]; TOO_SHORT); + check!("am", [fixed(LowerAmPm)]; hour_div_12: 0); + check!("pm", [fixed(LowerAmPm)]; hour_div_12: 1); + check!("AM", [fixed(LowerAmPm)]; hour_div_12: 0); + check!("PM", [fixed(LowerAmPm)]; hour_div_12: 1); + check!("am", [fixed(UpperAmPm)]; hour_div_12: 0); + check!("pm", [fixed(UpperAmPm)]; hour_div_12: 1); + check!("AM", [fixed(UpperAmPm)]; hour_div_12: 0); + check!("PM", [fixed(UpperAmPm)]; hour_div_12: 1); + check!("Am", [fixed(LowerAmPm)]; hour_div_12: 0); + check!(" Am", [fixed(LowerAmPm)]; INVALID); + check!("ame", [fixed(LowerAmPm)]; TOO_LONG); // `am` is parsed + check!("a", [fixed(LowerAmPm)]; TOO_SHORT); + check!("p", [fixed(LowerAmPm)]; TOO_SHORT); + check!("x", [fixed(LowerAmPm)]; TOO_SHORT); + check!("xx", [fixed(LowerAmPm)]; INVALID); + check!("", [fixed(LowerAmPm)]; TOO_SHORT); } #[test] fn test_parse_fixed_nanosecond() { - use crate::format::Fixed; + use crate::format::Fixed::Nanosecond; use crate::format::InternalInternal::*; - use crate::format::Numeric::*; + use crate::format::Numeric::Second; // fixed: dot plus nanoseconds - check!("", [fixed(Fixed::Nanosecond)]; ); // no field set, but not an error - check!(".", [fixed(Fixed::Nanosecond)]; TOO_SHORT); - check!("4", [fixed(Fixed::Nanosecond)]; TOO_LONG); // never consumes `4` - check!("4", [fixed(Fixed::Nanosecond), num(Second)]; second: 4); - check!(".0", [fixed(Fixed::Nanosecond)]; nanosecond: 0); - check!(".4", [fixed(Fixed::Nanosecond)]; nanosecond: 400_000_000); - check!(".42", [fixed(Fixed::Nanosecond)]; nanosecond: 420_000_000); - check!(".421", [fixed(Fixed::Nanosecond)]; nanosecond: 421_000_000); - check!(".42195", [fixed(Fixed::Nanosecond)]; nanosecond: 421_950_000); - check!(".421950803", [fixed(Fixed::Nanosecond)]; nanosecond: 421_950_803); - check!(".421950803547", [fixed(Fixed::Nanosecond)]; nanosecond: 421_950_803); - check!(".000000003547", [fixed(Fixed::Nanosecond)]; nanosecond: 3); - check!(".000000000547", [fixed(Fixed::Nanosecond)]; nanosecond: 0); - check!(".", [fixed(Fixed::Nanosecond)]; TOO_SHORT); - check!(".4x", [fixed(Fixed::Nanosecond)]; TOO_LONG); - check!(". 4", [fixed(Fixed::Nanosecond)]; INVALID); - check!(" .4", [fixed(Fixed::Nanosecond)]; TOO_LONG); // no automatic trimming + check!("", [fixed(Nanosecond)]; ); // no field set, but not an error + check!(".", [fixed(Nanosecond)]; TOO_SHORT); + check!("4", [fixed(Nanosecond)]; TOO_LONG); // never consumes `4` + check!("4", [fixed(Nanosecond), num(Second)]; second: 4); + check!(".0", [fixed(Nanosecond)]; nanosecond: 0); + check!(".4", [fixed(Nanosecond)]; nanosecond: 400_000_000); + check!(".42", [fixed(Nanosecond)]; nanosecond: 420_000_000); + check!(".421", [fixed(Nanosecond)]; nanosecond: 421_000_000); + check!(".42195", [fixed(Nanosecond)]; nanosecond: 421_950_000); + check!(".421950803", [fixed(Nanosecond)]; nanosecond: 421_950_803); + check!(".421950803547", [fixed(Nanosecond)]; nanosecond: 421_950_803); + check!(".000000003547", [fixed(Nanosecond)]; nanosecond: 3); + check!(".000000000547", [fixed(Nanosecond)]; nanosecond: 0); + check!(".", [fixed(Nanosecond)]; TOO_SHORT); + check!(".4x", [fixed(Nanosecond)]; TOO_LONG); + check!(". 4", [fixed(Nanosecond)]; INVALID); + check!(" .4", [fixed(Nanosecond)]; TOO_LONG); // no automatic trimming // fixed: nanoseconds without the dot check!("", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); @@ -838,287 +838,286 @@ mod tests { #[test] fn test_parse_fixed_timezone_offset() { - use crate::format::Fixed; + use crate::format::Fixed::*; use crate::format::InternalInternal::*; use crate::format::Item::Literal; - use crate::format::Numeric::*; // TimezoneOffset - check!("1", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("12", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("123", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("1234", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("12345", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("123456", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("1234567", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("+1", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); - check!("+12", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); - check!("+123", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); - check!("+1234", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("+12345", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+123456", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+1234567", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12345678", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12:", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); - check!("+12:3", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); - check!("+12:34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("-12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("βˆ’12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12:34:5", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12:34:56", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12:34:56:", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12 34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("+12 34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("12:34:56", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("+12::34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("+12: :34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("+12:::34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("+12::::34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("+12::34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("+12:34:56", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12:3456", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+1234:56", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+1234:567", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+00:00", [fixed(Fixed::TimezoneOffset)]; offset: 0); - check!("-00:00", [fixed(Fixed::TimezoneOffset)]; offset: 0); - check!("βˆ’00:00", [fixed(Fixed::TimezoneOffset)]; offset: 0); // MINUS SIGN (U+2212) - check!("+00:01", [fixed(Fixed::TimezoneOffset)]; offset: 60); - check!("-00:01", [fixed(Fixed::TimezoneOffset)]; offset: -60); - check!("+00:30", [fixed(Fixed::TimezoneOffset)]; offset: 1_800); - check!("-00:30", [fixed(Fixed::TimezoneOffset)]; offset: -1_800); - check!("+24:00", [fixed(Fixed::TimezoneOffset)]; offset: 86_400); - check!("-24:00", [fixed(Fixed::TimezoneOffset)]; offset: -86_400); - check!("βˆ’24:00", [fixed(Fixed::TimezoneOffset)]; offset: -86_400); // MINUS SIGN (U+2212) - check!("+99:59", [fixed(Fixed::TimezoneOffset)]; offset: 359_940); - check!("-99:59", [fixed(Fixed::TimezoneOffset)]; offset: -359_940); - check!("+00:60", [fixed(Fixed::TimezoneOffset)]; OUT_OF_RANGE); - check!("+00:99", [fixed(Fixed::TimezoneOffset)]; OUT_OF_RANGE); - check!("#12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("+12:34 ", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12 34 ", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!(" +12:34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!(" βˆ’12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("12:34 ", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!(" +12:34", [fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("\t -12:34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fixed(Fixed::TimezoneOffset)]; offset: -45_240); - check!(" 12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); - check!("+", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); - check!("+12345", [fixed(Fixed::TimezoneOffset), num(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fixed(Fixed::TimezoneOffset), num(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fixed(Fixed::TimezoneOffset), Literal(":")]; offset: 45_240); - check!("Z12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("X12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("Z+12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("X+12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("Xβˆ’12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); // MINUS SIGN (U+2212) - check!("🀠+12:34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("+12:34🀠", [fixed(Fixed::TimezoneOffset)]; TOO_LONG); - check!("+12:🀠34", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("+1234🀠", [fixed(Fixed::TimezoneOffset), Literal("🀠")]; offset: 45_240); - check!("-1234🀠", [fixed(Fixed::TimezoneOffset), Literal("🀠")]; offset: -45_240); - check!("βˆ’1234🀠", [fixed(Fixed::TimezoneOffset), Literal("🀠")]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34🀠", [fixed(Fixed::TimezoneOffset), Literal("🀠")]; offset: 45_240); - check!("-12:34🀠", [fixed(Fixed::TimezoneOffset), Literal("🀠")]; offset: -45_240); - check!("βˆ’12:34🀠", [fixed(Fixed::TimezoneOffset), Literal("🀠")]; offset: -45_240); // MINUS SIGN (U+2212) - check!("🀠+12:34", [Literal("🀠"), fixed(Fixed::TimezoneOffset)]; offset: 45_240); - check!("Z", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("A", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("PST", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("#Z", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!(":Z", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("+Z", [fixed(Fixed::TimezoneOffset)]; TOO_SHORT); - check!("+:Z", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("+Z:", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!("z", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!(" :Z", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!(" Z", [fixed(Fixed::TimezoneOffset)]; INVALID); - check!(" z", [fixed(Fixed::TimezoneOffset)]; INVALID); + check!("1", [fixed(TimezoneOffset)]; INVALID); + check!("12", [fixed(TimezoneOffset)]; INVALID); + check!("123", [fixed(TimezoneOffset)]; INVALID); + check!("1234", [fixed(TimezoneOffset)]; INVALID); + check!("12345", [fixed(TimezoneOffset)]; INVALID); + check!("123456", [fixed(TimezoneOffset)]; INVALID); + check!("1234567", [fixed(TimezoneOffset)]; INVALID); + check!("+1", [fixed(TimezoneOffset)]; TOO_SHORT); + check!("+12", [fixed(TimezoneOffset)]; TOO_SHORT); + check!("+123", [fixed(TimezoneOffset)]; TOO_SHORT); + check!("+1234", [fixed(TimezoneOffset)]; offset: 45_240); + check!("+12345", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+123456", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+1234567", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12345678", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12:", [fixed(TimezoneOffset)]; TOO_SHORT); + check!("+12:3", [fixed(TimezoneOffset)]; TOO_SHORT); + check!("+12:34", [fixed(TimezoneOffset)]; offset: 45_240); + check!("-12:34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("βˆ’12:34", [fixed(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12:34:5", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12:34:56", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12:34:56:", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12 34", [fixed(TimezoneOffset)]; offset: 45_240); + check!("+12 34", [fixed(TimezoneOffset)]; offset: 45_240); + check!("12:34", [fixed(TimezoneOffset)]; INVALID); + check!("12:34:56", [fixed(TimezoneOffset)]; INVALID); + check!("+12::34", [fixed(TimezoneOffset)]; offset: 45_240); + check!("+12: :34", [fixed(TimezoneOffset)]; offset: 45_240); + check!("+12:::34", [fixed(TimezoneOffset)]; offset: 45_240); + check!("+12::::34", [fixed(TimezoneOffset)]; offset: 45_240); + check!("+12::34", [fixed(TimezoneOffset)]; offset: 45_240); + check!("+12:34:56", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12:3456", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+1234:56", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+1234:567", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+00:00", [fixed(TimezoneOffset)]; offset: 0); + check!("-00:00", [fixed(TimezoneOffset)]; offset: 0); + check!("βˆ’00:00", [fixed(TimezoneOffset)]; offset: 0); // MINUS SIGN (U+2212) + check!("+00:01", [fixed(TimezoneOffset)]; offset: 60); + check!("-00:01", [fixed(TimezoneOffset)]; offset: -60); + check!("+00:30", [fixed(TimezoneOffset)]; offset: 1_800); + check!("-00:30", [fixed(TimezoneOffset)]; offset: -1_800); + check!("+24:00", [fixed(TimezoneOffset)]; offset: 86_400); + check!("-24:00", [fixed(TimezoneOffset)]; offset: -86_400); + check!("βˆ’24:00", [fixed(TimezoneOffset)]; offset: -86_400); // MINUS SIGN (U+2212) + check!("+99:59", [fixed(TimezoneOffset)]; offset: 359_940); + check!("-99:59", [fixed(TimezoneOffset)]; offset: -359_940); + check!("+00:60", [fixed(TimezoneOffset)]; OUT_OF_RANGE); + check!("+00:99", [fixed(TimezoneOffset)]; OUT_OF_RANGE); + check!("#12:34", [fixed(TimezoneOffset)]; INVALID); + check!("+12:34 ", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12 34 ", [fixed(TimezoneOffset)]; TOO_LONG); + check!(" +12:34", [fixed(TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fixed(TimezoneOffset)]; offset: -45_240); + check!(" βˆ’12:34", [fixed(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("12:34 ", [fixed(TimezoneOffset)]; INVALID); + check!(" +12:34", [fixed(TimezoneOffset)]; offset: 45_240); + check!(" -12:34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("\t -12:34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("-12: 34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("-12 :34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); + check!(" 12:34", [fixed(TimezoneOffset)]; INVALID); + check!("", [fixed(TimezoneOffset)]; TOO_SHORT); + check!("+", [fixed(TimezoneOffset)]; TOO_SHORT); + check!("+12345", [fixed(TimezoneOffset), num(Numeric::Day)]; offset: 45_240, day: 5); + check!("+12:345", [fixed(TimezoneOffset), num(Numeric::Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fixed(TimezoneOffset), Literal(":")]; offset: 45_240); + check!("Z12:34", [fixed(TimezoneOffset)]; INVALID); + check!("X12:34", [fixed(TimezoneOffset)]; INVALID); + check!("Z+12:34", [fixed(TimezoneOffset)]; INVALID); + check!("X+12:34", [fixed(TimezoneOffset)]; INVALID); + check!("Xβˆ’12:34", [fixed(TimezoneOffset)]; INVALID); // MINUS SIGN (U+2212) + check!("🀠+12:34", [fixed(TimezoneOffset)]; INVALID); + check!("+12:34🀠", [fixed(TimezoneOffset)]; TOO_LONG); + check!("+12:🀠34", [fixed(TimezoneOffset)]; INVALID); + check!("+1234🀠", [fixed(TimezoneOffset), Literal("🀠")]; offset: 45_240); + check!("-1234🀠", [fixed(TimezoneOffset), Literal("🀠")]; offset: -45_240); + check!("βˆ’1234🀠", [fixed(TimezoneOffset), Literal("🀠")]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34🀠", [fixed(TimezoneOffset), Literal("🀠")]; offset: 45_240); + check!("-12:34🀠", [fixed(TimezoneOffset), Literal("🀠")]; offset: -45_240); + check!("βˆ’12:34🀠", [fixed(TimezoneOffset), Literal("🀠")]; offset: -45_240); // MINUS SIGN (U+2212) + check!("🀠+12:34", [Literal("🀠"), fixed(TimezoneOffset)]; offset: 45_240); + check!("Z", [fixed(TimezoneOffset)]; INVALID); + check!("A", [fixed(TimezoneOffset)]; INVALID); + check!("PST", [fixed(TimezoneOffset)]; INVALID); + check!("#Z", [fixed(TimezoneOffset)]; INVALID); + check!(":Z", [fixed(TimezoneOffset)]; INVALID); + check!("+Z", [fixed(TimezoneOffset)]; TOO_SHORT); + check!("+:Z", [fixed(TimezoneOffset)]; INVALID); + check!("+Z:", [fixed(TimezoneOffset)]; INVALID); + check!("z", [fixed(TimezoneOffset)]; INVALID); + check!(" :Z", [fixed(TimezoneOffset)]; INVALID); + check!(" Z", [fixed(TimezoneOffset)]; INVALID); + check!(" z", [fixed(TimezoneOffset)]; INVALID); // TimezoneOffsetColon - check!("1", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("123", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("1234", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12345", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("123456", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("1234567", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12345678", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("+1", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); - check!("+12", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); - check!("+123", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); - check!("+1234", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("-1234", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); - check!("βˆ’1234", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+123456", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+1234567", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+12345678", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("1:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12:3", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12:34", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12:34:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12:34:5", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("12:34:56", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("+1:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("+12:", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:3", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("-12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); - check!("βˆ’12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:5", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:7", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:78", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+12:3456", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("+1234:56", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!("βˆ’12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("βˆ’12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12 :34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12: 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12: 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12 :34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("-12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: -45_240); - check!("+12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12: :34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12:::34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12::::34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("#1234", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("#12:34", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("+12:34 ", [fixed(Fixed::TimezoneOffsetColon)]; TOO_LONG); - check!(" +12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("\t+12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("\t\t+12:34", [fixed(Fixed::TimezoneOffsetColon)]; offset: 45_240); - check!("12:34 ", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!(" 12:34", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); - check!("+", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); - check!(":", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("+12345", [fixed(Fixed::TimezoneOffsetColon), num(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fixed(Fixed::TimezoneOffsetColon), num(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fixed(Fixed::TimezoneOffsetColon), Literal(":")]; offset: 45_240); - check!("Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("A", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("PST", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("#Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!(":Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("+Z", [fixed(Fixed::TimezoneOffsetColon)]; TOO_SHORT); - check!("+:Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("+Z:", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!("z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!(" :Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!(" Z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); - check!(" z", [fixed(Fixed::TimezoneOffsetColon)]; INVALID); + check!("1", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12", [fixed(TimezoneOffsetColon)]; INVALID); + check!("123", [fixed(TimezoneOffsetColon)]; INVALID); + check!("1234", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12345", [fixed(TimezoneOffsetColon)]; INVALID); + check!("123456", [fixed(TimezoneOffsetColon)]; INVALID); + check!("1234567", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12345678", [fixed(TimezoneOffsetColon)]; INVALID); + check!("+1", [fixed(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12", [fixed(TimezoneOffsetColon)]; TOO_SHORT); + check!("+123", [fixed(TimezoneOffsetColon)]; TOO_SHORT); + check!("+1234", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("-1234", [fixed(TimezoneOffsetColon)]; offset: -45_240); + check!("βˆ’1234", [fixed(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+123456", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+1234567", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+12345678", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("1:", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12:", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12:3", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12:34", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12:34:", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12:34:5", [fixed(TimezoneOffsetColon)]; INVALID); + check!("12:34:56", [fixed(TimezoneOffsetColon)]; INVALID); + check!("+1:", [fixed(TimezoneOffsetColon)]; INVALID); + check!("+12:", [fixed(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:3", [fixed(TimezoneOffsetColon)]; TOO_SHORT); + check!("+12:34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("-12:34", [fixed(TimezoneOffsetColon)]; offset: -45_240); + check!("βˆ’12:34", [fixed(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:5", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:7", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34:56:78", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:3456", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("+1234:56", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!("βˆ’12:34", [fixed(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("βˆ’12 : 34", [fixed(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12 :34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 :34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("-12 : 34", [fixed(TimezoneOffsetColon)]; offset: -45_240); + check!("+12 : 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12 : 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12: :34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12:::34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::::34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("+12::34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("#1234", [fixed(TimezoneOffsetColon)]; INVALID); + check!("#12:34", [fixed(TimezoneOffsetColon)]; INVALID); + check!("+12:34 ", [fixed(TimezoneOffsetColon)]; TOO_LONG); + check!(" +12:34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("\t+12:34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("\t\t+12:34", [fixed(TimezoneOffsetColon)]; offset: 45_240); + check!("12:34 ", [fixed(TimezoneOffsetColon)]; INVALID); + check!(" 12:34", [fixed(TimezoneOffsetColon)]; INVALID); + check!("", [fixed(TimezoneOffsetColon)]; TOO_SHORT); + check!("+", [fixed(TimezoneOffsetColon)]; TOO_SHORT); + check!(":", [fixed(TimezoneOffsetColon)]; INVALID); + check!("+12345", [fixed(TimezoneOffsetColon), num(Numeric::Day)]; offset: 45_240, day: 5); + check!("+12:345", [fixed(TimezoneOffsetColon), num(Numeric::Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fixed(TimezoneOffsetColon), Literal(":")]; offset: 45_240); + check!("Z", [fixed(TimezoneOffsetColon)]; INVALID); + check!("A", [fixed(TimezoneOffsetColon)]; INVALID); + check!("PST", [fixed(TimezoneOffsetColon)]; INVALID); + check!("#Z", [fixed(TimezoneOffsetColon)]; INVALID); + check!(":Z", [fixed(TimezoneOffsetColon)]; INVALID); + check!("+Z", [fixed(TimezoneOffsetColon)]; TOO_SHORT); + check!("+:Z", [fixed(TimezoneOffsetColon)]; INVALID); + check!("+Z:", [fixed(TimezoneOffsetColon)]; INVALID); + check!("z", [fixed(TimezoneOffsetColon)]; INVALID); + check!(" :Z", [fixed(TimezoneOffsetColon)]; INVALID); + check!(" Z", [fixed(TimezoneOffsetColon)]; INVALID); + check!(" z", [fixed(TimezoneOffsetColon)]; INVALID); // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` // and `TimezoneOffsetTripleColon` for function `parse_internal`. // No need for separate tests for `TimezoneOffsetDoubleColon` and // `TimezoneOffsetTripleColon`. // TimezoneOffsetZ - check!("1", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("123", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("1234", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12345", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("123456", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("1234567", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12345678", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("+1", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("+12", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("+123", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("+1234", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("-1234", [fixed(Fixed::TimezoneOffsetZ)]; offset: -45_240); - check!("βˆ’1234", [fixed(Fixed::TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+123456", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+1234567", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12345678", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("1:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12:3", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12:34", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12:34:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12:34:5", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("12:34:56", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("+1:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("+12:", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:3", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("-12:34", [fixed(Fixed::TimezoneOffsetZ)]; offset: -45_240); - check!("βˆ’12:34", [fixed(Fixed::TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:5", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:7", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:78", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12::34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12:3456", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+1234:56", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12: 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12 :34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("12:34 ", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!(" 12:34", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("+12:34 ", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34 ", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!(" +12:34", [fixed(Fixed::TimezoneOffsetZ)]; offset: 45_240); - check!("+12345", [fixed(Fixed::TimezoneOffsetZ), num(Day)]; offset: 45_240, day: 5); - check!("+12:345", [fixed(Fixed::TimezoneOffsetZ), num(Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fixed(Fixed::TimezoneOffsetZ), Literal(":")]; offset: 45_240); - check!("Z12:34", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("X12:34", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("Z", [fixed(Fixed::TimezoneOffsetZ)]; offset: 0); - check!("z", [fixed(Fixed::TimezoneOffsetZ)]; offset: 0); - check!(" Z", [fixed(Fixed::TimezoneOffsetZ)]; offset: 0); - check!(" z", [fixed(Fixed::TimezoneOffsetZ)]; offset: 0); - check!("\u{0363}Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("Z ", [fixed(Fixed::TimezoneOffsetZ)]; TOO_LONG); - check!("A", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("PST", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("#Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!(":Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!(":z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("+Z", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("-Z", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("+A", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("+πŸ™ƒ", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("+Z:", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!(" :Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!(" +Z", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!(" -Z", [fixed(Fixed::TimezoneOffsetZ)]; TOO_SHORT); - check!("+:Z", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("Y", [fixed(Fixed::TimezoneOffsetZ)]; INVALID); - check!("Zulu", [fixed(Fixed::TimezoneOffsetZ), Literal("ulu")]; offset: 0); - check!("zulu", [fixed(Fixed::TimezoneOffsetZ), Literal("ulu")]; offset: 0); - check!("+1234ulu", [fixed(Fixed::TimezoneOffsetZ), Literal("ulu")]; offset: 45_240); - check!("+12:34ulu", [fixed(Fixed::TimezoneOffsetZ), Literal("ulu")]; offset: 45_240); + check!("1", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12", [fixed(TimezoneOffsetZ)]; INVALID); + check!("123", [fixed(TimezoneOffsetZ)]; INVALID); + check!("1234", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12345", [fixed(TimezoneOffsetZ)]; INVALID); + check!("123456", [fixed(TimezoneOffsetZ)]; INVALID); + check!("1234567", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12345678", [fixed(TimezoneOffsetZ)]; INVALID); + check!("+1", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("+123", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("+1234", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("-1234", [fixed(TimezoneOffsetZ)]; offset: -45_240); + check!("βˆ’1234", [fixed(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12345", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+123456", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+1234567", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12345678", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("1:", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12:", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12:3", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12:34", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12:34:", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12:34:5", [fixed(TimezoneOffsetZ)]; INVALID); + check!("12:34:56", [fixed(TimezoneOffsetZ)]; INVALID); + check!("+1:", [fixed(TimezoneOffsetZ)]; INVALID); + check!("+12:", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:3", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("+12:34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("-12:34", [fixed(TimezoneOffsetZ)]; offset: -45_240); + check!("βˆ’12:34", [fixed(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) + check!("+12:34:", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:5", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:7", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12:34:56:78", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12::34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12:3456", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+1234:56", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12: 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 :34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12 : 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("12:34 ", [fixed(TimezoneOffsetZ)]; INVALID); + check!(" 12:34", [fixed(TimezoneOffsetZ)]; INVALID); + check!("+12:34 ", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("+12 34 ", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!(" +12:34", [fixed(TimezoneOffsetZ)]; offset: 45_240); + check!("+12345", [fixed(TimezoneOffsetZ), num(Numeric::Day)]; offset: 45_240, day: 5); + check!("+12:345", [fixed(TimezoneOffsetZ), num(Numeric::Day)]; offset: 45_240, day: 5); + check!("+12:34:", [fixed(TimezoneOffsetZ), Literal(":")]; offset: 45_240); + check!("Z12:34", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("X12:34", [fixed(TimezoneOffsetZ)]; INVALID); + check!("Z", [fixed(TimezoneOffsetZ)]; offset: 0); + check!("z", [fixed(TimezoneOffsetZ)]; offset: 0); + check!(" Z", [fixed(TimezoneOffsetZ)]; offset: 0); + check!(" z", [fixed(TimezoneOffsetZ)]; offset: 0); + check!("\u{0363}Z", [fixed(TimezoneOffsetZ)]; INVALID); + check!("Z ", [fixed(TimezoneOffsetZ)]; TOO_LONG); + check!("A", [fixed(TimezoneOffsetZ)]; INVALID); + check!("PST", [fixed(TimezoneOffsetZ)]; INVALID); + check!("#Z", [fixed(TimezoneOffsetZ)]; INVALID); + check!(":Z", [fixed(TimezoneOffsetZ)]; INVALID); + check!(":z", [fixed(TimezoneOffsetZ)]; INVALID); + check!("+Z", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("-Z", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("+A", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("+πŸ™ƒ", [fixed(TimezoneOffsetZ)]; INVALID); + check!("+Z:", [fixed(TimezoneOffsetZ)]; INVALID); + check!(" :Z", [fixed(TimezoneOffsetZ)]; INVALID); + check!(" +Z", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!(" -Z", [fixed(TimezoneOffsetZ)]; TOO_SHORT); + check!("+:Z", [fixed(TimezoneOffsetZ)]; INVALID); + check!("Y", [fixed(TimezoneOffsetZ)]; INVALID); + check!("Zulu", [fixed(TimezoneOffsetZ), Literal("ulu")]; offset: 0); + check!("zulu", [fixed(TimezoneOffsetZ), Literal("ulu")]; offset: 0); + check!("+1234ulu", [fixed(TimezoneOffsetZ), Literal("ulu")]; offset: 45_240); + check!("+12:34ulu", [fixed(TimezoneOffsetZ), Literal("ulu")]; offset: 45_240); // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` // in function `parse_internal`. // No need for separate tests for `TimezoneOffsetColonZ`. @@ -1184,8 +1183,8 @@ mod tests { check!(" +12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); check!(" -12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); check!(" βˆ’12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [internal_fixed(TimezoneOffsetPermissive), num(Day)]; offset: 45_240, day: 5); - check!("+12:345", [internal_fixed(TimezoneOffsetPermissive), num(Day)]; offset: 45_240, day: 5); + check!("+12345", [internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)]; offset: 45_240, day: 5); + check!("+12:345", [internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)]; offset: 45_240, day: 5); check!("+12:34:", [internal_fixed(TimezoneOffsetPermissive), Literal(":")]; offset: 45_240); check!("🀠+12:34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); check!("+12:34🀠", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); @@ -1215,14 +1214,14 @@ mod tests { check!("Y", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); // TimezoneName - check!("CEST", [fixed(Fixed::TimezoneName)]; ); - check!("cest", [fixed(Fixed::TimezoneName)]; ); // lowercase - check!("XXXXXXXX", [fixed(Fixed::TimezoneName)]; ); // not a real timezone name - check!("!!!!", [fixed(Fixed::TimezoneName)]; ); // not a real timezone name! - check!("CEST 5", [fixed(Fixed::TimezoneName), Literal(" "), num(Day)]; day: 5); - check!("CEST ", [fixed(Fixed::TimezoneName)]; TOO_LONG); - check!(" CEST", [fixed(Fixed::TimezoneName)]; TOO_LONG); - check!("CE ST", [fixed(Fixed::TimezoneName)]; TOO_LONG); + check!("CEST", [fixed(TimezoneName)]; ); + check!("cest", [fixed(TimezoneName)]; ); // lowercase + check!("XXXXXXXX", [fixed(TimezoneName)]; ); // not a real timezone name + check!("!!!!", [fixed(TimezoneName)]; ); // not a real timezone name! + check!("CEST 5", [fixed(TimezoneName), Literal(" "), num(Numeric::Day)]; day: 5); + check!("CEST ", [fixed(TimezoneName)]; TOO_LONG); + check!(" CEST", [fixed(TimezoneName)]; TOO_LONG); + check!("CE ST", [fixed(TimezoneName)]; TOO_LONG); } #[test] From 823e98190dac4822d084e73d9469f97d78cd9469 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 5 Jul 2023 15:55:34 +0200 Subject: [PATCH 55/68] Backport more checks in `test_parse` from main --- src/format/parse.rs | 146 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 140 insertions(+), 6 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 1b6773fa8f..7483280298 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -589,15 +589,66 @@ mod tests { check!("", []; ); check!(" ", []; TOO_LONG); check!("a", []; TOO_LONG); + check!("abc", []; TOO_LONG); + check!("🀠", []; TOO_LONG); // whitespaces check!("", [Space("")]; ); + check!(" ", [Space(" ")]; ); + check!(" ", [Space(" ")]; ); + check!(" ", [Space(" ")]; ); check!(" ", [Space("")]; ); + check!(" ", [Space(" ")]; ); + check!(" ", [Space(" ")]; ); + check!(" ", [Space(" ")]; ); + check!("", [Space(" ")]; ); + check!(" ", [Space(" ")]; ); + check!(" ", [Space(" ")]; ); + check!(" ", [Space(" "), Space(" ")]; ); + check!(" ", [Space(" "), Space(" ")]; ); + check!(" ", [Space(" "), Space(" ")]; ); + check!(" ", [Space(" "), Space(" ")]; ); + check!(" ", [Space(" "), Space(" ")]; ); + check!(" ", [Space(" "), Space(" "), Space(" ")]; ); check!("\t", [Space("")]; ); check!(" \n\r \n", [Space("")]; ); + check!("\t", [Space("\t")]; ); + check!("\t", [Space(" ")]; ); + check!(" ", [Space("\t")]; ); + check!("\t\r", [Space("\t\r")]; ); + check!("\t\r ", [Space("\t\r ")]; ); + check!("\t \r", [Space("\t \r")]; ); + check!(" \t\r", [Space(" \t\r")]; ); + check!(" \n\r \n", [Space(" \n\r \n")]; ); + check!(" \t\n", [Space(" \t")]; ); + check!(" \n\t", [Space(" \t\n")]; ); + check!("\u{2002}", [Space("\u{2002}")]; ); + // most unicode whitespace characters + check!( + "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", + [Space("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}")]; + ); + // most unicode whitespace characters + check!( + "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", + [ + Space("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}"), + Space("\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}") + ]; + ); check!("a", [Space("")]; TOO_LONG); + check!("a", [Space(" ")]; TOO_LONG); + // a Space containing a literal can match a literal, but this should not be done + check!("a", [Space("a")]; TOO_LONG); + check!("abc", [Space("")]; TOO_LONG); + check!("abc", [Space(" ")]; TOO_LONG); + check!(" abc", [Space("")]; TOO_LONG); + check!(" abc", [Space(" ")]; TOO_LONG); + + // `\u{0363}` is combining diacritic mark "COMBINING LATIN SMALL LETTER A" // literal + check!("", [Literal("")]; ); check!("", [Literal("a")]; TOO_SHORT); check!(" ", [Literal("a")]; INVALID); check!("a", [Literal("a")]; ); @@ -607,7 +658,14 @@ mod tests { // a Literal may contain whitespace and match whitespace, but this should not be done check!(" ", [Literal(" ")]; ); check!("aa", [Literal("a")]; TOO_LONG); + check!("🀠", [Literal("a")]; INVALID); check!("A", [Literal("a")]; INVALID); + check!("a", [Literal("z")]; INVALID); + check!("a", [Literal("🀠")]; TOO_SHORT); + check!("a", [Literal("\u{0363}a")]; TOO_SHORT); + check!("\u{0363}a", [Literal("a")]; INVALID); + check!("\u{0363}a", [Literal("\u{0363}a")]; ); + check!("a", [Literal("ab")]; TOO_SHORT); check!("xy", [Literal("xy")]; ); check!("xy", [Literal("x"), Literal("y")]; ); check!("1", [Literal("1")]; ); @@ -632,6 +690,16 @@ mod tests { check!("x y", [Literal("x"), Literal("y")]; INVALID); check!("xy", [Literal("x"), Space(""), Literal("y")]; ); check!("x y", [Literal("x"), Space(""), Literal("y")]; ); + check!("x y", [Literal("x"), Space(" "), Literal("y")]; ); + + // whitespaces + literals + check!("a\n", [Literal("a"), Space("\n")]; ); + check!("\tab\n", [Space("\t"), Literal("ab"), Space("\n")]; ); + check!("ab\tcd\ne", [Literal("ab"), Space("\t"), Literal("cd"), Space("\n"), Literal("e")]; ); + check!("+1ab\tcd\r\n+,.", [Literal("+1ab"), Space("\t"), Literal("cd"), Space("\r\n"), Literal("+,.")]; ); + // whitespace and literals can be intermixed + check!("a\tb", [Literal("a\tb")]; ); + check!("a\tb", [Literal("a"), Space("\t"), Literal("b")]; ); } #[test] @@ -644,10 +712,14 @@ mod tests { check!("1987 ", [num(Year)]; TOO_LONG); check!("0x12", [num(Year)]; TOO_LONG); // `0` is parsed check!("x123", [num(Year)]; INVALID); + check!("o123", [num(Year)]; INVALID); check!("2015", [num(Year)]; year: 2015); check!("0000", [num(Year)]; year: 0); check!("9999", [num(Year)]; year: 9999); check!(" \t987", [num(Year)]; year: 987); + check!(" \t987", [Space(" \t"), num(Year)]; year: 987); + check!(" \t987🀠", [Space(" \t"), num(Year), Literal("🀠")]; year: 987); + check!("987🀠", [num(Year), Literal("🀠")]; year: 987); check!("5", [num(Year)]; year: 5); check!("5\0", [num(Year)]; TOO_LONG); check!("\x005", [num(Year)]; INVALID); @@ -657,11 +729,15 @@ mod tests { check!("12345", [num0(Year), Literal("5")]; year: 1234); check!("12341234", [num(Year), num(Year)]; year: 1234); check!("1234 1234", [num(Year), num(Year)]; year: 1234); + check!("1234 1234", [num(Year), Space(" "), num(Year)]; year: 1234); check!("1234 1235", [num(Year), num(Year)]; IMPOSSIBLE); check!("1234 1234", [num(Year), Literal("x"), num(Year)]; INVALID); check!("1234x1234", [num(Year), Literal("x"), num(Year)]; year: 1234); - check!("1234xx1234", [num(Year), Literal("x"), num(Year)]; INVALID); check!("1234 x 1234", [num(Year), Literal("x"), num(Year)]; INVALID); + check!("1234xx1234", [num(Year), Literal("x"), num(Year)]; INVALID); + check!("1234xx1234", [num(Year), Literal("xx"), num(Year)]; year: 1234); + check!("1234 x 1234", [num(Year), Space(" "), Literal("x"), Space(" "), num(Year)]; year: 1234); + check!("1234 x 1235", [num(Year), Space(" "), Literal("x"), Space(" "), Literal("1235")]; year: 1234); // signed numeric check!("-42", [num(Year)]; year: -42); @@ -672,7 +748,11 @@ mod tests { check!("βˆ’42195", [num(Year)]; INVALID); // MINUS SIGN (U+2212) check!("+42195", [num(Year)]; year: 42195); check!(" -42195", [num(Year)]; year: -42195); + check!(" +42195", [num(Year)]; year: 42195); + check!(" -42195", [num(Year)]; year: -42195); check!(" +42195", [num(Year)]; year: 42195); + check!("-42195 ", [num(Year)]; TOO_LONG); + check!("+42195 ", [num(Year)]; TOO_LONG); check!(" - 42", [num(Year)]; INVALID); check!(" + 42", [num(Year)]; INVALID); check!(" -42195", [Space(" "), num(Year)]; year: -42195); @@ -697,8 +777,12 @@ mod tests { check!("\u{0363}345", [num(Ordinal)]; INVALID); check!(" +345", [num(Ordinal)]; INVALID); check!(" -345", [num(Ordinal)]; INVALID); + check!("\t345", [Space("\t"), num(Ordinal)]; ordinal: 345); + check!(" +345", [Space(" "), num(Ordinal)]; INVALID); + check!(" -345", [Space(" "), num(Ordinal)]; INVALID); // various numeric fields + check!("1234 5678", [num(Year), num(IsoYear)]; year: 1234, isoyear: 5678); check!("1234 5678", [num(Year), num(IsoYear)]; year: 1234, isoyear: 5678); @@ -721,13 +805,14 @@ mod tests { #[test] fn test_parse_fixed() { use crate::format::Fixed::*; - use crate::format::Item::Literal; + use crate::format::Item::{Literal, Space}; // fixed: month and weekday names check!("apr", [fixed(ShortMonthName)]; month: 4); check!("Apr", [fixed(ShortMonthName)]; month: 4); check!("APR", [fixed(ShortMonthName)]; month: 4); check!("ApR", [fixed(ShortMonthName)]; month: 4); + check!("\u{0363}APR", [fixed(ShortMonthName)]; INVALID); check!("April", [fixed(ShortMonthName)]; TOO_LONG); // `Apr` is parsed check!("A", [fixed(ShortMonthName)]; TOO_SHORT); check!("Sol", [fixed(ShortMonthName)]; INVALID); @@ -765,7 +850,15 @@ mod tests { check!("AM", [fixed(UpperAmPm)]; hour_div_12: 0); check!("PM", [fixed(UpperAmPm)]; hour_div_12: 1); check!("Am", [fixed(LowerAmPm)]; hour_div_12: 0); + check!(" Am", [Space(" "), fixed(LowerAmPm)]; hour_div_12: 0); + check!("Am🀠", [fixed(LowerAmPm), Literal("🀠")]; hour_div_12: 0); + check!("🀠Am", [Literal("🀠"), fixed(LowerAmPm)]; hour_div_12: 0); + check!("\u{0363}am", [fixed(LowerAmPm)]; INVALID); + check!("\u{0360}am", [fixed(LowerAmPm)]; INVALID); check!(" Am", [fixed(LowerAmPm)]; INVALID); + check!("Am ", [fixed(LowerAmPm)]; TOO_LONG); + check!("a.m.", [fixed(LowerAmPm)]; INVALID); + check!("A.M.", [fixed(LowerAmPm)]; INVALID); check!("ame", [fixed(LowerAmPm)]; TOO_LONG); // `am` is parsed check!("a", [fixed(LowerAmPm)]; TOO_SHORT); check!("p", [fixed(LowerAmPm)]; TOO_SHORT); @@ -778,6 +871,7 @@ mod tests { fn test_parse_fixed_nanosecond() { use crate::format::Fixed::Nanosecond; use crate::format::InternalInternal::*; + use crate::format::Item::Literal; use crate::format::Numeric::Second; // fixed: dot plus nanoseconds @@ -790,11 +884,21 @@ mod tests { check!(".42", [fixed(Nanosecond)]; nanosecond: 420_000_000); check!(".421", [fixed(Nanosecond)]; nanosecond: 421_000_000); check!(".42195", [fixed(Nanosecond)]; nanosecond: 421_950_000); + check!(".421951", [fixed(Nanosecond)]; nanosecond: 421_951_000); + check!(".4219512", [fixed(Nanosecond)]; nanosecond: 421_951_200); + check!(".42195123", [fixed(Nanosecond)]; nanosecond: 421_951_230); check!(".421950803", [fixed(Nanosecond)]; nanosecond: 421_950_803); + check!(".4219508035", [fixed(Nanosecond)]; nanosecond: 421_950_803); + check!(".42195080354", [fixed(Nanosecond)]; nanosecond: 421_950_803); check!(".421950803547", [fixed(Nanosecond)]; nanosecond: 421_950_803); + check!(".000000003", [fixed(Nanosecond)]; nanosecond: 3); + check!(".0000000031", [fixed(Nanosecond)]; nanosecond: 3); + check!(".0000000035", [fixed(Nanosecond)]; nanosecond: 3); check!(".000000003547", [fixed(Nanosecond)]; nanosecond: 3); + check!(".0000000009", [fixed(Nanosecond)]; nanosecond: 0); check!(".000000000547", [fixed(Nanosecond)]; nanosecond: 0); - check!(".", [fixed(Nanosecond)]; TOO_SHORT); + check!(".0000000009999999999999999999999999", [fixed(Nanosecond)]; nanosecond: 0); + check!(".4🀠", [fixed(Nanosecond), Literal("🀠")]; nanosecond: 400_000_000); check!(".4x", [fixed(Nanosecond)]; TOO_LONG); check!(". 4", [fixed(Nanosecond)]; INVALID); check!(" .4", [fixed(Nanosecond)]; TOO_LONG); // no automatic trimming @@ -806,8 +910,12 @@ mod tests { check!("4", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); check!("42", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); check!("421", [internal_fixed(Nanosecond3NoDot)]; nanosecond: 421_000_000); + check!("4210", [internal_fixed(Nanosecond3NoDot)]; TOO_LONG); check!("42143", [internal_fixed(Nanosecond3NoDot), num(Second)]; nanosecond: 421_000_000, second: 43); + check!("421🀠", [internal_fixed(Nanosecond3NoDot), Literal("🀠")]; nanosecond: 421_000_000); + check!("🀠421", [Literal("🀠"), internal_fixed(Nanosecond3NoDot)]; nanosecond: 421_000_000); check!("42195", [internal_fixed(Nanosecond3NoDot)]; TOO_LONG); + check!("123456789", [internal_fixed(Nanosecond3NoDot)]; TOO_LONG); check!("4x", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); check!(" 4", [internal_fixed(Nanosecond3NoDot)]; INVALID); check!(".421", [internal_fixed(Nanosecond3NoDot)]; INVALID); @@ -815,10 +923,13 @@ mod tests { check!("", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); check!(".", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); check!("0", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); - check!("42195", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); + check!("1234", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); + check!("12345", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); check!("421950", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 421_950_000); check!("000003", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 3000); check!("000000", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 0); + check!("1234567", [internal_fixed(Nanosecond6NoDot)]; TOO_LONG); + check!("123456789", [internal_fixed(Nanosecond6NoDot)]; TOO_LONG); check!("4x", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); check!(" 4", [internal_fixed(Nanosecond6NoDot)]; INVALID); check!(".42100", [internal_fixed(Nanosecond6NoDot)]; INVALID); @@ -826,10 +937,11 @@ mod tests { check!("", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); check!(".", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); check!("42195", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); + check!("12345678", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); check!("421950803", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 421_950_803); check!("000000003", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 3); check!("42195080354", [internal_fixed(Nanosecond9NoDot), num(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 - check!("421950803547", [internal_fixed(Nanosecond9NoDot)]; TOO_LONG); + check!("1234567890", [internal_fixed(Nanosecond9NoDot)]; TOO_LONG); check!("000000000", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 0); check!("00000000x", [internal_fixed(Nanosecond9NoDot)]; INVALID); check!(" 4", [internal_fixed(Nanosecond9NoDot)]; INVALID); @@ -900,7 +1012,6 @@ mod tests { check!(" +12:34", [fixed(TimezoneOffset)]; offset: 45_240); check!(" -12:34", [fixed(TimezoneOffset)]; offset: -45_240); check!(" βˆ’12:34", [fixed(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("12:34 ", [fixed(TimezoneOffset)]; INVALID); check!(" +12:34", [fixed(TimezoneOffset)]; offset: 45_240); check!(" -12:34", [fixed(TimezoneOffset)]; offset: -45_240); check!("\t -12:34", [fixed(TimezoneOffset)]; offset: -45_240); @@ -912,6 +1023,7 @@ mod tests { check!("-12: 34", [fixed(TimezoneOffset)]; offset: -45_240); check!("-12 :34", [fixed(TimezoneOffset)]; offset: -45_240); check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); + check!("12:34 ", [fixed(TimezoneOffset)]; INVALID); check!(" 12:34", [fixed(TimezoneOffset)]; INVALID); check!("", [fixed(TimezoneOffset)]; TOO_SHORT); check!("+", [fixed(TimezoneOffset)]; TOO_SHORT); @@ -1257,6 +1369,12 @@ mod tests { num(Minute), Literal(":"), num(Second), Space(" "), Literal("GMT")]; year: 2013, month: 6, day: 10, weekday: Weekday::Mon, hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); + check!("🀠Mon, 10 Jun🀠2013 09:32:37 GMT🀠", + [Literal("🀠"), fixed(Fixed::ShortWeekdayName), Literal(","), Space(" "), num(Day), Space(" "), + fixed(Fixed::ShortMonthName), Literal("🀠"), num(Year), Space(" "), num(Hour), Literal(":"), + num(Minute), Literal(":"), num(Second), Space(" "), Literal("GMT"), Literal("🀠")]; + year: 2013, month: 6, day: 10, weekday: Weekday::Mon, + hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); check!("Sun Aug 02 13:39:15 CEST 2020", [fixed(Fixed::ShortWeekdayName), Space(" "), fixed(Fixed::ShortMonthName), Space(" "), num(Day), Space(" "), num(Hour), Literal(":"), num(Minute), Literal(":"), @@ -1275,6 +1393,22 @@ mod tests { check!("12345678901234.56789", [num(Timestamp), fixed(Fixed::Nanosecond)]; nanosecond: 567_890_000, timestamp: 12_345_678_901_234); + + // docstring examples from `impl str::FromStr` + check!("2000-01-02T03:04:05Z", + [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + internal_fixed(TimezoneOffsetPermissive)]; + year: 2000, month: 1, day: 2, + hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, + offset: 0); + check!("2000-01-02 03:04:05Z", + [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Space(" "), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + internal_fixed(TimezoneOffsetPermissive)]; + year: 2000, month: 1, day: 2, + hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, + offset: 0); } #[test] From 20e7dd5f1d2a6bf80c16bee73227ebd769dbb43b Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Thu, 6 Jul 2023 10:05:25 +0200 Subject: [PATCH 56/68] Convert macro to function --- src/format/parse.rs | 1636 +++++++++++++++++++++++-------------------- 1 file changed, 893 insertions(+), 743 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 7483280298..746772af6c 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -564,20 +564,11 @@ mod tests { use crate::format::*; use crate::{DateTime, FixedOffset, TimeZone, Timelike, Utc}; - fn parse_all(s: &str, items: &[Item]) -> ParseResult { - let mut parsed = Parsed::new(); - parse(&mut parsed, s, items.iter())?; - Ok(parsed) - } - - macro_rules! check { - ($fmt:expr, $items:expr; $err:tt) => ( - assert_eq!(parse_all($fmt, &$items), Err($err)) - ); - ($fmt:expr, $items:expr; $($k:ident: $v:expr),*) => (#[allow(unused_mut)] { + macro_rules! parsed { + ($($k:ident: $v:expr),*) => (#[allow(unused_mut)] { let mut expected = Parsed::new(); $(expected.$k = Some($v);)* - assert_eq!(parse_all($fmt, &$items), Ok(expected)) + Ok(expected) }); } @@ -586,120 +577,125 @@ mod tests { use crate::format::Item::{Literal, Space}; // empty string - check!("", []; ); - check!(" ", []; TOO_LONG); - check!("a", []; TOO_LONG); - check!("abc", []; TOO_LONG); - check!("🀠", []; TOO_LONG); + parses("", &[]); + check(" ", &[], Err(TOO_LONG)); + check("a", &[], Err(TOO_LONG)); + check("abc", &[], Err(TOO_LONG)); + check("🀠", &[], Err(TOO_LONG)); // whitespaces - check!("", [Space("")]; ); - check!(" ", [Space(" ")]; ); - check!(" ", [Space(" ")]; ); - check!(" ", [Space(" ")]; ); - check!(" ", [Space("")]; ); - check!(" ", [Space(" ")]; ); - check!(" ", [Space(" ")]; ); - check!(" ", [Space(" ")]; ); - check!("", [Space(" ")]; ); - check!(" ", [Space(" ")]; ); - check!(" ", [Space(" ")]; ); - check!(" ", [Space(" "), Space(" ")]; ); - check!(" ", [Space(" "), Space(" ")]; ); - check!(" ", [Space(" "), Space(" ")]; ); - check!(" ", [Space(" "), Space(" ")]; ); - check!(" ", [Space(" "), Space(" ")]; ); - check!(" ", [Space(" "), Space(" "), Space(" ")]; ); - check!("\t", [Space("")]; ); - check!(" \n\r \n", [Space("")]; ); - check!("\t", [Space("\t")]; ); - check!("\t", [Space(" ")]; ); - check!(" ", [Space("\t")]; ); - check!("\t\r", [Space("\t\r")]; ); - check!("\t\r ", [Space("\t\r ")]; ); - check!("\t \r", [Space("\t \r")]; ); - check!(" \t\r", [Space(" \t\r")]; ); - check!(" \n\r \n", [Space(" \n\r \n")]; ); - check!(" \t\n", [Space(" \t")]; ); - check!(" \n\t", [Space(" \t\n")]; ); - check!("\u{2002}", [Space("\u{2002}")]; ); + parses("", &[Space("")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space("")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses("", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" ")]); + parses(" ", &[Space(" "), Space(" "), Space(" ")]); + parses("\t", &[Space("")]); + parses(" \n\r \n", &[Space("")]); + parses("\t", &[Space("\t")]); + parses("\t", &[Space(" ")]); + parses(" ", &[Space("\t")]); + parses("\t\r", &[Space("\t\r")]); + parses("\t\r ", &[Space("\t\r ")]); + parses("\t \r", &[Space("\t \r")]); + parses(" \t\r", &[Space(" \t\r")]); + parses(" \n\r \n", &[Space(" \n\r \n")]); + parses(" \t\n", &[Space(" \t")]); + parses(" \n\t", &[Space(" \t\n")]); + parses("\u{2002}", &[Space("\u{2002}")]); // most unicode whitespace characters - check!( + parses( "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", - [Space("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}")]; + &[Space("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}")] ); // most unicode whitespace characters - check!( + parses( "\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}", - [ + &[ Space("\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}"), Space("\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{3000}") - ]; + ] ); - check!("a", [Space("")]; TOO_LONG); - check!("a", [Space(" ")]; TOO_LONG); - // a Space containing a literal can match a literal, but this should not be done - check!("a", [Space("a")]; TOO_LONG); - check!("abc", [Space("")]; TOO_LONG); - check!("abc", [Space(" ")]; TOO_LONG); - check!(" abc", [Space("")]; TOO_LONG); - check!(" abc", [Space(" ")]; TOO_LONG); + check("a", &[Space("")], Err(TOO_LONG)); + check("a", &[Space(" ")], Err(TOO_LONG)); + // a Space containing a literal does not match a literal + check("a", &[Space("a")], Err(TOO_LONG)); + check("abc", &[Space("")], Err(TOO_LONG)); + check("abc", &[Space(" ")], Err(TOO_LONG)); + check(" abc", &[Space("")], Err(TOO_LONG)); + check(" abc", &[Space(" ")], Err(TOO_LONG)); // `\u{0363}` is combining diacritic mark "COMBINING LATIN SMALL LETTER A" // literal - check!("", [Literal("")]; ); - check!("", [Literal("a")]; TOO_SHORT); - check!(" ", [Literal("a")]; INVALID); - check!("a", [Literal("a")]; ); - check!("+", [Literal("+")]; ); - check!("-", [Literal("-")]; ); - check!("βˆ’", [Literal("βˆ’")]; ); // MINUS SIGN (U+2212) - // a Literal may contain whitespace and match whitespace, but this should not be done - check!(" ", [Literal(" ")]; ); - check!("aa", [Literal("a")]; TOO_LONG); - check!("🀠", [Literal("a")]; INVALID); - check!("A", [Literal("a")]; INVALID); - check!("a", [Literal("z")]; INVALID); - check!("a", [Literal("🀠")]; TOO_SHORT); - check!("a", [Literal("\u{0363}a")]; TOO_SHORT); - check!("\u{0363}a", [Literal("a")]; INVALID); - check!("\u{0363}a", [Literal("\u{0363}a")]; ); - check!("a", [Literal("ab")]; TOO_SHORT); - check!("xy", [Literal("xy")]; ); - check!("xy", [Literal("x"), Literal("y")]; ); - check!("1", [Literal("1")]; ); - check!("1234", [Literal("1234")]; ); - check!("+1234", [Literal("+1234")]; ); - check!("-1234", [Literal("-1234")]; ); - check!("βˆ’1234", [Literal("βˆ’1234")]; ); // MINUS SIGN (U+2212) - check!("PST", [Literal("PST")]; ); - check!("🀠", [Literal("🀠")]; ); - check!("🀠a", [Literal("🀠"), Literal("a")]; ); - check!("🀠a🀠", [Literal("🀠"), Literal("a🀠")]; ); - check!("a🀠b", [Literal("a"), Literal("🀠"), Literal("b")]; ); + parses("", &[Literal("")]); + check("", &[Literal("a")], Err(TOO_SHORT)); + check(" ", &[Literal("a")], Err(INVALID)); + parses("a", &[Literal("a")]); + parses("+", &[Literal("+")]); + parses("-", &[Literal("-")]); + parses("βˆ’", &[Literal("βˆ’")]); // MINUS SIGN (U+2212) + parses(" ", &[Literal(" ")]); // a Literal may contain whitespace and match whitespace + check("aa", &[Literal("a")], Err(TOO_LONG)); + check("🀠", &[Literal("a")], Err(INVALID)); + check("A", &[Literal("a")], Err(INVALID)); + check("a", &[Literal("z")], Err(INVALID)); + check("a", &[Literal("🀠")], Err(TOO_SHORT)); + check("a", &[Literal("\u{0363}a")], Err(TOO_SHORT)); + check("\u{0363}a", &[Literal("a")], Err(INVALID)); + parses("\u{0363}a", &[Literal("\u{0363}a")]); + check("a", &[Literal("ab")], Err(TOO_SHORT)); + parses("xy", &[Literal("xy")]); + parses("xy", &[Literal("x"), Literal("y")]); + parses("1", &[Literal("1")]); + parses("1234", &[Literal("1234")]); + parses("+1234", &[Literal("+1234")]); + parses("-1234", &[Literal("-1234")]); + parses("βˆ’1234", &[Literal("βˆ’1234")]); // MINUS SIGN (U+2212) + parses("PST", &[Literal("PST")]); + parses("🀠", &[Literal("🀠")]); + parses("🀠a", &[Literal("🀠"), Literal("a")]); + parses("🀠a🀠", &[Literal("🀠"), Literal("a🀠")]); + parses("a🀠b", &[Literal("a"), Literal("🀠"), Literal("b")]); // literals can be together - check!("xy", [Literal("xy")]; ); - check!("xyz", [Literal("xyz")]; ); + parses("xy", &[Literal("xy")]); + parses("xyz", &[Literal("xyz")]); // or literals can be apart - check!("xy", [Literal("x"), Literal("y")]; ); - check!("xyz", [Literal("x"), Literal("yz")]; ); - check!("xyz", [Literal("xy"), Literal("z")]; ); - check!("xyz", [Literal("x"), Literal("y"), Literal("z")]; ); + parses("xy", &[Literal("x"), Literal("y")]); + parses("xyz", &[Literal("x"), Literal("yz")]); + parses("xyz", &[Literal("xy"), Literal("z")]); + parses("xyz", &[Literal("x"), Literal("y"), Literal("z")]); // - check!("x y", [Literal("x"), Literal("y")]; INVALID); - check!("xy", [Literal("x"), Space(""), Literal("y")]; ); - check!("x y", [Literal("x"), Space(""), Literal("y")]; ); - check!("x y", [Literal("x"), Space(" "), Literal("y")]; ); + check("x y", &[Literal("x"), Literal("y")], Err(INVALID)); + parses("xy", &[Literal("x"), Space(""), Literal("y")]); + parses("x y", &[Literal("x"), Space(""), Literal("y")]); + parses("x y", &[Literal("x"), Space(" "), Literal("y")]); // whitespaces + literals - check!("a\n", [Literal("a"), Space("\n")]; ); - check!("\tab\n", [Space("\t"), Literal("ab"), Space("\n")]; ); - check!("ab\tcd\ne", [Literal("ab"), Space("\t"), Literal("cd"), Space("\n"), Literal("e")]; ); - check!("+1ab\tcd\r\n+,.", [Literal("+1ab"), Space("\t"), Literal("cd"), Space("\r\n"), Literal("+,.")]; ); + parses("a\n", &[Literal("a"), Space("\n")]); + parses("\tab\n", &[Space("\t"), Literal("ab"), Space("\n")]); + parses( + "ab\tcd\ne", + &[Literal("ab"), Space("\t"), Literal("cd"), Space("\n"), Literal("e")], + ); + parses( + "+1ab\tcd\r\n+,.", + &[Literal("+1ab"), Space("\t"), Literal("cd"), Space("\r\n"), Literal("+,.")], + ); // whitespace and literals can be intermixed - check!("a\tb", [Literal("a\tb")]; ); - check!("a\tb", [Literal("a"), Space("\t"), Literal("b")]; ); + parses("a\tb", &[Literal("a\tb")]); + parses("a\tb", &[Literal("a"), Space("\t"), Literal("b")]); } #[test] @@ -708,98 +704,110 @@ mod tests { use crate::format::Numeric::*; // numeric - check!("1987", [num(Year)]; year: 1987); - check!("1987 ", [num(Year)]; TOO_LONG); - check!("0x12", [num(Year)]; TOO_LONG); // `0` is parsed - check!("x123", [num(Year)]; INVALID); - check!("o123", [num(Year)]; INVALID); - check!("2015", [num(Year)]; year: 2015); - check!("0000", [num(Year)]; year: 0); - check!("9999", [num(Year)]; year: 9999); - check!(" \t987", [num(Year)]; year: 987); - check!(" \t987", [Space(" \t"), num(Year)]; year: 987); - check!(" \t987🀠", [Space(" \t"), num(Year), Literal("🀠")]; year: 987); - check!("987🀠", [num(Year), Literal("🀠")]; year: 987); - check!("5", [num(Year)]; year: 5); - check!("5\0", [num(Year)]; TOO_LONG); - check!("\x005", [num(Year)]; INVALID); - check!("", [num(Year)]; TOO_SHORT); - check!("12345", [num(Year), Literal("5")]; year: 1234); - check!("12345", [nums(Year), Literal("5")]; year: 1234); - check!("12345", [num0(Year), Literal("5")]; year: 1234); - check!("12341234", [num(Year), num(Year)]; year: 1234); - check!("1234 1234", [num(Year), num(Year)]; year: 1234); - check!("1234 1234", [num(Year), Space(" "), num(Year)]; year: 1234); - check!("1234 1235", [num(Year), num(Year)]; IMPOSSIBLE); - check!("1234 1234", [num(Year), Literal("x"), num(Year)]; INVALID); - check!("1234x1234", [num(Year), Literal("x"), num(Year)]; year: 1234); - check!("1234 x 1234", [num(Year), Literal("x"), num(Year)]; INVALID); - check!("1234xx1234", [num(Year), Literal("x"), num(Year)]; INVALID); - check!("1234xx1234", [num(Year), Literal("xx"), num(Year)]; year: 1234); - check!("1234 x 1234", [num(Year), Space(" "), Literal("x"), Space(" "), num(Year)]; year: 1234); - check!("1234 x 1235", [num(Year), Space(" "), Literal("x"), Space(" "), Literal("1235")]; year: 1234); + check("1987", &[num(Year)], parsed!(year: 1987)); + check("1987 ", &[num(Year)], Err(TOO_LONG)); + check("0x12", &[num(Year)], Err(TOO_LONG)); // `0` is parsed + check("x123", &[num(Year)], Err(INVALID)); + check("o123", &[num(Year)], Err(INVALID)); + check("2015", &[num(Year)], parsed!(year: 2015)); + check("0000", &[num(Year)], parsed!(year: 0)); + check("9999", &[num(Year)], parsed!(year: 9999)); + check(" \t987", &[num(Year)], parsed!(year: 987)); + check(" \t987", &[Space(" \t"), num(Year)], parsed!(year: 987)); + check(" \t987🀠", &[Space(" \t"), num(Year), Literal("🀠")], parsed!(year: 987)); + check("987🀠", &[num(Year), Literal("🀠")], parsed!(year: 987)); + check("5", &[num(Year)], parsed!(year: 5)); + check("5\0", &[num(Year)], Err(TOO_LONG)); + check("\x005", &[num(Year)], Err(INVALID)); + check("", &[num(Year)], Err(TOO_SHORT)); + check("12345", &[num(Year), Literal("5")], parsed!(year: 1234)); + check("12345", &[nums(Year), Literal("5")], parsed!(year: 1234)); + check("12345", &[num0(Year), Literal("5")], parsed!(year: 1234)); + check("12341234", &[num(Year), num(Year)], parsed!(year: 1234)); + check("1234 1234", &[num(Year), num(Year)], parsed!(year: 1234)); + check("1234 1234", &[num(Year), Space(" "), num(Year)], parsed!(year: 1234)); + check("1234 1235", &[num(Year), num(Year)], Err(IMPOSSIBLE)); + check("1234 1234", &[num(Year), Literal("x"), num(Year)], Err(INVALID)); + check("1234x1234", &[num(Year), Literal("x"), num(Year)], parsed!(year: 1234)); + check("1234 x 1234", &[num(Year), Literal("x"), num(Year)], Err(INVALID)); + check("1234xx1234", &[num(Year), Literal("x"), num(Year)], Err(INVALID)); + check("1234xx1234", &[num(Year), Literal("xx"), num(Year)], parsed!(year: 1234)); + check( + "1234 x 1234", + &[num(Year), Space(" "), Literal("x"), Space(" "), num(Year)], + parsed!(year: 1234), + ); + check( + "1234 x 1235", + &[num(Year), Space(" "), Literal("x"), Space(" "), Literal("1235")], + parsed!(year: 1234), + ); // signed numeric - check!("-42", [num(Year)]; year: -42); - check!("+42", [num(Year)]; year: 42); - check!("-0042", [num(Year)]; year: -42); - check!("+0042", [num(Year)]; year: 42); - check!("-42195", [num(Year)]; year: -42195); - check!("βˆ’42195", [num(Year)]; INVALID); // MINUS SIGN (U+2212) - check!("+42195", [num(Year)]; year: 42195); - check!(" -42195", [num(Year)]; year: -42195); - check!(" +42195", [num(Year)]; year: 42195); - check!(" -42195", [num(Year)]; year: -42195); - check!(" +42195", [num(Year)]; year: 42195); - check!("-42195 ", [num(Year)]; TOO_LONG); - check!("+42195 ", [num(Year)]; TOO_LONG); - check!(" - 42", [num(Year)]; INVALID); - check!(" + 42", [num(Year)]; INVALID); - check!(" -42195", [Space(" "), num(Year)]; year: -42195); - check!(" βˆ’42195", [Space(" "), num(Year)]; INVALID); // MINUS SIGN (U+2212) - check!(" +42195", [Space(" "), num(Year)]; year: 42195); - check!(" - 42", [Space(" "), num(Year)]; INVALID); - check!(" + 42", [Space(" "), num(Year)]; INVALID); - check!("-", [num(Year)]; TOO_SHORT); - check!("+", [num(Year)]; TOO_SHORT); + check("-42", &[num(Year)], parsed!(year: -42)); + check("+42", &[num(Year)], parsed!(year: 42)); + check("-0042", &[num(Year)], parsed!(year: -42)); + check("+0042", &[num(Year)], parsed!(year: 42)); + check("-42195", &[num(Year)], parsed!(year: -42195)); + check("βˆ’42195", &[num(Year)], Err(INVALID)); // MINUS SIGN (U+2212) + check("+42195", &[num(Year)], parsed!(year: 42195)); + check(" -42195", &[num(Year)], parsed!(year: -42195)); + check(" +42195", &[num(Year)], parsed!(year: 42195)); + check(" -42195", &[num(Year)], parsed!(year: -42195)); + check(" +42195", &[num(Year)], parsed!(year: 42195)); + check("-42195 ", &[num(Year)], Err(TOO_LONG)); + check("+42195 ", &[num(Year)], Err(TOO_LONG)); + check(" - 42", &[num(Year)], Err(INVALID)); + check(" + 42", &[num(Year)], Err(INVALID)); + check(" -42195", &[Space(" "), num(Year)], parsed!(year: -42195)); + check(" βˆ’42195", &[Space(" "), num(Year)], Err(INVALID)); // MINUS SIGN (U+2212) + check(" +42195", &[Space(" "), num(Year)], parsed!(year: 42195)); + check(" - 42", &[Space(" "), num(Year)], Err(INVALID)); + check(" + 42", &[Space(" "), num(Year)], Err(INVALID)); + check("-", &[num(Year)], Err(TOO_SHORT)); + check("+", &[num(Year)], Err(TOO_SHORT)); // unsigned numeric - check!("345", [num(Ordinal)]; ordinal: 345); - check!("+345", [num(Ordinal)]; INVALID); - check!("-345", [num(Ordinal)]; INVALID); - check!(" 345", [num(Ordinal)]; ordinal: 345); - check!("βˆ’345", [num(Ordinal)]; INVALID); // MINUS SIGN (U+2212) - check!("345 ", [num(Ordinal)]; TOO_LONG); - check!(" 345", [Space(" "), num(Ordinal)]; ordinal: 345); - check!("345 ", [num(Ordinal), Space(" ")]; ordinal: 345); - check!("345🀠 ", [num(Ordinal), Literal("🀠"), Space(" ")]; ordinal: 345); - check!("345🀠", [num(Ordinal)]; TOO_LONG); - check!("\u{0363}345", [num(Ordinal)]; INVALID); - check!(" +345", [num(Ordinal)]; INVALID); - check!(" -345", [num(Ordinal)]; INVALID); - check!("\t345", [Space("\t"), num(Ordinal)]; ordinal: 345); - check!(" +345", [Space(" "), num(Ordinal)]; INVALID); - check!(" -345", [Space(" "), num(Ordinal)]; INVALID); + check("345", &[num(Ordinal)], parsed!(ordinal: 345)); + check("+345", &[num(Ordinal)], Err(INVALID)); + check("-345", &[num(Ordinal)], Err(INVALID)); + check(" 345", &[num(Ordinal)], parsed!(ordinal: 345)); + check("βˆ’345", &[num(Ordinal)], Err(INVALID)); // MINUS SIGN (U+2212) + check("345 ", &[num(Ordinal)], Err(TOO_LONG)); + check(" 345", &[Space(" "), num(Ordinal)], parsed!(ordinal: 345)); + check("345 ", &[num(Ordinal), Space(" ")], parsed!(ordinal: 345)); + check("345🀠 ", &[num(Ordinal), Literal("🀠"), Space(" ")], parsed!(ordinal: 345)); + check("345🀠", &[num(Ordinal)], Err(TOO_LONG)); + check("\u{0363}345", &[num(Ordinal)], Err(INVALID)); + check(" +345", &[num(Ordinal)], Err(INVALID)); + check(" -345", &[num(Ordinal)], Err(INVALID)); + check("\t345", &[Space("\t"), num(Ordinal)], parsed!(ordinal: 345)); + check(" +345", &[Space(" "), num(Ordinal)], Err(INVALID)); + check(" -345", &[Space(" "), num(Ordinal)], Err(INVALID)); // various numeric fields - check!("1234 5678", [num(Year), num(IsoYear)]; year: 1234, isoyear: 5678); - check!("1234 5678", - [num(Year), num(IsoYear)]; - year: 1234, isoyear: 5678); - check!("12 34 56 78", - [num(YearDiv100), num(YearMod100), num(IsoYearDiv100), num(IsoYearMod100)]; - year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78); - check!("1 2 3 4 5 6", - [num(Month), num(Day), num(WeekFromSun), num(WeekFromMon), num(IsoWeek), - num(NumDaysFromSun)]; - month: 1, day: 2, week_from_sun: 3, week_from_mon: 4, isoweek: 5, weekday: Weekday::Sat); - check!("7 89 01", - [num(WeekdayFromMon), num(Ordinal), num(Hour12)]; - weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1); - check!("23 45 6 78901234 567890123", - [num(Hour), num(Minute), num(Second), num(Nanosecond), num(Timestamp)]; - hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, - timestamp: 567_890_123); + check("1234 5678", &[num(Year), num(IsoYear)], parsed!(year: 1234, isoyear: 5678)); + check("1234 5678", &[num(Year), num(IsoYear)], parsed!(year: 1234, isoyear: 5678)); + check( + "12 34 56 78", + &[num(YearDiv100), num(YearMod100), num(IsoYearDiv100), num(IsoYearMod100)], + parsed!(year_div_100: 12, year_mod_100: 34, isoyear_div_100: 56, isoyear_mod_100: 78), + ); + check( + "1 2 3 4 5", + &[num(Month), num(Day), num(WeekFromSun), num(NumDaysFromSun), num(IsoWeek)], + parsed!(month: 1, day: 2, week_from_sun: 3, weekday: Weekday::Thu, isoweek: 5), + ); + check( + "6 7 89 01", + &[num(WeekFromMon), num(WeekdayFromMon), num(Ordinal), num(Hour12)], + parsed!(week_from_mon: 6, weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1), + ); + check( + "23 45 6 78901234 567890123", + &[num(Hour), num(Minute), num(Second), num(Nanosecond), num(Timestamp)], + parsed!(hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, timestamp: 567_890_123), + ); } #[test] @@ -808,63 +816,63 @@ mod tests { use crate::format::Item::{Literal, Space}; // fixed: month and weekday names - check!("apr", [fixed(ShortMonthName)]; month: 4); - check!("Apr", [fixed(ShortMonthName)]; month: 4); - check!("APR", [fixed(ShortMonthName)]; month: 4); - check!("ApR", [fixed(ShortMonthName)]; month: 4); - check!("\u{0363}APR", [fixed(ShortMonthName)]; INVALID); - check!("April", [fixed(ShortMonthName)]; TOO_LONG); // `Apr` is parsed - check!("A", [fixed(ShortMonthName)]; TOO_SHORT); - check!("Sol", [fixed(ShortMonthName)]; INVALID); - check!("Apr", [fixed(LongMonthName)]; month: 4); - check!("Apri", [fixed(LongMonthName)]; TOO_LONG); // `Apr` is parsed - check!("April", [fixed(LongMonthName)]; month: 4); - check!("Aprill", [fixed(LongMonthName)]; TOO_LONG); - check!("Aprill", [fixed(LongMonthName), Literal("l")]; month: 4); - check!("Aprl", [fixed(LongMonthName), Literal("l")]; month: 4); - check!("April", [fixed(LongMonthName), Literal("il")]; TOO_SHORT); // do not backtrack - check!("thu", [fixed(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thu", [fixed(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("THU", [fixed(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("tHu", [fixed(ShortWeekdayName)]; weekday: Weekday::Thu); - check!("Thursday", [fixed(ShortWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("T", [fixed(ShortWeekdayName)]; TOO_SHORT); - check!("The", [fixed(ShortWeekdayName)]; INVALID); - check!("Nop", [fixed(ShortWeekdayName)]; INVALID); - check!("Thu", [fixed(LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thur", [fixed(LongWeekdayName)]; TOO_LONG); // `Thu` is parsed - check!("Thurs", [fixed(LongWeekdayName)]; TOO_LONG); // ditto - check!("Thursday", [fixed(LongWeekdayName)]; weekday: Weekday::Thu); - check!("Thursdays", [fixed(LongWeekdayName)]; TOO_LONG); - check!("Thursdays", [fixed(LongWeekdayName), Literal("s")]; weekday: Weekday::Thu); - check!("Thus", [fixed(LongWeekdayName), Literal("s")]; weekday: Weekday::Thu); - check!("Thursday", [fixed(LongWeekdayName), Literal("rsday")]; TOO_SHORT); // do not backtrack + check("apr", &[fixed(ShortMonthName)], parsed!(month: 4)); + check("Apr", &[fixed(ShortMonthName)], parsed!(month: 4)); + check("APR", &[fixed(ShortMonthName)], parsed!(month: 4)); + check("ApR", &[fixed(ShortMonthName)], parsed!(month: 4)); + check("\u{0363}APR", &[fixed(ShortMonthName)], Err(INVALID)); + check("April", &[fixed(ShortMonthName)], Err(TOO_LONG)); // `Apr` is parsed + check("A", &[fixed(ShortMonthName)], Err(TOO_SHORT)); + check("Sol", &[fixed(ShortMonthName)], Err(INVALID)); + check("Apr", &[fixed(LongMonthName)], parsed!(month: 4)); + check("Apri", &[fixed(LongMonthName)], Err(TOO_LONG)); // `Apr` is parsed + check("April", &[fixed(LongMonthName)], parsed!(month: 4)); + check("Aprill", &[fixed(LongMonthName)], Err(TOO_LONG)); + check("Aprill", &[fixed(LongMonthName), Literal("l")], parsed!(month: 4)); + check("Aprl", &[fixed(LongMonthName), Literal("l")], parsed!(month: 4)); + check("April", &[fixed(LongMonthName), Literal("il")], Err(TOO_SHORT)); // do not backtrack + check("thu", &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("Thu", &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("THU", &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("tHu", &[fixed(ShortWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("Thursday", &[fixed(ShortWeekdayName)], Err(TOO_LONG)); // `Thu` is parsed + check("T", &[fixed(ShortWeekdayName)], Err(TOO_SHORT)); + check("The", &[fixed(ShortWeekdayName)], Err(INVALID)); + check("Nop", &[fixed(ShortWeekdayName)], Err(INVALID)); + check("Thu", &[fixed(LongWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("Thur", &[fixed(LongWeekdayName)], Err(TOO_LONG)); // `Thu` is parsed + check("Thurs", &[fixed(LongWeekdayName)], Err(TOO_LONG)); // `Thu` is parsed + check("Thursday", &[fixed(LongWeekdayName)], parsed!(weekday: Weekday::Thu)); + check("Thursdays", &[fixed(LongWeekdayName)], Err(TOO_LONG)); + check("Thursdays", &[fixed(LongWeekdayName), Literal("s")], parsed!(weekday: Weekday::Thu)); + check("Thus", &[fixed(LongWeekdayName), Literal("s")], parsed!(weekday: Weekday::Thu)); + check("Thursday", &[fixed(LongWeekdayName), Literal("rsday")], Err(TOO_SHORT)); // do not backtrack // fixed: am/pm - check!("am", [fixed(LowerAmPm)]; hour_div_12: 0); - check!("pm", [fixed(LowerAmPm)]; hour_div_12: 1); - check!("AM", [fixed(LowerAmPm)]; hour_div_12: 0); - check!("PM", [fixed(LowerAmPm)]; hour_div_12: 1); - check!("am", [fixed(UpperAmPm)]; hour_div_12: 0); - check!("pm", [fixed(UpperAmPm)]; hour_div_12: 1); - check!("AM", [fixed(UpperAmPm)]; hour_div_12: 0); - check!("PM", [fixed(UpperAmPm)]; hour_div_12: 1); - check!("Am", [fixed(LowerAmPm)]; hour_div_12: 0); - check!(" Am", [Space(" "), fixed(LowerAmPm)]; hour_div_12: 0); - check!("Am🀠", [fixed(LowerAmPm), Literal("🀠")]; hour_div_12: 0); - check!("🀠Am", [Literal("🀠"), fixed(LowerAmPm)]; hour_div_12: 0); - check!("\u{0363}am", [fixed(LowerAmPm)]; INVALID); - check!("\u{0360}am", [fixed(LowerAmPm)]; INVALID); - check!(" Am", [fixed(LowerAmPm)]; INVALID); - check!("Am ", [fixed(LowerAmPm)]; TOO_LONG); - check!("a.m.", [fixed(LowerAmPm)]; INVALID); - check!("A.M.", [fixed(LowerAmPm)]; INVALID); - check!("ame", [fixed(LowerAmPm)]; TOO_LONG); // `am` is parsed - check!("a", [fixed(LowerAmPm)]; TOO_SHORT); - check!("p", [fixed(LowerAmPm)]; TOO_SHORT); - check!("x", [fixed(LowerAmPm)]; TOO_SHORT); - check!("xx", [fixed(LowerAmPm)]; INVALID); - check!("", [fixed(LowerAmPm)]; TOO_SHORT); + check("am", &[fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check("pm", &[fixed(LowerAmPm)], parsed!(hour_div_12: 1)); + check("AM", &[fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check("PM", &[fixed(LowerAmPm)], parsed!(hour_div_12: 1)); + check("am", &[fixed(UpperAmPm)], parsed!(hour_div_12: 0)); + check("pm", &[fixed(UpperAmPm)], parsed!(hour_div_12: 1)); + check("AM", &[fixed(UpperAmPm)], parsed!(hour_div_12: 0)); + check("PM", &[fixed(UpperAmPm)], parsed!(hour_div_12: 1)); + check("Am", &[fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check(" Am", &[Space(" "), fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check("Am🀠", &[fixed(LowerAmPm), Literal("🀠")], parsed!(hour_div_12: 0)); + check("🀠Am", &[Literal("🀠"), fixed(LowerAmPm)], parsed!(hour_div_12: 0)); + check("\u{0363}am", &[fixed(LowerAmPm)], Err(INVALID)); + check("\u{0360}am", &[fixed(LowerAmPm)], Err(INVALID)); + check(" Am", &[fixed(LowerAmPm)], Err(INVALID)); + check("Am ", &[fixed(LowerAmPm)], Err(TOO_LONG)); + check("a.m.", &[fixed(LowerAmPm)], Err(INVALID)); + check("A.M.", &[fixed(LowerAmPm)], Err(INVALID)); + check("ame", &[fixed(LowerAmPm)], Err(TOO_LONG)); // `am` is parsed + check("a", &[fixed(LowerAmPm)], Err(TOO_SHORT)); + check("p", &[fixed(LowerAmPm)], Err(TOO_SHORT)); + check("x", &[fixed(LowerAmPm)], Err(TOO_SHORT)); + check("xx", &[fixed(LowerAmPm)], Err(INVALID)); + check("", &[fixed(LowerAmPm)], Err(TOO_SHORT)); } #[test] @@ -875,77 +883,93 @@ mod tests { use crate::format::Numeric::Second; // fixed: dot plus nanoseconds - check!("", [fixed(Nanosecond)]; ); // no field set, but not an error - check!(".", [fixed(Nanosecond)]; TOO_SHORT); - check!("4", [fixed(Nanosecond)]; TOO_LONG); // never consumes `4` - check!("4", [fixed(Nanosecond), num(Second)]; second: 4); - check!(".0", [fixed(Nanosecond)]; nanosecond: 0); - check!(".4", [fixed(Nanosecond)]; nanosecond: 400_000_000); - check!(".42", [fixed(Nanosecond)]; nanosecond: 420_000_000); - check!(".421", [fixed(Nanosecond)]; nanosecond: 421_000_000); - check!(".42195", [fixed(Nanosecond)]; nanosecond: 421_950_000); - check!(".421951", [fixed(Nanosecond)]; nanosecond: 421_951_000); - check!(".4219512", [fixed(Nanosecond)]; nanosecond: 421_951_200); - check!(".42195123", [fixed(Nanosecond)]; nanosecond: 421_951_230); - check!(".421950803", [fixed(Nanosecond)]; nanosecond: 421_950_803); - check!(".4219508035", [fixed(Nanosecond)]; nanosecond: 421_950_803); - check!(".42195080354", [fixed(Nanosecond)]; nanosecond: 421_950_803); - check!(".421950803547", [fixed(Nanosecond)]; nanosecond: 421_950_803); - check!(".000000003", [fixed(Nanosecond)]; nanosecond: 3); - check!(".0000000031", [fixed(Nanosecond)]; nanosecond: 3); - check!(".0000000035", [fixed(Nanosecond)]; nanosecond: 3); - check!(".000000003547", [fixed(Nanosecond)]; nanosecond: 3); - check!(".0000000009", [fixed(Nanosecond)]; nanosecond: 0); - check!(".000000000547", [fixed(Nanosecond)]; nanosecond: 0); - check!(".0000000009999999999999999999999999", [fixed(Nanosecond)]; nanosecond: 0); - check!(".4🀠", [fixed(Nanosecond), Literal("🀠")]; nanosecond: 400_000_000); - check!(".4x", [fixed(Nanosecond)]; TOO_LONG); - check!(". 4", [fixed(Nanosecond)]; INVALID); - check!(" .4", [fixed(Nanosecond)]; TOO_LONG); // no automatic trimming + check("", &[fixed(Nanosecond)], parsed!()); // no field set, but not an error + check(".", &[fixed(Nanosecond)], Err(TOO_SHORT)); + check("4", &[fixed(Nanosecond)], Err(TOO_LONG)); // never consumes `4` + check("4", &[fixed(Nanosecond), num(Second)], parsed!(second: 4)); + check(".0", &[fixed(Nanosecond)], parsed!(nanosecond: 0)); + check(".4", &[fixed(Nanosecond)], parsed!(nanosecond: 400_000_000)); + check(".42", &[fixed(Nanosecond)], parsed!(nanosecond: 420_000_000)); + check(".421", &[fixed(Nanosecond)], parsed!(nanosecond: 421_000_000)); + check(".42195", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_000)); + check(".421951", &[fixed(Nanosecond)], parsed!(nanosecond: 421_951_000)); + check(".4219512", &[fixed(Nanosecond)], parsed!(nanosecond: 421_951_200)); + check(".42195123", &[fixed(Nanosecond)], parsed!(nanosecond: 421_951_230)); + check(".421950803", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); + check(".4219508035", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); + check(".42195080354", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); + check(".421950803547", &[fixed(Nanosecond)], parsed!(nanosecond: 421_950_803)); + check(".000000003", &[fixed(Nanosecond)], parsed!(nanosecond: 3)); + check(".0000000031", &[fixed(Nanosecond)], parsed!(nanosecond: 3)); + check(".0000000035", &[fixed(Nanosecond)], parsed!(nanosecond: 3)); + check(".000000003547", &[fixed(Nanosecond)], parsed!(nanosecond: 3)); + check(".0000000009", &[fixed(Nanosecond)], parsed!(nanosecond: 0)); + check(".000000000547", &[fixed(Nanosecond)], parsed!(nanosecond: 0)); + check(".0000000009999999999999999999999999", &[fixed(Nanosecond)], parsed!(nanosecond: 0)); + check(".4🀠", &[fixed(Nanosecond), Literal("🀠")], parsed!(nanosecond: 400_000_000)); + check(".4x", &[fixed(Nanosecond)], Err(TOO_LONG)); + check(". 4", &[fixed(Nanosecond)], Err(INVALID)); + check(" .4", &[fixed(Nanosecond)], Err(TOO_LONG)); // no automatic trimming // fixed: nanoseconds without the dot - check!("", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); - check!(".", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); - check!("0", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); - check!("4", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); - check!("42", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); - check!("421", [internal_fixed(Nanosecond3NoDot)]; nanosecond: 421_000_000); - check!("4210", [internal_fixed(Nanosecond3NoDot)]; TOO_LONG); - check!("42143", [internal_fixed(Nanosecond3NoDot), num(Second)]; nanosecond: 421_000_000, second: 43); - check!("421🀠", [internal_fixed(Nanosecond3NoDot), Literal("🀠")]; nanosecond: 421_000_000); - check!("🀠421", [Literal("🀠"), internal_fixed(Nanosecond3NoDot)]; nanosecond: 421_000_000); - check!("42195", [internal_fixed(Nanosecond3NoDot)]; TOO_LONG); - check!("123456789", [internal_fixed(Nanosecond3NoDot)]; TOO_LONG); - check!("4x", [internal_fixed(Nanosecond3NoDot)]; TOO_SHORT); - check!(" 4", [internal_fixed(Nanosecond3NoDot)]; INVALID); - check!(".421", [internal_fixed(Nanosecond3NoDot)]; INVALID); - - check!("", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); - check!(".", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); - check!("0", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); - check!("1234", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); - check!("12345", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); - check!("421950", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 421_950_000); - check!("000003", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 3000); - check!("000000", [internal_fixed(Nanosecond6NoDot)]; nanosecond: 0); - check!("1234567", [internal_fixed(Nanosecond6NoDot)]; TOO_LONG); - check!("123456789", [internal_fixed(Nanosecond6NoDot)]; TOO_LONG); - check!("4x", [internal_fixed(Nanosecond6NoDot)]; TOO_SHORT); - check!(" 4", [internal_fixed(Nanosecond6NoDot)]; INVALID); - check!(".42100", [internal_fixed(Nanosecond6NoDot)]; INVALID); - - check!("", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); - check!(".", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); - check!("42195", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); - check!("12345678", [internal_fixed(Nanosecond9NoDot)]; TOO_SHORT); - check!("421950803", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 421_950_803); - check!("000000003", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 3); - check!("42195080354", [internal_fixed(Nanosecond9NoDot), num(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 - check!("1234567890", [internal_fixed(Nanosecond9NoDot)]; TOO_LONG); - check!("000000000", [internal_fixed(Nanosecond9NoDot)]; nanosecond: 0); - check!("00000000x", [internal_fixed(Nanosecond9NoDot)]; INVALID); - check!(" 4", [internal_fixed(Nanosecond9NoDot)]; INVALID); - check!(".42100000", [internal_fixed(Nanosecond9NoDot)]; INVALID); + check("", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check(".", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check("0", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check("4", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check("42", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check("421", &[internal_fixed(Nanosecond3NoDot)], parsed!(nanosecond: 421_000_000)); + check("4210", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_LONG)); + check( + "42143", + &[internal_fixed(Nanosecond3NoDot), num(Second)], + parsed!(nanosecond: 421_000_000, second: 43), + ); + check( + "421🀠", + &[internal_fixed(Nanosecond3NoDot), Literal("🀠")], + parsed!(nanosecond: 421_000_000), + ); + check( + "🀠421", + &[Literal("🀠"), internal_fixed(Nanosecond3NoDot)], + parsed!(nanosecond: 421_000_000), + ); + check("42195", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_LONG)); + check("123456789", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_LONG)); + check("4x", &[internal_fixed(Nanosecond3NoDot)], Err(TOO_SHORT)); + check(" 4", &[internal_fixed(Nanosecond3NoDot)], Err(INVALID)); + check(".421", &[internal_fixed(Nanosecond3NoDot)], Err(INVALID)); + + check("", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check(".", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check("0", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check("1234", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check("12345", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check("421950", &[internal_fixed(Nanosecond6NoDot)], parsed!(nanosecond: 421_950_000)); + check("000003", &[internal_fixed(Nanosecond6NoDot)], parsed!(nanosecond: 3000)); + check("000000", &[internal_fixed(Nanosecond6NoDot)], parsed!(nanosecond: 0)); + check("1234567", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_LONG)); + check("123456789", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_LONG)); + check("4x", &[internal_fixed(Nanosecond6NoDot)], Err(TOO_SHORT)); + check(" 4", &[internal_fixed(Nanosecond6NoDot)], Err(INVALID)); + check(".42100", &[internal_fixed(Nanosecond6NoDot)], Err(INVALID)); + + check("", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); + check(".", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); + check("42195", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); + check("12345678", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_SHORT)); + check("421950803", &[internal_fixed(Nanosecond9NoDot)], parsed!(nanosecond: 421_950_803)); + check("000000003", &[internal_fixed(Nanosecond9NoDot)], parsed!(nanosecond: 3)); + check( + "42195080354", + &[internal_fixed(Nanosecond9NoDot), num(Second)], + parsed!(nanosecond: 421_950_803, second: 54), + ); // don't skip digits that come after the 9 + check("1234567890", &[internal_fixed(Nanosecond9NoDot)], Err(TOO_LONG)); + check("000000000", &[internal_fixed(Nanosecond9NoDot)], parsed!(nanosecond: 0)); + check("00000000x", &[internal_fixed(Nanosecond9NoDot)], Err(INVALID)); + check(" 4", &[internal_fixed(Nanosecond9NoDot)], Err(INVALID)); + check(".42100000", &[internal_fixed(Nanosecond9NoDot)], Err(INVALID)); } #[test] @@ -955,460 +979,586 @@ mod tests { use crate::format::Item::Literal; // TimezoneOffset - check!("1", [fixed(TimezoneOffset)]; INVALID); - check!("12", [fixed(TimezoneOffset)]; INVALID); - check!("123", [fixed(TimezoneOffset)]; INVALID); - check!("1234", [fixed(TimezoneOffset)]; INVALID); - check!("12345", [fixed(TimezoneOffset)]; INVALID); - check!("123456", [fixed(TimezoneOffset)]; INVALID); - check!("1234567", [fixed(TimezoneOffset)]; INVALID); - check!("+1", [fixed(TimezoneOffset)]; TOO_SHORT); - check!("+12", [fixed(TimezoneOffset)]; TOO_SHORT); - check!("+123", [fixed(TimezoneOffset)]; TOO_SHORT); - check!("+1234", [fixed(TimezoneOffset)]; offset: 45_240); - check!("+12345", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+123456", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+1234567", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12345678", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12:", [fixed(TimezoneOffset)]; TOO_SHORT); - check!("+12:3", [fixed(TimezoneOffset)]; TOO_SHORT); - check!("+12:34", [fixed(TimezoneOffset)]; offset: 45_240); - check!("-12:34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("βˆ’12:34", [fixed(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12:34:5", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12:34:56", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12:34:56:", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12 34", [fixed(TimezoneOffset)]; offset: 45_240); - check!("+12 34", [fixed(TimezoneOffset)]; offset: 45_240); - check!("12:34", [fixed(TimezoneOffset)]; INVALID); - check!("12:34:56", [fixed(TimezoneOffset)]; INVALID); - check!("+12::34", [fixed(TimezoneOffset)]; offset: 45_240); - check!("+12: :34", [fixed(TimezoneOffset)]; offset: 45_240); - check!("+12:::34", [fixed(TimezoneOffset)]; offset: 45_240); - check!("+12::::34", [fixed(TimezoneOffset)]; offset: 45_240); - check!("+12::34", [fixed(TimezoneOffset)]; offset: 45_240); - check!("+12:34:56", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12:3456", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+1234:56", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+1234:567", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+00:00", [fixed(TimezoneOffset)]; offset: 0); - check!("-00:00", [fixed(TimezoneOffset)]; offset: 0); - check!("βˆ’00:00", [fixed(TimezoneOffset)]; offset: 0); // MINUS SIGN (U+2212) - check!("+00:01", [fixed(TimezoneOffset)]; offset: 60); - check!("-00:01", [fixed(TimezoneOffset)]; offset: -60); - check!("+00:30", [fixed(TimezoneOffset)]; offset: 1_800); - check!("-00:30", [fixed(TimezoneOffset)]; offset: -1_800); - check!("+24:00", [fixed(TimezoneOffset)]; offset: 86_400); - check!("-24:00", [fixed(TimezoneOffset)]; offset: -86_400); - check!("βˆ’24:00", [fixed(TimezoneOffset)]; offset: -86_400); // MINUS SIGN (U+2212) - check!("+99:59", [fixed(TimezoneOffset)]; offset: 359_940); - check!("-99:59", [fixed(TimezoneOffset)]; offset: -359_940); - check!("+00:60", [fixed(TimezoneOffset)]; OUT_OF_RANGE); - check!("+00:99", [fixed(TimezoneOffset)]; OUT_OF_RANGE); - check!("#12:34", [fixed(TimezoneOffset)]; INVALID); - check!("+12:34 ", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12 34 ", [fixed(TimezoneOffset)]; TOO_LONG); - check!(" +12:34", [fixed(TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fixed(TimezoneOffset)]; offset: -45_240); - check!(" βˆ’12:34", [fixed(TimezoneOffset)]; offset: -45_240); // MINUS SIGN (U+2212) - check!(" +12:34", [fixed(TimezoneOffset)]; offset: 45_240); - check!(" -12:34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("\t -12:34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("-12: 34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("-12 :34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("-12 : 34", [fixed(TimezoneOffset)]; offset: -45_240); - check!("12:34 ", [fixed(TimezoneOffset)]; INVALID); - check!(" 12:34", [fixed(TimezoneOffset)]; INVALID); - check!("", [fixed(TimezoneOffset)]; TOO_SHORT); - check!("+", [fixed(TimezoneOffset)]; TOO_SHORT); - check!("+12345", [fixed(TimezoneOffset), num(Numeric::Day)]; offset: 45_240, day: 5); - check!("+12:345", [fixed(TimezoneOffset), num(Numeric::Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fixed(TimezoneOffset), Literal(":")]; offset: 45_240); - check!("Z12:34", [fixed(TimezoneOffset)]; INVALID); - check!("X12:34", [fixed(TimezoneOffset)]; INVALID); - check!("Z+12:34", [fixed(TimezoneOffset)]; INVALID); - check!("X+12:34", [fixed(TimezoneOffset)]; INVALID); - check!("Xβˆ’12:34", [fixed(TimezoneOffset)]; INVALID); // MINUS SIGN (U+2212) - check!("🀠+12:34", [fixed(TimezoneOffset)]; INVALID); - check!("+12:34🀠", [fixed(TimezoneOffset)]; TOO_LONG); - check!("+12:🀠34", [fixed(TimezoneOffset)]; INVALID); - check!("+1234🀠", [fixed(TimezoneOffset), Literal("🀠")]; offset: 45_240); - check!("-1234🀠", [fixed(TimezoneOffset), Literal("🀠")]; offset: -45_240); - check!("βˆ’1234🀠", [fixed(TimezoneOffset), Literal("🀠")]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34🀠", [fixed(TimezoneOffset), Literal("🀠")]; offset: 45_240); - check!("-12:34🀠", [fixed(TimezoneOffset), Literal("🀠")]; offset: -45_240); - check!("βˆ’12:34🀠", [fixed(TimezoneOffset), Literal("🀠")]; offset: -45_240); // MINUS SIGN (U+2212) - check!("🀠+12:34", [Literal("🀠"), fixed(TimezoneOffset)]; offset: 45_240); - check!("Z", [fixed(TimezoneOffset)]; INVALID); - check!("A", [fixed(TimezoneOffset)]; INVALID); - check!("PST", [fixed(TimezoneOffset)]; INVALID); - check!("#Z", [fixed(TimezoneOffset)]; INVALID); - check!(":Z", [fixed(TimezoneOffset)]; INVALID); - check!("+Z", [fixed(TimezoneOffset)]; TOO_SHORT); - check!("+:Z", [fixed(TimezoneOffset)]; INVALID); - check!("+Z:", [fixed(TimezoneOffset)]; INVALID); - check!("z", [fixed(TimezoneOffset)]; INVALID); - check!(" :Z", [fixed(TimezoneOffset)]; INVALID); - check!(" Z", [fixed(TimezoneOffset)]; INVALID); - check!(" z", [fixed(TimezoneOffset)]; INVALID); + check("1", &[fixed(TimezoneOffset)], Err(INVALID)); + check("12", &[fixed(TimezoneOffset)], Err(INVALID)); + check("123", &[fixed(TimezoneOffset)], Err(INVALID)); + check("1234", &[fixed(TimezoneOffset)], Err(INVALID)); + check("12345", &[fixed(TimezoneOffset)], Err(INVALID)); + check("123456", &[fixed(TimezoneOffset)], Err(INVALID)); + check("1234567", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+1", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+12", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+123", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+1234", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12345", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+123456", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+1234567", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12345678", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+12:3", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+12:34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("-12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("βˆ’12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34:", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:34:5", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:34:56", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:34:56:", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12 34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12 34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("12:34:56", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+12::34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12: :34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12:::34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12::::34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12::34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("+12:34:56", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:3456", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+1234:56", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+1234:567", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+00:00", &[fixed(TimezoneOffset)], parsed!(offset: 0)); + check("-00:00", &[fixed(TimezoneOffset)], parsed!(offset: 0)); + check("βˆ’00:00", &[fixed(TimezoneOffset)], parsed!(offset: 0)); // MINUS SIGN (U+2212) + check("+00:01", &[fixed(TimezoneOffset)], parsed!(offset: 60)); + check("-00:01", &[fixed(TimezoneOffset)], parsed!(offset: -60)); + check("+00:30", &[fixed(TimezoneOffset)], parsed!(offset: 1_800)); + check("-00:30", &[fixed(TimezoneOffset)], parsed!(offset: -1_800)); + check("+24:00", &[fixed(TimezoneOffset)], parsed!(offset: 86_400)); + check("-24:00", &[fixed(TimezoneOffset)], parsed!(offset: -86_400)); + check("βˆ’24:00", &[fixed(TimezoneOffset)], parsed!(offset: -86_400)); // MINUS SIGN (U+2212) + check("+99:59", &[fixed(TimezoneOffset)], parsed!(offset: 359_940)); + check("-99:59", &[fixed(TimezoneOffset)], parsed!(offset: -359_940)); + check("+00:60", &[fixed(TimezoneOffset)], Err(OUT_OF_RANGE)); + check("+00:99", &[fixed(TimezoneOffset)], Err(OUT_OF_RANGE)); + check("#12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+12:34 ", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12 34 ", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check(" +12:34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check(" -12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check(" βˆ’12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check(" +12:34", &[fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check(" -12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("\t -12:34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12: 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 :34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 : 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 : 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 : 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12: 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 :34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("-12 : 34", &[fixed(TimezoneOffset)], parsed!(offset: -45_240)); + check("12:34 ", &[fixed(TimezoneOffset)], Err(INVALID)); + check(" 12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check( + "+12345", + &[fixed(TimezoneOffset), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:345", + &[fixed(TimezoneOffset), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check("+12:34:", &[fixed(TimezoneOffset), Literal(":")], parsed!(offset: 45_240)); + check("Z12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("X12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("Z+12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("X+12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("Xβˆ’12:34", &[fixed(TimezoneOffset)], Err(INVALID)); // MINUS SIGN (U+2212) + check("🀠+12:34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+12:34🀠", &[fixed(TimezoneOffset)], Err(TOO_LONG)); + check("+12:🀠34", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+1234🀠", &[fixed(TimezoneOffset), Literal("🀠")], parsed!(offset: 45_240)); + check("-1234🀠", &[fixed(TimezoneOffset), Literal("🀠")], parsed!(offset: -45_240)); + check("βˆ’1234🀠", &[fixed(TimezoneOffset), Literal("🀠")], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34🀠", &[fixed(TimezoneOffset), Literal("🀠")], parsed!(offset: 45_240)); + check("-12:34🀠", &[fixed(TimezoneOffset), Literal("🀠")], parsed!(offset: -45_240)); + check("βˆ’12:34🀠", &[fixed(TimezoneOffset), Literal("🀠")], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("🀠+12:34", &[Literal("🀠"), fixed(TimezoneOffset)], parsed!(offset: 45_240)); + check("Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check("A", &[fixed(TimezoneOffset)], Err(INVALID)); + check("PST", &[fixed(TimezoneOffset)], Err(INVALID)); + check("#Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check(":Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+Z", &[fixed(TimezoneOffset)], Err(TOO_SHORT)); + check("+:Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check("+Z:", &[fixed(TimezoneOffset)], Err(INVALID)); + check("z", &[fixed(TimezoneOffset)], Err(INVALID)); + check(" :Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check(" Z", &[fixed(TimezoneOffset)], Err(INVALID)); + check(" z", &[fixed(TimezoneOffset)], Err(INVALID)); // TimezoneOffsetColon - check!("1", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12", [fixed(TimezoneOffsetColon)]; INVALID); - check!("123", [fixed(TimezoneOffsetColon)]; INVALID); - check!("1234", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12345", [fixed(TimezoneOffsetColon)]; INVALID); - check!("123456", [fixed(TimezoneOffsetColon)]; INVALID); - check!("1234567", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12345678", [fixed(TimezoneOffsetColon)]; INVALID); - check!("+1", [fixed(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12", [fixed(TimezoneOffsetColon)]; TOO_SHORT); - check!("+123", [fixed(TimezoneOffsetColon)]; TOO_SHORT); - check!("+1234", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("-1234", [fixed(TimezoneOffsetColon)]; offset: -45_240); - check!("βˆ’1234", [fixed(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+123456", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+1234567", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+12345678", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("1:", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12:", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12:3", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12:34", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12:34:", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12:34:5", [fixed(TimezoneOffsetColon)]; INVALID); - check!("12:34:56", [fixed(TimezoneOffsetColon)]; INVALID); - check!("+1:", [fixed(TimezoneOffsetColon)]; INVALID); - check!("+12:", [fixed(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:3", [fixed(TimezoneOffsetColon)]; TOO_SHORT); - check!("+12:34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("-12:34", [fixed(TimezoneOffsetColon)]; offset: -45_240); - check!("βˆ’12:34", [fixed(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:5", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:7", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:34:56:78", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+12:3456", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("+1234:56", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!("βˆ’12:34", [fixed(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("βˆ’12 : 34", [fixed(TimezoneOffsetColon)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12 :34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 :34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("-12 : 34", [fixed(TimezoneOffsetColon)]; offset: -45_240); - check!("+12 : 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12 : 34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12: :34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12:::34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::::34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("+12::34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("#1234", [fixed(TimezoneOffsetColon)]; INVALID); - check!("#12:34", [fixed(TimezoneOffsetColon)]; INVALID); - check!("+12:34 ", [fixed(TimezoneOffsetColon)]; TOO_LONG); - check!(" +12:34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("\t+12:34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("\t\t+12:34", [fixed(TimezoneOffsetColon)]; offset: 45_240); - check!("12:34 ", [fixed(TimezoneOffsetColon)]; INVALID); - check!(" 12:34", [fixed(TimezoneOffsetColon)]; INVALID); - check!("", [fixed(TimezoneOffsetColon)]; TOO_SHORT); - check!("+", [fixed(TimezoneOffsetColon)]; TOO_SHORT); - check!(":", [fixed(TimezoneOffsetColon)]; INVALID); - check!("+12345", [fixed(TimezoneOffsetColon), num(Numeric::Day)]; offset: 45_240, day: 5); - check!("+12:345", [fixed(TimezoneOffsetColon), num(Numeric::Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fixed(TimezoneOffsetColon), Literal(":")]; offset: 45_240); - check!("Z", [fixed(TimezoneOffsetColon)]; INVALID); - check!("A", [fixed(TimezoneOffsetColon)]; INVALID); - check!("PST", [fixed(TimezoneOffsetColon)]; INVALID); - check!("#Z", [fixed(TimezoneOffsetColon)]; INVALID); - check!(":Z", [fixed(TimezoneOffsetColon)]; INVALID); - check!("+Z", [fixed(TimezoneOffsetColon)]; TOO_SHORT); - check!("+:Z", [fixed(TimezoneOffsetColon)]; INVALID); - check!("+Z:", [fixed(TimezoneOffsetColon)]; INVALID); - check!("z", [fixed(TimezoneOffsetColon)]; INVALID); - check!(" :Z", [fixed(TimezoneOffsetColon)]; INVALID); - check!(" Z", [fixed(TimezoneOffsetColon)]; INVALID); - check!(" z", [fixed(TimezoneOffsetColon)]; INVALID); + check("1", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("123", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("1234", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12345", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("123456", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("1234567", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12345678", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+1", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+12", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+123", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+1234", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("-1234", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); + check("βˆ’1234", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12345", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+123456", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+1234567", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12345678", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("1:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:3", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:34", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:34:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:34:5", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("12:34:56", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+1:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+12:", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+12:3", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("-12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); + check("βˆ’12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34:", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:5", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:56", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:56:", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:56:7", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:34:56:78", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+12:3456", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("+1234:56", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check("βˆ’12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("βˆ’12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12 :34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12: 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12: 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 :34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("-12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: -45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12::34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12: :34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12:::34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12::::34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("+12::34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("#1234", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("#12:34", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+12:34 ", &[fixed(TimezoneOffsetColon)], Err(TOO_LONG)); + check(" +12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("\t+12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("\t\t+12:34", &[fixed(TimezoneOffsetColon)], parsed!(offset: 45_240)); + check("12:34 ", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(" 12:34", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check(":", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check( + "+12345", + &[fixed(TimezoneOffsetColon), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:345", + &[fixed(TimezoneOffsetColon), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check("+12:34:", &[fixed(TimezoneOffsetColon), Literal(":")], parsed!(offset: 45_240)); + check("Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("A", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("PST", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("#Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(":Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+Z", &[fixed(TimezoneOffsetColon)], Err(TOO_SHORT)); + check("+:Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("+Z:", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check("z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(" :Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(" Z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); + check(" z", &[fixed(TimezoneOffsetColon)], Err(INVALID)); // testing `TimezoneOffsetColon` also tests same path as `TimezoneOffsetDoubleColon` // and `TimezoneOffsetTripleColon` for function `parse_internal`. // No need for separate tests for `TimezoneOffsetDoubleColon` and // `TimezoneOffsetTripleColon`. // TimezoneOffsetZ - check!("1", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12", [fixed(TimezoneOffsetZ)]; INVALID); - check!("123", [fixed(TimezoneOffsetZ)]; INVALID); - check!("1234", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12345", [fixed(TimezoneOffsetZ)]; INVALID); - check!("123456", [fixed(TimezoneOffsetZ)]; INVALID); - check!("1234567", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12345678", [fixed(TimezoneOffsetZ)]; INVALID); - check!("+1", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("+123", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("+1234", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("-1234", [fixed(TimezoneOffsetZ)]; offset: -45_240); - check!("βˆ’1234", [fixed(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+123456", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+1234567", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12345678", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("1:", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12:", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12:3", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12:34", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12:34:", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12:34:5", [fixed(TimezoneOffsetZ)]; INVALID); - check!("12:34:56", [fixed(TimezoneOffsetZ)]; INVALID); - check!("+1:", [fixed(TimezoneOffsetZ)]; INVALID); - check!("+12:", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:3", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("+12:34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("-12:34", [fixed(TimezoneOffsetZ)]; offset: -45_240); - check!("βˆ’12:34", [fixed(TimezoneOffsetZ)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:5", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:7", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12:34:56:78", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12::34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12:3456", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+1234:56", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12: 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 :34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12 : 34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("12:34 ", [fixed(TimezoneOffsetZ)]; INVALID); - check!(" 12:34", [fixed(TimezoneOffsetZ)]; INVALID); - check!("+12:34 ", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("+12 34 ", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!(" +12:34", [fixed(TimezoneOffsetZ)]; offset: 45_240); - check!("+12345", [fixed(TimezoneOffsetZ), num(Numeric::Day)]; offset: 45_240, day: 5); - check!("+12:345", [fixed(TimezoneOffsetZ), num(Numeric::Day)]; offset: 45_240, day: 5); - check!("+12:34:", [fixed(TimezoneOffsetZ), Literal(":")]; offset: 45_240); - check!("Z12:34", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("X12:34", [fixed(TimezoneOffsetZ)]; INVALID); - check!("Z", [fixed(TimezoneOffsetZ)]; offset: 0); - check!("z", [fixed(TimezoneOffsetZ)]; offset: 0); - check!(" Z", [fixed(TimezoneOffsetZ)]; offset: 0); - check!(" z", [fixed(TimezoneOffsetZ)]; offset: 0); - check!("\u{0363}Z", [fixed(TimezoneOffsetZ)]; INVALID); - check!("Z ", [fixed(TimezoneOffsetZ)]; TOO_LONG); - check!("A", [fixed(TimezoneOffsetZ)]; INVALID); - check!("PST", [fixed(TimezoneOffsetZ)]; INVALID); - check!("#Z", [fixed(TimezoneOffsetZ)]; INVALID); - check!(":Z", [fixed(TimezoneOffsetZ)]; INVALID); - check!(":z", [fixed(TimezoneOffsetZ)]; INVALID); - check!("+Z", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("-Z", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("+A", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("+πŸ™ƒ", [fixed(TimezoneOffsetZ)]; INVALID); - check!("+Z:", [fixed(TimezoneOffsetZ)]; INVALID); - check!(" :Z", [fixed(TimezoneOffsetZ)]; INVALID); - check!(" +Z", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!(" -Z", [fixed(TimezoneOffsetZ)]; TOO_SHORT); - check!("+:Z", [fixed(TimezoneOffsetZ)]; INVALID); - check!("Y", [fixed(TimezoneOffsetZ)]; INVALID); - check!("Zulu", [fixed(TimezoneOffsetZ), Literal("ulu")]; offset: 0); - check!("zulu", [fixed(TimezoneOffsetZ), Literal("ulu")]; offset: 0); - check!("+1234ulu", [fixed(TimezoneOffsetZ), Literal("ulu")]; offset: 45_240); - check!("+12:34ulu", [fixed(TimezoneOffsetZ), Literal("ulu")]; offset: 45_240); + check("1", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("123", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("1234", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12345", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("123456", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("1234567", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12345678", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+1", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+12", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+123", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+1234", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("-1234", &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); + check("βˆ’1234", &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12345", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+123456", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+1234567", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12345678", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("1:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:3", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:34", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:34:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:34:5", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("12:34:56", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+1:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+12:", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+12:3", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+12:34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("-12:34", &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); + check("βˆ’12:34", &[fixed(TimezoneOffsetZ)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34:", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:5", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:56", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:56:", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:56:7", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12:34:56:78", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12::34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12:3456", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+1234:56", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12: 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 :34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("+12 : 34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check("12:34 ", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(" 12:34", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+12:34 ", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("+12 34 ", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check(" +12:34", &[fixed(TimezoneOffsetZ)], parsed!(offset: 45_240)); + check( + "+12345", + &[fixed(TimezoneOffsetZ), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:345", + &[fixed(TimezoneOffsetZ), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check("+12:34:", &[fixed(TimezoneOffsetZ), Literal(":")], parsed!(offset: 45_240)); + check("Z12:34", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("X12:34", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("Z", &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); + check("z", &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); + check(" Z", &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); + check(" z", &[fixed(TimezoneOffsetZ)], parsed!(offset: 0)); + check("\u{0363}Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("Z ", &[fixed(TimezoneOffsetZ)], Err(TOO_LONG)); + check("A", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("PST", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("#Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(":Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(":z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+Z", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("-Z", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+A", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+πŸ™ƒ", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("+Z:", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(" :Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check(" +Z", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check(" -Z", &[fixed(TimezoneOffsetZ)], Err(TOO_SHORT)); + check("+:Z", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("Y", &[fixed(TimezoneOffsetZ)], Err(INVALID)); + check("Zulu", &[fixed(TimezoneOffsetZ), Literal("ulu")], parsed!(offset: 0)); + check("zulu", &[fixed(TimezoneOffsetZ), Literal("ulu")], parsed!(offset: 0)); + check("+1234ulu", &[fixed(TimezoneOffsetZ), Literal("ulu")], parsed!(offset: 45_240)); + check("+12:34ulu", &[fixed(TimezoneOffsetZ), Literal("ulu")], parsed!(offset: 45_240)); // Testing `TimezoneOffsetZ` also tests same path as `TimezoneOffsetColonZ` // in function `parse_internal`. // No need for separate tests for `TimezoneOffsetColonZ`. // TimezoneOffsetPermissive - check!("1", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("123", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("1234", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12345", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("123456", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("1234567", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12345678", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+1", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+12", [internal_fixed(TimezoneOffsetPermissive)]; offset: 43_200); - check!("+123", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+1234", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("-1234", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); - check!("βˆ’1234", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+123456", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+1234567", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12345678", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("1:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12:3", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12:34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:5", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("12:34:56", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+1:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+12:", [internal_fixed(TimezoneOffsetPermissive)]; offset: 43_200); - check!("+12:3", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("-12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); - check!("βˆ’12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12:34:", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:5", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:7", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:34:56:78", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 :34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 : 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 :34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 : 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 ::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: :34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:: 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12 ::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12: :34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:: 34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12:::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("+12::::34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("12:34 ", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!(" 12:34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34 ", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!(" +12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!(" -12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); - check!(" βˆ’12:34", [internal_fixed(TimezoneOffsetPermissive)]; offset: -45_240); // MINUS SIGN (U+2212) - check!("+12345", [internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)]; offset: 45_240, day: 5); - check!("+12:345", [internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)]; offset: 45_240, day: 5); - check!("+12:34:", [internal_fixed(TimezoneOffsetPermissive), Literal(":")]; offset: 45_240); - check!("🀠+12:34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34🀠", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("+12:🀠34", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+12:34🀠", [internal_fixed(TimezoneOffsetPermissive), Literal("🀠")]; offset: 45_240); - check!("🀠+12:34", [Literal("🀠"), internal_fixed(TimezoneOffsetPermissive)]; offset: 45_240); - check!("Z", [internal_fixed(TimezoneOffsetPermissive)]; offset: 0); - check!("A", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("PST", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("z", [internal_fixed(TimezoneOffsetPermissive)]; offset: 0); - check!(" Z", [internal_fixed(TimezoneOffsetPermissive)]; offset: 0); - check!(" z", [internal_fixed(TimezoneOffsetPermissive)]; offset: 0); - check!("Z ", [internal_fixed(TimezoneOffsetPermissive)]; TOO_LONG); - check!("#Z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!(":Z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!(":z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+Z", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("-Z", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+A", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+PST", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+πŸ™ƒ", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("+Z:", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!(" :Z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!(" +Z", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); - check!(" -Z", [internal_fixed(TimezoneOffsetPermissive)]; TOO_SHORT); - check!("+:Z", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); - check!("Y", [internal_fixed(TimezoneOffsetPermissive)]; INVALID); + check("1", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("123", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("1234", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12345", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("123456", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("1234567", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12345678", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+1", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+12", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 43_200)); + check("+123", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+1234", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("-1234", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); + check("βˆ’1234", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12345", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+123456", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+1234567", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12345678", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("1:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:3", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:34", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:34:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:34:5", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("12:34:56", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+1:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+12:", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 43_200)); + check("+12:3", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("-12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); + check("βˆ’12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check("+12:34:", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:5", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:56", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:56:", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:56:7", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:34:56:78", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 :34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12: 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 : 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 :34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12: 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 : 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 ::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12: :34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12:: 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12 ::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12: :34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12:: 34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12:::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("+12::::34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check("12:34 ", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(" 12:34", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+12:34 ", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check(" +12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 45_240)); + check(" -12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); + check(" βˆ’12:34", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: -45_240)); // MINUS SIGN (U+2212) + check( + "+12345", + &[internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:345", + &[internal_fixed(TimezoneOffsetPermissive), num(Numeric::Day)], + parsed!(offset: 45_240, day: 5), + ); + check( + "+12:34:", + &[internal_fixed(TimezoneOffsetPermissive), Literal(":")], + parsed!(offset: 45_240), + ); + check("🀠+12:34", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+12:34🀠", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("+12:🀠34", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check( + "+12:34🀠", + &[internal_fixed(TimezoneOffsetPermissive), Literal("🀠")], + parsed!(offset: 45_240), + ); + check( + "🀠+12:34", + &[Literal("🀠"), internal_fixed(TimezoneOffsetPermissive)], + parsed!(offset: 45_240), + ); + check("Z", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); + check("A", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("PST", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("z", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); + check(" Z", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); + check(" z", &[internal_fixed(TimezoneOffsetPermissive)], parsed!(offset: 0)); + check("Z ", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_LONG)); + check("#Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(":Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(":z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("-Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+A", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+PST", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+πŸ™ƒ", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("+Z:", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(" :Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check(" +Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check(" -Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(TOO_SHORT)); + check("+:Z", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); + check("Y", &[internal_fixed(TimezoneOffsetPermissive)], Err(INVALID)); // TimezoneName - check!("CEST", [fixed(TimezoneName)]; ); - check!("cest", [fixed(TimezoneName)]; ); // lowercase - check!("XXXXXXXX", [fixed(TimezoneName)]; ); // not a real timezone name - check!("!!!!", [fixed(TimezoneName)]; ); // not a real timezone name! - check!("CEST 5", [fixed(TimezoneName), Literal(" "), num(Numeric::Day)]; day: 5); - check!("CEST ", [fixed(TimezoneName)]; TOO_LONG); - check!(" CEST", [fixed(TimezoneName)]; TOO_LONG); - check!("CE ST", [fixed(TimezoneName)]; TOO_LONG); + check("CEST", &[fixed(TimezoneName)], parsed!()); + check("cest", &[fixed(TimezoneName)], parsed!()); // lowercase + check("XXXXXXXX", &[fixed(TimezoneName)], parsed!()); // not a real timezone name + check("!!!!", &[fixed(TimezoneName)], parsed!()); // not a real timezone name! + check("CEST 5", &[fixed(TimezoneName), Literal(" "), num(Numeric::Day)], parsed!(day: 5)); + check("CEST ", &[fixed(TimezoneName)], Err(TOO_LONG)); + check(" CEST", &[fixed(TimezoneName)], Err(TOO_LONG)); + check("CE ST", &[fixed(TimezoneName)], Err(TOO_LONG)); } #[test] + #[rustfmt::skip] fn test_parse_practical_examples() { use crate::format::InternalInternal::*; use crate::format::Item::{Literal, Space}; use crate::format::Numeric::*; // some practical examples - check!("2015-02-04T14:37:05+09:00", - [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), - num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), fixed(Fixed::TimezoneOffset)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, offset: 32400); - check!("2015-02-04T14:37:05-09:00", - [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), - num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), fixed(Fixed::TimezoneOffset)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, offset: -32400); - check!("2015-02-04T14:37:05βˆ’09:00", // timezone offset using MINUS SIGN (U+2212) - [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), - num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), fixed(Fixed::TimezoneOffset)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, offset: -32400); - check!("20150204143705567", - [num(Year), num(Month), num(Day), - num(Hour), num(Minute), num(Second), internal_fixed(Nanosecond3NoDot)]; - year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, - minute: 37, second: 5, nanosecond: 567000000); - check!("Mon, 10 Jun 2013 09:32:37 GMT", - [fixed(Fixed::ShortWeekdayName), Literal(","), Space(" "), num(Day), Space(" "), - fixed(Fixed::ShortMonthName), Space(" "), num(Year), Space(" "), num(Hour), Literal(":"), - num(Minute), Literal(":"), num(Second), Space(" "), Literal("GMT")]; - year: 2013, month: 6, day: 10, weekday: Weekday::Mon, - hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); - check!("🀠Mon, 10 Jun🀠2013 09:32:37 GMT🀠", - [Literal("🀠"), fixed(Fixed::ShortWeekdayName), Literal(","), Space(" "), num(Day), Space(" "), - fixed(Fixed::ShortMonthName), Literal("🀠"), num(Year), Space(" "), num(Hour), Literal(":"), - num(Minute), Literal(":"), num(Second), Space(" "), Literal("GMT"), Literal("🀠")]; - year: 2013, month: 6, day: 10, weekday: Weekday::Mon, - hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37); - check!("Sun Aug 02 13:39:15 CEST 2020", - [fixed(Fixed::ShortWeekdayName), Space(" "), fixed(Fixed::ShortMonthName), Space(" "), - num(Day), Space(" "), num(Hour), Literal(":"), num(Minute), Literal(":"), - num(Second), Space(" "), fixed(Fixed::TimezoneName), Space(" "), num(Year)]; + check( + "2015-02-04T14:37:05+09:00", + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + fixed(Fixed::TimezoneOffset), + ], + parsed!( + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, + second: 5, offset: 32400 + ), + ); + check( + "2015-02-04T14:37:05-09:00", + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + fixed(Fixed::TimezoneOffset), + ], + parsed!( + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, + second: 5, offset: -32400 + ), + ); + check( + "2015-02-04T14:37:05βˆ’09:00", // timezone offset using MINUS SIGN (U+2212) + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + fixed(Fixed::TimezoneOffset) + ], + parsed!( + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, + second: 5, offset: -32400 + ), + ); + check( + "20150204143705567", + &[ + num(Year), num(Month), num(Day), num(Hour), num(Minute), num(Second), + internal_fixed(Nanosecond3NoDot) + ], + parsed!( + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, + second: 5, nanosecond: 567000000 + ), + ); + check( + "Mon, 10 Jun 2013 09:32:37 GMT", + &[ + fixed(Fixed::ShortWeekdayName), Literal(","), Space(" "), num(Day), Space(" "), + fixed(Fixed::ShortMonthName), Space(" "), num(Year), Space(" "), num(Hour), + Literal(":"), num(Minute), Literal(":"), num(Second), Space(" "), Literal("GMT") + ], + parsed!( + year: 2013, month: 6, day: 10, weekday: Weekday::Mon, + hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37 + ), + ); + check( + "🀠Mon, 10 Jun🀠2013 09:32:37 GMT🀠", + &[ + Literal("🀠"), fixed(Fixed::ShortWeekdayName), Literal(","), Space(" "), num(Day), + Space(" "), fixed(Fixed::ShortMonthName), Literal("🀠"), num(Year), Space(" "), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), Space(" "), + Literal("GMT"), Literal("🀠") + ], + parsed!( + year: 2013, month: 6, day: 10, weekday: Weekday::Mon, + hour_div_12: 0, hour_mod_12: 9, minute: 32, second: 37 + ), + ); + check( + "Sun Aug 02 13:39:15 CEST 2020", + &[ + fixed(Fixed::ShortWeekdayName), Space(" "), fixed(Fixed::ShortMonthName), + Space(" "), num(Day), Space(" "), num(Hour), Literal(":"), num(Minute), + Literal(":"), num(Second), Space(" "), fixed(Fixed::TimezoneName), Space(" "), + num(Year) + ], + parsed!( year: 2020, month: 8, day: 2, weekday: Weekday::Sun, - hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15); - check!("20060102150405", - [num(Year), num(Month), num(Day), num(Hour), num(Minute), num(Second)]; - year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5); - check!("3:14PM", - [num(Hour12), Literal(":"), num(Minute), fixed(Fixed::LowerAmPm)]; - hour_div_12: 1, hour_mod_12: 3, minute: 14); - check!("12345678901234.56789", - [num(Timestamp), Literal("."), num(Nanosecond)]; - nanosecond: 56_789, timestamp: 12_345_678_901_234); - check!("12345678901234.56789", - [num(Timestamp), fixed(Fixed::Nanosecond)]; - nanosecond: 567_890_000, timestamp: 12_345_678_901_234); + hour_div_12: 1, hour_mod_12: 1, minute: 39, second: 15 + ), + ); + check( + "20060102150405", + &[num(Year), num(Month), num(Day), num(Hour), num(Minute), num(Second)], + parsed!( + year: 2006, month: 1, day: 2, hour_div_12: 1, hour_mod_12: 3, minute: 4, second: 5 + ), + ); + check( + "3:14PM", + &[num(Hour12), Literal(":"), num(Minute), fixed(Fixed::LowerAmPm)], + parsed!(hour_div_12: 1, hour_mod_12: 3, minute: 14), + ); + check( + "12345678901234.56789", + &[num(Timestamp), Literal("."), num(Nanosecond)], + parsed!(nanosecond: 56_789, timestamp: 12_345_678_901_234), + ); + check( + "12345678901234.56789", + &[num(Timestamp), fixed(Fixed::Nanosecond)], + parsed!(nanosecond: 567_890_000, timestamp: 12_345_678_901_234), + ); // docstring examples from `impl str::FromStr` - check!("2000-01-02T03:04:05Z", - [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), - num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), - internal_fixed(TimezoneOffsetPermissive)]; - year: 2000, month: 1, day: 2, - hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, - offset: 0); - check!("2000-01-02 03:04:05Z", - [num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Space(" "), - num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), - internal_fixed(TimezoneOffsetPermissive)]; - year: 2000, month: 1, day: 2, - hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, - offset: 0); + check( + "2000-01-02T03:04:05Z", + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Literal("T"), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + internal_fixed(TimezoneOffsetPermissive) + ], + parsed!( + year: 2000, month: 1, day: 2, hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, + offset: 0 + ), + ); + check( + "2000-01-02 03:04:05Z", + &[ + num(Year), Literal("-"), num(Month), Literal("-"), num(Day), Space(" "), + num(Hour), Literal(":"), num(Minute), Literal(":"), num(Second), + internal_fixed(TimezoneOffsetPermissive) + ], + parsed!( + year: 2000, month: 1, day: 2, hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, + offset: 0 + ), + ); + } + + #[track_caller] + fn parses(s: &str, items: &[Item]) { + let mut parsed = Parsed::new(); + assert!(parse(&mut parsed, s, items.iter()).is_ok()); + } + + #[track_caller] + fn check(s: &str, items: &[Item], expected: ParseResult) { + let mut parsed = Parsed::new(); + let result = parse(&mut parsed, s, items.iter()); + let parsed = result.map(|_| parsed); + assert_eq!(parsed, expected); } #[test] From 677dbbeb3ee229ab25428e813cd519ec99bc51a0 Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Sun, 28 May 2023 17:24:25 -0700 Subject: [PATCH 57/68] Revert changes to `TimeZoneName::new` parser --- src/offset/local/tz_info/timezone.rs | 115 ++++++--------------------- 1 file changed, 26 insertions(+), 89 deletions(-) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index ac7c3f2616..d2de060237 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -486,49 +486,29 @@ struct TimeZoneName { impl TimeZoneName { /// Construct a time zone name - /// - /// Note: Converts `βˆ’` MINUS SIGN (U+2212) to `-` HYPHEN-MINUS (U+2D). - /// Multi-byte MINUS SIGN is allowed in [ISO 8601 / RFC 3339]. But - /// working with single-byte HYPHEN-MINUS is easier and more common. - /// - /// [ISO 8601 / RFC 3339]: https://en.wikipedia.org/w/index.php?title=ISO_8601&oldid=1114309368#Time_offsets_from_UTC fn new(input: &[u8]) -> Result { - let s = match str::from_utf8(input) { - Ok(s) => s, - Err(_err) => return Err(Error::LocalTimeType("invalid UTF-8")), - }; + let len = input.len(); - if !(3..=7).contains(&s.chars().count()) { + if !(3..=7).contains(&len) { return Err(Error::LocalTimeType( "time zone name must have between 3 and 7 characters", )); } let mut bytes = [0; 8]; - let mut copied = 0; - for (i, c) in s.chars().enumerate() { - match c { - '0'..='9' | 'A'..='Z' | 'a'..='z' - // ISO 8601 / RFC 3339 proscribes use of `+` PLUS SIGN (U+2B) - // in timezone - | '+' - // ISO 8601 / RFC 3339 allows use of `-` HYPHEN-MINUS (U+2D) - // in timezone - | '-' => { - bytes[i + 1] = c as u8; - } - // ISO 8601 / RFC 3339 recommends the use of - // `βˆ’` MINUS SIGN (U+2212) in timezone. - // But replace with single-byte `-` HYPHEN-MINUS (U+2D) for - // easier byte <-> char conversions later on. - | 'βˆ’' => { - bytes[i + 1] = b'-'; - } + bytes[0] = input.len() as u8; + + let mut i = 0; + while i < len { + let b = input[i]; + match b { + b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'+' | b'-' => {} _ => return Err(Error::LocalTimeType("invalid characters in time zone name")), } - copied += 1; + + bytes[i + 1] = b; + i += 1; } - bytes[0] = copied as u8; Ok(Self { bytes }) } @@ -761,63 +741,20 @@ mod tests { } #[test] - fn test_timezonename_new() -> Result<(), Error> { - // expect Error::LocalTimeType() - const INPUT_ERR: &[&str] = &[ - "", - "1", - "+", - "-", - "βˆ’", // MINUS SIGN (U+2212) - "12", - "--", - "βˆ’βˆ’", // MINUS SIGN (U+2212) - "AB", - "ab", - "12345678", - "ABCDEFGH", - "123456789", - "1234567890", - "--------", - "123\0\0\0", - "\0\0\0", - "\x00123", - "123\0", - ]; - for input_ in INPUT_ERR.iter() { - eprintln!("TimeZoneName::new({:?}) (expect Error::LocalTimeType)", input_); - let input_ = input_.as_bytes(); - let err = TimeZoneName::new(input_); - eprintln!("err = {:?}", err); - assert!(matches!(err, Err(Error::LocalTimeType(_)))); - } - // expect Ok - const INPUT_OK_EXPECT: &[(&str, &str)] = &[ - ("123", "123"), - ("abc", "abc"), - ("ABC", "ABC"), - ("1234", "1234"), - ("12345", "12345"), - ("123456", "123456"), - ("1234567", "1234567"), - ("+1234", "+1234"), - ("+1234", "+1234"), - ("-1234", "-1234"), - ("βˆ’1234", "-1234"), // MINUS SIGN (U+2212) to HYPHEN-MINUS (U+002D) - // Ok nonsense - ("+++", "+++"), - ("-----", "-----"), - ("βˆ’βˆ’βˆ’", "---"), // MINUS SIGN (U+2212) to HYPHEN-MINUS (U+002D) - ("βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’", "-------"), // MINUS SIGN (U+2212) to HYPHEN-MINUS (U+002D) - ]; - for (input_, expect) in INPUT_OK_EXPECT.iter() { - eprintln!("TimeZoneName::new({:?})", input_); - let output = TimeZoneName::new(input_.as_bytes()); - match output { - Ok(output) => assert_eq!(output.as_bytes(), expect.as_bytes()), - Err(error) => panic!("Failed: input {:?}, error {}", input_, error), - } - } + fn test_tz_ascii_str() -> Result<(), Error> { + assert!(matches!(TimeZoneName::new(b""), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"1"), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"12"), Err(Error::LocalTimeType(_)))); + assert_eq!(TimeZoneName::new(b"123")?.as_bytes(), b"123"); + assert_eq!(TimeZoneName::new(b"1234")?.as_bytes(), b"1234"); + assert_eq!(TimeZoneName::new(b"12345")?.as_bytes(), b"12345"); + assert_eq!(TimeZoneName::new(b"123456")?.as_bytes(), b"123456"); + assert_eq!(TimeZoneName::new(b"1234567")?.as_bytes(), b"1234567"); + assert!(matches!(TimeZoneName::new(b"12345678"), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"123456789"), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"1234567890"), Err(Error::LocalTimeType(_)))); + + assert!(matches!(TimeZoneName::new(b"123\0\0\0"), Err(Error::LocalTimeType(_)))); Ok(()) } From 8599d7c5ce4f3758bd47eacc279f32fc48a671ce Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 3 Jul 2023 19:07:33 +0200 Subject: [PATCH 58/68] Add quote from man page --- src/offset/local/tz_info/timezone.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index d2de060237..43d7f42da6 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -486,6 +486,11 @@ struct TimeZoneName { impl TimeZoneName { /// Construct a time zone name + /// + /// man tzfile(5): + /// Time zone designations should consist of at least three (3) and no more than six (6) ASCII + /// characters from the set of alphanumerics, β€œ-”, and β€œ+”. This is for compatibility with + /// POSIX requirements for time zone abbreviations. fn new(input: &[u8]) -> Result { let len = input.len(); From 56c696766b6704ada56cc4ab763746b44a2d0bfa Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Tue, 4 Jul 2023 12:25:45 +0200 Subject: [PATCH 59/68] Test some realistic values in `test_tz_ascii_str` --- src/offset/local/tz_info/timezone.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/offset/local/tz_info/timezone.rs b/src/offset/local/tz_info/timezone.rs index 43d7f42da6..36b2808105 100644 --- a/src/offset/local/tz_info/timezone.rs +++ b/src/offset/local/tz_info/timezone.rs @@ -748,18 +748,17 @@ mod tests { #[test] fn test_tz_ascii_str() -> Result<(), Error> { assert!(matches!(TimeZoneName::new(b""), Err(Error::LocalTimeType(_)))); - assert!(matches!(TimeZoneName::new(b"1"), Err(Error::LocalTimeType(_)))); - assert!(matches!(TimeZoneName::new(b"12"), Err(Error::LocalTimeType(_)))); - assert_eq!(TimeZoneName::new(b"123")?.as_bytes(), b"123"); - assert_eq!(TimeZoneName::new(b"1234")?.as_bytes(), b"1234"); - assert_eq!(TimeZoneName::new(b"12345")?.as_bytes(), b"12345"); - assert_eq!(TimeZoneName::new(b"123456")?.as_bytes(), b"123456"); - assert_eq!(TimeZoneName::new(b"1234567")?.as_bytes(), b"1234567"); + assert!(matches!(TimeZoneName::new(b"A"), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"AB"), Err(Error::LocalTimeType(_)))); + assert_eq!(TimeZoneName::new(b"CET")?.as_bytes(), b"CET"); + assert_eq!(TimeZoneName::new(b"CHADT")?.as_bytes(), b"CHADT"); + assert_eq!(TimeZoneName::new(b"abcdefg")?.as_bytes(), b"abcdefg"); + assert_eq!(TimeZoneName::new(b"UTC+02")?.as_bytes(), b"UTC+02"); + assert_eq!(TimeZoneName::new(b"-1230")?.as_bytes(), b"-1230"); + assert!(matches!(TimeZoneName::new("βˆ’0330".as_bytes()), Err(Error::LocalTimeType(_)))); // MINUS SIGN (U+2212) + assert!(matches!(TimeZoneName::new(b"\x00123"), Err(Error::LocalTimeType(_)))); assert!(matches!(TimeZoneName::new(b"12345678"), Err(Error::LocalTimeType(_)))); - assert!(matches!(TimeZoneName::new(b"123456789"), Err(Error::LocalTimeType(_)))); - assert!(matches!(TimeZoneName::new(b"1234567890"), Err(Error::LocalTimeType(_)))); - - assert!(matches!(TimeZoneName::new(b"123\0\0\0"), Err(Error::LocalTimeType(_)))); + assert!(matches!(TimeZoneName::new(b"GMT\0\0\0"), Err(Error::LocalTimeType(_)))); Ok(()) } From e5f322e26ba74aaa10ab711a222b23322c3d3bd6 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 15 Jul 2023 07:59:08 +0200 Subject: [PATCH 60/68] Remove `#![deny(dead_code)]` --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2eb1a010dc..6d02e6302d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -387,7 +387,6 @@ #![deny(missing_docs)] #![deny(missing_debug_implementations)] #![warn(unreachable_pub)] -#![deny(dead_code)] #![deny(clippy::tests_outside_test_module)] #![cfg_attr(not(any(feature = "std", test)), no_std)] // can remove this if/when rustc-serialize support is removed From 4c18df43aa6e182bed67c4967651738c30bef374 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 15 Jul 2023 07:42:42 +0200 Subject: [PATCH 61/68] Remove needless `any` in feature gate --- src/round.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/round.rs b/src/round.rs index b6e771e0f0..69ecef9e8b 100644 --- a/src/round.rs +++ b/src/round.rs @@ -100,11 +100,11 @@ const fn span_for_digits(digits: u16) -> u32 { /// will also fail if the `Duration` is bigger than the timestamp. pub trait DurationRound: Sized { /// Error that can occur in rounding or truncating - #[cfg(any(feature = "std"))] + #[cfg(feature = "std")] type Err: std::error::Error; /// Error that can occur in rounding or truncating - #[cfg(not(any(feature = "std")))] + #[cfg(not(feature = "std"))] type Err: fmt::Debug + fmt::Display; /// Return a copy rounded by Duration. @@ -299,7 +299,7 @@ impl fmt::Display for RoundingError { } } -#[cfg(any(feature = "std"))] +#[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for RoundingError { #[allow(deprecated)] From c50e5857dac93493d9606300d2a7c5a353c7b3be Mon Sep 17 00:00:00 2001 From: DarkWanderer Date: Wed, 14 Dec 2022 18:20:30 +0100 Subject: [PATCH 62/68] Adding automatic dependency management --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..5cd6ac397c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: cargo + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" From f8029b5f358e4f46e9e96c48d3641e4dfeaa7997 Mon Sep 17 00:00:00 2001 From: "Oleg V. Kozlyuk" Date: Thu, 15 Dec 2022 22:57:58 +0300 Subject: [PATCH 63/68] Update per review notes --- .github/dependabot.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5cd6ac397c..55047562e7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,8 +3,12 @@ updates: - package-ecosystem: cargo directory: "/" schedule: - interval: "daily" + interval: "weekly" + - package-ecosystem: cargo + directory: "/fuzz/" + schedule: + interval: "weekly" - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "weekly" From f4b3ebad63467246c4f74834e1c1f036249d3254 Mon Sep 17 00:00:00 2001 From: LingMan Date: Tue, 14 Feb 2023 14:33:11 +0100 Subject: [PATCH 64/68] Wrap all Dependabot ecosystem names in quotes No functional change but makes this file self-consistent and consistent with the Dependabot docs. --- .github/dependabot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 55047562e7..0f67083a2f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,10 +1,10 @@ version: 2 updates: - - package-ecosystem: cargo + - package-ecosystem: "cargo" directory: "/" schedule: interval: "weekly" - - package-ecosystem: cargo + - package-ecosystem: "cargo" directory: "/fuzz/" schedule: interval: "weekly" From 13e7924767b41fca3c00be49d931bc469d58f102 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 29 Aug 2022 10:54:27 +0200 Subject: [PATCH 65/68] Add branch policy explanation to PR template --- .github/pull_request_template.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ba92e741d0..950451f3e0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,3 +1,6 @@ ### Thanks for contributing to chrono! +If your feature is semver-compatible, please target the 0.4.x branch; +the main branch will be used for 0.5.0 development going forward. + Please consider adding a test to ensure your bug fix/feature will not break in the future. From c6a75156ba0666b18f02dd6719bee3d499328b5a Mon Sep 17 00:00:00 2001 From: jtmoon79 <815261+jtmoon79@users.noreply.github.com> Date: Thu, 8 Sep 2022 16:25:18 -0700 Subject: [PATCH 66/68] Add various tests for current parsing Add more varying testing for most parsing functions. Tests emphasize whitespace, literals, timezones, and timezone delimiters (colons and whitespace). Add tests for multiple-byte characters and combining characters in and around data and parsing formats. These tests are added to aid humans verifying the next commit that changes parsing behavior. Issue #660 --- src/datetime/mod.rs | 5 + src/datetime/tests.rs | 898 +++++++++++++++++++++++++++++++++++- src/format/strftime.rs | 107 +++++ src/naive/date.rs | 33 +- src/naive/datetime/tests.rs | 45 +- src/naive/time/tests.rs | 70 ++- 6 files changed, 1128 insertions(+), 30 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 8686340e16..24f67298cf 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -700,6 +700,9 @@ impl DateTime { /// RFC 2822 is the internet message standard that specifies the representation of times in HTTP /// and email headers. /// + /// The RFC 2822 standard allows arbitrary intermixed whitespace. + /// See [RFC 2822 Appendix A.5] + /// /// ``` /// # use chrono::{DateTime, FixedOffset, TimeZone}; /// assert_eq!( @@ -707,6 +710,8 @@ impl DateTime { /// FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap() /// ); /// ``` + /// + /// [RFC 2822 Appendix A.5]: https://www.rfc-editor.org/rfc/rfc2822#appendix-A.5 pub fn parse_from_rfc2822(s: &str) -> ParseResult> { const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)]; let mut parsed = Parsed::new(); diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 2d273f605d..b8338267f2 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -357,8 +357,10 @@ fn test_datetime_with_timezone() { #[test] #[cfg(any(feature = "alloc", feature = "std"))] -fn test_datetime_rfc2822_and_rfc3339() { +fn test_datetime_rfc2822() { let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + + // timezone 0 assert_eq!( Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0000" @@ -367,6 +369,7 @@ fn test_datetime_rfc2822_and_rfc3339() { Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc3339(), "2015-02-18T23:16:09+00:00" ); + // timezone +05 assert_eq!( edt.from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) @@ -389,6 +392,7 @@ fn test_datetime_rfc2822_and_rfc3339() { .to_rfc3339(), "2015-02-18T23:16:09.150+05:00" ); + // seconds 60 assert_eq!( edt.from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) @@ -421,7 +425,131 @@ fn test_datetime_rfc2822_and_rfc3339() { Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( - DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), + edt.ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + .to_rfc2822(), + "Wed, 18 Feb 2015 23:59:60 +0500" + ); + assert_eq!( + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap()) + ); + + // many varying whitespace intermixed + assert_eq!( + DateTime::::parse_from_rfc2822( + "\t\t\tWed,\n\t\t18 \r\n\t\tFeb \u{3000} 2015\r\n\t\t\t23:59:60 \t+0500" + ), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap()) + ); + // example from RFC 2822 Appendix A.5. + assert_eq!( + DateTime::::parse_from_rfc2822( + "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330 (Newfoundland Time)" + ), + Ok(FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60) + .unwrap() + .ymd_opt(1969, 2, 13) + .unwrap() + .and_hms_opt(23, 32, 0) + .unwrap() + ) + ); + // example from RFC 2822 Appendix A.5. without trailing " (Newfoundland Time)" + assert_eq!( + DateTime::::parse_from_rfc2822( + "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330" + ), + Ok(FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60) + .unwrap() + .ymd_opt(1969, 2, 13) + .unwrap() + .and_hms_opt(23, 32, 0) + .unwrap()) + ); + + // bad year + assert!(DateTime::::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); + // wrong format + assert!( + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +00:00").is_err() + ); + // full name day of week + assert!(DateTime::::parse_from_rfc2822("Wednesday, 18 Feb 2015 23:16:09 +0000") + .is_err()); + // full name day of week + assert!(DateTime::::parse_from_rfc2822("Wednesday 18 Feb 2015 23:16:09 +0000") + .is_err()); + // wrong day of week separator '.' + assert!(DateTime::::parse_from_rfc2822("Wed. 18 Feb 2015 23:16:09 +0000").is_err()); + // *trailing* space causes failure + assert!( + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000 ").is_err() + ); +} + +#[test] +#[cfg(any(feature = "alloc", feature = "std"))] +fn test_datetime_rfc3339() { + let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + assert_eq!( + Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc3339(), + "2015-02-18T23:16:09+00:00" + ); + assert_eq!( + edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" + ); + assert_eq!( + edt.ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + .to_rfc3339(), + "2015-02-18T23:59:60.234567+05:00" + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123+05:00"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 123_000).unwrap()) + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123456+05:00"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 123_456).unwrap()) + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123456789+05:00"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_nano_opt(23, 59, 59, 123_456_789).unwrap()) + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:16:09Z"), + Ok(FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_opt(23, 16, 9) + .unwrap()) + ); + + assert_eq!( + edt.ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_micro_opt(23, 59, 59, 1_234_567) + .unwrap() + .to_rfc3339(), + "2015-02-18T23:59:60.234567+05:00" + ); + assert_eq!( + edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), + Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 1_234_567).unwrap()) + ); + assert_eq!( + DateTime::::parse_from_rfc3339("2015-02-18T23:16:09Z"), Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( @@ -447,6 +575,44 @@ fn test_datetime_rfc2822_and_rfc3339() { ) .unwrap()) ); + assert_eq!( + Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc3339(), + "2015-02-18T23:16:09+00:00" + ); + + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567 +05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:059:60.234567+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00PST").is_err() + ); + assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+PST").is_err()); + assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567PST").is_err()); + assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+0500").is_err()); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18 23:59:60.234567+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567:+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00 ").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339(" 2015-02-18T23:59:60.234567+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015- 02-18T23:59:60.234567+05:00").is_err() + ); + assert!( + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567A+05:00").is_err() + ); } #[test] @@ -590,7 +756,94 @@ fn test_datetime_from_str() { } #[test] -fn test_datetime_parse_from_str() { +fn test_parse_datetime_utc() { + // valid cases + let valid = [ + "2001-02-03T04:05:06Z", + "2001-02-03T04:05:06+0000", + "2001-02-03T04:05:06-00:00", + "2001-02-03T04:05:06-01:00", + "2012-12-12T12:12:12Z", + "2012 -12-12T12:12:12Z", + "2012 -12-12T12:12:12Z", + "2012- 12-12T12:12:12Z", + "2012- 12-12T12:12:12Z", + "2012-12-12T 12:12:12Z", + "2012-12-12T12 :12:12Z", + "2012-12-12T12 :12:12Z", + "2012-12-12T12: 12:12Z", + "2012-12-12T12: 12:12Z", + "2012-12-12T12 : 12:12Z", + "2012-12-12T12:12:12Z ", + " 2012-12-12T12:12:12Z", + "2015-02-18T23:16:09.153Z", + "2015-2-18T23:16:09.153Z", + "+2015-2-18T23:16:09.153Z", + "-77-02-18T23:16:09Z", + "+82701-05-6T15:9:60.898989898989Z", + ]; + for &s in &valid { + eprintln!("test_parse_datetime_utc valid {:?}", s); + let d = match s.parse::>() { + Ok(d) => d, + Err(e) => panic!("parsing `{}` has failed: {}", s, e), + }; + let s_ = format!("{:?}", d); + // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same + let d_ = match s_.parse::>() { + Ok(d) => d, + Err(e) => { + panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) + } + }; + assert!( + d == d_, + "`{}` is parsed into `{:?}`, but reparsed result \ + `{:?}` does not match", + s, + d, + d_ + ); + } + + // some invalid cases + // since `ParseErrorKind` is private, all we can do is to check if there was an error + let invalid = [ + "", // empty + "Z", // missing data + "15Z", // missing data + "15:8:9Z", // missing date + "15-8-9Z", // missing time or date + "Fri, 09 Aug 2013 23:54:35 GMT", // valid datetime, wrong format + "Sat Jun 30 23:59:60 2012", // valid datetime, wrong format + "1441497364.649", // valid datetime, wrong format + "+1441497364.649", // valid datetime, wrong format + "+1441497364", // valid datetime, wrong format + "+1441497364Z", // valid datetime, wrong format + "2014/02/03 04:05:06Z", // valid datetime, wrong format + "2001-02-03T04:05:0600:00", // valid datetime, timezone too close + "2015-15-15T15:15:15Z", // invalid datetime + "2012-12-12T12:12:12x", // invalid timezone + "2012-123-12T12:12:12Z", // invalid month + "2012-12-77T12:12:12Z", // invalid day + "2012-12-12T26:12:12Z", // invalid hour + "2012-12-12T12:61:12Z", // invalid minute + "2012-12-12T12:12:62Z", // invalid second + "2012-12-12 T12:12:12Z", // space after date + "2012-12-12t12:12:12Z", // wrong divider 't' + "2012-12-12T12:12:12ZZ", // trailing literal 'Z' + "+802701-12-12T12:12:12Z", // invalid year (out of bounds) + "+ 2012-12-12T12:12:12Z", // invalid space before year + " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 Z", // valid datetime, wrong format + ]; + for &s in &invalid { + eprintln!("test_parse_datetime_utc invalid {:?}", s); + assert!(s.parse::>().is_err()); + } +} + +#[test] +fn test_utc_datetime_from_str() { let ymdhms = |y, m, d, h, n, s, off| { FixedOffset::east_opt(off).unwrap().with_ymd_and_hms(y, m, d, h, n, s).unwrap() }; @@ -605,6 +858,645 @@ fn test_datetime_parse_from_str() { Utc.datetime_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), Ok(Utc.with_ymd_and_hms(2013, 8, 9, 23, 54, 35).unwrap()) ); + + assert_eq!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15Z".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15 UTC".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + assert_eq!( + "2015-02-18T23:16:9.15UTC".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(FixedOffset::east_opt(0) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap()) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(FixedOffset::west_opt(10 * 3600) + .unwrap() + .ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(13, 16, 9, 150) + .unwrap()) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + + assert_eq!( + "2015-2-18T23:16:9.15Z".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + assert_eq!( + "2015-2-18T13:16:9.15-10:00".parse::>(), + Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + ); + assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + + // no test for `DateTime`, we cannot verify that much. +} + +#[test] +fn test_utc_datetime_from_str_with_spaces() { + let dt = Utc.ymd_opt(2013, 8, 9).unwrap().and_hms_opt(23, 54, 35).unwrap(); + // with varying spaces - should succeed + assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt),); + assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35 ", " %b %d %Y %H:%M:%S "), Ok(dt),); + assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!( + Utc.datetime_from_str("\n\tAug 09 2013 23:54:35 ", "\n\t%b %d %Y %H:%M:%S "), + Ok(dt), + ); + assert_eq!(Utc.datetime_from_str("\tAug 09 2013 23:54:35\t", "\t%b %d %Y %H:%M:%S\t"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013\t23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013\t\t23:54:35", "%b %d %Y\t\t%H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S "), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); + assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n"), Ok(dt),); + // with varying spaces - should fail + // leading space in data + assert!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err()); + // trailing space in data + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S").is_err()); + // trailing tab in data + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35\t", "%b %d %Y %H:%M:%S").is_err()); + // mismatched newlines + assert!(Utc.datetime_from_str("\nAug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err()); + // trailing literal in data + assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 !!!", "%b %d %Y %H:%M:%S ").is_err()); +} + +#[test] +fn test_datetime_parse_from_str() { + let dt = FixedOffset::east_opt(-9 * 60 * 60) + .unwrap() + .ymd_opt(2013, 8, 9) + .unwrap() + .and_hms_opt(23, 54, 35) + .unwrap(); + + // timezone variations + + // + // %Z + // + // wrong timezone format + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %Z" + ) + .is_err()); + // bad timezone data? + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 PST", + "%b %d %Y %H:%M:%S %Z" + ) + .is_err()); + // bad timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 XXXXX", + "%b %d %Y %H:%M:%S %Z" + ) + .is_err()); + + // + // %z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 00", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 --0900", + "%b %d %Y %H:%M:%S -%z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 +-0900", + "%b %d %Y %H:%M:%S +%z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00 ", + "%b %d %Y %H:%M:%S %z " + ), + Ok(dt), + ); + // trailing newline after timezone + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00\n", + "%b %d %Y %H:%M:%S %z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00\n", + "%b %d %Y %H:%M:%S %z " + ), + Ok(dt), + ); + // trailing colon + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:", + "%b %d %Y %H:%M:%S %z" + ) + .is_err()); + // trailing colon with space + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00: ", + "%b %d %Y %H:%M:%S %z " + ) + .is_err()); + // trailing colon, mismatch space + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:", + "%b %d %Y %H:%M:%S %z " + ) + .is_err()); + // wrong timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09::00", + "%b %d %Y %H:%M:%S %z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900::", + "%b %d %Y %H:%M:%S %z::" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %z:00" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00 ", + "%b %d %Y %H:%M:%S %z:00 " + ), + Ok(dt), + ); + + // + // %:z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 00", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00:", + "%b %d %Y %H:%M:%S %:z:" + ), + Ok(dt), + ); + // wrong timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %:z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09::00", + "%b %d %Y %H:%M:%S %:z" + ), + Ok(dt), + ); + // timezone data hs too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:", + "%b %d %Y %H:%M:%S %:z" + ) + .is_err()); + // timezone data hs too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00::", + "%b %d %Y %H:%M:%S %:z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00::", + "%b %d %Y %H:%M:%S %:z::" + ), + Ok(dt), + ); + + // + // %:::z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %::z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %::z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09 : 00", + "%b %d %Y %H:%M:%S %::z" + ), + Ok(dt), + ); + // mismatching colon expectations + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %::z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09::00", + "%b %d %Y %H:%M:%S %::z" + ), + Ok(dt), + ); + // wrong timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %::z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09001234", + "%b %d %Y %H:%M:%S %::z1234" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:001234", + "%b %d %Y %H:%M:%S %::z1234" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900 ", + "%b %d %Y %H:%M:%S %::z " + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900\t\n", + "%b %d %Y %H:%M:%S %::z\t\n" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900:", + "%b %d %Y %H:%M:%S %::z:" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 :-0900:0", + "%b %d %Y %H:%M:%S :%::z:0" + ), + Ok(dt), + ); + // mismatching colons and spaces + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 :-0900: ", + "%b %d %Y %H:%M:%S :%::z::" + ) + .is_err()); + // mismatching colons expectations + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %::z" + ) + .is_err()); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -0900: 23:54:35", + "%b %d %Y %::z: %H:%M:%S" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 :-0900:0 23:54:35", + "%b %d %Y :%::z:0 %H:%M:%S" + ), + Ok(dt), + ); + // mismatching colons expectations mid-string + assert!(DateTime::::parse_from_str( + "Aug 09 2013 :-0900: 23:54:35", + "%b %d %Y :%::z %H:%M:%S" + ) + .is_err()); + // mismatching colons expectations, before end + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00 ", + "%b %d %Y %H:%M:%S %::z " + ) + .is_err()); + + // + // %:::z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %:::z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %:::z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900 ", + "%b %d %Y %H:%M:%S %:::z " + ), + Ok(dt), + ); + // wrong timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %:::z" + ) + .is_err()); + + // + // %::::z + // + // too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %::::z" + ) + .is_err()); + // too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %::::z" + ) + .is_err()); + // too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:", + "%b %d %Y %H:%M:%S %::::z" + ) + .is_err()); + // too many colons + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %::::z" + ) + .is_err()); + + // + // %#z + // + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00 ", + "%b %d %Y %H:%M:%S %#z " + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900 ", + "%b %d %Y %H:%M:%S %#z " + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -0900", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:", + "%b %d %Y %H:%M:%S %#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09: ", + "%b %d %Y %H:%M:%S %#z " + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35+-09", + "%b %d %Y %H:%M:%S+%#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 23:54:35--09", + "%b %d %Y %H:%M:%S-%#z" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -09:00 23:54:35", + "%b %d %Y %#z%H:%M:%S" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -0900 23:54:35", + "%b %d %Y %#z%H:%M:%S" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -090023:54:35", + "%b %d %Y %#z%H:%M:%S" + ), + Ok(dt), + ); + assert_eq!( + DateTime::::parse_from_str( + "Aug 09 2013 -09:0023:54:35", + "%b %d %Y %#z%H:%M:%S" + ), + Ok(dt), + ); + // timezone with partial minutes adjacent hours + assert_ne!( + DateTime::::parse_from_str("Aug 09 2013 -09023:54:35", "%b %d %Y %#z%H:%M:%S"), + Ok(dt), + ); + // bad timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -09:00:00", + "%b %d %Y %H:%M:%S %#z" + ) + .is_err()); + // bad timezone data (partial minutes) + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -090", + "%b %d %Y %H:%M:%S %#z" + ) + .is_err()); + // bad timezone data (partial minutes) with trailing space + assert!(DateTime::::parse_from_str( + "Aug 09 2013 23:54:35 -090 ", + "%b %d %Y %H:%M:%S %#z " + ) + .is_err()); + // bad timezone data (partial minutes) mid-string + assert!(DateTime::::parse_from_str( + "Aug 09 2013 -090 23:54:35", + "%b %d %Y %#z %H:%M:%S" + ) + .is_err()); + // bad timezone data + assert!(DateTime::::parse_from_str( + "Aug 09 2013 -09:00:00 23:54:35", + "%b %d %Y %#z %H:%M:%S" + ) + .is_err()); + // timezone data ambiguous with hours + assert!(DateTime::::parse_from_str( + "Aug 09 2013 -09:00:23:54:35", + "%b %d %Y %#z%H:%M:%S" + ) + .is_err()); assert_eq!( DateTime::parse_from_str("0", "%s").unwrap(), NaiveDateTime::from_timestamp_opt(0, 0).unwrap().and_utc().fixed_offset() diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 5ca7507043..50d4ac7cc3 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -508,12 +508,32 @@ mod tests { fn test_strftime_items() { fn parse_and_collect(s: &str) -> Vec> { // map any error into `[Item::Error]`. useful for easy testing. + eprintln!("test_strftime_items: parse_and_collect({:?})", s); let items = StrftimeItems::new(s); let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) }); items.collect::>>().unwrap_or_else(|| vec![Item::Error]) } assert_eq!(parse_and_collect(""), []); + assert_eq!(parse_and_collect(" "), [Space(" ")]); + assert_eq!(parse_and_collect(" "), [Space(" ")]); + // ne! + assert_ne!(parse_and_collect(" "), [Space(" "), Space(" ")]); + // eq! + assert_eq!(parse_and_collect(" "), [Space(" ")]); + assert_eq!(parse_and_collect("a"), [Literal("a")]); + assert_eq!(parse_and_collect("ab"), [Literal("ab")]); + assert_eq!(parse_and_collect("😽"), [Literal("😽")]); + assert_eq!(parse_and_collect("a😽"), [Literal("a😽")]); + assert_eq!(parse_and_collect("😽a"), [Literal("😽a")]); + assert_eq!(parse_and_collect(" 😽"), [Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect("😽 "), [Literal("😽"), Space(" ")]); + // ne! + assert_ne!(parse_and_collect("😽😽"), [Literal("😽")]); + assert_ne!(parse_and_collect("😽"), [Literal("😽😽")]); + assert_ne!(parse_and_collect("😽😽"), [Literal("😽😽"), Literal("😽")]); + // eq! + assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]); assert_eq!(parse_and_collect(" \t\n\r "), [Space(" \t\n\r ")]); assert_eq!(parse_and_collect("hello?"), [Literal("hello?")]); assert_eq!( @@ -530,12 +550,88 @@ mod tests { parse_and_collect("%Y-%m-%d"), [num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)] ); + assert_eq!(parse_and_collect("😽 "), [Literal("😽"), Space(" ")]); + assert_eq!(parse_and_collect("😽😽"), [Literal("😽😽")]); + assert_eq!(parse_and_collect("😽😽😽"), [Literal("😽😽😽")]); + assert_eq!(parse_and_collect("😽😽 😽"), [Literal("😽😽"), Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect("😽😽a 😽"), [Literal("😽😽a"), Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect("😽😽a b😽"), [Literal("😽😽a"), Space(" "), Literal("b😽")]); + assert_eq!( + parse_and_collect("😽😽a b😽c"), + [Literal("😽😽a"), Space(" "), Literal("b😽c")] + ); + assert_eq!(parse_and_collect("😽😽 "), [Literal("😽😽"), Space(" ")]); + assert_eq!(parse_and_collect("😽😽 😽"), [Literal("😽😽"), Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect(" 😽"), [Space(" "), Literal("😽")]); + assert_eq!(parse_and_collect(" 😽 "), [Space(" "), Literal("😽"), Space(" ")]); + assert_eq!( + parse_and_collect(" 😽 😽"), + [Space(" "), Literal("😽"), Space(" "), Literal("😽")] + ); + assert_eq!( + parse_and_collect(" 😽 😽 "), + [Space(" "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 😽 "), + [Space(" "), Literal("😽"), Space(" "), Literal("😽"), Space(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 😽😽 "), + [Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space(" ")] + ); + assert_eq!(parse_and_collect(" 😽😽"), [Space(" "), Literal("😽😽")]); + assert_eq!(parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")]); + assert_eq!( + parse_and_collect(" 😽😽 "), + [Space(" "), Literal("😽😽"), Space(" ")] + ); + assert_eq!( + parse_and_collect(" 😽😽 "), + [Space(" "), Literal("😽😽"), Space(" ")] + ); + assert_eq!(parse_and_collect(" 😽😽 "), [Space(" "), Literal("😽😽"), Space(" ")]); + assert_eq!( + parse_and_collect(" 😽 😽😽 "), + [Space(" "), Literal("😽"), Space(" "), Literal("😽😽"), Space(" ")] + ); + assert_eq!( + parse_and_collect(" 😽 πŸ˜½γ―γ„πŸ˜½ ハンバーガー"), + [ + Space(" "), + Literal("😽"), + Space(" "), + Literal("πŸ˜½γ―γ„πŸ˜½"), + Space(" "), + Literal("ハンバーガー") + ] + ); + assert_eq!( + parse_and_collect("%%😽%%😽"), + [Literal("%"), Literal("😽"), Literal("%"), Literal("😽")] + ); + assert_eq!(parse_and_collect("%Y--%m"), [num0(Year), Literal("--"), num0(Month)]); assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]")); + assert_eq!(parse_and_collect("100%%😽"), [Literal("100"), Literal("%"), Literal("😽")]); + assert_eq!( + parse_and_collect("100%%😽%%a"), + [Literal("100"), Literal("%"), Literal("😽"), Literal("%"), Literal("a")] + ); + assert_eq!(parse_and_collect("😽100%%"), [Literal("😽100"), Literal("%")]); assert_eq!(parse_and_collect("%m %d"), [num0(Month), Space(" "), num0(Day)]); assert_eq!(parse_and_collect("%"), [Item::Error]); assert_eq!(parse_and_collect("%%"), [Literal("%")]); assert_eq!(parse_and_collect("%%%"), [Item::Error]); + assert_eq!(parse_and_collect("%a"), [fixed(Fixed::ShortWeekdayName)]); + assert_eq!(parse_and_collect("%aa"), [fixed(Fixed::ShortWeekdayName), Literal("a")]); + assert_eq!(parse_and_collect("%%a%"), [Item::Error]); + assert_eq!(parse_and_collect("%😽"), [Item::Error]); + assert_eq!(parse_and_collect("%😽😽"), [Item::Error]); assert_eq!(parse_and_collect("%%%%"), [Literal("%"), Literal("%")]); + assert_eq!( + parse_and_collect("%%%%ハンバーガー"), + [Literal("%"), Literal("%"), Literal("ハンバーガー")] + ); assert_eq!(parse_and_collect("foo%?"), [Item::Error]); assert_eq!(parse_and_collect("bar%42"), [Item::Error]); assert_eq!(parse_and_collect("quux% +"), [Item::Error]); @@ -555,6 +651,10 @@ mod tests { assert_eq!(parse_and_collect("%0e"), [num0(Day)]); assert_eq!(parse_and_collect("%_e"), [nums(Day)]); assert_eq!(parse_and_collect("%z"), [fixed(Fixed::TimezoneOffset)]); + assert_eq!(parse_and_collect("%:z"), [fixed(Fixed::TimezoneOffsetColon)]); + assert_eq!(parse_and_collect("%Z"), [fixed(Fixed::TimezoneName)]); + assert_eq!(parse_and_collect("%ZZZZ"), [fixed(Fixed::TimezoneName), Literal("ZZZ")]); + assert_eq!(parse_and_collect("%Z😽"), [fixed(Fixed::TimezoneName), Literal("😽")]); assert_eq!( parse_and_collect("%#z"), [internal_fixed(InternalInternal::TimezoneOffsetPermissive)] @@ -664,6 +764,13 @@ mod tests { assert_eq!(dt.format("%t").to_string(), "\t"); assert_eq!(dt.format("%n").to_string(), "\n"); assert_eq!(dt.format("%%").to_string(), "%"); + + // complex format specifiers + assert_eq!(dt.format(" %Y%d%m%%%%%t%H%M%S\t").to_string(), " 20010807%%\t003460\t"); + assert_eq!( + dt.format(" %Y%d%m%%%%%t%H:%P:%M%S%:::z\t").to_string(), + " 20010807%%\t00:am:3460+09\t" + ); } #[test] diff --git a/src/naive/date.rs b/src/naive/date.rs index 3acbc1fe03..1f7838847b 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -2967,16 +2967,20 @@ mod tests { "360-02-29", "0360-02-29", "2015-2 -18", + "2015-02-18", "+70-2-18", "+70000-2-18", "+00007-2-18", ]; for &s in &valid { + eprintln!("test_date_from_str valid {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), }; + eprintln!("d {:?} (NaiveDate)", d); let s_ = format!("{:?}", d); + eprintln!("s_ {:?}", s_); // `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same let d_ = match s_.parse::() { Ok(d) => d, @@ -2984,6 +2988,7 @@ mod tests { panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e) } }; + eprintln!("d_ {:?} (NaiveDate)", d_); assert!( d == d_, "`{}` is parsed into `{:?}`, but reparsed result \ @@ -2996,13 +3001,27 @@ mod tests { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("2014".parse::().is_err()); - assert!("2014-01".parse::().is_err()); - assert!("2014-01-00".parse::().is_err()); - assert!("2014-13-57".parse::().is_err()); - assert!("9999999-9-9".parse::().is_err()); // out-of-bounds + let invalid = [ + "", // empty + "x", // invalid + "Fri, 09 Aug 2013 GMT", // valid date, wrong format + "Sat Jun 30 2012", // valid date, wrong format + "1441497364.649", // valid datetime, wrong format + "+1441497364.649", // valid datetime, wrong format + "+1441497364", // valid datetime, wrong format + "2014/02/03", // valid date, wrong format + "2014", // datetime missing data + "2014-01", // datetime missing data + "2014-01-00", // invalid day + "2014-11-32", // invalid day + "2014-13-01", // invalid month + "2014-13-57", // invalid month, day + "9999999-9-9", // invalid year (out of bounds) + ]; + for &s in &invalid { + eprintln!("test_date_from_str invalid {:?}", s); + assert!(s.parse::().is_err()); + } } #[test] diff --git a/src/naive/datetime/tests.rs b/src/naive/datetime/tests.rs index 4656147d64..b16f68b9ec 100644 --- a/src/naive/datetime/tests.rs +++ b/src/naive/datetime/tests.rs @@ -199,11 +199,16 @@ fn test_datetime_timestamp() { fn test_datetime_from_str() { // valid cases let valid = [ - "2015-2-18T23:16:9.15", + "2001-02-03T04:05:06", + "2012-12-12T12:12:12", + "2015-02-18T23:16:09.153", + "2015-2-18T23:16:09.153", "-77-02-18T23:16:09", + "+82701-05-6T15:9:60.898989898989", " +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ", ]; for &s in &valid { + eprintln!("test_parse_naivedatetime valid {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), @@ -228,16 +233,34 @@ fn test_datetime_from_str() { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("15".parse::().is_err()); - assert!("15:8:9".parse::().is_err()); - assert!("15-8-9".parse::().is_err()); - assert!("2015-15-15T15:15:15".parse::().is_err()); - assert!("2012-12-12T12:12:12x".parse::().is_err()); - assert!("2012-123-12T12:12:12".parse::().is_err()); - assert!("+ 82701-123-12T12:12:12".parse::().is_err()); - assert!("+802701-123-12T12:12:12".parse::().is_err()); // out-of-bound + let invalid = [ + "", // empty + "x", // invalid / missing data + "15", // missing data + "15:8:9", // looks like a time (invalid date) + "15-8-9", // looks like a date (invalid) + "Fri, 09 Aug 2013 23:54:35 GMT", // valid date, wrong format + "Sat Jun 30 23:59:60 2012", // valid date, wrong format + "1441497364.649", // valid date, wrong format + "+1441497364.649", // valid date, wrong format + "+1441497364", // valid date, wrong format + "2014/02/03 04:05:06", // valid date, wrong format + "2015-15-15T15:15:15", // invalid date + "2012-12-12T12:12:12x", // bad timezone / trailing literal + "2012-12-12T12:12:12+00:00", // unexpected timezone / trailing literal + "2012-12-12T12:12:12 +00:00", // unexpected timezone / trailing literal + "2012-12-12T12:12:12 GMT", // unexpected timezone / trailing literal + "2012-123-12T12:12:12", // invalid month + "2012-12-12t12:12:12", // bad divider 't' + "2012-12-12 12:12:12", // missing divider 'T' + "2012-12-12T12:12:12Z", // trailing char 'Z' + "+ 82701-123-12T12:12:12", // strange year, invalid month + "+802701-123-12T12:12:12", // out-of-bound year, invalid month + ]; + for &s in &invalid { + eprintln!("test_datetime_from_str invalid {:?}", s); + assert!(s.parse::().is_err()); + } } #[test] diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index 72491fbd2c..3eb83ba167 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -235,9 +235,37 @@ fn test_date_from_str() { " 4 : 3 : 2.1 ", " 09:08:07 ", " 9:8:07 ", + "01:02:03", + "4:3:2.1", + "9:8:7", + "09:8:7", + "9:08:7", + "9:8:07", + "09:08:7", + "09:8:07", + "09:08:7", + "9:08:07", + "09:08:07", + "9:8:07.123", + "9:08:7.123", + "09:8:7.123", + "09:08:7.123", + "9:08:07.123", + "09:8:07.123", + "09:08:07.123", + "09:08:07.123", + "09:08:07.1234", + "09:08:07.12345", + "09:08:07.123456", + "09:08:07.1234567", + "09:08:07.12345678", + "09:08:07.123456789", + "09:08:07.1234567891", + "09:08:07.12345678912", "23:59:60.373929310237", ]; for &s in &valid { + eprintln!("test_time_parse_from_str valid {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), @@ -262,15 +290,30 @@ fn test_date_from_str() { // some invalid cases // since `ParseErrorKind` is private, all we can do is to check if there was an error - assert!("".parse::().is_err()); - assert!("x".parse::().is_err()); - assert!("15".parse::().is_err()); - assert!("15:8".parse::().is_err()); - assert!("15:8:x".parse::().is_err()); - assert!("15:8:9x".parse::().is_err()); - assert!("23:59:61".parse::().is_err()); - assert!("12:34:56.x".parse::().is_err()); - assert!("12:34:56. 0".parse::().is_err()); + let invalid = [ + "", // empty + "x", // invalid + "15", // missing data + "15:8", // missing data + "15:8:x", // missing data, invalid data + "15:8:9x", // missing data, invalid data + "23:59:61", // invalid second (out of bounds) + "23:54:35 GMT", // invalid (timezone non-sensical for NaiveTime) + "23:54:35 +0000", // invalid (timezone non-sensical for NaiveTime) + "1441497364.649", // valid datetime, not a NaiveTime + "+1441497364.649", // valid datetime, not a NaiveTime + "+1441497364", // valid datetime, not a NaiveTime + "001:02:03", // invalid hour + "01:002:03", // invalid minute + "01:02:003", // invalid second + "12:34:56.x", // invalid fraction + "12:34:56. 0", // invalid fraction format + "09:08:00000000007", // invalid second / invalid fraction format + ]; + for &s in &invalid { + eprintln!("test_time_parse_from_str invalid {:?}", s); + assert!(s.parse::().is_err()); + } } #[test] @@ -281,6 +324,15 @@ fn test_time_parse_from_str() { Ok(hms(12, 34, 56)) ); // ignore date and offset assert_eq!(NaiveTime::parse_from_str("PM 12:59", "%P %H:%M"), Ok(hms(12, 59, 0))); + assert_eq!(NaiveTime::parse_from_str("12:59 \n\t PM", "%H:%M \n\t %P"), Ok(hms(12, 59, 0))); + assert_eq!(NaiveTime::parse_from_str("\t\t12:59\tPM\t", "\t\t%H:%M\t%P\t"), Ok(hms(12, 59, 0))); + assert_eq!( + NaiveTime::parse_from_str("\t\t1259\t\tPM\t", "\t\t%H%M\t\t%P\t"), + Ok(hms(12, 59, 0)) + ); + assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M\t%P").is_ok()); + assert!(NaiveTime::parse_from_str("\t\t12:59 PM\t", "\t\t%H:%M\t%P\t").is_ok()); + assert!(NaiveTime::parse_from_str("12:59 PM", "%H:%M %P").is_ok()); assert!(NaiveTime::parse_from_str("12:3456", "%H:%M:%S").is_err()); } From 90511c8840370f1976be9167c0cae44cc9ec3bba Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 7 Jul 2023 17:37:05 +0200 Subject: [PATCH 67/68] Backport various changes to datetime/tests.rs --- src/datetime/tests.rs | 928 +++++++++++++----------------------------- 1 file changed, 273 insertions(+), 655 deletions(-) diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index b8338267f2..24101039d1 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -238,6 +238,100 @@ fn test_datetime_sub_months() { ); } +// local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] +fn ymdhms( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, +) -> DateTime { + fixedoffset.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() +} + +// local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] +fn ymdhms_milli( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + milli: i64, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .checked_add_signed(Duration::milliseconds(milli)) + .unwrap() +} + +// local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] +#[cfg(any(feature = "alloc", feature = "std"))] +fn ymdhms_micro( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + micro: i64, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .checked_add_signed(Duration::microseconds(micro)) + .unwrap() +} + +// local helper function to easily create a DateTime +#[allow(clippy::too_many_arguments)] +#[cfg(any(feature = "alloc", feature = "std"))] +fn ymdhms_nano( + fixedoffset: &FixedOffset, + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + nano: i64, +) -> DateTime { + fixedoffset + .with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .checked_add_signed(Duration::nanoseconds(nano)) + .unwrap() +} + +// local helper function to easily create a DateTime +fn ymdhms_utc(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> DateTime { + Utc.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap() +} + +// local helper function to easily create a DateTime +fn ymdhms_milli_utc( + year: i32, + month: u32, + day: u32, + hour: u32, + min: u32, + sec: u32, + milli: i64, +) -> DateTime { + Utc.with_ymd_and_hms(year, month, day, hour, min, sec) + .unwrap() + .checked_add_signed(Duration::milliseconds(milli)) + .unwrap() +} + #[test] fn test_datetime_offset() { let est = FixedOffset::west_opt(5 * 60 * 60).unwrap(); @@ -365,10 +459,6 @@ fn test_datetime_rfc2822() { Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0000" ); - assert_eq!( - Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc3339(), - "2015-02-18T23:16:09+00:00" - ); // timezone +05 assert_eq!( edt.from_local_datetime( @@ -381,17 +471,6 @@ fn test_datetime_rfc2822() { .to_rfc2822(), "Wed, 18 Feb 2015 23:16:09 +0500" ); - assert_eq!( - edt.from_local_datetime( - &NaiveDate::from_ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_milli_opt(23, 16, 9, 150) - .unwrap() - ) - .unwrap() - .to_rfc3339(), - "2015-02-18T23:16:09.150+05:00" - ); // seconds 60 assert_eq!( edt.from_local_datetime( @@ -404,17 +483,6 @@ fn test_datetime_rfc2822() { .to_rfc2822(), "Wed, 18 Feb 2015 23:59:60 +0500" ); - assert_eq!( - edt.from_local_datetime( - &NaiveDate::from_ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_micro_opt(23, 59, 59, 1_234_567) - .unwrap() - ) - .unwrap() - .to_rfc3339(), - "2015-02-18T23:59:60.234567+05:00" - ); assert_eq!( DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"), @@ -425,136 +493,130 @@ fn test_datetime_rfc2822() { Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) ); assert_eq!( - edt.ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_micro_opt(23, 59, 59, 1_234_567) - .unwrap() - .to_rfc2822(), - "Wed, 18 Feb 2015 23:59:60 +0500" + ymdhms_milli(&edt, 2015, 2, 18, 23, 59, 58, 1_234_567).to_rfc2822(), + "Thu, 19 Feb 2015 00:20:32 +0500" ); assert_eq!( - DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap()) + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:58 +0500"), + Ok(ymdhms(&edt, 2015, 2, 18, 23, 59, 58)) + ); + assert_ne!( + DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:58 +0500"), + Ok(ymdhms_milli(&edt, 2015, 2, 18, 23, 59, 58, 500)) ); // many varying whitespace intermixed assert_eq!( - DateTime::::parse_from_rfc2822( - "\t\t\tWed,\n\t\t18 \r\n\t\tFeb \u{3000} 2015\r\n\t\t\t23:59:60 \t+0500" + DateTime::parse_from_rfc2822( + "\t\t\tWed,\n\t\t18 \r\n\t\tFeb \u{3000} 2015\r\n\t\t\t23:59:58 \t+0500" ), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 59, 59, 1_000).unwrap()) + Ok(ymdhms(&edt, 2015, 2, 18, 23, 59, 58)) ); // example from RFC 2822 Appendix A.5. assert_eq!( - DateTime::::parse_from_rfc2822( + DateTime::parse_from_rfc2822( "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330 (Newfoundland Time)" ), - Ok(FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60) - .unwrap() - .ymd_opt(1969, 2, 13) - .unwrap() - .and_hms_opt(23, 32, 0) - .unwrap() + Ok( + ymdhms( + &FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60).unwrap(), + 1969, 2, 13, 23, 32, 0, + ) ) ); // example from RFC 2822 Appendix A.5. without trailing " (Newfoundland Time)" assert_eq!( - DateTime::::parse_from_rfc2822( + DateTime::parse_from_rfc2822( "Thu,\n\t13\n Feb\n 1969\n 23:32\n -0330" ), - Ok(FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60) - .unwrap() - .ymd_opt(1969, 2, 13) - .unwrap() - .and_hms_opt(23, 32, 0) - .unwrap()) + Ok( + ymdhms(&FixedOffset::east_opt(-3 * 60 * 60 - 30 * 60).unwrap(), 1969, 2, 13, 23, 32, 0,) + ) ); // bad year - assert!(DateTime::::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); + assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); // wrong format - assert!( - DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +00:00").is_err() - ); + assert!(DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +00:00").is_err()); // full name day of week - assert!(DateTime::::parse_from_rfc2822("Wednesday, 18 Feb 2015 23:16:09 +0000") - .is_err()); + assert!(DateTime::parse_from_rfc2822("Wednesday, 18 Feb 2015 23:16:09 +0000").is_err()); // full name day of week - assert!(DateTime::::parse_from_rfc2822("Wednesday 18 Feb 2015 23:16:09 +0000") - .is_err()); + assert!(DateTime::parse_from_rfc2822("Wednesday 18 Feb 2015 23:16:09 +0000").is_err()); // wrong day of week separator '.' - assert!(DateTime::::parse_from_rfc2822("Wed. 18 Feb 2015 23:16:09 +0000").is_err()); + assert!(DateTime::parse_from_rfc2822("Wed. 18 Feb 2015 23:16:09 +0000").is_err()); // *trailing* space causes failure - assert!( - DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000 ").is_err() - ); + assert!(DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000 ").is_err()); } #[test] #[cfg(any(feature = "alloc", feature = "std"))] fn test_datetime_rfc3339() { - let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + let edt5 = FixedOffset::east_opt(5 * 60 * 60).unwrap(); + let edt0 = FixedOffset::east_opt(0).unwrap(); + + // timezone 0 assert_eq!( - Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc3339(), + Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc3339(), "2015-02-18T23:16:09+00:00" ); + // timezone +05 assert_eq!( - edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), + edt5.from_local_datetime( + &NaiveDate::from_ymd_opt(2015, 2, 18) + .unwrap() + .and_hms_milli_opt(23, 16, 9, 150) + .unwrap() + ) + .unwrap() + .to_rfc3339(), "2015-02-18T23:16:09.150+05:00" ); + + assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00"); assert_eq!( - edt.ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_micro_opt(23, 59, 59, 1_234_567) - .unwrap() - .to_rfc3339(), - "2015-02-18T23:59:60.234567+05:00" + ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).to_rfc3339(), + "2015-02-18T23:16:09.150+05:00" ); assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123+05:00"), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 123_000).unwrap()) + ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(), + "2015-02-19T00:00:00.234567+05:00" ); assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123456+05:00"), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 123_456).unwrap()) + DateTime::parse_from_rfc3339("2015-02-18T23:59:59.123+05:00"), + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 123_000)) ); assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:59.123456789+05:00"), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_nano_opt(23, 59, 59, 123_456_789).unwrap()) + DateTime::parse_from_rfc3339("2015-02-18T23:59:59.123456+05:00"), + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 123_456)) ); assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:16:09Z"), - Ok(FixedOffset::east_opt(0) - .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_opt(23, 16, 9) - .unwrap()) + DateTime::parse_from_rfc3339("2015-02-18T23:59:59.123456789+05:00"), + Ok(ymdhms_nano(&edt5, 2015, 2, 18, 23, 59, 59, 123_456_789)) + ); + assert_eq!( + DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), + Ok(ymdhms(&edt0, 2015, 2, 18, 23, 16, 9)) ); assert_eq!( - edt.ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_micro_opt(23, 59, 59, 1_234_567) - .unwrap() - .to_rfc3339(), - "2015-02-18T23:59:60.234567+05:00" + ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(), + "2015-02-19T00:00:00.234567+05:00" ); assert_eq!( - edt.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap().to_rfc3339(), + ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).to_rfc3339(), "2015-02-18T23:16:09.150+05:00" ); assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), - Ok(edt.ymd_opt(2015, 2, 18).unwrap().and_hms_micro_opt(23, 59, 59, 1_234_567).unwrap()) + DateTime::parse_from_rfc3339("2015-02-18T00:00:00.234567+05:00"), + Ok(ymdhms_micro(&edt5, 2015, 2, 18, 0, 0, 0, 234_567)) ); assert_eq!( - DateTime::::parse_from_rfc3339("2015-02-18T23:16:09Z"), - Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()) + DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), + Ok(ymdhms(&edt0, 2015, 2, 18, 23, 16, 9)) ); assert_eq!( DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), - Ok(edt + Ok(edt5 .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() @@ -566,7 +628,7 @@ fn test_datetime_rfc3339() { assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); assert_eq!( DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), - Ok(edt + Ok(edt5 .from_local_datetime( &NaiveDate::from_ymd_opt(2015, 2, 18) .unwrap() @@ -575,44 +637,21 @@ fn test_datetime_rfc3339() { ) .unwrap()) ); - assert_eq!( - Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_opt(23, 16, 9).unwrap().to_rfc3339(), - "2015-02-18T23:16:09+00:00" - ); - - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567 +05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:059:60.234567+05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00PST").is_err() - ); - assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+PST").is_err()); - assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567PST").is_err()); - assert!(DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+0500").is_err()); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18 23:59:60.234567+05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567:+05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00 ").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339(" 2015-02-18T23:59:60.234567+05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015- 02-18T23:59:60.234567+05:00").is_err() - ); - assert!( - DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567A+05:00").is_err() - ); + assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00"); + + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567 +05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:059:60.234567+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00PST").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+PST").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567PST").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+0500").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18 23:59:60.234567+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567:+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00 ").is_err()); + assert!(DateTime::parse_from_rfc3339(" 2015-02-18T23:59:60.234567+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015- 02-18T23:59:60.234567+05:00").is_err()); + assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567A+05:00").is_err()); } #[test] @@ -844,12 +883,12 @@ fn test_parse_datetime_utc() { #[test] fn test_utc_datetime_from_str() { - let ymdhms = |y, m, d, h, n, s, off| { - FixedOffset::east_opt(off).unwrap().with_ymd_and_hms(y, m, d, h, n, s).unwrap() - }; + let edt = FixedOffset::east_opt(570 * 60).unwrap(); + let edt0 = FixedOffset::east_opt(0).unwrap(); + let wdt = FixedOffset::west_opt(10 * 3600).unwrap(); assert_eq!( DateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), - Ok(ymdhms(2014, 5, 7, 12, 34, 56, 570 * 60)) + Ok(ymdhms(&edt, 2014, 5, 7, 12, 34, 56)) ); // ignore offset assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset assert!(DateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT") @@ -858,56 +897,45 @@ fn test_utc_datetime_from_str() { Utc.datetime_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), Ok(Utc.with_ymd_and_hms(2013, 8, 9, 23, 54, 35).unwrap()) ); + assert_eq!( + DateTime::parse_from_str("0", "%s").unwrap(), + NaiveDateTime::from_timestamp_opt(0, 0).unwrap().and_utc().fixed_offset() + ); assert_eq!( "2015-02-18T23:16:9.15Z".parse::>(), - Ok(FixedOffset::east_opt(0) - .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_milli_opt(23, 16, 9, 150) - .unwrap()) + Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-02-18T23:16:9.15Z".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)), ); assert_eq!( "2015-02-18T23:16:9.15 UTC".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-02-18T23:16:9.15UTC".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-2-18T23:16:9.15Z".parse::>(), - Ok(FixedOffset::east_opt(0) - .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_milli_opt(23, 16, 9, 150) - .unwrap()) + Ok(ymdhms_milli(&edt0, 2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-2-18T13:16:9.15-10:00".parse::>(), - Ok(FixedOffset::west_opt(10 * 3600) - .unwrap() - .ymd_opt(2015, 2, 18) - .unwrap() - .and_hms_milli_opt(13, 16, 9, 150) - .unwrap()) + Ok(ymdhms_milli(&wdt, 2015, 2, 18, 13, 16, 9, 150)) ); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); assert_eq!( "2015-2-18T23:16:9.15Z".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); assert_eq!( "2015-2-18T13:16:9.15-10:00".parse::>(), - Ok(Utc.ymd_opt(2015, 2, 18).unwrap().and_hms_milli_opt(23, 16, 9, 150).unwrap()) + Ok(ymdhms_milli_utc(2015, 2, 18, 23, 16, 9, 150)) ); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); @@ -916,7 +944,7 @@ fn test_utc_datetime_from_str() { #[test] fn test_utc_datetime_from_str_with_spaces() { - let dt = Utc.ymd_opt(2013, 8, 9).unwrap().and_hms_opt(23, 54, 35).unwrap(); + let dt = ymdhms_utc(2013, 8, 9, 23, 54, 35); // with varying spaces - should succeed assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),); assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt),); @@ -952,12 +980,8 @@ fn test_utc_datetime_from_str_with_spaces() { #[test] fn test_datetime_parse_from_str() { - let dt = FixedOffset::east_opt(-9 * 60 * 60) - .unwrap() - .ymd_opt(2013, 8, 9) - .unwrap() - .and_hms_opt(23, 54, 35) - .unwrap(); + let dt = ymdhms(&FixedOffset::east_opt(-9 * 60 * 60).unwrap(), 2013, 8, 9, 23, 54, 35); + let parse = DateTime::parse_from_str; // timezone variations @@ -965,542 +989,136 @@ fn test_datetime_parse_from_str() { // %Z // // wrong timezone format - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %Z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %Z").is_err()); // bad timezone data? - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 PST", - "%b %d %Y %H:%M:%S %Z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 PST", "%b %d %Y %H:%M:%S %Z").is_err()); // bad timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 XXXXX", - "%b %d %Y %H:%M:%S %Z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 XXXXX", "%b %d %Y %H:%M:%S %Z").is_err()); // // %z // - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 00", - "%b %d %Y %H:%M:%S %z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 : 00", - "%b %d %Y %H:%M:%S %z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 --0900", - "%b %d %Y %H:%M:%S -%z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 +-0900", - "%b %d %Y %H:%M:%S +%z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00 ", - "%b %d %Y %H:%M:%S %z " - ), - Ok(dt), - ); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 --0900", "%b %d %Y %H:%M:%S -%z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 +-0900", "%b %d %Y %H:%M:%S +%z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00 ", "%b %d %Y %H:%M:%S %z "), Ok(dt)); // trailing newline after timezone - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00\n", - "%b %d %Y %H:%M:%S %z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00\n", - "%b %d %Y %H:%M:%S %z " - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09:00\n", "%b %d %Y %H:%M:%S %z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00\n", "%b %d %Y %H:%M:%S %z "), Ok(dt)); // trailing colon - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:", - "%b %d %Y %H:%M:%S %z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %z").is_err()); // trailing colon with space - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00: ", - "%b %d %Y %H:%M:%S %z " - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00: ", "%b %d %Y %H:%M:%S %z ").is_err()); // trailing colon, mismatch space - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:", - "%b %d %Y %H:%M:%S %z " - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %z ").is_err()); // wrong timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09", - "%b %d %Y %H:%M:%S %z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09::00", - "%b %d %Y %H:%M:%S %z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900::", - "%b %d %Y %H:%M:%S %z::" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00", - "%b %d %Y %H:%M:%S %z:00" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00 ", - "%b %d %Y %H:%M:%S %z:00 " - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900::", "%b %d %Y %H:%M:%S %z::"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %z:00"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00:00 ", "%b %d %Y %H:%M:%S %z:00 "), Ok(dt)); // // %:z // - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %:z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %:z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 00", - "%b %d %Y %H:%M:%S %:z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 : 00", - "%b %d %Y %H:%M:%S %:z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 : 00:", - "%b %d %Y %H:%M:%S %:z:" - ), - Ok(dt), - ); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00:", "%b %d %Y %H:%M:%S %:z:"), Ok(dt)); // wrong timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09", - "%b %d %Y %H:%M:%S %:z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09::00", - "%b %d %Y %H:%M:%S %:z" - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %:z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); // timezone data hs too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:", - "%b %d %Y %H:%M:%S %:z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %:z").is_err()); // timezone data hs too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00::", - "%b %d %Y %H:%M:%S %:z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00::", - "%b %d %Y %H:%M:%S %:z::" - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09:00::", "%b %d %Y %H:%M:%S %:z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00::", "%b %d %Y %H:%M:%S %:z::"), Ok(dt)); // - // %:::z + // %::z // - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %::z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %::z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09 : 00", - "%b %d %Y %H:%M:%S %::z" - ), - Ok(dt), - ); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09 : 00", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); // mismatching colon expectations - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00", - "%b %d %Y %H:%M:%S %::z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09::00", - "%b %d %Y %H:%M:%S %::z" - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09::00", "%b %d %Y %H:%M:%S %:z"), Ok(dt)); // wrong timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09", - "%b %d %Y %H:%M:%S %::z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09001234", - "%b %d %Y %H:%M:%S %::z1234" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:001234", - "%b %d %Y %H:%M:%S %::z1234" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900 ", - "%b %d %Y %H:%M:%S %::z " - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900\t\n", - "%b %d %Y %H:%M:%S %::z\t\n" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900:", - "%b %d %Y %H:%M:%S %::z:" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 :-0900:0", - "%b %d %Y %H:%M:%S :%::z:0" - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %::z").is_err()); + assert_eq!(parse("Aug 09 2013 23:54:35 -09001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:001234", "%b %d %Y %H:%M:%S %::z1234"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %::z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900\t\n", "%b %d %Y %H:%M:%S %::z\t\n"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900:", "%b %d %Y %H:%M:%S %::z:"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 :-0900:0", "%b %d %Y %H:%M:%S :%::z:0"), Ok(dt)); // mismatching colons and spaces - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 :-0900: ", - "%b %d %Y %H:%M:%S :%::z::" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 :-0900: ", "%b %d %Y %H:%M:%S :%::z::").is_err()); // mismatching colons expectations - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00", - "%b %d %Y %H:%M:%S %::z" - ) - .is_err()); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 -0900: 23:54:35", - "%b %d %Y %::z: %H:%M:%S" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 :-0900:0 23:54:35", - "%b %d %Y :%::z:0 %H:%M:%S" - ), - Ok(dt), - ); + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::z").is_err()); + assert_eq!(parse("Aug 09 2013 -0900: 23:54:35", "%b %d %Y %::z: %H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 :-0900:0 23:54:35", "%b %d %Y :%::z:0 %H:%M:%S"), Ok(dt)); // mismatching colons expectations mid-string - assert!(DateTime::::parse_from_str( - "Aug 09 2013 :-0900: 23:54:35", - "%b %d %Y :%::z %H:%M:%S" - ) - .is_err()); + assert!(parse("Aug 09 2013 :-0900: 23:54:35", "%b %d %Y :%::z %H:%M:%S").is_err()); // mismatching colons expectations, before end - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00 ", - "%b %d %Y %H:%M:%S %::z " - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:00 ", "%b %d %Y %H:%M:%S %::z ").is_err()); // // %:::z // - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %:::z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %:::z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900 ", - "%b %d %Y %H:%M:%S %:::z " - ), - Ok(dt), - ); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %:::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %:::z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %:::z "), Ok(dt)); // wrong timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09", - "%b %d %Y %H:%M:%S %:::z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %:::z").is_err()); // // %::::z // // too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %::::z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %::::z").is_err()); // too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %::::z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %::::z").is_err()); // too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:", - "%b %d %Y %H:%M:%S %::::z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:", "%b %d %Y %H:%M:%S %::::z").is_err()); // too many colons - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00", - "%b %d %Y %H:%M:%S %::::z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %::::z").is_err()); // // %#z // - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00", - "%b %d %Y %H:%M:%S %#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00 ", - "%b %d %Y %H:%M:%S %#z " - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900 ", - "%b %d %Y %H:%M:%S %#z " - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09", - "%b %d %Y %H:%M:%S %#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -0900", - "%b %d %Y %H:%M:%S %#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:", - "%b %d %Y %H:%M:%S %#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09: ", - "%b %d %Y %H:%M:%S %#z " - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35+-09", - "%b %d %Y %H:%M:%S+%#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 23:54:35--09", - "%b %d %Y %H:%M:%S-%#z" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 -09:00 23:54:35", - "%b %d %Y %#z%H:%M:%S" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 -0900 23:54:35", - "%b %d %Y %#z%H:%M:%S" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 -090023:54:35", - "%b %d %Y %#z%H:%M:%S" - ), - Ok(dt), - ); - assert_eq!( - DateTime::::parse_from_str( - "Aug 09 2013 -09:0023:54:35", - "%b %d %Y %#z%H:%M:%S" - ), - Ok(dt), - ); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:00 ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900 ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -0900", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09:", "%b %d %Y %H:%M:%S %#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35 -09: ", "%b %d %Y %H:%M:%S %#z "), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35+-09", "%b %d %Y %H:%M:%S+%#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 23:54:35--09", "%b %d %Y %H:%M:%S-%#z"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 -09:00 23:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 -0900 23:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 -090023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); + assert_eq!(parse("Aug 09 2013 -09:0023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); // timezone with partial minutes adjacent hours - assert_ne!( - DateTime::::parse_from_str("Aug 09 2013 -09023:54:35", "%b %d %Y %#z%H:%M:%S"), - Ok(dt), - ); + assert_ne!(parse("Aug 09 2013 -09023:54:35", "%b %d %Y %#z%H:%M:%S"), Ok(dt)); // bad timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -09:00:00", - "%b %d %Y %H:%M:%S %#z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -09:00:00", "%b %d %Y %H:%M:%S %#z").is_err()); // bad timezone data (partial minutes) - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -090", - "%b %d %Y %H:%M:%S %#z" - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -090", "%b %d %Y %H:%M:%S %#z").is_err()); // bad timezone data (partial minutes) with trailing space - assert!(DateTime::::parse_from_str( - "Aug 09 2013 23:54:35 -090 ", - "%b %d %Y %H:%M:%S %#z " - ) - .is_err()); + assert!(parse("Aug 09 2013 23:54:35 -090 ", "%b %d %Y %H:%M:%S %#z ").is_err()); // bad timezone data (partial minutes) mid-string - assert!(DateTime::::parse_from_str( - "Aug 09 2013 -090 23:54:35", - "%b %d %Y %#z %H:%M:%S" - ) - .is_err()); + assert!(parse("Aug 09 2013 -090 23:54:35", "%b %d %Y %#z %H:%M:%S").is_err()); // bad timezone data - assert!(DateTime::::parse_from_str( - "Aug 09 2013 -09:00:00 23:54:35", - "%b %d %Y %#z %H:%M:%S" - ) - .is_err()); + assert!(parse("Aug 09 2013 -09:00:00 23:54:35", "%b %d %Y %#z %H:%M:%S").is_err()); // timezone data ambiguous with hours - assert!(DateTime::::parse_from_str( - "Aug 09 2013 -09:00:23:54:35", - "%b %d %Y %#z%H:%M:%S" - ) - .is_err()); - assert_eq!( - DateTime::parse_from_str("0", "%s").unwrap(), - NaiveDateTime::from_timestamp_opt(0, 0).unwrap().and_utc().fixed_offset() - ); + assert!(parse("Aug 09 2013 -09:00:23:54:35", "%b %d %Y %#z%H:%M:%S").is_err()); } #[test] From 3993c0273466ece5e70cb9c62fba5128d481016c Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 7 Jul 2023 17:08:37 +0200 Subject: [PATCH 68/68] Make imports similar to main branch --- src/datetime/mod.rs | 3 +-- src/format/parsed.rs | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 24f67298cf..5ff141d3c2 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -31,8 +31,7 @@ use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; use crate::oldtime::Duration as OldDuration; #[allow(deprecated)] use crate::Date; -use crate::Months; -use crate::{Datelike, Timelike, Weekday}; +use crate::{Datelike, Months, Timelike, Weekday}; #[cfg(feature = "rkyv")] use rkyv::{Archive, Deserialize, Serialize}; diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 809a5ae782..431833ec7d 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -8,9 +8,7 @@ use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone}; use crate::oldtime::Duration as OldDuration; -use crate::DateTime; -use crate::Weekday; -use crate::{Datelike, Timelike}; +use crate::{DateTime, Datelike, Timelike, Weekday}; /// Parsed parts of date and time. There are two classes of methods: ///