Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed Feb 16, 2024
1 parent b2b7ef5 commit 440391a
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 98 deletions.
122 changes: 48 additions & 74 deletions src/format/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ pub(crate) fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseRes
///
/// - Padding-agnostic (for numeric items).
/// The [`Pad`](./enum.Pad.html) field is completely ignored,
/// so one can prepend any number of zeroes before numbers.
/// so one can prepend any number of whitespace then any number of zeroes before numbers.
///
/// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`.
pub fn parse<'a, I, B>(parsed: &mut Parsed, s: &str, items: I) -> ParseResult<()>
Expand Down Expand Up @@ -326,37 +326,13 @@ where
s = &s[prefix.len()..];
}

Item::Space(item_space) => {
for expect in item_space.chars() {
let actual = match s.chars().next() {
Some(c) => c,
None => {
return Err((s, TOO_SHORT));
}
};
if expect != actual {
return Err((s, INVALID));
}
// advance `s` forward 1 char
s = scan::s_next(s);
}
Item::Space(_) => {
s = s.trim_start();
}

#[cfg(feature = "alloc")]
Item::OwnedSpace(ref item_space) => {
for expect in item_space.chars() {
let actual = match s.chars().next() {
Some(c) => c,
None => {
return Err((s, TOO_SHORT));
}
};
if expect != actual {
return Err((s, INVALID));
}
// advance `s` forward 1 char
s = scan::s_next(s);
}
Item::OwnedSpace(_) => {
s = s.trim_start();
}

Item::Numeric(ref spec, ref _pad) => {
Expand Down Expand Up @@ -389,6 +365,7 @@ where
Internal(ref int) => match int._dummy {},
};

s = s.trim_start();
let v = if signed {
if s.starts_with('-') {
let v = try_consume!(scan::number(&s[1..], 1, usize::MAX));
Expand Down Expand Up @@ -481,9 +458,8 @@ where
| &TimezoneOffsetDoubleColon
| &TimezoneOffsetTripleColon
| &TimezoneOffset => {
s = scan::trim1(s);
let offset = try_consume!(scan::timezone_offset(
s,
s.trim_start(),
scan::consume_colon_maybe,
false,
false,
Expand All @@ -493,9 +469,8 @@ where
}

&TimezoneOffsetColonZ | &TimezoneOffsetZ => {
s = scan::trim1(s);
let offset = try_consume!(scan::timezone_offset(
s,
s.trim_start(),
scan::consume_colon_maybe,
true,
false,
Expand All @@ -506,9 +481,8 @@ where
&Internal(InternalFixed {
val: InternalInternal::TimezoneOffsetPermissive,
}) => {
s = scan::trim1(s);
let offset = try_consume!(scan::timezone_offset(
s,
s.trim_start(),
scan::consume_colon_maybe,
true,
true,
Expand Down Expand Up @@ -544,12 +518,13 @@ where

/// Accepts a relaxed form of RFC3339.
/// A space or a 'T' are accepted as the separator between the date and time
/// parts.
/// parts. Additional spaces are allowed between each component.
///
/// ```
/// # use chrono::{DateTime, offset::FixedOffset};
/// "2000-01-02T03:04:05Z".parse::<DateTime<FixedOffset>>()?;
/// "2000-01-02 03:04:05Z".parse::<DateTime<FixedOffset>>()?;
/// "2012-12-12T12:12:12Z".parse::<DateTime<FixedOffset>>()?;
/// "2012-12-12 12:12:12Z".parse::<DateTime<FixedOffset>>()?;
/// "2012- 12-12T12: 12:12Z".parse::<DateTime<FixedOffset>>()?;
/// # Ok::<(), chrono::ParseError>(())
/// ```
impl str::FromStr for DateTime<FixedOffset> {
Expand Down Expand Up @@ -652,31 +627,31 @@ mod tests {
parses(" ", &[Space(" ")]);
parses(" ", &[Space(" ")]);
parses(" ", &[Space(" ")]);
check(" ", &[Space("")], Err(TOO_LONG));
check(" ", &[Space(" ")], Err(TOO_LONG));
check(" ", &[Space(" ")], Err(TOO_LONG));
check(" ", &[Space(" ")], Err(TOO_LONG));
check("", &[Space(" ")], Err(TOO_SHORT));
check(" ", &[Space(" ")], Err(TOO_SHORT));
check(" ", &[Space(" ")], Err(TOO_SHORT));
check(" ", &[Space(" "), Space(" ")], Err(TOO_SHORT));
check(" ", &[Space(" "), Space(" ")], Err(TOO_SHORT));
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(" ")]);
check("\t", &[Space("")], Err(TOO_LONG));
check(" \n\r \n", &[Space("")], Err(TOO_LONG));
parses("\t", &[Space("")]);
parses(" \n\r \n", &[Space("")]);
parses("\t", &[Space("\t")]);
check("\t", &[Space(" ")], Err(INVALID));
check(" ", &[Space("\t")], Err(INVALID));
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")]);
check(" \t\n", &[Space(" \t")], Err(TOO_LONG));
check(" \n\t", &[Space(" \t\n")], Err(INVALID));
parses(" \t\n", &[Space(" \t")]);
parses(" \n\t", &[Space(" \t\n")]);
parses("\u{2002}", &[Space("\u{2002}")]);
// most unicode whitespace characters
parses(
Expand All @@ -692,11 +667,11 @@ mod tests {
]
);
check("a", &[Space("")], Err(TOO_LONG));
check("a", &[Space(" ")], Err(INVALID));
// a Space containing a literal can match a literal, but this should not be done
parses("a", &[Space("a")]);
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(INVALID));
check("abc", &[Space(" ")], Err(TOO_LONG));
check(" abc", &[Space("")], Err(TOO_LONG));
check(" abc", &[Space(" ")], Err(TOO_LONG));

Expand Down Expand Up @@ -743,7 +718,7 @@ mod tests {
//
check("x y", &[Literal("x"), Literal("y")], Err(INVALID));
parses("xy", &[Literal("x"), Space(""), Literal("y")]);
check("x y", &[Literal("x"), Space(""), Literal("y")], Err(INVALID));
parses("x y", &[Literal("x"), Space(""), Literal("y")]);
parses("x y", &[Literal("x"), Space(" "), Literal("y")]);

// whitespaces + literals
Expand Down Expand Up @@ -776,7 +751,7 @@ mod tests {
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)], Err(INVALID));
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));
Expand All @@ -788,9 +763,9 @@ mod tests {
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)], Err(INVALID));
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(INVALID));
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));
Expand All @@ -815,10 +790,10 @@ mod tests {
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)], Err(INVALID));
check(" +42195", &[num(Year)], Err(INVALID));
check(" -42195", &[num(Year)], Err(INVALID));
check(" +42195", &[num(Year)], Err(INVALID));
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));
Expand All @@ -835,7 +810,7 @@ mod tests {
check("345", &[num(Ordinal)], parsed!(ordinal: 345));
check("+345", &[num(Ordinal)], Err(INVALID));
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));
Expand All @@ -849,28 +824,27 @@ mod tests {
check(" +345", &[Space(" "), num(Ordinal)], Err(INVALID));
check(" -345", &[Space(" "), num(Ordinal)], Err(INVALID));

const S: Item = Space(" ");
// various numeric fields
check("1234 5678", &[num(Year), S, num(IsoYear)], parsed!(year: 1234, isoyear: 5678));
check("1234 5678", &[num(Year), S, num(IsoYear)], parsed!(year: 1234, isoyear: 5678));
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), S, num(YearMod100), S, num(IsoYearDiv100), S, num(IsoYearMod100)],
&[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 45",
&[num(Month), S, num(Day), S, num(WeekFromSun), S, num(NumDaysFromSun), num(IsoWeek)],
"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), S, num(WeekdayFromMon), S, num(Ordinal), S, num(Hour12)],
&[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), S, num(Minute), S, num(Second), S, num(Nanosecond), S, num(Timestamp)],
&[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),
);
}
Expand Down
25 changes: 1 addition & 24 deletions src/format/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,6 @@ pub(super) fn s_next(s: &str) -> &str {
}
}

/// If the first `char` is whitespace then consume it and return `s`.
/// Else return `s`.
pub(super) fn trim1(s: &str) -> &str {
match s.chars().next() {
Some(c) if c.is_whitespace() => s_next(s),
Some(_) | None => s,
}
}

/// Consumes one colon char `:` if it is at the front of `s`.
/// Always returns `Ok(s)`.
pub(crate) fn consume_colon_maybe(mut s: &str) -> ParseResult<&str> {
Expand Down Expand Up @@ -387,7 +378,7 @@ enum CommentState {
mod tests {
use super::{
comment_2822, consume_colon_maybe, nanosecond, nanosecond_fixed, s_next,
short_or_long_month0, short_or_long_weekday, space, timezone_offset_2822, trim1,
short_or_long_month0, short_or_long_weekday, space, timezone_offset_2822,
};
use crate::format::{INVALID, TOO_SHORT};
use crate::Weekday;
Expand Down Expand Up @@ -488,20 +479,6 @@ mod tests {
assert_eq!(s_next("a😾c"), "😾c");
}

#[test]
fn test_trim1() {
assert_eq!(trim1(""), "");
assert_eq!(trim1(" "), "");
assert_eq!(trim1("\t"), "");
assert_eq!(trim1("\t\t"), "\t");
assert_eq!(trim1(" "), " ");
assert_eq!(trim1("a"), "a");
assert_eq!(trim1("a "), "a ");
assert_eq!(trim1("ab"), "ab");
assert_eq!(trim1("😼"), "😼");
assert_eq!(trim1("😼b"), "😼b");
}

#[test]
fn test_consume_colon_maybe() {
assert_eq!(consume_colon_maybe(""), Ok(""));
Expand Down

0 comments on commit 440391a

Please sign in to comment.