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).
|
/// 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 =>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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); }
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue