Minor additions to formatting items.

- Formatting item types are no longer `Copy`.

- `Numeric` and `Fixed` items now have `Internal` variants reserved
  for the future expansion. It had been hard to expand the items
  without totally breaking the backward compatibility (as per
  the API evolution guideline of RFC 1105).

- `Item::Owned{Literal,Space}` for the owned variant of
  `Item::{Literal,Space}` has been added.

Closes #76.
This commit is contained in:
Kang Seonghoon 2017-02-07 04:05:05 +09:00
parent 7ea1ce5080
commit 0ac41c70b1
No known key found for this signature in database
GPG Key ID: 82440FABA6709020
4 changed files with 71 additions and 8 deletions

View File

@ -185,7 +185,7 @@ assert_eq!(dt.ordinal(), 332); // the day of year
assert_eq!(dt.num_days_from_ce(), 735565); // the number of days from and including Jan 1, 1
// time zone accessor and manipulation
assert_eq!(dt.offset().local_minus_utc(), Duration::hours(9));
assert_eq!(dt.offset().fix().local_minus_utc(), 9 * 3600);
assert_eq!(dt.timezone(), FixedOffset::east(9 * 3600));
assert_eq!(dt.with_timezone(&UTC), UTC.ymd(2014, 11, 28).and_hms_nano(12, 45, 59, 324310806));

View File

@ -17,6 +17,10 @@ pub use self::strftime::StrftimeItems;
pub use self::parsed::Parsed;
pub use self::parse::parse;
/// An unhabitated type used for `InternalNumeric` and `InternalFixed` below.
#[derive(Clone, PartialEq, Eq)]
enum Void {}
/// Padding characters for numeric items.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Pad {
@ -41,7 +45,7 @@ pub enum Pad {
/// It also trims the preceding whitespaces if any.
/// It cannot parse the negative number, so some date and time cannot be formatted then
/// parsed with the same formatting items.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Numeric {
/// Full Gregorian year (FW=4, PW=∞).
/// May accept years before 1 BCE or after 9999 CE, given an initial sign.
@ -88,13 +92,31 @@ pub enum Numeric {
/// The number of non-leap seconds since the midnight UTC on January 1, 1970 (FW=1, PW=∞).
/// For formatting, it assumes UTC upon the absence of time zone offset.
Timestamp,
/// Internal uses only.
///
/// This item exists so that one can add additional internal-only formatting
/// without breaking major compatibility (as enum variants cannot be selectively private).
Internal(InternalNumeric),
}
/// An opaque type representing numeric item types for internal uses only.
#[derive(Clone, PartialEq, Eq)]
pub struct InternalNumeric {
_dummy: Void,
}
impl fmt::Debug for InternalNumeric {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<InternalNumeric>")
}
}
/// Fixed-format item types.
///
/// They have their own rules of formatting and parsing.
/// Otherwise noted, they print in the specified cases but parse case-insensitively.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Fixed {
/// Abbreviated month names.
///
@ -157,15 +179,37 @@ pub enum Fixed {
RFC2822,
/// RFC 3339 & ISO 8601 date and time syntax.
RFC3339,
/// Internal uses only.
///
/// This item exists so that one can add additional internal-only formatting
/// without breaking major compatibility (as enum variants cannot be selectively private).
Internal(InternalFixed),
}
/// An opaque type representing fixed-format item types for internal uses only.
#[derive(Clone, PartialEq, Eq)]
pub struct InternalFixed {
_dummy: Void,
}
impl fmt::Debug for InternalFixed {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<InternalFixed>")
}
}
/// A single formatting item. This is used for both formatting and parsing.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Item<'a> {
/// A literally printed and parsed text.
Literal(&'a str),
/// Same to `Literal` but with the string owned by the item.
OwnedLiteral(Box<str>),
/// Whitespace. Prints literally but reads zero or more whitespace.
Space(&'a str),
/// Same to `Space` but with the string owned by the item.
OwnedSpace(Box<str>),
/// Numeric item. Can be optionally padded to the maximal length (if any) when formatting;
/// the parser simply ignores any padded whitespace and zeroes.
Numeric(Numeric, Pad),
@ -268,6 +312,7 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt
for item in items {
match item {
Item::Literal(s) | Item::Space(s) => try!(write!(w, "{}", s)),
Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => try!(write!(w, "{}", s)),
Item::Numeric(spec, pad) => {
use self::Numeric::*;
@ -305,6 +350,9 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt
Some((d.and_time(*t) - off).timestamp()),
(_, _, _) => None
}),
// for the future expansion
Internal(ref int) => match int._dummy {},
};
if let Some(v) = v {
@ -420,6 +468,9 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt
} else {
None
},
// for the future expansion
Internal(ref int) => match int._dummy {},
};
match ret {

View File

@ -218,7 +218,13 @@ pub fn parse<'a, I>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResult<(
s = &s[prefix.len()..];
}
Item::Space(_) => {
Item::OwnedLiteral(ref prefix) => {
if s.len() < prefix.len() { return Err(TOO_SHORT); }
if !s.starts_with(&prefix[..]) { return Err(INVALID); }
s = &s[prefix.len()..];
}
Item::Space(_) | Item::OwnedSpace(_) => {
s = s.trim_left();
}
@ -247,6 +253,9 @@ pub fn parse<'a, I>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResult<(
Second => (2, false, Parsed::set_second),
Nanosecond => (9, false, Parsed::set_nanosecond),
Timestamp => (usize::MAX, false, Parsed::set_timestamp),
// for the future expansion
Internal(ref int) => match int._dummy {},
};
s = s.trim_left();
@ -324,6 +333,9 @@ pub fn parse<'a, I>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResult<(
RFC2822 => try_consume!(parse_rfc2822(parsed, s)),
RFC3339 => try_consume!(parse_rfc3339(parsed, s)),
// for the future expansion
Internal(ref int) => match int._dummy {},
}
}

View File

@ -173,7 +173,7 @@ impl<'a> Iterator for StrftimeItems<'a> {
fn next(&mut self) -> Option<Item<'a>> {
// we have some reconstructed items to return
if !self.recons.is_empty() {
let item = self.recons[0];
let item = self.recons[0].clone();
self.recons = &self.recons[1..];
return Some(item);
}
@ -292,8 +292,8 @@ impl<'a> Iterator for StrftimeItems<'a> {
// adjust `item` if we have any padding modifier
if let Some(new_pad) = pad_override {
match item {
Item::Numeric(kind, _pad) if self.recons.is_empty() =>
Some(Item::Numeric(kind, new_pad)),
Item::Numeric(ref kind, _pad) if self.recons.is_empty() =>
Some(Item::Numeric(kind.clone(), new_pad)),
_ => Some(Item::Error), // no reconstructed or non-numeric item allowed
}
} else {