Skip to content
109 changes: 92 additions & 17 deletions src/format/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,32 @@ where
parse_internal(parsed, s, items)
}

fn get_numeric_item_len<'a, B>(item: Option<&B>) -> Option<usize>
where
B: Borrow<Item<'a>>,
{
use super::Fixed::*;

item.map(|i| match *i.borrow() {
Item::Fixed(Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot })) => 3,
Item::Fixed(Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot })) => 6,
Item::Fixed(Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot })) => 9,
Item::Literal(prefix) => {
prefix.as_bytes().iter().take_while(|&&c| c.is_ascii_digit()).count()
}
_ => 0,
})
}

fn recalculate_numeric_item_width<'a, B>(s: &str, next_item: Option<&B>) -> usize
where
B: Borrow<Item<'a>>,
{
let next_width = get_numeric_item_len(next_item).unwrap_or(0);
let numeric_bytes_available = s.as_bytes().iter().take_while(|&&c| c.is_ascii_digit()).count();
numeric_bytes_available - next_width
}

fn parse_internal<'a, 'b, I, B>(
parsed: &mut Parsed,
mut s: &'b str,
Expand All @@ -300,7 +326,10 @@ where
}};
}

for item in items {
let mut items_iter = items.peekable();

while let Some(item) = items_iter.next() {
let next_item = items_iter.peek();
match *item.borrow() {
Item::Literal(prefix) => {
if s.len() < prefix.len() {
Expand Down Expand Up @@ -336,7 +365,16 @@ where
use super::Numeric::*;
type Setter = fn(&mut Parsed, i64) -> ParseResult<()>;

let (width, signed, set): (usize, bool, Setter) = match *spec {
s = s.trim_start();
let mut substr = s;
let negative = s.starts_with('-');
let positive = s.starts_with('+');
let starts_with_sign = negative || positive;
if starts_with_sign {
substr = &s[1..];
}

let (mut width, signed, set): (usize, bool, Setter) = match *spec {
Year => (4, true, Parsed::set_year),
YearDiv100 => (2, false, Parsed::set_year_div_100),
YearMod100 => (2, false, Parsed::set_year_mod_100),
Expand All @@ -356,26 +394,25 @@ where
Minute => (2, false, Parsed::set_minute),
Second => (2, false, Parsed::set_second),
Nanosecond => (9, false, Parsed::set_nanosecond),
Timestamp => (usize::MAX, false, Parsed::set_timestamp),
Timestamp => (
recalculate_numeric_item_width(substr, next_item),
false,
Parsed::set_timestamp,
),

// for the future expansion
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));
0i64.checked_sub(v).ok_or(OUT_OF_RANGE)?
} else if s.starts_with('+') {
try_consume!(scan::number(&s[1..], 1, usize::MAX))
} else {
// if there is no explicit sign, we respect the original `width`
try_consume!(scan::number(s, 1, width))
}
} else {
try_consume!(scan::number(s, 1, width))
};
if starts_with_sign && signed {
width = recalculate_numeric_item_width(substr, next_item);
} else if starts_with_sign {
return Err(INVALID);
}

let v = try_consume!(scan::number(substr, 1, width));
let v = if negative { 0i64.checked_sub(v).ok_or(OUT_OF_RANGE)? } else { v };

set(parsed, v)?;
}

Expand Down Expand Up @@ -765,6 +802,7 @@ mod tests {
&[num(Year), Space(" "), Literal("x"), Space(" "), Literal("1235")],
parsed!(year: 1234),
);
check("12341235", &[num(Year), Literal("1235")], parsed!(year: 1234));

// signed numeric
check("-42", &[num(Year)], parsed!(year: -42));
Expand All @@ -777,9 +815,12 @@ mod tests {
check(" -42195", &[num(Year)], parsed!(year: -42195));
check(" +42195", &[num(Year)], parsed!(year: 42195));
check(" -42195", &[num(Year)], parsed!(year: -42195));
check(" -42195123", &[num(Year), Literal("123")], parsed!(year: -42195));
check(" +42195", &[num(Year)], parsed!(year: 42195));
check(" +42195123", &[num(Year), Literal("123")], parsed!(year: 42195));
check("-42195 ", &[num(Year)], Err(TOO_LONG));
check("+42195 ", &[num(Year)], Err(TOO_LONG));
check("+42195123 ", &[num(Year), Literal("123")], Err(TOO_LONG));
check(" - 42", &[num(Year)], Err(INVALID));
check(" + 42", &[num(Year)], Err(INVALID));
check(" -42195", &[Space(" "), num(Year)], parsed!(year: -42195));
Expand Down Expand Up @@ -1482,6 +1523,15 @@ mod tests {
second: 5, nanosecond: 567000000
),
);
check(
"20151230204",
&[
num(Year), Literal("123"), num(Month), num(Day)
],
parsed!(
year: 2015, month: 2, day: 4
),
);
check(
"Mon, 10 Jun 2013 09:32:37 GMT",
&[
Expand Down Expand Up @@ -1537,6 +1587,31 @@ mod tests {
&[num(Timestamp), Literal("."), num(Nanosecond)],
parsed!(nanosecond: 56_789, timestamp: 12_345_678_901_234),
);
check(
"12345678901234111",
&[num(Timestamp), Literal("111")],
parsed!(timestamp: 12_345_678_901_234),
);
check(
"12345678901234567",
&[num(Timestamp), internal_fixed(Nanosecond3NoDot)],
parsed!(nanosecond: 567_000_000, timestamp: 12_345_678_901_234),
);
check(
"12345678901234567",
&[num(Timestamp), internal_fixed(Nanosecond3NoDot)],
parsed!(nanosecond: 567_000_000, timestamp: 12_345_678_901_234),
);
check(
"12345678901234567890",
&[num(Timestamp), internal_fixed(Nanosecond6NoDot)],
parsed!(nanosecond: 567_890_000, timestamp: 12_345_678_901_234),
);
check(
"12345678901234567890123",
&[num(Timestamp), internal_fixed(Nanosecond9NoDot)],
parsed!(nanosecond: 567_890_123, timestamp: 12_345_678_901_234),
);
check(
"12345678901234.56789",
&[num(Timestamp), fixed(Fixed::Nanosecond)],
Expand Down
6 changes: 4 additions & 2 deletions src/format/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ use crate::Weekday;
/// Any number that does not fit in `i64` is an error.
#[inline]
pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> {
assert!(min <= max);

// We are only interested in ascii numbers, so we can work with the `str` as bytes. We stop on
// the first non-numeric byte, which may be another ascii character or beginning of multi-byte
// UTF-8 character.
Expand All @@ -25,6 +23,10 @@ pub(super) fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)
return Err(TOO_SHORT);
}

if min > max {
return Err(INVALID);
}

let mut n = 0i64;
for (i, c) in bytes.iter().take(max).cloned().enumerate() {
// cloned() = copied()
Expand Down
16 changes: 16 additions & 0 deletions src/naive/datetime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ fn test_datetime_parse_from_str() {
NaiveDateTime::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))
); // ignore offset
assert_eq!(
NaiveDateTime::parse_from_str("2014123-5-7T12:34:56+09:30", "%Y123-%m-%dT%H:%M:%S%z"),
Ok(ymdhms(2014, 5, 7, 12, 34, 56))
); // ignore offset
assert_eq!(
NaiveDateTime::parse_from_str("2015-W06-1 000000", "%G-W%V-%u%H%M%S"),
Ok(ymdhms(2015, 2, 2, 0, 0, 0))
Expand Down Expand Up @@ -218,14 +222,26 @@ fn test_datetime_parse_from_str() {
NaiveDateTime::parse_from_str("1441497364.649", "%s%.3f"),
Ok(ymdhmsn(2015, 9, 5, 23, 56, 4, 649000000))
);
assert_eq!(
NaiveDateTime::parse_from_str("1441497364649", "%s%3f"),
Ok(ymdhmsn(2015, 9, 5, 23, 56, 4, 649000000))
);
assert_eq!(
NaiveDateTime::parse_from_str("1497854303.087654", "%s%.6f"),
Ok(ymdhmsn(2017, 6, 19, 6, 38, 23, 87654000))
);
assert_eq!(
NaiveDateTime::parse_from_str("1497854303087654", "%s%6f"),
Ok(ymdhmsn(2017, 6, 19, 6, 38, 23, 87654000))
);
assert_eq!(
NaiveDateTime::parse_from_str("1437742189.918273645", "%s%.9f"),
Ok(ymdhmsn(2015, 7, 24, 12, 49, 49, 918273645))
);
assert_eq!(
NaiveDateTime::parse_from_str("1437742189918273645", "%s%9f"),
Ok(ymdhmsn(2015, 7, 24, 12, 49, 49, 918273645))
);
}

#[test]
Expand Down
Loading