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:
Kang Seonghoon 2015-02-18 23:27:12 +09:00
parent ca865e3c24
commit 76b0873722
6 changed files with 313 additions and 85 deletions

View File

@ -84,6 +84,7 @@ pub enum Numeric {
/// The number of seconds since the last whole minute (FW=PW=2). /// The number of seconds since the last whole minute (FW=PW=2).
Second, Second,
/// The number of nanoseconds since the last whole second (FW=PW=9). /// The number of nanoseconds since the last whole second (FW=PW=9).
/// Note that this is *not* left-aligned; see also `Fixed::Nanosecond`.
Nanosecond, Nanosecond,
/// The number of non-leap seconds since January 1, 1970 0:00:00 UTC (FW=1, PW=infinity). /// The number of non-leap seconds since January 1, 1970 0:00:00 UTC (FW=1, PW=infinity).
Timestamp, Timestamp,
@ -119,6 +120,10 @@ pub enum Fixed {
/// ///
/// Prints in upper case, reads in any case. /// Prints in upper case, reads in any case.
UpperAmPm, 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. /// Timezone name.
/// ///
/// It does not support parsing, its use in the parser is an immediate failure. /// 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"})), time.map(|t| write!(w, "{}", if t.hour12().0 {"pm"} else {"am"})),
UpperAmPm => UpperAmPm =>
time.map(|t| write!(w, "{}", if t.hour12().0 {"PM"} else {"AM"})), 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 => TimezoneName =>
off.map(|&(ref name, _)| write!(w, "{}", *name)), off.map(|&(ref name, _)| write!(w, "{}", *name)),
TimezoneOffset => TimezoneOffset =>

View File

@ -95,14 +95,14 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st
} }
s = s.trim_left(); 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 s = try!(scan::space(s)); // mandatory
try!(parsed.set_month(1 + try_consume!(scan::short_month0(s)) as i64)); try!(parsed.set_month(1 + try_consume!(scan::short_month0(s)) as i64));
s = try!(scan::space(s)); // mandatory s = try!(scan::space(s)); // mandatory
// distinguish two- and three-digit years from four-digit years // distinguish two- and three-digit years from four-digit years
let prevlen = s.len(); 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(); let yearlen = prevlen - s.len();
match (yearlen, year) { match (yearlen, year) {
(2, 0...49) => { year += 2000; } // 47 -> 2047, 05 -> 2005 (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)); try!(parsed.set_year(year));
s = try!(scan::space(s)); // mandatory 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 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(); s = s.trim_left();
if !s.is_empty() { // [ ":" *S 2DIGIT ] if !s.is_empty() { // [ ":" *S 2DIGIT ]
s = try!(scan::char(s, b':')).trim_left(); 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 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. // 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. // 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'-')); 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'-')); 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() { s = match s.as_bytes().first() {
Some(&b't') | Some(&b'T') => &s[1..], 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), 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':')); 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':')); 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(".") { if s.starts_with(".") {
let nanosecond = try_consume!(scan::number(&s[1..], 1, 9, true)); let nanosecond = try_consume!(scan::nanosecond(&s[1..]));
s = s.trim_left_matches(|c: char| '0' <= c && c <= '9');
try!(parsed.set_nanosecond(nanosecond)); 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) => { Item::Numeric(spec, _pad) => {
use super::Numeric::*; use super::Numeric::*;
enum Mode { Right, Left, SignedRight } let (width, signed, set): (usize, bool,
fn(&mut Parsed, i64) -> ParseResult<()>) = match spec {
let (width, mode, set): (usize, Mode, Year => (4, true, Parsed::set_year),
fn(&mut Parsed, i64) -> ParseResult<()>) = match spec { YearDiv100 => (2, false, Parsed::set_year_div_100),
Year => (4, Mode::SignedRight, Parsed::set_year), YearMod100 => (2, false, Parsed::set_year_mod_100),
YearDiv100 => (2, Mode::Right, Parsed::set_year_div_100), IsoYear => (4, true, Parsed::set_isoyear),
YearMod100 => (2, Mode::Right, Parsed::set_year_mod_100), IsoYearDiv100 => (2, false, Parsed::set_isoyear_div_100),
IsoYear => (4, Mode::SignedRight, Parsed::set_isoyear), IsoYearMod100 => (2, false, Parsed::set_isoyear_mod_100),
IsoYearDiv100 => (2, Mode::Right, Parsed::set_isoyear_div_100), Month => (2, false, Parsed::set_month),
IsoYearMod100 => (2, Mode::Right, Parsed::set_isoyear_mod_100), Day => (2, false, Parsed::set_day),
Month => (2, Mode::Right, Parsed::set_month), WeekFromSun => (2, false, Parsed::set_week_from_sun),
Day => (2, Mode::Right, Parsed::set_day), WeekFromMon => (2, false, Parsed::set_week_from_mon),
WeekFromSun => (2, Mode::Right, Parsed::set_week_from_sun), IsoWeek => (2, false, Parsed::set_isoweek),
WeekFromMon => (2, Mode::Right, Parsed::set_week_from_mon), NumDaysFromSun => (1, false, set_weekday_with_num_days_from_sunday),
IsoWeek => (2, Mode::Right, Parsed::set_isoweek), WeekdayFromMon => (1, false, set_weekday_with_number_from_monday),
NumDaysFromSun => (1, Mode::Right, set_weekday_with_num_days_from_sunday), Ordinal => (3, false, Parsed::set_ordinal),
WeekdayFromMon => (1, Mode::Right, set_weekday_with_number_from_monday), Hour => (2, false, Parsed::set_hour),
Ordinal => (3, Mode::Right, Parsed::set_ordinal), Hour12 => (2, false, Parsed::set_hour12),
Hour => (2, Mode::Right, Parsed::set_hour), Minute => (2, false, Parsed::set_minute),
Hour12 => (2, Mode::Right, Parsed::set_hour12), Second => (2, false, Parsed::set_second),
Minute => (2, Mode::Right, Parsed::set_minute), Nanosecond => (9, false, Parsed::set_nanosecond),
Second => (2, Mode::Right, Parsed::set_second), Timestamp => (usize::MAX, false, Parsed::set_timestamp),
Nanosecond => (9, Mode::Left, Parsed::set_nanosecond),
Timestamp => (usize::MAX, Mode::Right, Parsed::set_timestamp),
}; };
s = s.trim_left(); s = s.trim_left();
let v = match mode { let v = if signed {
Mode::Right => try_consume!(scan::number(s, 1, width, false)), if s.starts_with("-") {
Mode::Left => try_consume!(scan::number(s, 1, width, true)), let v = try_consume!(scan::number(&s[1..], 1, usize::MAX));
Mode::SignedRight => { try!(0i64.checked_sub(v).ok_or(OUT_OF_RANGE))
if s.starts_with("-") { } else if s.starts_with("+") {
let v = try_consume!(scan::number(&s[1..], 1, usize::MAX, false)); try_consume!(scan::number(&s[1..], 1, usize::MAX))
try!(0i64.checked_sub(v).ok_or(OUT_OF_RANGE)) } else {
} else if s.starts_with("+") { // if there is no explicit sign, we respect the original `width`
try_consume!(scan::number(&s[1..], 1, usize::MAX, false)) try_consume!(scan::number(s, 1, width))
} else { }
// if there is no explicit sign, we respect the original `width` } else {
try_consume!(scan::number(s, 1, width, false)) try_consume!(scan::number(s, 1, width))
}
},
}; };
try!(set(parsed, v)); 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..]; 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), TimezoneName => return Err(BAD_FORMAT),
TimezoneOffset => { TimezoneOffset => {
@ -447,7 +449,7 @@ fn test_parse() {
weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1); weekday: Weekday::Sun, ordinal: 89, hour_mod_12: 1);
check!("23 45 6 78901234 567890123", check!("23 45 6 78901234 567890123",
[num!(Hour), num!(Minute), num!(Second), num!(Nanosecond), num!(Timestamp)]; [num!(Hour), num!(Minute), num!(Second), num!(Nanosecond), num!(Timestamp)];
hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 789_012_340, hour_div_12: 1, hour_mod_12: 11, minute: 45, second: 6, nanosecond: 78_901_234,
timestamp: 567_890_123); timestamp: 567_890_123);
// fixed: month and weekday names // fixed: month and weekday names
@ -500,6 +502,24 @@ fn test_parse() {
check!("xx", [fix!(LowerAmPm)]; INVALID); check!("xx", [fix!(LowerAmPm)]; INVALID);
check!("", [fix!(LowerAmPm)]; TOO_SHORT); 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 // fixed: timezone offsets
check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); check!("+00:00", [fix!(TimezoneOffset)]; offset: 0);
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); hour_div_12: 1, hour_mod_12: 3, minute: 14);
check!("12345678901234.56789", check!("12345678901234.56789",
[num!(Timestamp), lit!("."), num!(Nanosecond)]; [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); nanosecond: 567_890_000, timestamp: 12_345_678_901_234);
} }

View File

@ -21,15 +21,10 @@ fn equals(s: &str, pattern: &str) -> bool {
/// Tries to parse the non-negative number from `min` to `max` digits. /// 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. /// The absence of digits at all is an unconditional error.
/// More than `max` digits are consumed up to the first `max` digits. /// More than `max` digits are consumed up to the first `max` digits.
/// Any number that does not fit in `i64` is an error. /// 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); assert!(min <= max);
// limit `s` to given number of digits // 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`. // 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)); let 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));
}
}
Ok((&s[upto..], v)) 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. /// Tries to parse the month index (0 through 11) with the first three ASCII letters.
pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> { pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> {
if s.len() < 3 { return Err(TOO_SHORT); } if s.len() < 3 { return Err(TOO_SHORT); }

View File

@ -6,7 +6,7 @@
* ISO 8601 calendar date without timezone. * ISO 8601 calendar date without timezone.
*/ */
use std::{fmt, hash}; use std::{str, fmt, hash};
use std::num::{Int, ToPrimitive}; use std::num::{Int, ToPrimitive};
use std::ops::{Add, Sub}; use std::ops::{Add, Sub};
@ -15,7 +15,8 @@ use div::div_mod_floor;
use duration::Duration; use duration::Duration;
use naive::time::NaiveTime; use naive::time::NaiveTime;
use naive::datetime::NaiveDateTime; 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}; use self::internals::{DateImpl, Of, Mdf, YearFlags};
@ -501,14 +502,25 @@ impl fmt::Debug for NaiveDate {
} }
impl fmt::Display for NaiveDate { impl fmt::Display for NaiveDate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(self, f) }
let year = self.year(); }
let mdf = self.mdf();
if year >= 0 { impl str::FromStr for NaiveDate {
write!(f, "{:04}-{:02}-{:02}", year, mdf.month(), mdf.day()) type Err = ParseError;
} else {
write!(f, "{:+05}-{:02}-{:02}", year, mdf.month(), mdf.day()) 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()
} }
} }
@ -835,16 +847,60 @@ mod tests {
assert_eq!(format!("{:?}", NaiveDate::from_ymd(-307, 3, 4)), "-0307-03-04"); assert_eq!(format!("{:?}", NaiveDate::from_ymd(-307, 3, 4)), "-0307-03-04");
assert_eq!(format!("{:?}", NaiveDate::from_ymd(12345, 3, 4)), "+12345-03-04"); assert_eq!(format!("{:?}", NaiveDate::from_ymd(12345, 3, 4)), "+12345-03-04");
assert_eq!(NaiveDate::from_ymd(2012, 3, 4).to_string(), "2012-03-04"); 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(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(-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` // 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(1234, 5, 6)), "1234-05-06");
assert_eq!(format!("{:30?}", NaiveDate::from_ymd(12345, 6, 7)), "+12345-06-07"); 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] #[test]
fn test_date_parse_from_str() { fn test_date_parse_from_str() {
let ymd = |&: y,m,d| NaiveDate::from_ymd(y,m,d); let ymd = |&: y,m,d| NaiveDate::from_ymd(y,m,d);

View File

@ -6,7 +6,7 @@
* ISO 8601 date and time without timezone. * ISO 8601 date and time without timezone.
*/ */
use std::{fmt, hash}; use std::{str, fmt, hash};
use std::num::{Int, ToPrimitive}; use std::num::{Int, ToPrimitive};
use std::ops::{Add, Sub}; use std::ops::{Add, Sub};
@ -15,7 +15,8 @@ use div::div_mod_floor;
use duration::Duration; use duration::Duration;
use naive::time::NaiveTime; use naive::time::NaiveTime;
use naive::date::NaiveDate; 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. /// ISO 8601 combined date and time without timezone.
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] #[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)] #[cfg(test)]
mod tests { mod tests {
use super::NaiveDateTime; use super::NaiveDateTime;
@ -344,6 +370,44 @@ mod tests {
assert_eq!(to_timestamp(2038, 1, 19, 3, 14, 7), 0x7fffffff); 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] #[test]
fn test_datetime_parse_from_str() { 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); let ymdhms = |&: y,m,d,h,n,s| NaiveDate::from_ymd(y,m,d).and_hms(h,n,s);

View File

@ -6,7 +6,7 @@
* ISO 8601 time without timezone. * ISO 8601 time without timezone.
*/ */
use std::{fmt, hash}; use std::{str, fmt, hash};
use std::num::Int; use std::num::Int;
use std::ops::{Add, Sub}; use std::ops::{Add, Sub};
@ -14,7 +14,8 @@ use Timelike;
use div::div_mod_floor; use div::div_mod_floor;
use offset::Offset; use offset::Offset;
use duration::Duration; 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. /// ISO 8601 time without timezone.
/// Allows for the nanosecond precision and optional leap second representation. /// 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) } 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)] #[cfg(test)]
mod tests { mod tests {
use super::NaiveTime; 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"); 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] #[test]
fn test_time_parse_from_str() { fn test_time_parse_from_str() {
let hms = |&: h,m,s| NaiveTime::from_hms(h,m,s); let hms = |&: h,m,s| NaiveTime::from_hms(h,m,s);