From 0ac41c70b1c0ee5d4b3af72b6902132dc8da2add Mon Sep 17 00:00:00 2001 From: Kang Seonghoon Date: Tue, 7 Feb 2017 04:05:05 +0900 Subject: [PATCH] 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. --- README.md | 2 +- src/format/mod.rs | 57 +++++++++++++++++++++++++++++++++++++++--- src/format/parse.rs | 14 ++++++++++- src/format/strftime.rs | 6 ++--- 4 files changed, 71 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ccbe54d..1ceaf3a 100644 --- a/README.md +++ b/README.md @@ -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)); diff --git a/src/format/mod.rs b/src/format/mod.rs index 74f9fb5..e2a95f1 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -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, "") + } } /// 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, "") + } } /// 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), /// 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), /// 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 { diff --git a/src/format/parse.rs b/src/format/parse.rs index 89c8e81..3b58335 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -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 {}, } } diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 1553aad..2002d31 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -173,7 +173,7 @@ impl<'a> Iterator for StrftimeItems<'a> { fn next(&mut self) -> Option> { // 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 {