added `FromStr` impls to naive date and time types.
also, previously `Numeric::Nanosecond` had a special left-aligned parsing behavior. this commit replaces that with a newly designated `Fixed::Nanosecond` which also handles an empty string which is possible with an integral number of seconds.
This commit is contained in:
parent
ca865e3c24
commit
76b0873722
|
@ -84,6 +84,7 @@ pub enum Numeric {
|
|||
/// The number of seconds since the last whole minute (FW=PW=2).
|
||||
Second,
|
||||
/// The number of nanoseconds since the last whole second (FW=PW=9).
|
||||
/// Note that this is *not* left-aligned; see also `Fixed::Nanosecond`.
|
||||
Nanosecond,
|
||||
/// The number of non-leap seconds since January 1, 1970 0:00:00 UTC (FW=1, PW=infinity).
|
||||
Timestamp,
|
||||
|
@ -119,6 +120,10 @@ pub enum Fixed {
|
|||
///
|
||||
/// Prints in upper case, reads in any case.
|
||||
UpperAmPm,
|
||||
/// An optional dot plus one or more digits for left-aligned nanoseconds.
|
||||
/// May print nothing, 3, 6 or 9 digits according to the available accuracy.
|
||||
/// See also `Numeric::Nanosecond`.
|
||||
Nanosecond,
|
||||
/// Timezone name.
|
||||
///
|
||||
/// It does not support parsing, its use in the parser is an immediate failure.
|
||||
|
@ -340,6 +345,19 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt
|
|||
time.map(|t| write!(w, "{}", if t.hour12().0 {"pm"} else {"am"})),
|
||||
UpperAmPm =>
|
||||
time.map(|t| write!(w, "{}", if t.hour12().0 {"PM"} else {"AM"})),
|
||||
Nanosecond =>
|
||||
time.map(|t| {
|
||||
let nano = t.nanosecond() % 1_000_000_000;
|
||||
if nano == 0 {
|
||||
Ok(())
|
||||
} else if nano % 1_000_000 == 0 {
|
||||
write!(w, ".{:03}", nano / 1_000_000)
|
||||
} else if nano % 1_000 == 0 {
|
||||
write!(w, ".{:06}", nano / 1_000)
|
||||
} else {
|
||||
write!(w, ".{:09}", nano)
|
||||
}
|
||||
}),
|
||||
TimezoneName =>
|
||||
off.map(|&(ref name, _)| write!(w, "{}", *name)),
|
||||
TimezoneOffset =>
|
||||
|
|
|
@ -95,14 +95,14 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st
|
|||
}
|
||||
|
||||
s = s.trim_left();
|
||||
try!(parsed.set_day(try_consume!(scan::number(s, 1, 2, false))));
|
||||
try!(parsed.set_day(try_consume!(scan::number(s, 1, 2))));
|
||||
s = try!(scan::space(s)); // mandatory
|
||||
try!(parsed.set_month(1 + try_consume!(scan::short_month0(s)) as i64));
|
||||
s = try!(scan::space(s)); // mandatory
|
||||
|
||||
// distinguish two- and three-digit years from four-digit years
|
||||
let prevlen = s.len();
|
||||
let mut year = try_consume!(scan::number(s, 2, usize::MAX, false));
|
||||
let mut year = try_consume!(scan::number(s, 2, usize::MAX));
|
||||
let yearlen = prevlen - s.len();
|
||||
match (yearlen, year) {
|
||||
(2, 0...49) => { year += 2000; } // 47 -> 2047, 05 -> 2005
|
||||
|
@ -113,13 +113,13 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st
|
|||
try!(parsed.set_year(year));
|
||||
|
||||
s = try!(scan::space(s)); // mandatory
|
||||
try!(parsed.set_hour(try_consume!(scan::number(s, 2, 2, false))));
|
||||
try!(parsed.set_hour(try_consume!(scan::number(s, 2, 2))));
|
||||
s = try!(scan::char(s.trim_left(), b':')).trim_left(); // *S ":" *S
|
||||
try!(parsed.set_minute(try_consume!(scan::number(s, 2, 2, false))));
|
||||
try!(parsed.set_minute(try_consume!(scan::number(s, 2, 2))));
|
||||
s = s.trim_left();
|
||||
if !s.is_empty() { // [ ":" *S 2DIGIT ]
|
||||
s = try!(scan::char(s, b':')).trim_left();
|
||||
try!(parsed.set_second(try_consume!(scan::number(s, 2, 2, false))));
|
||||
try!(parsed.set_second(try_consume!(scan::number(s, 2, 2))));
|
||||
}
|
||||
|
||||
s = try!(scan::space(s)); // mandatory
|
||||
|
@ -163,11 +163,11 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st
|
|||
// note that this restriction is unique to RFC 3339 and not ISO 8601.
|
||||
// since this is not a typical Chrono behavior, we check it earlier.
|
||||
|
||||
try!(parsed.set_year(try_consume!(scan::number(s, 4, 4, false))));
|
||||
try!(parsed.set_year(try_consume!(scan::number(s, 4, 4))));
|
||||
s = try!(scan::char(s, b'-'));
|
||||
try!(parsed.set_month(try_consume!(scan::number(s, 2, 2, false))));
|
||||
try!(parsed.set_month(try_consume!(scan::number(s, 2, 2))));
|
||||
s = try!(scan::char(s, b'-'));
|
||||
try!(parsed.set_day(try_consume!(scan::number(s, 2, 2, false))));
|
||||
try!(parsed.set_day(try_consume!(scan::number(s, 2, 2))));
|
||||
|
||||
s = match s.as_bytes().first() {
|
||||
Some(&b't') | Some(&b'T') => &s[1..],
|
||||
|
@ -175,14 +175,13 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st
|
|||
None => return Err(TOO_SHORT),
|
||||
};
|
||||
|
||||
try!(parsed.set_hour(try_consume!(scan::number(s, 2, 2, false))));
|
||||
try!(parsed.set_hour(try_consume!(scan::number(s, 2, 2))));
|
||||
s = try!(scan::char(s, b':'));
|
||||
try!(parsed.set_minute(try_consume!(scan::number(s, 2, 2, false))));
|
||||
try!(parsed.set_minute(try_consume!(scan::number(s, 2, 2))));
|
||||
s = try!(scan::char(s, b':'));
|
||||
try!(parsed.set_second(try_consume!(scan::number(s, 2, 2, false))));
|
||||
try!(parsed.set_second(try_consume!(scan::number(s, 2, 2))));
|
||||
if s.starts_with(".") {
|
||||
let nanosecond = try_consume!(scan::number(&s[1..], 1, 9, true));
|
||||
s = s.trim_left_matches(|c: char| '0' <= c && c <= '9');
|
||||
let nanosecond = try_consume!(scan::nanosecond(&s[1..]));
|
||||
try!(parsed.set_nanosecond(nanosecond));
|
||||
}
|
||||
|
||||
|
@ -226,47 +225,43 @@ pub fn parse<'a, I>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResult<(
|
|||
Item::Numeric(spec, _pad) => {
|
||||
use super::Numeric::*;
|
||||
|
||||
enum Mode { Right, Left, SignedRight }
|
||||
|
||||
let (width, mode, set): (usize, Mode,
|
||||
let (width, signed, set): (usize, bool,
|
||||
fn(&mut Parsed, i64) -> ParseResult<()>) = match spec {
|
||||
Year => (4, Mode::SignedRight, Parsed::set_year),
|
||||
YearDiv100 => (2, Mode::Right, Parsed::set_year_div_100),
|
||||
YearMod100 => (2, Mode::Right, Parsed::set_year_mod_100),
|
||||
IsoYear => (4, Mode::SignedRight, Parsed::set_isoyear),
|
||||
IsoYearDiv100 => (2, Mode::Right, Parsed::set_isoyear_div_100),
|
||||
IsoYearMod100 => (2, Mode::Right, Parsed::set_isoyear_mod_100),
|
||||
Month => (2, Mode::Right, Parsed::set_month),
|
||||
Day => (2, Mode::Right, Parsed::set_day),
|
||||
WeekFromSun => (2, Mode::Right, Parsed::set_week_from_sun),
|
||||
WeekFromMon => (2, Mode::Right, Parsed::set_week_from_mon),
|
||||
IsoWeek => (2, Mode::Right, Parsed::set_isoweek),
|
||||
NumDaysFromSun => (1, Mode::Right, set_weekday_with_num_days_from_sunday),
|
||||
WeekdayFromMon => (1, Mode::Right, set_weekday_with_number_from_monday),
|
||||
Ordinal => (3, Mode::Right, Parsed::set_ordinal),
|
||||
Hour => (2, Mode::Right, Parsed::set_hour),
|
||||
Hour12 => (2, Mode::Right, Parsed::set_hour12),
|
||||
Minute => (2, Mode::Right, Parsed::set_minute),
|
||||
Second => (2, Mode::Right, Parsed::set_second),
|
||||
Nanosecond => (9, Mode::Left, Parsed::set_nanosecond),
|
||||
Timestamp => (usize::MAX, Mode::Right, Parsed::set_timestamp),
|
||||
Year => (4, true, Parsed::set_year),
|
||||
YearDiv100 => (2, false, Parsed::set_year_div_100),
|
||||
YearMod100 => (2, false, Parsed::set_year_mod_100),
|
||||
IsoYear => (4, true, Parsed::set_isoyear),
|
||||
IsoYearDiv100 => (2, false, Parsed::set_isoyear_div_100),
|
||||
IsoYearMod100 => (2, false, Parsed::set_isoyear_mod_100),
|
||||
Month => (2, false, Parsed::set_month),
|
||||
Day => (2, false, Parsed::set_day),
|
||||
WeekFromSun => (2, false, Parsed::set_week_from_sun),
|
||||
WeekFromMon => (2, false, Parsed::set_week_from_mon),
|
||||
IsoWeek => (2, false, Parsed::set_isoweek),
|
||||
NumDaysFromSun => (1, false, set_weekday_with_num_days_from_sunday),
|
||||
WeekdayFromMon => (1, false, set_weekday_with_number_from_monday),
|
||||
Ordinal => (3, false, Parsed::set_ordinal),
|
||||
Hour => (2, false, Parsed::set_hour),
|
||||
Hour12 => (2, false, Parsed::set_hour12),
|
||||
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),
|
||||
};
|
||||
|
||||
s = s.trim_left();
|
||||
let v = match mode {
|
||||
Mode::Right => try_consume!(scan::number(s, 1, width, false)),
|
||||
Mode::Left => try_consume!(scan::number(s, 1, width, true)),
|
||||
Mode::SignedRight => {
|
||||
let v = if signed {
|
||||
if s.starts_with("-") {
|
||||
let v = try_consume!(scan::number(&s[1..], 1, usize::MAX, false));
|
||||
let v = try_consume!(scan::number(&s[1..], 1, usize::MAX));
|
||||
try!(0i64.checked_sub(v).ok_or(OUT_OF_RANGE))
|
||||
} else if s.starts_with("+") {
|
||||
try_consume!(scan::number(&s[1..], 1, usize::MAX, false))
|
||||
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, false))
|
||||
try_consume!(scan::number(s, 1, width))
|
||||
}
|
||||
},
|
||||
} else {
|
||||
try_consume!(scan::number(s, 1, width))
|
||||
};
|
||||
try!(set(parsed, v));
|
||||
}
|
||||
|
@ -306,6 +301,13 @@ pub fn parse<'a, I>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResult<(
|
|||
s = &s[2..];
|
||||
}
|
||||
|
||||
Nanosecond => {
|
||||
if s.starts_with(".") {
|
||||
let nano = try_consume!(scan::nanosecond(&s[1..]));
|
||||
try!(parsed.set_nanosecond(nano));
|
||||
}
|
||||
}
|
||||
|
||||
TimezoneName => return Err(BAD_FORMAT),
|
||||
|
||||
TimezoneOffset => {
|
||||
|
@ -447,7 +449,7 @@ fn test_parse() {
|
|||
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: 789_012_340,
|
||||
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
|
||||
|
@ -500,6 +502,24 @@ fn test_parse() {
|
|||
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!("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: timezone offsets
|
||||
check!("+00:00", [fix!(TimezoneOffset)]; offset: 0);
|
||||
check!("-00:00", [fix!(TimezoneOffset)]; offset: 0);
|
||||
|
@ -560,6 +580,9 @@ fn test_parse() {
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,15 +21,10 @@ fn equals(s: &str, pattern: &str) -> bool {
|
|||
|
||||
/// Tries to parse the non-negative number from `min` to `max` digits.
|
||||
///
|
||||
/// If `left_aligned` is true, the number is assumed to be followed by zero digits
|
||||
/// so that the padded digits are exactly `max` digits long (like fractions).
|
||||
/// For example, given `max` of 8, `123` is parsed as 123 when right-aligned
|
||||
/// and 12300000 when left-aligned.
|
||||
///
|
||||
/// The absence of digits at all is an unconditional error.
|
||||
/// More than `max` digits are consumed up to the first `max` digits.
|
||||
/// Any number that does not fit in `i64` is an error.
|
||||
pub fn number(s: &str, min: usize, max: usize, left_aligned: bool) -> ParseResult<(&str, i64)> {
|
||||
pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> {
|
||||
assert!(min <= max);
|
||||
|
||||
// limit `s` to given number of digits
|
||||
|
@ -43,18 +38,29 @@ pub fn number(s: &str, min: usize, max: usize, left_aligned: bool) -> ParseResul
|
|||
}
|
||||
|
||||
// we can overflow here, which is the only possible cause of error from `parse`.
|
||||
let mut v: i64 = try!(s[..upto].parse().map_err(|_| OUT_OF_RANGE));
|
||||
|
||||
// scale the number if it is left-aligned. this can also overflow.
|
||||
if left_aligned {
|
||||
for _ in upto..max {
|
||||
v = try!(v.checked_mul(10).ok_or(OUT_OF_RANGE));
|
||||
}
|
||||
}
|
||||
|
||||
let v: i64 = try!(s[..upto].parse().map_err(|_| OUT_OF_RANGE));
|
||||
Ok((&s[upto..], v))
|
||||
}
|
||||
|
||||
/// Tries to consume at least one digits as a fractional second.
|
||||
/// Returns the number of whole nanoseconds (0--999,999,999).
|
||||
pub fn nanosecond(s: &str) -> ParseResult<(&str, i64)> {
|
||||
// record the number of digits consumed for later scaling.
|
||||
let origlen = s.len();
|
||||
let (s, v) = try!(number(s, 1, 9));
|
||||
let consumed = origlen - s.len();
|
||||
|
||||
// scale the number accordingly.
|
||||
static SCALE: [i64; 10] = [0, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000,
|
||||
1_000, 100, 10, 1];
|
||||
let v = try!(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| '0' <= c && c <= '9');
|
||||
|
||||
Ok((s, v))
|
||||
}
|
||||
|
||||
/// Tries to parse the month index (0 through 11) with the first three ASCII letters.
|
||||
pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> {
|
||||
if s.len() < 3 { return Err(TOO_SHORT); }
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* ISO 8601 calendar date without timezone.
|
||||
*/
|
||||
|
||||
use std::{fmt, hash};
|
||||
use std::{str, fmt, hash};
|
||||
use std::num::{Int, ToPrimitive};
|
||||
use std::ops::{Add, Sub};
|
||||
|
||||
|
@ -15,7 +15,8 @@ use div::div_mod_floor;
|
|||
use duration::Duration;
|
||||
use naive::time::NaiveTime;
|
||||
use naive::datetime::NaiveDateTime;
|
||||
use format::{parse, Item, Parsed, ParseResult, DelayedFormat, StrftimeItems};
|
||||
use format::{Item, Numeric, Pad};
|
||||
use format::{parse, Parsed, ParseError, ParseResult, DelayedFormat, StrftimeItems};
|
||||
|
||||
use self::internals::{DateImpl, Of, Mdf, YearFlags};
|
||||
|
||||
|
@ -501,14 +502,25 @@ impl fmt::Debug for NaiveDate {
|
|||
}
|
||||
|
||||
impl fmt::Display for NaiveDate {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let year = self.year();
|
||||
let mdf = self.mdf();
|
||||
if year >= 0 {
|
||||
write!(f, "{:04}-{:02}-{:02}", year, mdf.month(), mdf.day())
|
||||
} else {
|
||||
write!(f, "{:+05}-{:02}-{:02}", year, mdf.month(), mdf.day())
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(self, f) }
|
||||
}
|
||||
|
||||
impl str::FromStr for NaiveDate {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> ParseResult<NaiveDate> {
|
||||
const ITEMS: &'static [Item<'static>] = &[
|
||||
Item::Space(""), Item::Numeric(Numeric::Year, Pad::Zero),
|
||||
Item::Space(""), Item::Literal("-"),
|
||||
Item::Space(""), Item::Numeric(Numeric::Month, Pad::Zero),
|
||||
Item::Space(""), Item::Literal("-"),
|
||||
Item::Space(""), Item::Numeric(Numeric::Day, Pad::Zero),
|
||||
Item::Space(""),
|
||||
];
|
||||
|
||||
let mut parsed = Parsed::new();
|
||||
try!(parse(&mut parsed, s, ITEMS.iter().cloned()));
|
||||
parsed.to_naive_date()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -838,13 +850,57 @@ mod tests {
|
|||
assert_eq!(NaiveDate::from_ymd(2012, 3, 4).to_string(), "2012-03-04");
|
||||
assert_eq!(NaiveDate::from_ymd(0, 3, 4).to_string(), "0000-03-04");
|
||||
assert_eq!(NaiveDate::from_ymd(-307, 3, 4).to_string(), "-0307-03-04");
|
||||
assert_eq!(NaiveDate::from_ymd(12345, 3, 4).to_string(), "12345-03-04");
|
||||
assert_eq!(NaiveDate::from_ymd(12345, 3, 4).to_string(), "+12345-03-04");
|
||||
|
||||
// the format specifier should have no effect on `NaiveTime`
|
||||
assert_eq!(format!("{:+30?}", NaiveDate::from_ymd(1234, 5, 6)), "1234-05-06");
|
||||
assert_eq!(format!("{:30?}", NaiveDate::from_ymd(12345, 6, 7)), "+12345-06-07");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_from_str() {
|
||||
// valid cases
|
||||
let valid = [
|
||||
"-0000000123456-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",
|
||||
"+70-2-18",
|
||||
"+70000-2-18",
|
||||
"+00007-2-18",
|
||||
];
|
||||
for &s in &valid {
|
||||
let d = match s.parse::<NaiveDate>() {
|
||||
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::<NaiveDate>() {
|
||||
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
|
||||
assert!("".parse::<NaiveDate>().is_err());
|
||||
assert!("x".parse::<NaiveDate>().is_err());
|
||||
assert!("2014".parse::<NaiveDate>().is_err());
|
||||
assert!("2014-01".parse::<NaiveDate>().is_err());
|
||||
assert!("2014-01-00".parse::<NaiveDate>().is_err());
|
||||
assert!("2014-13-57".parse::<NaiveDate>().is_err());
|
||||
assert!("9999999-9-9".parse::<NaiveDate>().is_err()); // out-of-bounds
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_parse_from_str() {
|
||||
let ymd = |&: y,m,d| NaiveDate::from_ymd(y,m,d);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* ISO 8601 date and time without timezone.
|
||||
*/
|
||||
|
||||
use std::{fmt, hash};
|
||||
use std::{str, fmt, hash};
|
||||
use std::num::{Int, ToPrimitive};
|
||||
use std::ops::{Add, Sub};
|
||||
|
||||
|
@ -15,7 +15,8 @@ use div::div_mod_floor;
|
|||
use duration::Duration;
|
||||
use naive::time::NaiveTime;
|
||||
use naive::date::NaiveDate;
|
||||
use format::{parse, Item, Parsed, ParseResult, DelayedFormat, StrftimeItems};
|
||||
use format::{Item, Numeric, Pad, Fixed};
|
||||
use format::{parse, Parsed, ParseError, ParseResult, DelayedFormat, StrftimeItems};
|
||||
|
||||
/// ISO 8601 combined date and time without timezone.
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
|
||||
|
@ -264,6 +265,31 @@ impl fmt::Display for NaiveDateTime {
|
|||
}
|
||||
}
|
||||
|
||||
impl str::FromStr for NaiveDateTime {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> ParseResult<NaiveDateTime> {
|
||||
const ITEMS: &'static [Item<'static>] = &[
|
||||
Item::Space(""), Item::Numeric(Numeric::Year, Pad::Zero),
|
||||
Item::Space(""), Item::Literal("-"),
|
||||
Item::Space(""), Item::Numeric(Numeric::Month, Pad::Zero),
|
||||
Item::Space(""), Item::Literal("-"),
|
||||
Item::Space(""), Item::Numeric(Numeric::Day, Pad::Zero),
|
||||
Item::Space(""), Item::Literal("T"), // XXX shouldn't this be case-insensitive?
|
||||
Item::Space(""), Item::Numeric(Numeric::Hour, Pad::Zero),
|
||||
Item::Space(""), Item::Literal(":"),
|
||||
Item::Space(""), Item::Numeric(Numeric::Minute, Pad::Zero),
|
||||
Item::Space(""), Item::Literal(":"),
|
||||
Item::Space(""), Item::Numeric(Numeric::Second, Pad::Zero),
|
||||
Item::Fixed(Fixed::Nanosecond), Item::Space(""),
|
||||
];
|
||||
|
||||
let mut parsed = Parsed::new();
|
||||
try!(parse(&mut parsed, s, ITEMS.iter().cloned()));
|
||||
parsed.to_naive_datetime_with_offset(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::NaiveDateTime;
|
||||
|
@ -344,6 +370,44 @@ mod tests {
|
|||
assert_eq!(to_timestamp(2038, 1, 19, 3, 14, 7), 0x7fffffff);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_datetime_from_str() {
|
||||
// valid cases
|
||||
let valid = [
|
||||
"2015-2-18T23:16:9.15",
|
||||
"-77-02-18T23:16:09",
|
||||
" +82701 - 05 - 6 T 15 : 9 : 60.898989898989 ",
|
||||
];
|
||||
for &s in &valid {
|
||||
let d = match s.parse::<NaiveDateTime>() {
|
||||
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::<NaiveDateTime>() {
|
||||
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
|
||||
assert!("".parse::<NaiveDateTime>().is_err());
|
||||
assert!("x".parse::<NaiveDateTime>().is_err());
|
||||
assert!("15".parse::<NaiveDateTime>().is_err());
|
||||
assert!("15:8:9".parse::<NaiveDateTime>().is_err());
|
||||
assert!("15-8-9".parse::<NaiveDateTime>().is_err());
|
||||
assert!("2015-15-15T15:15:15".parse::<NaiveDateTime>().is_err());
|
||||
assert!("2012-12-12T12:12:12x".parse::<NaiveDateTime>().is_err());
|
||||
assert!("2012-123-12T12:12:12".parse::<NaiveDateTime>().is_err());
|
||||
assert!("+ 82701-123-12T12:12:12".parse::<NaiveDateTime>().is_err());
|
||||
assert!("+802701-123-12T12:12:12".parse::<NaiveDateTime>().is_err()); // out-of-bound
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_datetime_parse_from_str() {
|
||||
let ymdhms = |&: y,m,d,h,n,s| NaiveDate::from_ymd(y,m,d).and_hms(h,n,s);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* ISO 8601 time without timezone.
|
||||
*/
|
||||
|
||||
use std::{fmt, hash};
|
||||
use std::{str, fmt, hash};
|
||||
use std::num::Int;
|
||||
use std::ops::{Add, Sub};
|
||||
|
||||
|
@ -14,7 +14,8 @@ use Timelike;
|
|||
use div::div_mod_floor;
|
||||
use offset::Offset;
|
||||
use duration::Duration;
|
||||
use format::{parse, Item, Parsed, ParseResult, DelayedFormat, StrftimeItems};
|
||||
use format::{Item, Numeric, Pad, Fixed};
|
||||
use format::{parse, Parsed, ParseError, ParseResult, DelayedFormat, StrftimeItems};
|
||||
|
||||
/// ISO 8601 time without timezone.
|
||||
/// Allows for the nanosecond precision and optional leap second representation.
|
||||
|
@ -264,6 +265,25 @@ impl fmt::Display for NaiveTime {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(self, f) }
|
||||
}
|
||||
|
||||
impl str::FromStr for NaiveTime {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> ParseResult<NaiveTime> {
|
||||
const ITEMS: &'static [Item<'static>] = &[
|
||||
Item::Space(""), Item::Numeric(Numeric::Hour, Pad::Zero),
|
||||
Item::Space(""), Item::Literal(":"),
|
||||
Item::Space(""), Item::Numeric(Numeric::Minute, Pad::Zero),
|
||||
Item::Space(""), Item::Literal(":"),
|
||||
Item::Space(""), Item::Numeric(Numeric::Second, Pad::Zero),
|
||||
Item::Fixed(Fixed::Nanosecond), Item::Space(""),
|
||||
];
|
||||
|
||||
let mut parsed = Parsed::new();
|
||||
try!(parse(&mut parsed, s, ITEMS.iter().cloned()));
|
||||
parsed.to_naive_time()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::NaiveTime;
|
||||
|
@ -383,6 +403,47 @@ mod tests {
|
|||
assert_eq!(format!("{:30}", NaiveTime::from_hms_milli(3, 5, 7, 9)), "03:05:07.009");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_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 ",
|
||||
"23:59:60.373929310237",
|
||||
];
|
||||
for &s in &valid {
|
||||
let d = match s.parse::<NaiveTime>() {
|
||||
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::<NaiveTime>() {
|
||||
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
|
||||
assert!("".parse::<NaiveTime>().is_err());
|
||||
assert!("x".parse::<NaiveTime>().is_err());
|
||||
assert!("15".parse::<NaiveTime>().is_err());
|
||||
assert!("15:8".parse::<NaiveTime>().is_err());
|
||||
assert!("15:8:x".parse::<NaiveTime>().is_err());
|
||||
assert!("15:8:9x".parse::<NaiveTime>().is_err());
|
||||
assert!("23:59:61".parse::<NaiveTime>().is_err());
|
||||
assert!("12:34:56.x".parse::<NaiveTime>().is_err());
|
||||
assert!("12:34:56. 0".parse::<NaiveTime>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_parse_from_str() {
|
||||
let hms = |&: h,m,s| NaiveTime::from_hms(h,m,s);
|
||||
|
|
Loading…
Reference in New Issue