weakened the non-negative requirement of year/isoyear fields.
accepts ISO 8601-ish `+YYYYY` or `-YYYYY` notations. this is needed for the bijectivity of `to_string` and an upcoming `from_str`.
This commit is contained in:
parent
3f211dfe5f
commit
0399ba1849
|
@ -45,16 +45,17 @@ pub enum Pad {
|
||||||
/// parsed with the same formatting items.
|
/// parsed with the same formatting items.
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum Numeric {
|
pub enum Numeric {
|
||||||
/// Full Gregorian year (FW=PW=4).
|
/// Full Gregorian year (FW=4, PW=infinity).
|
||||||
|
/// May accept years before 1 BCE or after 9999 CE, given an initial sign.
|
||||||
Year,
|
Year,
|
||||||
/// Gregorian year divided by 100 (century number; FW=PW=2).
|
/// Gregorian year divided by 100 (century number; FW=PW=2). Implies the non-negative year.
|
||||||
/// Always rounds towards minus infinity.
|
|
||||||
YearDiv100,
|
YearDiv100,
|
||||||
/// Gregorian year modulo 100 (FW=PW=2). Cannot be negative.
|
/// Gregorian year modulo 100 (FW=PW=2). Cannot be negative.
|
||||||
YearMod100,
|
YearMod100,
|
||||||
/// Year in the ISO week date (FW=PW=4).
|
/// Year in the ISO week date (FW=4, PW=infinity).
|
||||||
|
/// May accept years before 1 BCE or after 9999 CE, given an initial sign.
|
||||||
IsoYear,
|
IsoYear,
|
||||||
/// Year in the ISO week date, divided by 100 (FW=PW=2). Always rounds towards minus infinity.
|
/// Year in the ISO week date, divided by 100 (FW=PW=2). Implies the non-negative year.
|
||||||
IsoYearDiv100,
|
IsoYearDiv100,
|
||||||
/// Year in the ISO week date, modulo 100 (FW=PW=2). Cannot be negative.
|
/// Year in the ISO week date, modulo 100 (FW=PW=2). Cannot be negative.
|
||||||
IsoYearMod100,
|
IsoYearMod100,
|
||||||
|
@ -285,10 +286,19 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(v) = v {
|
if let Some(v) = v {
|
||||||
match pad {
|
if (spec == Year || spec == IsoYear) && !(0 <= v && v < 10000) {
|
||||||
Pad::None => try!(write!(w, "{}", v)),
|
// non-four-digit years require an explicit sign as per ISO 8601
|
||||||
Pad::Zero => try!(write!(w, "{:01$}", v, width)),
|
match pad {
|
||||||
Pad::Space => try!(write!(w, "{:1$}", v, width)),
|
Pad::None => try!(write!(w, "{:+}", v)),
|
||||||
|
Pad::Zero => try!(write!(w, "{:+01$}", v, width)),
|
||||||
|
Pad::Space => try!(write!(w, "{:+1$}", v, width)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match pad {
|
||||||
|
Pad::None => try!(write!(w, "{}", v)),
|
||||||
|
Pad::Zero => try!(write!(w, "{:01$}", v, width)),
|
||||||
|
Pad::Space => try!(write!(w, "{:1$}", v, width)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(fmt::Error); // insufficient arguments for given format
|
return Err(fmt::Error); // insufficient arguments for given format
|
||||||
|
@ -348,7 +358,7 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt
|
||||||
},
|
},
|
||||||
RFC3339 => // (almost) same to `%Y-%m-%dT%H:%M:%S.%f%z`
|
RFC3339 => // (almost) same to `%Y-%m-%dT%H:%M:%S.%f%z`
|
||||||
if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
|
if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
|
||||||
// reuse `Debug` impls which already prints ISO 8601 format.
|
// reuse `Debug` impls which already print ISO 8601 format.
|
||||||
// this is faster in this way.
|
// this is faster in this way.
|
||||||
try!(write!(w, "{:?}T{:?}", d, t));
|
try!(write!(w, "{:?}T{:?}", d, t));
|
||||||
Some(write_local_minus_utc(w, off, false, true))
|
Some(write_local_minus_utc(w, off, false, true))
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::usize;
|
use std::usize;
|
||||||
|
use std::num::Int;
|
||||||
|
|
||||||
use Weekday;
|
use Weekday;
|
||||||
|
|
||||||
|
@ -225,31 +226,48 @@ 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::*;
|
||||||
|
|
||||||
let (width, frac, set): (usize, bool,
|
enum Mode { Right, Left, SignedRight }
|
||||||
|
|
||||||
|
let (width, mode, set): (usize, Mode,
|
||||||
fn(&mut Parsed, i64) -> ParseResult<()>) = match spec {
|
fn(&mut Parsed, i64) -> ParseResult<()>) = match spec {
|
||||||
Year => (4, false, Parsed::set_year),
|
Year => (4, Mode::SignedRight, Parsed::set_year),
|
||||||
YearDiv100 => (2, false, Parsed::set_year_div_100),
|
YearDiv100 => (2, Mode::Right, Parsed::set_year_div_100),
|
||||||
YearMod100 => (2, false, Parsed::set_year_mod_100),
|
YearMod100 => (2, Mode::Right, Parsed::set_year_mod_100),
|
||||||
IsoYear => (4, false, Parsed::set_isoyear),
|
IsoYear => (4, Mode::SignedRight, Parsed::set_isoyear),
|
||||||
IsoYearDiv100 => (2, false, Parsed::set_isoyear_div_100),
|
IsoYearDiv100 => (2, Mode::Right, Parsed::set_isoyear_div_100),
|
||||||
IsoYearMod100 => (2, false, Parsed::set_isoyear_mod_100),
|
IsoYearMod100 => (2, Mode::Right, Parsed::set_isoyear_mod_100),
|
||||||
Month => (2, false, Parsed::set_month),
|
Month => (2, Mode::Right, Parsed::set_month),
|
||||||
Day => (2, false, Parsed::set_day),
|
Day => (2, Mode::Right, Parsed::set_day),
|
||||||
WeekFromSun => (2, false, Parsed::set_week_from_sun),
|
WeekFromSun => (2, Mode::Right, Parsed::set_week_from_sun),
|
||||||
WeekFromMon => (2, false, Parsed::set_week_from_mon),
|
WeekFromMon => (2, Mode::Right, Parsed::set_week_from_mon),
|
||||||
IsoWeek => (2, false, Parsed::set_isoweek),
|
IsoWeek => (2, Mode::Right, Parsed::set_isoweek),
|
||||||
NumDaysFromSun => (1, false, set_weekday_with_num_days_from_sunday),
|
NumDaysFromSun => (1, Mode::Right, set_weekday_with_num_days_from_sunday),
|
||||||
WeekdayFromMon => (1, false, set_weekday_with_number_from_monday),
|
WeekdayFromMon => (1, Mode::Right, set_weekday_with_number_from_monday),
|
||||||
Ordinal => (3, false, Parsed::set_ordinal),
|
Ordinal => (3, Mode::Right, Parsed::set_ordinal),
|
||||||
Hour => (2, false, Parsed::set_hour),
|
Hour => (2, Mode::Right, Parsed::set_hour),
|
||||||
Hour12 => (2, false, Parsed::set_hour12),
|
Hour12 => (2, Mode::Right, Parsed::set_hour12),
|
||||||
Minute => (2, false, Parsed::set_minute),
|
Minute => (2, Mode::Right, Parsed::set_minute),
|
||||||
Second => (2, false, Parsed::set_second),
|
Second => (2, Mode::Right, Parsed::set_second),
|
||||||
Nanosecond => (9, true, Parsed::set_nanosecond),
|
Nanosecond => (9, Mode::Left, Parsed::set_nanosecond),
|
||||||
Timestamp => (usize::MAX, false, Parsed::set_timestamp),
|
Timestamp => (usize::MAX, Mode::Right, Parsed::set_timestamp),
|
||||||
};
|
};
|
||||||
|
|
||||||
let v = try_consume!(scan::number(s.trim_left(), 1, width, frac));
|
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 => {
|
||||||
|
if s.starts_with("-") {
|
||||||
|
let v = try_consume!(scan::number(&s[1..], 1, usize::MAX, false));
|
||||||
|
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))
|
||||||
|
} else {
|
||||||
|
// if there is no explicit sign, we respect the original `width`
|
||||||
|
try_consume!(scan::number(s, 1, width, false))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
try!(set(parsed, v));
|
try!(set(parsed, v));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,8 +395,6 @@ fn test_parse() {
|
||||||
check!("9999", [num!(Year)]; year: 9999);
|
check!("9999", [num!(Year)]; year: 9999);
|
||||||
check!(" \t987", [num!(Year)]; year: 987);
|
check!(" \t987", [num!(Year)]; year: 987);
|
||||||
check!("5", [num!(Year)]; year: 5);
|
check!("5", [num!(Year)]; year: 5);
|
||||||
check!("-42", [num!(Year)]; INVALID); // while `year` supports the negative year,
|
|
||||||
check!("+42", [num!(Year)]; INVALID); // the parser doesn't (for now).
|
|
||||||
check!("5\0", [num!(Year)]; TOO_LONG);
|
check!("5\0", [num!(Year)]; TOO_LONG);
|
||||||
check!("\05", [num!(Year)]; INVALID);
|
check!("\05", [num!(Year)]; INVALID);
|
||||||
check!("", [num!(Year)]; TOO_SHORT);
|
check!("", [num!(Year)]; TOO_SHORT);
|
||||||
|
@ -393,6 +409,28 @@ fn test_parse() {
|
||||||
check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID);
|
check!("1234xx1234", [num!(Year), lit!("x"), num!(Year)]; INVALID);
|
||||||
check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID);
|
check!("1234 x 1234", [num!(Year), lit!("x"), num!(Year)]; INVALID);
|
||||||
|
|
||||||
|
// signed numeric
|
||||||
|
check!("-42", [num!(Year)]; year: -42);
|
||||||
|
check!("+42", [num!(Year)]; year: 42);
|
||||||
|
check!("-0042", [num!(Year)]; year: -42);
|
||||||
|
check!("+0042", [num!(Year)]; year: 42);
|
||||||
|
check!("-42195", [num!(Year)]; year: -42195);
|
||||||
|
check!("+42195", [num!(Year)]; year: 42195);
|
||||||
|
check!(" -42195", [num!(Year)]; year: -42195);
|
||||||
|
check!(" +42195", [num!(Year)]; year: 42195);
|
||||||
|
check!(" - 42", [num!(Year)]; INVALID);
|
||||||
|
check!(" + 42", [num!(Year)]; INVALID);
|
||||||
|
check!("-", [num!(Year)]; TOO_SHORT);
|
||||||
|
check!("+", [num!(Year)]; TOO_SHORT);
|
||||||
|
|
||||||
|
// unsigned numeric
|
||||||
|
check!("345", [num!(Ordinal)]; ordinal: 345);
|
||||||
|
check!("+345", [num!(Ordinal)]; INVALID);
|
||||||
|
check!("-345", [num!(Ordinal)]; INVALID);
|
||||||
|
check!(" 345", [num!(Ordinal)]; ordinal: 345);
|
||||||
|
check!(" +345", [num!(Ordinal)]; INVALID);
|
||||||
|
check!(" -345", [num!(Ordinal)]; INVALID);
|
||||||
|
|
||||||
// various numeric fields
|
// various numeric fields
|
||||||
check!("1234 5678",
|
check!("1234 5678",
|
||||||
[num!(Year), num!(IsoYear)];
|
[num!(Year), num!(IsoYear)];
|
||||||
|
|
Loading…
Reference in New Issue