diff --git a/src/date.rs b/src/date.rs index ef409ef7cd..5fb1e28b61 100644 --- a/src/date.rs +++ b/src/date.rs @@ -21,6 +21,7 @@ use crate::offset::{TimeZone, Utc}; use crate::oldtime::Duration as OldDuration; use crate::DateTime; use crate::{Datelike, Weekday}; +use crate::dpl; /// ISO 8601 calendar date with time zone. /// @@ -337,6 +338,7 @@ where #[cfg(any(feature = "alloc", feature = "std", test))] #[inline] pub fn format<'a>(&self, fmt: &'a str) -> DelayedFormat> { + dpl!("{:?}.format({:?})", self, fmt); self.format_with_items(StrftimeItems::new(fmt)) } diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 815f3a5c42..2f4ab1d01c 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -8,6 +8,7 @@ use crate::offset::{FixedOffset, TimeZone, Utc}; use crate::oldtime::Duration; #[cfg(feature = "clock")] use crate::Datelike; +use crate::dpl; #[test] fn test_datetime_offset() { @@ -165,41 +166,50 @@ fn test_rfc3339_opts_nonexhaustive() { #[test] fn test_datetime_from_str() { + dpl!("test_datetime_from_str: {:?}", line!()); assert_eq!( "2015-02-18T23:16:9.15Z".parse::>(), Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) ); + dpl!("test_datetime_from_str: {:?}", line!()); assert_eq!( "2015-02-18T23:16:9.15Z".parse::>(), Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) ); + dpl!("test_datetime_from_str: {:?}", line!()); assert_eq!( "2015-02-18T23:16:9.15 UTC".parse::>(), Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) ); + dpl!("test_datetime_from_str: {:?}", line!()); assert_eq!( "2015-02-18T23:16:9.15UTC".parse::>(), Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) ); - + dpl!("test_datetime_from_str: {:?}", line!()); assert_eq!( "2015-2-18T23:16:9.15Z".parse::>(), Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) ); + dpl!("test_datetime_from_str: {:?}", line!()); assert_eq!( "2015-2-18T13:16:9.15-10:00".parse::>(), Ok(FixedOffset::west(10 * 3600).ymd(2015, 2, 18).and_hms_milli(13, 16, 9, 150)) ); + dpl!("test_datetime_from_str: {:?}", line!()); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); + dpl!("test_datetime_from_str: {:?}", line!()); assert_eq!( "2015-2-18T23:16:9.15Z".parse::>(), Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) ); + dpl!("test_datetime_from_str: {:?}", line!()); assert_eq!( "2015-2-18T13:16:9.15-10:00".parse::>(), Ok(Utc.ymd(2015, 2, 18).and_hms_milli(23, 16, 9, 150)) ); + dpl!("test_datetime_from_str: {:?}", line!()); assert!("2015-2-18T23:16:9.15".parse::>().is_err()); // no test for `DateTime`, we cannot verify that much. diff --git a/src/format/mod.rs b/src/format/mod.rs index b88ae4b632..ad802f203f 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -218,27 +218,27 @@ pub enum Fixed { /// /// It does not support parsing, its use in the parser is an immediate failure. TimezoneName, - /// Offset from the local time to UTC (`+09:00` or `-04:00` or `+00:00`). + /// Offset from the local time to UTC (`+09:00` or `-0400` or `+00:00`). /// - /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. + /// In the parser, the colon may be omitted, /// The offset is limited from `-24:00` to `+24:00`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. TimezoneOffsetColon, - /// Offset from the local time to UTC with seconds (`+09:00:00` or `-04:00:00` or `+00:00:00`). + /// Offset from the local time to UTC with seconds (`+09:00:00` or `-0400:00` or `+000000`). /// - /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. + /// In the parser, the colon may be omitted, /// The offset is limited from `-24:00:00` to `+24:00:00`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. TimezoneOffsetDoubleColon, /// Offset from the local time to UTC without minutes (`+09` or `-04` or `+00`). /// - /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace. + /// In the parser, the colon may be omitted, /// The offset is limited from `-24` to `+24`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. TimezoneOffsetTripleColon, - /// Offset from the local time to UTC (`+09:00` or `-04:00` or `Z`). + /// Offset from the local time to UTC (`+09:00` or `-0400` or `Z`). /// - /// In the parser, the colon can be omitted and/or surrounded with any amount of whitespace, + /// In the parser, the colon may be omitted, /// and `Z` can be either in upper case or in lower case. /// The offset is limited from `-24:00` to `+24:00`, /// which is the same as [`FixedOffset`](../offset/struct.FixedOffset.html)'s range. diff --git a/src/format/parse.rs b/src/format/parse.rs index a8321e7624..e225e23100 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -15,6 +15,7 @@ use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, Parsed}; use super::{ParseError, ParseErrorKind, ParseResult}; use super::{BAD_FORMAT, INVALID, NOT_ENOUGH, OUT_OF_RANGE, TOO_LONG, TOO_SHORT}; use crate::{DateTime, FixedOffset, Weekday}; +use crate::dpl; fn set_weekday_with_num_days_from_sunday(p: &mut Parsed, v: i64) -> ParseResult<()> { p.set_weekday(match v { @@ -42,6 +43,10 @@ fn set_weekday_with_number_from_monday(p: &mut Parsed, v: i64) -> ParseResult<() }) } +/// Parse an RFC 2822 format datetime +/// e.g. `Fri, 21 Nov 1997 09:55:06 -0600` +/// +/// This function allows arbitrary intermixed whitespace per RFC 2822 section A.5 fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a str, ())> { macro_rules! try_consume { ($e:expr) => {{ @@ -268,7 +273,7 @@ where } }}; } - eprintln!(" + dpl!(" ->parse_internal( parsed: {:?}, s: {:?}, @@ -279,85 +284,94 @@ where eprint!(" parse_internal item "); match *item.borrow() { Item::Literal(prefix) => { - eprintln!("match Item::Literal"); + dpl!("match Item::Literal({:?})", prefix); if s.len() < prefix.len() { - eprintln!(" ~~Err(TOO_SHORT, {:?})", s); + dpl!(" ~~Err(TOO_SHORT, {:?})", s); return Err((s, TOO_SHORT)); } if !s.starts_with(prefix) { - eprintln!(" ~~Err(INVALID, {:?})", s); + dpl!(" ~~Err(INVALID, {:?}) does not start with Literal prefix {:?}", s, prefix); return Err((s, INVALID)); } s = &s[prefix.len()..]; - eprintln!(" s {:?}", s); + dpl!(" s {:?}", s); } #[cfg(any(feature = "alloc", feature = "std", test))] Item::OwnedLiteral(ref prefix) => { - eprintln!("match Item::OwnedLiteral({:?})", prefix); - eprintln!(" s {:?}", s); + dpl!("match Item::OwnedLiteral({:?})", prefix); + dpl!(" s {:?}", s); if s.len() < prefix.len() { - eprintln!(" ~~Err(TOO_SHORT, {:?})", s); + dpl!(" ~~Err(TOO_SHORT, {:?})", s); return Err((s, TOO_SHORT)); } if !s.starts_with(&prefix[..]) { - eprintln!(" ~~Err(INVALID, {:?})", s); + dpl!(" ~~Err(INVALID, {:?}) does not start with OwnedLiteral prefix {:?}", s, prefix); return Err((s, INVALID)); } s = &s[prefix.len()..]; - eprintln!(" s {:?}", s); + dpl!(" s {:?}", s); } - Item::Space(space) => { - eprintln!("match Item::Space({:?})", space); - eprintln!(" s {:?}", s); - eprint!(" match s.chars().next() "); - match &s.chars().next() { - Some(c) => { - eprintln!("c {:?}", c); - if ! c.is_whitespace() { - // `s` whitespace must match each `item` - eprintln!(" ~~Err(INVALID, {:?}) (Item::Space but s.next() {:?} is not whitespace)", s, c); - return Err((s, INVALID)); - } - eprint!(" match space.chars().next() "); - // whitespace character must match - match &space.chars().next() { - Some(spacec) => { - eprintln!("c {:?}", c); - if c != spacec { - eprintln!(" ~~Err(INVALID, {:?}) (mismatched whitespace)", s); - return Err((s, INVALID)); - } - } - None => { - eprintln!("None"); - eprintln!(" ~~Err(INVALID, {:?})", s); - return Err((s, INVALID)); - } + Item::Space(item_space) => { + dpl!("match Item::Space({:?})", item_space); + if item_space.len() == 0 && s.len() == 0 { + break; + } + for item_c in item_space.chars() { + // copy a single character from the item + dpl!(" item_c {:?}", item_c); + // copy first character from the data string + let s_c = match s.chars().next() { + Some(c) => c, + None => { + dpl!(" ~~Err(TOO_SHORT, {:?})", s); + return Err((s, TOO_SHORT)); } - s = &s[1..]; - }, - None => { - eprintln!("None"); - // `s` whitespace must match each `items` - eprintln!(" ~~Err(INVALID, {:?})", s); + }; + dpl!(" s_c {:?}", s_c); + // do the chars match? + if item_c != s_c { + dpl!(" ~~Err(INVALID, {:?}) {:?} != {:?} Space chars do not match", s, item_c, s_c); return Err((s, INVALID)); } + // advance `s` forward 1 char + s = scan::s_next(s); + dpl!(" s {:?}", s); } - eprintln!(" s {:?}", s); } #[cfg(any(feature = "alloc", feature = "std", test))] - Item::OwnedSpace(ref _s) => { - eprintln!("match Item::OwnedSpace({:?}", _s); - eprintln!(" s {:?}", s); - s = s.trim_left(); - eprintln!(" s {:?}", s); + Item::OwnedSpace(ref item_space) => { + dpl!("match Item::OwnedSpace({:?}", item_space); + if item_space.len() == 0 && s.len() == 0 { + break; + } + for item_c in item_space.chars() { + // copy a single character from the item + dpl!(" item_c {:?}", item_c); + // copy first character from the data string + let s_c = match s.chars().next() { + Some(c) => c, + None => { + dpl!(" ~~Err(TOO_SHORT, {:?})", s); + return Err((s, TOO_SHORT)); + } + }; + dpl!(" s_c {:?}", s_c); + // do the chars match? + if item_c != s_c { + dpl!(" ~~Err(INVALID, {:?}) {:?} != {:?} OwnedSpace chars do not match", s, item_c, s_c); + return Err((s, INVALID)); + } + // advance `s` forward 1 char + s = scan::s_next(s); + dpl!(" s {:?}", s); + } } Item::Numeric(ref spec, ref _pad) => { - eprintln!("match Item::Numeric"); + dpl!("match Item::Numeric"); use super::Numeric::*; type Setter = fn(&mut Parsed, i64) -> ParseResult<()>; @@ -387,9 +401,9 @@ where Internal(ref int) => match int._dummy {}, }; - eprintln!(" width {:?}, signed {:?}, set ...", width, signed); + dpl!(" width {:?}, signed {:?}, set ...", width, signed); s = s.trim_left(); - eprintln!(" s {:?}", s); + dpl!(" s {:?}", s); let v = if signed { if s.starts_with('-') { let v = try_consume!(scan::number(&s[1..], 1, usize::MAX)); @@ -407,42 +421,47 @@ where } Item::Fixed(ref spec) => { - eprintln!("match Item::Fixed"); + dpl!("match Item::Fixed"); use super::Fixed::*; eprint!(" parse_internal spec "); match spec { &ShortMonthName => { - eprintln!("match ShortMonthName"); + dpl!("match ShortMonthName"); + dpl!(" s {:?}", s); let month0 = try_consume!(scan::short_month0(s)); parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?; - eprintln!(" s {:?}", s); + dpl!(" s {:?}", s); } &LongMonthName => { - eprintln!("match LongMonthName"); + dpl!("match LongMonthName"); + dpl!(" s {:?}", s); let month0 = try_consume!(scan::short_or_long_month0(s)); parsed.set_month(i64::from(month0) + 1).map_err(|e| (s, e))?; - eprintln!(" s {:?}", s); + dpl!(" s {:?}", s); } &ShortWeekdayName => { - eprintln!("match ShortWeekdayName"); + dpl!("match ShortWeekdayName"); + dpl!(" s {:?}", s); let weekday = try_consume!(scan::short_weekday(s)); parsed.set_weekday(weekday).map_err(|e| (s, e))?; - eprintln!(" s {:?}", s); + dpl!(" s {:?}", s); } &LongWeekdayName => { - eprintln!("match LongWeekdayName"); + dpl!("match LongWeekdayName"); + dpl!(" s {:?}", s); let weekday = try_consume!(scan::short_or_long_weekday(s)); parsed.set_weekday(weekday).map_err(|e| (s, e))?; - eprintln!(" s {:?}", s); + dpl!(" s {:?}", s); } &LowerAmPm | &UpperAmPm => { - eprintln!("match LowerAmPm|UpperAmPm"); - eprintln!(" s {:?}", s); + dpl!("match LowerAmPm|UpperAmPm"); + dpl!(" s {:?}", s); + dpl!(" s {:?}", s); if s.len() < 2 { return Err((s, TOO_SHORT)); } @@ -453,11 +472,11 @@ where }; parsed.set_ampm(ampm).map_err(|e| (s, e))?; s = &s[2..]; - eprintln!(" s {:?}", s); + dpl!(" s {:?}", s); } &Nanosecond | &Nanosecond3 | &Nanosecond6 | &Nanosecond9 => { - eprintln!("match Nanosecond|..."); + dpl!("match Nanosecond|..."); if s.starts_with('.') { let nano = try_consume!(scan::nanosecond(&s[1..])); parsed.set_nanosecond(nano).map_err(|e| (s, e))?; @@ -465,7 +484,7 @@ where } &Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { - eprintln!("match Internal(Nanosecond3NoDot)"); + dpl!("match Internal(Nanosecond3NoDot)"); if s.len() < 3 { return Err((s, TOO_SHORT)); } @@ -474,19 +493,18 @@ where } &Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { - eprintln!("match Internal(Nanosecond6NoDot)"); + dpl!("match Internal(Nanosecond6NoDot)"); if s.len() < 6 { return Err((s, TOO_SHORT)); } let nano = try_consume!(scan::nanosecond_fixed(s, 6)); parsed.set_nanosecond(nano).map_err(|e| (s, e))?; - eprintln!(" s {:?}", s); } &Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { - eprintln!("match Internal(Nanosecond9NoDot)"); + dpl!("match Internal(Nanosecond9NoDot)"); if s.len() < 9 { - eprintln!(" ~~parse_internal Err(TOO_SHORT, {:?})", s); + dpl!(" ~~parse_internal Err(TOO_SHORT, {:?})", s); return Err((s, TOO_SHORT)); } let nano = try_consume!(scan::nanosecond_fixed(s, 9)); @@ -494,7 +512,7 @@ where } &TimezoneName => { - eprintln!("match TimezoneName"); + dpl!("match TimezoneName"); try_consume!(scan::timezone_name_skip(s)); } @@ -502,37 +520,44 @@ where | &TimezoneOffsetDoubleColon | &TimezoneOffsetTripleColon | &TimezoneOffset => { - eprintln!("match TimezoneOffsetColon|..."); - eprintln!(" s {:?}", s); + dpl!("match TimezoneOffset|TimezoneOffsetColon|TimezoneOffsetDoubleColon|TimezoneOffsetTripleColon"); + dpl!(" s {:?}", s); + s = scan::space1(s); let offset = try_consume!(scan::timezone_offset( - s.trim_left(), + s, scan::colon_or_space )); + dpl!(" offset {:?}", offset); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; - eprintln!(" s {:?}", s); + dpl!(" s {:?}", s); } &TimezoneOffsetColonZ | &TimezoneOffsetZ => { - eprintln!("match TimezoneOffsetColonZ|TimezoneOffsetZ"); - eprintln!(" s {:?}", s); + dpl!("match TimezoneOffsetColonZ|TimezoneOffsetZ"); + dpl!(" s {:?}", s); + s = scan::space1(s); let offset = try_consume!(scan::timezone_offset_zulu( - s.trim_left(), + s, scan::colon_or_space )); + dpl!(" offset {:?}", offset); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; - eprintln!(" s {:?}", s); + dpl!(" s {:?}", s); } + &Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive, }) => { - eprintln!("match InternalFixed"); - eprintln!(" s {:?}", s); + dpl!("match InternalFixed(TimezoneOffsetPermissive)"); + dpl!(" s {:?}", s); + s = scan::space1(s); let offset = try_consume!(scan::timezone_offset_permissive( - s.trim_left(), + s, scan::colon_or_space )); + dpl!(" offset {:?}", offset); parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?; - eprintln!(" s {:?}", s); + dpl!(" s {:?}", s); } &RFC2822 => try_consume!(parse_rfc2822(parsed, s)), @@ -541,7 +566,7 @@ where } Item::Error => { - eprintln!(" ~~parse_internal Err(BAD_FORMAT, {:?})", s); + dpl!(" ~~parse_internal Err(BAD_FORMAT, {:?})", s); return Err((s, BAD_FORMAT)); } } @@ -549,24 +574,22 @@ where // if there are trailling chars, it is an error if !s.is_empty() { - eprintln!(" ~~parse_internal Err(TOO_LONG, {:?})", s); + dpl!(" ~~parse_internal Err(TOO_LONG, {:?})", s); Err((s, TOO_LONG)) } else { - eprintln!(" ~~parse_internal Ok({:?})", s); + dpl!(" ~~parse_internal Ok({:?})", s); Ok(s) } } /// Accepts a relaxed form of RFC3339. -/// A space or a 'T' are acepted as the separator between the date and time -/// parts. Additional spaces are allowed between each component. +/// A space or a 'T' are accepted as the separator between the date and time +/// parts. /// -/// All of these examples are equivalent: /// ``` /// # use chrono::{DateTime, offset::FixedOffset}; -/// "2012-12-12T12:12:12Z".parse::>(); -/// "2012-12-12 12:12:12Z".parse::>(); -/// "2012- 12-12T12: 12:12Z".parse::>(); +/// "2000-01-02T03:04:05Z".parse::>(); +/// "2000-01-02 03:04:05Z".parse::>(); /// ``` impl str::FromStr for DateTime { type Err = ParseError; @@ -574,25 +597,19 @@ impl str::FromStr for DateTime { fn from_str(s: &str) -> ParseResult> { const DATE_ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Year, Pad::Zero), - Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Month, Pad::Zero), - Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Day, Pad::Zero), ]; const TIME_ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Hour, Pad::Zero), - Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Minute, Pad::Zero), - Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Second, Pad::Zero), Item::Fixed(Fixed::Nanosecond), - Item::Space(""), Item::Fixed(Fixed::TimezoneOffsetZ), - Item::Space(""), ]; let mut parsed = Parsed::new(); @@ -631,7 +648,11 @@ fn test_parse() { ($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)) + let items_ = &$items; + dpl!("test_parse: parse_all(\n {:?}, {} items) (line {})", $fmt, items_.len(), line!()); + let actual = parse_all($fmt, items_); + let expected = Ok(expected); + assert_eq!(actual, expected); }); } @@ -639,35 +660,66 @@ fn test_parse() { check!("", []; ); check!(" ", []; TOO_LONG); check!("a", []; TOO_LONG); + check!("abc", []; TOO_LONG); // whitespaces check!("", [sp!("")]; ); - check!(" ", [sp!("")]; ); - check!("\t", [sp!("")]; ); - check!(" \n\r \n", [sp!("")]; ); + check!(" ", [sp!(" ")]; ); + check!(" ", [sp!(" ")]; ); + check!(" ", [sp!(" "), sp!(" ")]; ); + check!("\t", [sp!("\t")]; ); + check!("\t\r", [sp!("\t\r")]; ); + check!("\t\r ", [sp!("\t\r ")]; ); + check!(" \n\r \n", [sp!(" \n\r \n")]; ); + check!("abc", []; TOO_LONG); check!("a", [sp!("")]; TOO_LONG); + check!("abc", [sp!("")]; TOO_LONG); + check!(" ", [sp!(" ")]; TOO_LONG); + check!(" \t\n", [sp!(" \t")]; TOO_LONG); + check!("", [sp!(" ")]; TOO_SHORT); + check!(" ", [sp!(" ")]; TOO_SHORT); + check!(" ", [sp!(" ")]; TOO_SHORT); + check!(" ", [sp!(" "), sp!(" ")]; TOO_SHORT); // literal + check!("", [lit!("")]; ); check!("", [lit!("a")]; TOO_SHORT); + check!("๐Ÿค ", [lit!("๐Ÿค ")]; ); + check!("๐Ÿค a", [lit!("๐Ÿค "), lit!("a")]; ); + check!("๐Ÿค a๐Ÿค ", [lit!("๐Ÿค "), lit!("a๐Ÿค ")]; ); check!(" ", [lit!("a")]; INVALID); check!("a", [lit!("a")]; ); 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!("PST", [lit!("PST")]; ); + check!("xy", [lit!("xy")]; ); // literals can be together + check!("xy", [lit!("x"), lit!("y")]; ); // or literals can be apart 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", [lit!("x"), sp!(" "), lit!("y")]; ); + + // whitespaces + literals + check!("a\n", [lit!("a"), sp!("\n")]; ); + check!("\tab\n", [sp!("\t"), lit!("ab"), sp!("\n")]; ); + check!("ab\tcd\ne", [lit!("ab"), sp!("\t"), lit!("cd"), sp!("\n"), lit!("e")]; ); + check!("+1ab\tcd\r\n+,.", [lit!("+1ab"), sp!("\t"), lit!("cd"), sp!("\r\n"), lit!("+,.")]; ); + // whitespace and literals can be intermixed + check!("a\tb", [lit!("a\tb")]; ); + check!("a\tb", [sp!("a\tb")]; ); // 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", [sp!(" \t"), num!(Year)]; year: 987); + check!(" \t987๐Ÿค ", [sp!(" \t"), num!(Year), lit!("๐Ÿค ")]; year: 987); check!("5", [num!(Year)]; year: 5); check!("5\0", [num!(Year)]; TOO_LONG); check!("\x005", [num!(Year)]; INVALID); @@ -676,12 +728,14 @@ fn test_parse() { 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 1234", [num!(Year), sp!(" "), 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!("1234xx1234", [num!(Year), lit!("xx"), num!(Year)]; year: 1234); + check!("1234 x 1234", [num!(Year), sp!(" "), lit!("x"), sp!(" "), num!(Year)]; year: 1234); + check!("1234 x 1235", [num!(Year), sp!(" "), lit!("x"), sp!(" "), lit!("1235")]; year: 1234); // signed numeric check!("-42", [num!(Year)]; year: -42); @@ -690,10 +744,10 @@ fn test_parse() { check!("+0042", [num!(Year)]; year: 42); 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!(" - 42", [num!(Year)]; INVALID); - check!(" + 42", [num!(Year)]; INVALID); + check!(" -42195", [sp!(" "), num!(Year)]; year: -42195); + 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); @@ -701,26 +755,28 @@ fn test_parse() { 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); - check!(" -345", [num!(Ordinal)]; INVALID); + check!("\t345", [sp!("\t"), num!(Ordinal)]; ordinal: 345); + check!(" +345", [sp!(" "), num!(Ordinal)]; INVALID); + check!(" -345", [sp!(" "), num!(Ordinal)]; INVALID); // various numeric fields check!("1234 5678", - [num!(Year), num!(IsoYear)]; + [num!(Year), sp!(" "), num!(IsoYear)]; year: 1234, isoyear: 5678); check!("12 34 56 78", - [num!(YearDiv100), num!(YearMod100), num!(IsoYearDiv100), num!(IsoYearMod100)]; + [num!(YearDiv100), sp!(" "), num!(YearMod100), sp!(" "), num!(IsoYearDiv100), + sp!(" "), 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)]; + check!("1 2 3\t4 5\n6", + [num!(Month), sp!(" "), num!(Day), sp!(" "), num!(WeekFromSun), sp!("\t"), + num!(WeekFromMon), sp!(" "), num!(IsoWeek), sp!("\n"), 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), sp!(" "), num!(Ordinal), sp!(" "), 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), sp!(" "), num!(Minute), sp!(" "), num!(Second), sp!(" "), + num!(Nanosecond), sp!(" "), num!(Timestamp)]; hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234, timestamp: 567_890_123); @@ -766,7 +822,10 @@ fn test_parse() { 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", [sp!(" "), fix!(LowerAmPm)]; hour_div_12: 0); check!(" Am", [fix!(LowerAmPm)]; INVALID); + check!("a.m.", [fix!(LowerAmPm)]; INVALID); + check!("A.M.", [fix!(LowerAmPm)]; INVALID); check!("ame", [fix!(LowerAmPm)]; TOO_LONG); // `am` is parsed check!("a", [fix!(LowerAmPm)]; TOO_SHORT); check!("p", [fix!(LowerAmPm)]; TOO_SHORT); @@ -783,10 +842,14 @@ fn test_parse() { 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!(".421951", [fix!(Nanosecond)]; nanosecond: 421_951_000); + check!(".4219512", [fix!(Nanosecond)]; nanosecond: 421_951_200); + check!(".42195123", [fix!(Nanosecond)]; nanosecond: 421_951_230); 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!(".0000000009999999999999999999999999", [fix!(Nanosecond)]; nanosecond: 0); check!(".", [fix!(Nanosecond)]; TOO_SHORT); check!(".4x", [fix!(Nanosecond)]; TOO_LONG); check!(". 4", [fix!(Nanosecond)]; INVALID); @@ -826,6 +889,8 @@ fn test_parse() { check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID); // fixed: timezone offsets + + // TimezoneOffset check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); check!("+00:01", [fix!(TimezoneOffset)]; offset: 60); @@ -844,8 +909,12 @@ fn test_parse() { 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!("-12:34", [fix!(TimezoneOffset)]; offset: -754 * 60); check!(" +12:34", [fix!(TimezoneOffset)]; offset: 754 * 60); - check!("\t -12:34", [fix!(TimezoneOffset)]; offset: -754 * 60); + check!(" -12:34", [fix!(TimezoneOffset)]; offset: -754 * 60); + check!(" +12:34", [fix!(TimezoneOffset)]; INVALID); + check!(" -12:34", [fix!(TimezoneOffset)]; INVALID); check!("", [fix!(TimezoneOffset)]; TOO_SHORT); check!("+", [fix!(TimezoneOffset)]; TOO_SHORT); check!("+1", [fix!(TimezoneOffset)]; TOO_SHORT); @@ -854,20 +923,94 @@ fn test_parse() { 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!(TimezoneOffset)]; INVALID); + check!(" :Z", [fix!(TimezoneOffset)]; INVALID); + check!(" Z", [fix!(TimezoneOffset)]; INVALID); + check!(" z", [fix!(TimezoneOffset)]; INVALID); + + // TimezoneOffsetColon + check!("+00:00", [fix!(TimezoneOffsetColon)]; offset: 0); + check!("+0000", [fix!(TimezoneOffsetColon)]; offset: 0); + check!("-00:00", [fix!(TimezoneOffsetColon)]; offset: 0); + check!("-0000", [fix!(TimezoneOffsetColon)]; offset: 0); + check!("+00:01", [fix!(TimezoneOffsetColon)]; offset: 60); + check!("+0001", [fix!(TimezoneOffsetColon)]; offset: 60); + check!("-00:01", [fix!(TimezoneOffsetColon)]; offset: -60); + check!("-0001", [fix!(TimezoneOffsetColon)]; offset: -60); + check!("+00:30", [fix!(TimezoneOffsetColon)]; offset: 30 * 60); + check!("+0030", [fix!(TimezoneOffsetColon)]; offset: 30 * 60); + check!("-00:30", [fix!(TimezoneOffsetColon)]; offset: -30 * 60); + check!("-0030", [fix!(TimezoneOffsetColon)]; offset: -30 * 60); + check!("+04:56", [fix!(TimezoneOffsetColon)]; offset: 296 * 60); + check!("+0456", [fix!(TimezoneOffsetColon)]; offset: 296 * 60); + check!("-04:56", [fix!(TimezoneOffsetColon)]; offset: -296 * 60); + check!("-0456", [fix!(TimezoneOffsetColon)]; offset: -296 * 60); + check!("+24:00", [fix!(TimezoneOffsetColon)]; offset: 24 * 60 * 60); + check!("+2400", [fix!(TimezoneOffsetColon)]; offset: 24 * 60 * 60); + check!("#12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("#1234", [fix!(TimezoneOffsetColon)]; INVALID); + check!("1234", [fix!(TimezoneOffsetColon)]; INVALID); + check!("12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("+12:34 ", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12:34", [fix!(TimezoneOffsetColon)]; offset: 754 * 60); + check!("+1234", [fix!(TimezoneOffsetColon)]; offset: 754 * 60); + check!("-12:34", [fix!(TimezoneOffsetColon)]; offset: -754 * 60); + check!("-1234", [fix!(TimezoneOffsetColon)]; offset: -754 * 60); + check!("\t+12:34", [fix!(TimezoneOffsetColon)]; offset: 754 * 60); + check!("\t\t+12:34", [fix!(TimezoneOffsetColon)]; INVALID); + check!("", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!("+", [fix!(TimezoneOffsetColon)]; TOO_SHORT); + check!(":", [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: 754 * 60); + check!("+12345", [fix!(TimezoneOffsetColon)]; TOO_LONG); + check!("+12345", [fix!(TimezoneOffsetColon), num!(Day)]; offset: 754 * 60, day: 5); + // testing `TimezoneOffsetColon` also tests `TimezoneOffsetDoubleColon` + // and `TimezoneOffsetTripleColon` for function `parse_internal`. + // No need for separate tests. + + // TimezoneOffsetZ 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!("Z ", [fix!(TimezoneOffsetZ)]; TOO_LONG); + check!(":Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(":z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("-Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" +Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!(" -Z", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+:Z", [fix!(TimezoneOffsetZ)]; INVALID); check!("Y", [fix!(TimezoneOffsetZ)]; INVALID); + check!("+00:01", [fix!(TimezoneOffsetZ)]; offset: 60); + check!("-00:01", [fix!(TimezoneOffsetZ)]; offset: -60); + check!("00:01", [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); + // Testing `TimezoneOffsetZ` also tests `TimezoneOffsetColonZ` + // in function `parse_internal`. + // So no need for separate tests. + + // TimezoneOffsetPermissive 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); + + // TimezoneName + check!("CEST", [fix!(TimezoneName)]; ); + check!("cest", [fix!(TimezoneName)]; ); + check!("XXXXXXXX", [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); // some practical examples check!("2015-02-04T14:37:05+09:00", @@ -875,23 +1018,57 @@ 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); + // XXX: known failures + //check!("2015-02-04T14:37:05+09 ", + // [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), + // num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset), + // lit!(" ")]; + // 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๐Ÿค ", + // [num!(Year), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), + // num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset), + // lit!("๐Ÿค ")]; + // 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", + check!("20150204143705.567", + [num!(Year), num!(Month), num!(Day), + num!(Hour), num!(Minute), num!(Second), fix!(Nanosecond)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, nanosecond: 567000000); + check!("20150204143705.567891", + [num!(Year), num!(Month), num!(Day), + num!(Hour), num!(Minute), num!(Second), fix!(Nanosecond)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, nanosecond: 567891000); + check!("20150204143705.567891023", + [num!(Year), num!(Month), num!(Day), + num!(Hour), num!(Minute), num!(Second), fix!(Nanosecond)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, nanosecond: 567891023); + 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")]; + 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!("๐Ÿค Mon, 10 Jun๐Ÿค 2013 09:32:37 GMT๐Ÿค ", + [lit!("๐Ÿค "), fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "), + fix!(ShortMonthName), lit!("๐Ÿค "), num!(Year), sp!(" "), num!(Hour), lit!(":"), + num!(Minute), lit!(":"), num!(Second), sp!(" "), lit!("GMT"), lit!("๐Ÿค ")]; 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!(" "), + [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); + 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); @@ -904,11 +1081,27 @@ fn test_parse() { check!("12345678901234.56789", [num!(Timestamp), fix!(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), lit!("-"), num!(Month), lit!("-"), num!(Day), lit!("T"), + num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), + internal_fix!(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), lit!("-"), num!(Month), lit!("-"), num!(Day), sp!(" "), + num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), + internal_fix!(TimezoneOffsetPermissive)]; + year: 2000, month: 1, day: 2, + hour_div_12: 0, hour_mod_12: 3, minute: 4, second: 5, + offset: 0); } #[cfg(test)] #[test] -fn test_rfc2822() { +fn test_parse_rfc2822() { use super::NOT_ENOUGH; use super::*; use crate::offset::FixedOffset; @@ -920,6 +1113,8 @@ fn test_rfc2822() { ("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 (UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // intermixed arbitrary whitespace + ("Tue, 20 Jan 2015\t17:35:20\t-0800\t\t(UTC)", Ok("Tue, 20 Jan 2015 17:35:20 -0800")), // intermixed arbitrary whitespace ( r"Tue, 20 Jan 2015 17:35:20 -0800 ( (UTC ) (\( (a)\(( \t ) ) \\( \) ))", Ok("Tue, 20 Jan 2015 17:35:20 -0800"), @@ -944,6 +1139,7 @@ fn test_rfc2822() { ("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 + ("Tue, 20 Jan 2015๐Ÿ˜ˆ17:35:20 -0800", Err(INVALID)), // bad character! ]; fn rfc2822_to_datetime(date: &str) -> ParseResult> { @@ -976,7 +1172,7 @@ fn test_rfc2822() { #[cfg(test)] #[test] -fn parse_rfc850() { +fn test_parse_rfc850() { use crate::{TimeZone, Utc}; static RFC850_FMT: &str = "%A, %d-%b-%y %T GMT"; @@ -990,8 +1186,7 @@ fn parse_rfc850() { // 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). + // Check that the rest of the weekdays parse correctly let testdates = [ (Utc.ymd(1994, 11, 7).and_hms(8, 49, 37), "Monday, 07-Nov-94 08:49:37 GMT"), (Utc.ymd(1994, 11, 8).and_hms(8, 49, 37), "Tuesday, 08-Nov-94 08:49:37 GMT"), @@ -1008,7 +1203,7 @@ fn parse_rfc850() { #[cfg(test)] #[test] -fn test_rfc3339() { +fn test_parse_rfc3339() { use super::*; use crate::offset::FixedOffset; use crate::DateTime; diff --git a/src/format/parsed.rs b/src/format/parsed.rs index e3fdc15014..fb45591ef9 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -14,6 +14,7 @@ use crate::oldtime::Duration as OldDuration; use crate::DateTime; use crate::Weekday; use crate::{Datelike, Timelike}; +use crate::dpl; /// Parsed parts of date and time. There are two classes of methods: /// @@ -649,11 +650,11 @@ impl Parsed { /// If parsed fields include an UTC offset, it also has to be consistent to /// [`offset`](#structfield.offset). pub fn to_datetime_with_timezone(&self, tz: &Tz) -> ParseResult> { - eprintln!("->to_datetime_with_timezone(tz)"); + dpl!("->to_datetime_with_timezone(tz)"); // if we have `timestamp` specified, guess an offset from that. let mut guessed_offset = 0; if let Some(timestamp) = self.timestamp { - eprintln!(" to_datetime_with_timezone: timestamp = {:?}", self.timestamp); + dpl!(" to_datetime_with_timezone: timestamp = {:?}", self.timestamp); // make a naive `DateTime` from given timestamp and (if any) nanosecond. // an empty `nanosecond` is always equal to zero, so missing nanosecond is fine. let nanosecond = self.nanosecond.unwrap_or(0); @@ -664,7 +665,7 @@ impl Parsed { // checks if the given `DateTime` has a consistent `Offset` with given `self.offset`. let check_offset = |dt: &DateTime| { - eprintln!(" to_datetime_with_timezone: check_offset = {:?}", self.offset); + dpl!(" to_datetime_with_timezone: check_offset = {:?}", self.offset); if let Some(offset) = self.offset { dt.offset().fix().local_minus_utc() == offset } else { @@ -675,24 +676,24 @@ impl Parsed { // `guessed_offset` should be correct when `self.timestamp` is given. // it will be 0 otherwise, but this is fine as the algorithm ignores offset for that case. let datetime = self.to_naive_datetime_with_offset(guessed_offset)?; - eprintln!(" to_datetime_with_timezone: datetime {:?}", datetime); + dpl!(" to_datetime_with_timezone: datetime {:?}", datetime); match tz.from_local_datetime(&datetime) { LocalResult::None => { - eprintln!("~~to_datetime_with_timezone: LocalResult::None return Err(IMPOSSIBLE)"); + dpl!("~~to_datetime_with_timezone: LocalResult::None return Err(IMPOSSIBLE)"); Err(IMPOSSIBLE) } LocalResult::Single(t) => { - eprintln!(" to_datetime_with_timezone: LocalResult::Single({:?})", t); + dpl!(" to_datetime_with_timezone: LocalResult::Single({:?})", t); if check_offset(&t) { - eprintln!("~~to_datetime_with_timezone: Ok({:?})", t); + dpl!("~~to_datetime_with_timezone: Ok({:?})", t); Ok(t) } else { - eprintln!("~~to_datetime_with_timezone: Err(IMPOSSIBLE)"); + dpl!("~~to_datetime_with_timezone: Err(IMPOSSIBLE)"); Err(IMPOSSIBLE) } } LocalResult::Ambiguous(min, max) => { - eprintln!("~~to_datetime_with_timezone: LocalResult::Ambiguous({:?}, {:?})", min, max); + dpl!("~~to_datetime_with_timezone: LocalResult::Ambiguous({:?}, {:?})", min, max); // try to disambiguate two possible local dates by offset. match (check_offset(&min), check_offset(&max)) { (false, false) => Err(IMPOSSIBLE), diff --git a/src/format/scan.rs b/src/format/scan.rs index 7334a3b2ed..0708477637 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -9,6 +9,7 @@ use super::{ParseResult, INVALID, OUT_OF_RANGE, TOO_SHORT}; use crate::Weekday; +use crate::dpl; /// Returns true when two slices are equal case-insensitively (in ASCII). /// Assumes that the `pattern` is already converted to lower case. @@ -198,9 +199,89 @@ pub(super) fn space(s: &str) -> ParseResult<&str> { } } -/// Consumes any number (including zero) of colon or spaces. +/// returns slice `s` remaining after first char +/// if `s.len() <= 1` then return an empty slice +pub (super) fn s_next(s: &str) -> &str { + if s.len() <= 1 { + return &s[s.len()..]; + } + match s.char_indices().nth(1) { + Some((offset, _)) => { + &s[offset..] + } + None => { + panic!("unexpected None for s {:?}.char_indices().nth(1)", s) + } + } +} + +/// Consume one whitespace from the start of `s` if the first `char` is +/// whitespace. +pub(super) fn space1(s: &str) -> &str { + match s.chars().next() { + Some(c) if c.is_whitespace() => { + s_next(s) + } + Some(_) | + None => s + } +} + +/// Allow a colon with possible whitespace padding. +/// Consumes zero or one of leading patterns +/// `":"`, `" "`, `" :"`, `": "`, or `" : "` pub(super) fn colon_or_space(s: &str) -> ParseResult<&str> { - Ok(s.trim_left_matches(|c: char| c == ':' || c.is_whitespace())) + dpl!(" ->colon_or_space({:?})", s); + let c0_ = match s.chars().next() { + Some(c) => c, + None => { + dpl!(" ~~colon_or_space Ok({:?})", s); + return Ok(s); + } + }; + if c0_ != ':' && !c0_.is_whitespace() { + dpl!(" ~~colon_or_space Ok({:?})", s); + return Ok(s); + } + let c1_ = s.chars().nth(1); + match (c0_, c1_) { + (c0, None) if c0 == ':' || c0.is_whitespace() => { + dpl!(" ~~colon_or_space Ok({:?})", s_next(s)); + return Ok(s_next(s)); + } + (c0, Some(c1)) if c0 == ':' && c1.is_whitespace() => { + dpl!(" ~~colon_or_space Ok({:?})", s_next(s_next(s))); + return Ok(s_next(s_next(s))); + } + (c0, Some(c1)) if c0 == ':' && !c1.is_whitespace() => { + dpl!(" ~~colon_or_space Ok({:?})", s_next(s)); + return Ok(s_next(s)); + } + (c0, Some(c1)) if c0.is_whitespace() && (!c1.is_whitespace() && c1 != ':') => { + dpl!(" ~~colon_or_space Ok({:?})", s_next(s)); + return Ok(s_next(s)); + } + _ => {}, + } + let c2_ = s.chars().nth(2); + match (c0_, c1_, c2_) { + (c0, Some(c1), None) if c0.is_whitespace() && c1 == ':' => { + dpl!(" ~~colon_or_space Ok({:?})", s_next(s_next(s))); + Ok(s_next(s_next(s))) + } + (c0, Some(c1), Some(c2)) if c0.is_whitespace() && c1 == ':' && !c2.is_whitespace() => { + dpl!(" ~~colon_or_space Ok({:?})", s_next(s_next(s))); + Ok(s_next(s_next(s))) + } + (c0, Some(c1), Some(c2)) if c0.is_whitespace() && c1 == ':' && c2.is_whitespace() => { + dpl!(" ~~colon_or_space Ok({:?})", s_next(s_next(s_next(s)))); + Ok(s_next(s_next(s_next(s)))) + } + _ => { + dpl!(" ~~colon_or_space Ok({:?})", s); + Ok(s) + } + } } /// Tries to parse `[-+]\d\d` continued by `\d\d`. Return an offset in seconds if possible. @@ -222,6 +303,7 @@ fn timezone_offset_internal( where F: FnMut(&str) -> ParseResult<&str>, { + dpl!(" ->timezone_offset_internal({:?}, F, {:?})", s, allow_missing_minutes); fn digits(s: &str) -> ParseResult<(u8, u8)> { let b = s.as_bytes(); if b.len() < 2 { @@ -237,6 +319,18 @@ where None => return Err(TOO_SHORT), }; s = &s[1..]; + dpl!(" timezone_offset_internal {} s {:?}", line!(), s); + + // special check for `Z` to return more accurate error `INVALID`. + // Otherwise the upcoming match for digits might return error `TOO_SHORT` + // which is confusing for the user. + match s.as_bytes().first() { + Some(&b'Z') + | Some(&b'z') => { + return Err(INVALID); + } + _ => {} + } // hours (00--99) let hours = match digits(s)? { @@ -244,9 +338,11 @@ where _ => return Err(INVALID), }; s = &s[2..]; + dpl!(" timezone_offset_internal {} s {:?}", line!(), s); // colons (and possibly other separators) s = consume_colon(s)?; + dpl!(" timezone_offset_internal {} s {:?}", line!(), s); // minutes (00--59) // if the next two items are digits then we have to add minutes @@ -266,8 +362,10 @@ where len if len == 0 => s, _ => return Err(TOO_SHORT), }; + dpl!(" timezone_offset_internal {} s {:?}", line!(), s); let seconds = hours * 3600 + minutes * 60; + dpl!(" ~~timezone_offset_internal return {:?}{:?}", if negative {'-'} else {'+'}, s); Ok((s, if negative { -seconds } else { seconds })) } @@ -276,17 +374,20 @@ pub(super) fn timezone_offset_zulu(s: &str, colon: F) -> ParseResult<(&str, i where F: FnMut(&str) -> ParseResult<&str>, { + dpl!(" ->timezone_offset_zulu({:?}, ...)", s); 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]); + dpl!(" timezone_offset_zulu b {:?} c {:?}", b, c); match (b | 32, c | 32) { (b't', b'c') => Ok((&s[3..], 0)), _ => Err(INVALID), } } else { + dpl!(" ~~timezone_offset_zulu INVALID {:?}", line!()); Err(INVALID) } } @@ -413,3 +514,31 @@ fn test_rfc2822_comments() { ); } } + +#[cfg(test)] +#[test] +fn test_colon_or_space() { + assert_eq!(colon_or_space(""), Ok("")); + assert_eq!(colon_or_space(" "), Ok("")); + assert_eq!(colon_or_space(":"), Ok("")); + assert_eq!(colon_or_space(" :"), Ok("")); + assert_eq!(colon_or_space(": "), Ok("")); + assert_eq!(colon_or_space(" : "), Ok("")); + assert_eq!(colon_or_space(" :: "), Ok(": ")); + assert_eq!(colon_or_space("๐Ÿ˜ธ"), Ok("๐Ÿ˜ธ")); + assert_eq!(colon_or_space("๐Ÿ˜ธ๐Ÿ˜ธ"), Ok("๐Ÿ˜ธ๐Ÿ˜ธ")); + assert_eq!(colon_or_space("๐Ÿ˜ธ:"), Ok("๐Ÿ˜ธ:")); + assert_eq!(colon_or_space("๐Ÿ˜ธ "), Ok("๐Ÿ˜ธ ")); + assert_eq!(colon_or_space(" ๐Ÿ˜ธ"), Ok("๐Ÿ˜ธ")); + assert_eq!(colon_or_space(":๐Ÿ˜ธ"), Ok("๐Ÿ˜ธ")); + assert_eq!(colon_or_space(":๐Ÿ˜ธ "), Ok("๐Ÿ˜ธ ")); + assert_eq!(colon_or_space(" :๐Ÿ˜ธ"), Ok("๐Ÿ˜ธ")); + assert_eq!(colon_or_space(" :๐Ÿ˜ธ "), Ok("๐Ÿ˜ธ ")); + assert_eq!(colon_or_space(" :๐Ÿ˜ธ:"), Ok("๐Ÿ˜ธ:")); + assert_eq!(colon_or_space(": ๐Ÿ˜ธ"), Ok("๐Ÿ˜ธ")); + assert_eq!(colon_or_space(": ๐Ÿ˜ธ"), Ok(" ๐Ÿ˜ธ")); + assert_eq!(colon_or_space(": :๐Ÿ˜ธ"), Ok(":๐Ÿ˜ธ")); + assert_eq!(colon_or_space(" : ๐Ÿ˜ธ"), Ok("๐Ÿ˜ธ")); + assert_eq!(colon_or_space(" ::๐Ÿ˜ธ"), Ok(":๐Ÿ˜ธ")); + assert_eq!(colon_or_space(" :: ๐Ÿ˜ธ"), Ok(": ๐Ÿ˜ธ")); +} diff --git a/src/format/strftime.rs b/src/format/strftime.rs index b552a72028..a6d9f38e4c 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -177,6 +177,7 @@ Notes: #[cfg(feature = "unstable-locales")] use super::{locales, Locale}; use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad}; +use crate::dpl; #[cfg(feature = "unstable-locales")] type Fmt<'a> = Vec>; @@ -266,7 +267,7 @@ impl<'a> Iterator for StrftimeItems<'a> { type Item = Item<'a>; fn next(&mut self) -> Option> { - eprintln!(" ->StrftimeItems::Iterator::next()"); + dpl!(" ->StrftimeItems::Iterator::next()"); // we have some reconstructed items to return if !self.recons.is_empty() { let item; @@ -279,6 +280,7 @@ impl<'a> Iterator for StrftimeItems<'a> { item = self.recons[0].clone(); self.recons = &self.recons[1..]; } + dpl!(" ~~StrftimeItems::Iterator::next() return Some({:?})", item); return Some(item); } @@ -287,13 +289,14 @@ impl<'a> Iterator for StrftimeItems<'a> { match self.remainder.chars().next() { // we are done None => { - eprintln!("None"); + dpl!("None"); + dpl!(" ~~StrftimeItems::Iterator::next() return None"); None }, // the next item is a specifier Some('%') => { - eprintln!("%"); + dpl!("%"); self.remainder = &self.remainder[1..]; macro_rules! next { @@ -303,7 +306,10 @@ impl<'a> Iterator for StrftimeItems<'a> { self.remainder = &self.remainder[x.len_utf8()..]; x } - None => return Some(Item::Error), // premature end of string + None => { + dpl!(" ~~StrftimeItems::Iterator::next() return Some(Error)"); + return Some(Item::Error); // premature end of string + } } }; } @@ -318,6 +324,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) { + dpl!(" ~~StrftimeItems::Iterator::next() return Some(Error)"); return Some(Item::Error); } @@ -463,47 +470,78 @@ impl<'a> Iterator for StrftimeItems<'a> { if let Some(new_pad) = pad_override { match item { Item::Numeric(ref kind, _pad) if self.recons.is_empty() => { + dpl!(" ~~StrftimeItems::Iterator::next() return Some(Item::Numeric({:?}, {:?}))", kind.clone(), new_pad); Some(Item::Numeric(kind.clone(), new_pad)) } - _ => Some(Item::Error), // no reconstructed or non-numeric item allowed + _ => { + dpl!(" ~~StrftimeItems::Iterator::next() return Some(Error)"); + Some(Item::Error) // no reconstructed or non-numeric item allowed + } } } else { + dpl!(" ~~StrftimeItems::Iterator::next() return Some({:?})", item); Some(item) } } - // the next item is space + // whitespace are analyzed char by char Some(c) if c.is_whitespace() => { - eprintln!("c.is_whitespace()"); - // `%` is not a whitespace, so `c != '%'` is redundant - let mut nextspec = self - .remainder - .find(|c: char| !c.is_whitespace()) - .unwrap_or(self.remainder.len()); - if nextspec > 1 { - eprintln!(" nextspec {:?} set to 1", nextspec); - nextspec = 1; + dpl!("Some(c).is_whitespace {:?}", c); + dpl!(" remainder {:?}", self.remainder); + let ws = self.remainder; + let mut end: usize = 0; + dpl!(" self.remainder.char_indices()"); + for (offset, c_) in self.remainder.char_indices() { + if ! c_.is_whitespace() { + break; + } + // advance `end` by 1 char + end = offset; + dpl!(" ({:?}, {:?}) end {:?}", offset, c_, end); } - assert!(nextspec > 0); - let item = sp!(&self.remainder[..nextspec]); - eprintln!(" remainder {:?}", self.remainder); - self.remainder = &self.remainder[nextspec..]; - eprintln!(" remainder {:?}", self.remainder); + // get the offset of the last char too + end += match &self.remainder[end..].char_indices().nth(1) { + Some((offset, _c)) => *offset, + None => self.remainder[end..].len(), + }; + dpl!(" remainder end {:?}, len {:?}", end, self.remainder.len()); + self.remainder = &self.remainder[end..]; + dpl!(" remainder {:?}", self.remainder); + let item = sp!(&ws[..end]); + dpl!(" ~~StrftimeItems::Iterator::next() return Some({:?})", item); Some(item) } - // the next item is literal - _l => { - eprintln!("literal {:?}", _l); - let nextspec = self - .remainder - .find(|c: char| c.is_whitespace() || c == '%') - .unwrap_or(self.remainder.len()); - assert!(nextspec > 0); - let item = lit!(&self.remainder[..nextspec]); - eprintln!(" remainder {:?}", self.remainder); - self.remainder = &self.remainder[nextspec..]; - eprintln!(" remainder {:?}", self.remainder); + // literals are analyzed char by char + Some(_c) => { + dpl!("Some(c) (literal {:?})", _c); + dpl!(" remainder {:?}", self.remainder); + let ws = self.remainder; + let mut end: usize = 0; + dpl!(" self.remainder.char_indices()"); + fn is_literal(c: &char) -> bool { + if ! c.is_whitespace() && c != &'%' { + return true; + } + false + } + for (offset, c_) in self.remainder.char_indices() { + if ! is_literal(&c_) { + break; + } + // advance `end` by 1 char + end = offset; + dpl!(" ({:?}, {:?}) end {:?}", offset, c_, end); + } + // get the offset of the last char too + end += match &self.remainder[end..].char_indices().nth(1) { + Some((offset, _c)) => *offset, + None => self.remainder[end..].len(), + }; + self.remainder = &self.remainder[end..]; + dpl!(" remainder {:?}", self.remainder); + let item = lit!(&ws[..end]); + dpl!(" ~~StrftimeItems::Iterator::next() return Some({:?})", item); Some(item) } } @@ -515,8 +553,11 @@ impl<'a> Iterator for StrftimeItems<'a> { fn test_strftime_items() { fn parse_and_collect(s: &str) -> Vec> { // map any error into `[Item::Error]`. useful for easy testing. + dpl!("parse_and_collect({:?})", s); let items = StrftimeItems::new(s); + dpl!(" items: {:?}", &items); let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) }); + dpl!(" items: {:?}", &items); items.collect::>>().unwrap_or_else(|| vec![Item::Error]) } @@ -534,6 +575,7 @@ fn test_strftime_items() { parse_and_collect("%Y-%m-%d"), [num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)] ); + assert_eq!(parse_and_collect("%Y--%m"), [num0!(Year), lit!("--"), num0!(Month)]); 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("%"), [Item::Error]); @@ -558,7 +600,10 @@ fn test_strftime_items() { 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"), [fix!(TimezoneOffset)]); + assert_eq!(parse_and_collect("%:z"), [fix!(TimezoneOffsetColon)]); + assert_eq!(parse_and_collect("%Z"), [fix!(TimezoneName)]); + assert_eq!(parse_and_collect("%ZZZZ"), [fix!(TimezoneName), lit!("ZZZ")]); assert_eq!(parse_and_collect("%#z"), [internal_fix!(TimezoneOffsetPermissive)]); assert_eq!(parse_and_collect("%#m"), [Item::Error]); } diff --git a/src/lib.rs b/src/lib.rs index eed9b259ac..3b01b1d646 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -531,3 +531,29 @@ macro_rules! matches { } } } + +/// debug eprintln! +#[doc(hidden)] +#[macro_export] +macro_rules! dpl { + ( + $($args:tt)* + ) => { + #[cfg(test)] + eprintln!($($args)*) + } +} +//pub use dpl; + +/// debug eprint! +#[doc(hidden)] +#[macro_export] +macro_rules! dp { + ( + $($args:tt)* + ) => { + #[cfg(test)] + eprint!($($args)*) + } +} +//pub use dp; diff --git a/src/naive/date.rs b/src/naive/date.rs index f54432f26f..415a7064a1 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -21,6 +21,7 @@ use crate::month::Months; use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; use crate::oldtime::Duration as OldDuration; use crate::{Datelike, Duration, Weekday}; +use crate::dpl; use super::internals::{self, DateImpl, Mdf, Of, YearFlags}; use super::isoweek; @@ -592,6 +593,7 @@ impl NaiveDate { /// assert!(parse_from_str("Sat, 09 Aug 2013", "%a, %d %b %Y").is_err()); /// ``` pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult { + dpl!("parse_from_str({:?}, {:?})", s, fmt); let mut parsed = Parsed::new(); parse(&mut parsed, s, StrftimeItems::new(fmt))?; parsed.to_naive_date() @@ -1952,13 +1954,10 @@ impl str::FromStr for NaiveDate { fn from_str(s: &str) -> ParseResult { const ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Year, Pad::Zero), - Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Month, Pad::Zero), - Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Day, Pad::Zero), - Item::Space(""), ]; let mut parsed = Parsed::new(); @@ -2641,24 +2640,28 @@ mod tests { // valid cases let valid = [ "-0000000123456-1-2", - " -123456 - 1 - 2 ", + "-123456-1-2", "-12345-1-2", "-1234-12-31", "-7-6-5", "350-2-28", "360-02-29", "0360-02-29", - "2015-2 -18", + "2015-2-18", + "2015-02-18", "+70-2-18", "+70000-2-18", "+00007-2-18", ]; for &s in &valid { + eprintln!("test_date_from_str test case {:?}", 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, @@ -2666,6 +2669,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 \ @@ -2695,7 +2699,7 @@ mod tests { Ok(ymd(2014, 5, 7)) ); // ignore time and offset assert_eq!( - NaiveDate::parse_from_str("2015-W06-1=2015-033", "%G-W%V-%u = %Y-%j"), + NaiveDate::parse_from_str("2015-W06-1=2015-033", "%G-W%V-%u=%Y-%j"), Ok(ymd(2015, 2, 2)) ); assert_eq!( diff --git a/src/naive/datetime/mod.rs b/src/naive/datetime/mod.rs index 64a7921a21..176827abeb 100644 --- a/src/naive/datetime/mod.rs +++ b/src/naive/datetime/mod.rs @@ -1621,23 +1621,17 @@ impl str::FromStr for NaiveDateTime { fn from_str(s: &str) -> ParseResult { const ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Year, Pad::Zero), - Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Month, Pad::Zero), - Item::Space(""), Item::Literal("-"), Item::Numeric(Numeric::Day, Pad::Zero), - Item::Space(""), - Item::Literal("T"), // XXX shouldn't this be case-insensitive? + Item::Literal("T"), Item::Numeric(Numeric::Hour, Pad::Zero), - Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Minute, Pad::Zero), - Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Second, Pad::Zero), Item::Fixed(Fixed::Nanosecond), - Item::Space(""), ]; let mut parsed = Parsed::new(); diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index f882ccd454..9a39139ad8 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -1302,14 +1302,11 @@ impl str::FromStr for NaiveTime { fn from_str(s: &str) -> ParseResult { const ITEMS: &[Item<'static>] = &[ Item::Numeric(Numeric::Hour, Pad::Zero), - Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Minute, Pad::Zero), - Item::Space(""), Item::Literal(":"), Item::Numeric(Numeric::Second, Pad::Zero), Item::Fixed(Fixed::Nanosecond), - Item::Space(""), ]; let mut parsed = Parsed::new(); diff --git a/src/naive/time/tests.rs b/src/naive/time/tests.rs index b853c9f22d..024d46c5ca 100644 --- a/src/naive/time/tests.rs +++ b/src/naive/time/tests.rs @@ -189,19 +189,24 @@ fn test_time_fmt() { assert_eq!(format!("{:30}", NaiveTime::from_hms_milli(3, 5, 7, 9)), "03:05:07.009"); } +// LAST WORKING HERE 20220823 need to add more tests for new behavior + #[test] -fn test_date_from_str() { +fn test_time_parse_from_str() { // valid cases let valid = [ "0:0:0", "0:0:0.0000000", "0:0:0.0000003", - " 4 : 3 : 2.1 ", - " 09:08:07 ", - " 9:8:07 ", + "4:3:2.1", + "09:08:07", + "09:08:07.123", + "09:08:07.123", + "9:8:07", "23:59:60.373929310237", ]; for &s in &valid { + eprintln!("test_time_parse_from_str: test case {:?}", s); let d = match s.parse::() { Ok(d) => d, Err(e) => panic!("parsing `{}` has failed: {}", s, e), @@ -238,7 +243,7 @@ fn test_date_from_str() { } #[test] -fn test_time_parse_from_str() { +fn test_naivetime_parse_from_str() { let hms = NaiveTime::from_hms; assert_eq!( NaiveTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), diff --git a/src/offset/local/mod.rs b/src/offset/local/mod.rs index 0058b20c76..81fc9ab326 100644 --- a/src/offset/local/mod.rs +++ b/src/offset/local/mod.rs @@ -227,7 +227,7 @@ mod tests { #[test] #[cfg(unix)] fn try_verify_against_date_command() { - let date_path = "/usr/bin/dateDONOTRUN"; + let date_path = "/usr/bin/date DO NOT RUN THIS TEST IS RIDICULOUSLY LONG"; // DO NOT COMMIT THIS if !path::Path::new(date_path).exists() { // date command not found, skipping diff --git a/src/offset/mod.rs b/src/offset/mod.rs index 5c48bd40d3..a8c9c4cd36 100644 --- a/src/offset/mod.rs +++ b/src/offset/mod.rs @@ -24,6 +24,7 @@ use crate::format::{parse, ParseResult, Parsed, StrftimeItems}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::Weekday; use crate::{Date, DateTime}; +use crate::{dp, dpl}; mod fixed; pub use self::fixed::FixedOffset; @@ -426,21 +427,15 @@ pub trait TimeZone: Sized + Clone { /// See also [`DateTime::parse_from_str`] which gives a [`DateTime`] with /// parsed [`FixedOffset`]. fn datetime_from_str(&self, s: &str, fmt: &str) -> ParseResult> { - eprintln!("->datetime_from_str({:?}, {:?})", s, fmt); + dpl!("->datetime_from_str({:?}, {:?})", s, fmt); let mut parsed = Parsed::new(); //eprintln!(" datetime_from_str: parsed {:?}", parsed); - eprint!(" datetime_from_str: "); + dp!(" datetime_from_str: "); dbg!(&parsed); let items = StrftimeItems::new(fmt); - //eprintln!(" datetime_from_str: items {:?}", items); - eprint!(" datetime_from_str: "); - dbg!(&items); parse(&mut parsed, s, items)?; - //eprintln!(" datetime_from_str: parsed {:?}", parsed); - eprint!(" datetime_from_str: "); - dbg!(&parsed); let r = parsed.to_datetime_with_timezone(self); - eprintln!("~~datetime_from_str: r {:?}", r); + dpl!("~~datetime_from_str: r {:?}", r); r }