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:
Kang Seonghoon 2015-02-17 22:18:39 +09:00
parent 3f211dfe5f
commit 0399ba1849
2 changed files with 82 additions and 34 deletions

View File

@ -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))

View File

@ -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)];