From 1de3658433c8d5b9a0167bfa20a10cd5e158c260 Mon Sep 17 00:00:00 2001 From: Evan Schwartz Date: Fri, 8 Jun 2018 14:37:03 -0400 Subject: [PATCH 1/7] Add nanoseconds without dots --- src/format/mod.rs | 23 ++++++++++++++++++++++- src/format/parse.rs | 12 +++++++++++- src/format/strftime.rs | 15 +++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index caecb2b..32b5054 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -52,7 +52,7 @@ pub enum Pad { /// If the number is too long or (in some cases) negative, it is printed as is. /// /// The **parsing width** is the maximal width to be scanned. -/// The parser only tries to consume from one to given number of digits (greedily). +/// The parser only tries to consume from one to given number of digits (greedily). /// 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. @@ -177,6 +177,12 @@ pub enum Fixed { Nanosecond6, /// Same to [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 9. Nanosecond9, + /// Same to [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 3 and there is no leading dot. + Nanosecond3NoDot, + /// Same to [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 6 and there is no leading dot. + Nanosecond6NoDot, + /// Same to [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 9 and there is no leading dot. + Nanosecond9NoDot, /// Timezone name. /// /// It does not support parsing, its use in the parser is an immediate failure. @@ -475,6 +481,21 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt let nano = t.nanosecond() % 1_000_000_000; write!(w, ".{:09}", nano) }), + Nanosecond3NoDot => + time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:03}", nano / 1_000_000) + }), + Nanosecond6NoDot => + time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:06}", nano / 1_000) + }), + Nanosecond9NoDot => + time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:09}", nano) + }), TimezoneName => off.map(|&(ref name, _)| write!(w, "{}", *name)), TimezoneOffsetColon => diff --git a/src/format/parse.rs b/src/format/parse.rs index c5b14a2..2892145 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -308,13 +308,18 @@ pub fn parse<'a, I>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResult<( s = &s[2..]; } - Nanosecond | Nanosecond3 | Nanosecond6 | Nanosecond9=> { + Nanosecond | Nanosecond3 | Nanosecond6 | Nanosecond9 => { if s.starts_with('.') { let nano = try_consume!(scan::nanosecond(&s[1..])); try!(parsed.set_nanosecond(nano)); } } + Nanosecond3NoDot | Nanosecond6NoDot | Nanosecond9NoDot => { + let nano = try_consume!(scan::nanosecond(&s[1..])); + try!(parsed.set_nanosecond(nano)); + } + TimezoneName => return Err(BAD_FORMAT), TimezoneOffsetColon | TimezoneOffset => { @@ -584,6 +589,11 @@ fn test_parse() { num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, second: 5, offset: 32400); + check!("20150204143705567", + [num!(Year), num!(Month), num!(Day), + num!(Hour), num!(Minute), num!(Second), num!(Nanosecond)]; + year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, + minute: 37, second: 5, nanosecond: 567000000); check!("Mon, 10 Jun 2013 09:32:37 GMT", [fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "), fix!(ShortMonthName), sp!(" "), num!(Year), sp!(" "), num!(Hour), lit!(":"), diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 3d65019..6226a17 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -295,6 +295,18 @@ impl<'a> Iterator for StrftimeItems<'a> { 'f' => fix!(Nanosecond), _ => Item::Error, }, + '3' => match next!() { + 'f' => fix!(Nanosecond3NoDot), + _ => Item::Error, + }, + '6' => match next!() { + 'f' => fix!(Nanosecond6NoDot), + _ => Item::Error, + }, + '9' => match next!() { + 'f' => fix!(Nanosecond9NoDot), + _ => Item::Error, + }, '%' => lit!("%"), _ => Item::Error, // no such specifier }; @@ -433,6 +445,9 @@ fn test_strftime_docs() { assert_eq!(dt.format("%.3f").to_string(), ".026"); assert_eq!(dt.format("%.6f").to_string(), ".026490"); assert_eq!(dt.format("%.9f").to_string(), ".026490000"); + assert_eq!(dt.format("%3f").to_string(), "026"); + assert_eq!(dt.format("%6f").to_string(), "026490"); + assert_eq!(dt.format("%9f").to_string(), "026490000"); assert_eq!(dt.format("%R").to_string(), "00:34"); assert_eq!(dt.format("%T").to_string(), "00:34:60"); assert_eq!(dt.format("%X").to_string(), "00:34:60"); From f9d3094d4e966e74637ec0a079866ccff1ef2454 Mon Sep 17 00:00:00 2001 From: Evan Schwartz Date: Fri, 8 Jun 2018 17:45:16 -0400 Subject: [PATCH 2/7] Test nanosecond parsing without the dot --- src/format/parse.rs | 23 ++++++++++++++++++++--- src/format/parsed.rs | 10 ++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 2892145..ab3b1ac 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -316,8 +316,10 @@ pub fn parse<'a, I>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResult<( } Nanosecond3NoDot | Nanosecond6NoDot | Nanosecond9NoDot => { - let nano = try_consume!(scan::nanosecond(&s[1..])); - try!(parsed.set_nanosecond(nano)); + if !s.is_empty() { + let nano = try_consume!(scan::nanosecond(&s[..])); + try!(parsed.set_nanosecond(nano)); + } } TimezoneName => return Err(BAD_FORMAT), @@ -539,6 +541,21 @@ fn test_parse() { check!(". 4", [fix!(Nanosecond)]; INVALID); check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming + // fixed: nanoseconds without the dot + check!("", [fix!(Nanosecond3NoDot)]; ); // no field set, but not an error + check!("0", [fix!(Nanosecond3NoDot)]; nanosecond: 0); + check!("4", [fix!(Nanosecond3NoDot)]; nanosecond: 400_000_000); + check!("42", [fix!(Nanosecond3NoDot)]; nanosecond: 420_000_000); + check!("421", [fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); + check!("42195", [fix!(Nanosecond3NoDot)]; nanosecond: 421_950_000); + check!("421950803", [fix!(Nanosecond3NoDot)]; nanosecond: 421_950_803); + check!("421950803547", [fix!(Nanosecond3NoDot)]; nanosecond: 421_950_803); + check!("000000003547", [fix!(Nanosecond3NoDot)]; nanosecond: 3); + check!("000000000547", [fix!(Nanosecond3NoDot)]; nanosecond: 0); + check!("4x", [fix!(Nanosecond3NoDot)]; TOO_LONG); + check!(" 4", [fix!(Nanosecond3NoDot)]; INVALID); + check!(".421", [fix!(Nanosecond3NoDot)]; INVALID); + // fixed: timezone offsets check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); @@ -591,7 +608,7 @@ fn test_parse() { minute: 37, second: 5, offset: 32400); check!("20150204143705567", [num!(Year), num!(Month), num!(Day), - num!(Hour), num!(Minute), num!(Second), num!(Nanosecond)]; + num!(Hour), num!(Minute), num!(Second), fix!(Nanosecond3NoDot)]; year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, second: 5, nanosecond: 567000000); check!("Mon, 10 Jun 2013 09:32:37 GMT", diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 7b9708d..4d34e45 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -246,6 +246,16 @@ impl Parsed { set_if_consistent(&mut self.nanosecond, try!(value.to_u32().ok_or(OUT_OF_RANGE))) } + /// Tries to set the [`nanosecond`](#structfield.nanosecond) field from given value. + pub fn set_millisecond(&mut self, value: i64) -> ParseResult<()> { + set_if_consistent(&mut self.nanosecond, try!(value.to_u32().ok_or(OUT_OF_RANGE)) * 1_000_000) + } + + /// Tries to set the [`nanosecond`](#structfield.nanosecond) field from given value. + pub fn set_microsecond(&mut self, value: i64) -> ParseResult<()> { + set_if_consistent(&mut self.nanosecond, try!(value.to_u32().ok_or(OUT_OF_RANGE)) * 1_000) + } + /// Tries to set the [`timestamp`](#structfield.timestamp) field from given value. pub fn set_timestamp(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.timestamp, value) From 074e92fc634e27ab86d248307a1082188f688459 Mon Sep 17 00:00:00 2001 From: Evan Schwartz Date: Fri, 8 Jun 2018 17:52:02 -0400 Subject: [PATCH 3/7] Document millisecond format without the dot --- CHANGELOG.md | 1 + src/format/strftime.rs | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c29e310..6e3262a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Versions with only mechnical changes will be omitted from the following list. * Added a serde serialise/deserialise module for nanosecond timestamps. (@harkonenbade #247) * Added "Permissive" timezone parsing which allows a numeric timezone to be specified without minutes. (@quodlibetor #242) +* Added support for parsing nanoseconds without the leading dot (@emschwartz #251) ## 0.4.2 diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 6226a17..31f7ff1 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -58,6 +58,9 @@ The following specifiers are available both to formatting and parsing. | `%.3f`| `.026` | Similar to `.%f` but left-aligned but fixed to a length of 3. [8] | | `%.6f`| `.026490` | Similar to `.%f` but left-aligned but fixed to a length of 6. [8] | | `%.9f`| `.026490000` | Similar to `.%f` but left-aligned but fixed to a length of 9. [8] | +| `%3f` | `026` | Similar to `%.3f` but without the leading dot. [8] | +| `%6f` | `026490` | Similar to `%.6f` but without the leading dot. [8] | +| `%9f` | `026490000` | Similar to `%.9f` but without the leading dot. [8] | | | | | | `%R` | `00:34` | Hour-minute format. Same to `%H:%M`. | | `%T` | `00:34:60` | Hour-minute-second format. Same to `%H:%M:%S`. | @@ -123,7 +126,7 @@ Notes: For the purpose of Chrono, it only accounts for non-leap seconds so it slightly differs from ISO C `strftime` behavior. -8. `%f`, `%.f`, `%.3f`, `%.6f`, `%.9f`: +8. `%f`, `%.f`, `%.3f`, `%.6f`, `%.9f`, `%3f`, `%6f`, `%9f`: The default `%f` is right-aligned and always zero-padded to 9 digits for the compatibility with glibc and others, @@ -145,6 +148,12 @@ Notes: Note that they can read nothing if the fractional part is zero or the next character is not `.` however will print with the specified length. + The variant `%3f`, `%6f` and `%9f` are left-aligned and print 3, 6 or 9 fractional digits + according to the number preceding `f`, but without the leading dot. + E.g. 70ms after the last second under `%3f` will print `070` (note: not `07`), + and parsing `07`, `070000` etc. will yield the same. + Note that they can read nothing if the fractional part is zero. + */ use super::{Item, Numeric, Fixed, InternalFixed, InternalInternal, Pad}; From 8a2b09962a50077fa62af8dfd711aa46d0069c0d Mon Sep 17 00:00:00 2001 From: Evan Schwartz Date: Fri, 8 Jun 2018 18:47:38 -0400 Subject: [PATCH 4/7] Only parse up to max number of digits for nanoseconds w/o dots --- src/format/parse.rs | 55 +++++++++++++++++++++++++++++++++++++++----- src/format/parsed.rs | 10 -------- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index ab3b1ac..8f64cff 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -5,6 +5,7 @@ //! Date and time parsing routines. use std::usize; +use std::cmp; use Weekday; @@ -315,9 +316,26 @@ pub fn parse<'a, I>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResult<( } } - Nanosecond3NoDot | Nanosecond6NoDot | Nanosecond9NoDot => { + Nanosecond3NoDot => { if !s.is_empty() { - let nano = try_consume!(scan::nanosecond(&s[..])); + let max_index = cmp::min(s.len() as u64, 3) as usize; + let nano = try_consume!(scan::nanosecond(&s[..max_index])); + try!(parsed.set_nanosecond(nano)); + } + } + + Nanosecond6NoDot => { + if !s.is_empty() { + let max_index = cmp::min(s.len() as u64, 6) as usize; + let nano = try_consume!(scan::nanosecond(&s[..max_index])); + try!(parsed.set_nanosecond(nano)); + } + } + + Nanosecond9NoDot => { + if !s.is_empty() { + let max_index = cmp::min(s.len() as u64, 9) as usize; + let nano = try_consume!(scan::nanosecond(&s[..max_index])); try!(parsed.set_nanosecond(nano)); } } @@ -547,15 +565,40 @@ fn test_parse() { check!("4", [fix!(Nanosecond3NoDot)]; nanosecond: 400_000_000); check!("42", [fix!(Nanosecond3NoDot)]; nanosecond: 420_000_000); check!("421", [fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); - check!("42195", [fix!(Nanosecond3NoDot)]; nanosecond: 421_950_000); - check!("421950803", [fix!(Nanosecond3NoDot)]; nanosecond: 421_950_803); - check!("421950803547", [fix!(Nanosecond3NoDot)]; nanosecond: 421_950_803); - check!("000000003547", [fix!(Nanosecond3NoDot)]; nanosecond: 3); + check!("42195", [fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); // ignores everything after 3 digits check!("000000000547", [fix!(Nanosecond3NoDot)]; nanosecond: 0); check!("4x", [fix!(Nanosecond3NoDot)]; TOO_LONG); check!(" 4", [fix!(Nanosecond3NoDot)]; INVALID); check!(".421", [fix!(Nanosecond3NoDot)]; INVALID); + check!("", [fix!(Nanosecond6NoDot)]; ); // no field set, but not an error + check!("0", [fix!(Nanosecond6NoDot)]; nanosecond: 0); + check!("4", [fix!(Nanosecond6NoDot)]; nanosecond: 400_000_000); + check!("42", [fix!(Nanosecond6NoDot)]; nanosecond: 420_000_000); + check!("421", [fix!(Nanosecond6NoDot)]; nanosecond: 421_000_000); + check!("42195", [fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); + check!("421950803", [fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); // ignores everything after 6 digits + check!("000003547", [fix!(Nanosecond6NoDot)]; nanosecond: 3000); + check!("000000547", [fix!(Nanosecond6NoDot)]; nanosecond: 0); + check!("4x", [fix!(Nanosecond6NoDot)]; TOO_LONG); + check!(" 4", [fix!(Nanosecond6NoDot)]; INVALID); + check!(".421", [fix!(Nanosecond6NoDot)]; INVALID); + + check!("", [fix!(Nanosecond9NoDot)]; ); // no field set, but not an error + check!("0", [fix!(Nanosecond9NoDot)]; nanosecond: 0); + check!("4", [fix!(Nanosecond9NoDot)]; nanosecond: 400_000_000); + check!("42", [fix!(Nanosecond9NoDot)]; nanosecond: 420_000_000); + check!("421", [fix!(Nanosecond9NoDot)]; nanosecond: 421_000_000); + check!("42195", [fix!(Nanosecond9NoDot)]; nanosecond: 421_950_000); + check!("421950803", [fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); + check!("421950803547", [fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); + // check!("421950803547", [fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 + check!("000000003547", [fix!(Nanosecond9NoDot)]; nanosecond: 3); + check!("000000000547", [fix!(Nanosecond9NoDot)]; nanosecond: 0); + check!("4x", [fix!(Nanosecond9NoDot)]; TOO_LONG); + check!(" 4", [fix!(Nanosecond9NoDot)]; INVALID); + check!(".421", [fix!(Nanosecond9NoDot)]; INVALID); + // fixed: timezone offsets check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 4d34e45..7b9708d 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -246,16 +246,6 @@ impl Parsed { set_if_consistent(&mut self.nanosecond, try!(value.to_u32().ok_or(OUT_OF_RANGE))) } - /// Tries to set the [`nanosecond`](#structfield.nanosecond) field from given value. - pub fn set_millisecond(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.nanosecond, try!(value.to_u32().ok_or(OUT_OF_RANGE)) * 1_000_000) - } - - /// Tries to set the [`nanosecond`](#structfield.nanosecond) field from given value. - pub fn set_microsecond(&mut self, value: i64) -> ParseResult<()> { - set_if_consistent(&mut self.nanosecond, try!(value.to_u32().ok_or(OUT_OF_RANGE)) * 1_000) - } - /// Tries to set the [`timestamp`](#structfield.timestamp) field from given value. pub fn set_timestamp(&mut self, value: i64) -> ParseResult<()> { set_if_consistent(&mut self.timestamp, value) From 937189ee52da17c9b16a7b291fb1e8d6705cf4ff Mon Sep 17 00:00:00 2001 From: Evan Schwartz Date: Tue, 12 Jun 2018 10:49:13 -0400 Subject: [PATCH 5/7] Take exactly num digits specified for nanosecond fixed Error if it's too long or short, and don't take any more digits --- src/format/parse.rs | 82 +++++++++++++++++++-------------------------- src/format/scan.rs | 14 ++++++++ 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 8f64cff..85c6551 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -5,7 +5,6 @@ //! Date and time parsing routines. use std::usize; -use std::cmp; use Weekday; @@ -317,27 +316,21 @@ pub fn parse<'a, I>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResult<( } Nanosecond3NoDot => { - if !s.is_empty() { - let max_index = cmp::min(s.len() as u64, 3) as usize; - let nano = try_consume!(scan::nanosecond(&s[..max_index])); - try!(parsed.set_nanosecond(nano)); - } + if s.len() < 3 { return Err(TOO_SHORT); } + let nano = try_consume!(scan::nanosecond_fixed(s, 3)); + try!(parsed.set_nanosecond(nano)); } Nanosecond6NoDot => { - if !s.is_empty() { - let max_index = cmp::min(s.len() as u64, 6) as usize; - let nano = try_consume!(scan::nanosecond(&s[..max_index])); - try!(parsed.set_nanosecond(nano)); - } + if s.len() < 6 { return Err(TOO_SHORT); } + let nano = try_consume!(scan::nanosecond_fixed(s, 6)); + try!(parsed.set_nanosecond(nano)); } Nanosecond9NoDot => { - if !s.is_empty() { - let max_index = cmp::min(s.len() as u64, 9) as usize; - let nano = try_consume!(scan::nanosecond(&s[..max_index])); - try!(parsed.set_nanosecond(nano)); - } + if s.len() < 9 { return Err(TOO_SHORT); } + let nano = try_consume!(scan::nanosecond_fixed(s, 9)); + try!(parsed.set_nanosecond(nano)); } TimezoneName => return Err(BAD_FORMAT), @@ -560,44 +553,37 @@ fn test_parse() { check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming // fixed: nanoseconds without the dot - check!("", [fix!(Nanosecond3NoDot)]; ); // no field set, but not an error - check!("0", [fix!(Nanosecond3NoDot)]; nanosecond: 0); - check!("4", [fix!(Nanosecond3NoDot)]; nanosecond: 400_000_000); - check!("42", [fix!(Nanosecond3NoDot)]; nanosecond: 420_000_000); + check!("", [fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!("0", [fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!("4", [fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!("42", [fix!(Nanosecond3NoDot)]; TOO_SHORT); check!("421", [fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); - check!("42195", [fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); // ignores everything after 3 digits - check!("000000000547", [fix!(Nanosecond3NoDot)]; nanosecond: 0); - check!("4x", [fix!(Nanosecond3NoDot)]; TOO_LONG); + check!("42143", [fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43); + check!("42195", [fix!(Nanosecond3NoDot)]; TOO_LONG); + check!("4x", [fix!(Nanosecond3NoDot)]; TOO_SHORT); check!(" 4", [fix!(Nanosecond3NoDot)]; INVALID); check!(".421", [fix!(Nanosecond3NoDot)]; INVALID); - check!("", [fix!(Nanosecond6NoDot)]; ); // no field set, but not an error - check!("0", [fix!(Nanosecond6NoDot)]; nanosecond: 0); - check!("4", [fix!(Nanosecond6NoDot)]; nanosecond: 400_000_000); - check!("42", [fix!(Nanosecond6NoDot)]; nanosecond: 420_000_000); - check!("421", [fix!(Nanosecond6NoDot)]; nanosecond: 421_000_000); - check!("42195", [fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); - check!("421950803", [fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); // ignores everything after 6 digits - check!("000003547", [fix!(Nanosecond6NoDot)]; nanosecond: 3000); - check!("000000547", [fix!(Nanosecond6NoDot)]; nanosecond: 0); - check!("4x", [fix!(Nanosecond6NoDot)]; TOO_LONG); - check!(" 4", [fix!(Nanosecond6NoDot)]; INVALID); - check!(".421", [fix!(Nanosecond6NoDot)]; INVALID); + check!("", [fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!("0", [fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!("42195", [fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!("421950", [fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); + check!("000003", [fix!(Nanosecond6NoDot)]; nanosecond: 3000); + check!("000000", [fix!(Nanosecond6NoDot)]; nanosecond: 0); + check!("4x", [fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!(" 4", [fix!(Nanosecond6NoDot)]; INVALID); + check!(".42100", [fix!(Nanosecond6NoDot)]; INVALID); - check!("", [fix!(Nanosecond9NoDot)]; ); // no field set, but not an error - check!("0", [fix!(Nanosecond9NoDot)]; nanosecond: 0); - check!("4", [fix!(Nanosecond9NoDot)]; nanosecond: 400_000_000); - check!("42", [fix!(Nanosecond9NoDot)]; nanosecond: 420_000_000); - check!("421", [fix!(Nanosecond9NoDot)]; nanosecond: 421_000_000); - check!("42195", [fix!(Nanosecond9NoDot)]; nanosecond: 421_950_000); + check!("", [fix!(Nanosecond9NoDot)]; TOO_SHORT); + check!("42195", [fix!(Nanosecond9NoDot)]; TOO_SHORT); check!("421950803", [fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); - check!("421950803547", [fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); - // check!("421950803547", [fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 - check!("000000003547", [fix!(Nanosecond9NoDot)]; nanosecond: 3); - check!("000000000547", [fix!(Nanosecond9NoDot)]; nanosecond: 0); - check!("4x", [fix!(Nanosecond9NoDot)]; TOO_LONG); - check!(" 4", [fix!(Nanosecond9NoDot)]; INVALID); - check!(".421", [fix!(Nanosecond9NoDot)]; INVALID); + check!("000000003", [fix!(Nanosecond9NoDot)]; nanosecond: 3); + check!("42195080354", [fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 + check!("421950803547", [fix!(Nanosecond9NoDot)]; TOO_LONG); + check!("000000000", [fix!(Nanosecond9NoDot)]; nanosecond: 0); + check!("00000000x", [fix!(Nanosecond9NoDot)]; INVALID); + check!(" 4", [fix!(Nanosecond9NoDot)]; INVALID); + check!(".42100000", [fix!(Nanosecond9NoDot)]; INVALID); // fixed: timezone offsets check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); diff --git a/src/format/scan.rs b/src/format/scan.rs index 4a00641..f2f97d9 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -66,6 +66,20 @@ pub fn nanosecond(s: &str) -> ParseResult<(&str, i64)> { Ok((s, v)) } +/// Tries to consume a fixed number of digits as a fractional second. +/// Returns the number of whole nanoseconds (0--999,999,999). +pub fn nanosecond_fixed(s: &str, digits: usize) -> ParseResult<(&str, i64)> { + // record the number of digits consumed for later scaling. + let (s, v) = try!(number(s, digits, digits)); + + // 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[digits]).ok_or(OUT_OF_RANGE)); + + Ok((s, v)) +} + /// Tries to parse the month index (0 through 11) with the first three ASCII letters. pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> { if s.len() < 3 { return Err(TOO_SHORT); } From 11cfb3696e825a68ed358fbf9cc6d8dfb11ec199 Mon Sep 17 00:00:00 2001 From: Evan Schwartz Date: Tue, 12 Jun 2018 14:53:49 -0400 Subject: [PATCH 6/7] Move NanosecondXNoDot to internal enum To avoid adding new variants to the public enum (see https://github.com/chronotope/chrono/pull/251#issuecomment-396692148) --- src/format/mod.rs | 18 ++++++------ src/format/parse.rs | 66 +++++++++++++++++++++--------------------- src/format/strftime.rs | 6 ++-- 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/format/mod.rs b/src/format/mod.rs index 32b5054..bd8337b 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -177,12 +177,6 @@ pub enum Fixed { Nanosecond6, /// Same to [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 9. Nanosecond9, - /// Same to [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 3 and there is no leading dot. - Nanosecond3NoDot, - /// Same to [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 6 and there is no leading dot. - Nanosecond6NoDot, - /// Same to [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 9 and there is no leading dot. - Nanosecond9NoDot, /// Timezone name. /// /// It does not support parsing, its use in the parser is an immediate failure. @@ -235,6 +229,12 @@ enum InternalInternal { /// /// [iso8601]: https://en.wikipedia.org/wiki/ISO_8601#Time_offsets_from_UTC TimezoneOffsetPermissive, + /// Same to [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 3 and there is no leading dot. + Nanosecond3NoDot, + /// Same to [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 6 and there is no leading dot. + Nanosecond6NoDot, + /// Same to [`Nanosecond`](#variant.Nanosecond) but the accuracy is fixed to 9 and there is no leading dot. + Nanosecond9NoDot, } /// A single formatting item. This is used for both formatting and parsing. @@ -481,17 +481,17 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt let nano = t.nanosecond() % 1_000_000_000; write!(w, ".{:09}", nano) }), - Nanosecond3NoDot => + Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; write!(w, "{:03}", nano / 1_000_000) }), - Nanosecond6NoDot => + Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; write!(w, "{:06}", nano / 1_000) }), - Nanosecond9NoDot => + Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => time.map(|t| { let nano = t.nanosecond() % 1_000_000_000; write!(w, "{:09}", nano) diff --git a/src/format/parse.rs b/src/format/parse.rs index 85c6551..88c32d1 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -315,19 +315,19 @@ pub fn parse<'a, I>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResult<( } } - Nanosecond3NoDot => { + Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { if s.len() < 3 { return Err(TOO_SHORT); } let nano = try_consume!(scan::nanosecond_fixed(s, 3)); try!(parsed.set_nanosecond(nano)); } - Nanosecond6NoDot => { + Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { if s.len() < 6 { return Err(TOO_SHORT); } let nano = try_consume!(scan::nanosecond_fixed(s, 6)); try!(parsed.set_nanosecond(nano)); } - Nanosecond9NoDot => { + Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { if s.len() < 9 { return Err(TOO_SHORT); } let nano = try_consume!(scan::nanosecond_fixed(s, 9)); try!(parsed.set_nanosecond(nano)); @@ -553,37 +553,37 @@ fn test_parse() { check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming // fixed: nanoseconds without the dot - check!("", [fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("0", [fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("4", [fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("42", [fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!("421", [fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); - check!("42143", [fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43); - check!("42195", [fix!(Nanosecond3NoDot)]; TOO_LONG); - check!("4x", [fix!(Nanosecond3NoDot)]; TOO_SHORT); - check!(" 4", [fix!(Nanosecond3NoDot)]; INVALID); - check!(".421", [fix!(Nanosecond3NoDot)]; INVALID); + check!("", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!("0", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!("421", [internal_fix!(Nanosecond3NoDot)]; nanosecond: 421_000_000); + check!("42143", [internal_fix!(Nanosecond3NoDot), num!(Second)]; nanosecond: 421_000_000, second: 43); + check!("42195", [internal_fix!(Nanosecond3NoDot)]; TOO_LONG); + check!("4x", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); + check!(" 4", [internal_fix!(Nanosecond3NoDot)]; INVALID); + check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID); - check!("", [fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("0", [fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("42195", [fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!("421950", [fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); - check!("000003", [fix!(Nanosecond6NoDot)]; nanosecond: 3000); - check!("000000", [fix!(Nanosecond6NoDot)]; nanosecond: 0); - check!("4x", [fix!(Nanosecond6NoDot)]; TOO_SHORT); - check!(" 4", [fix!(Nanosecond6NoDot)]; INVALID); - check!(".42100", [fix!(Nanosecond6NoDot)]; INVALID); + check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!("42195", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!("421950", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 421_950_000); + check!("000003", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 3000); + check!("000000", [internal_fix!(Nanosecond6NoDot)]; nanosecond: 0); + check!("4x", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); + check!(" 4", [internal_fix!(Nanosecond6NoDot)]; INVALID); + check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID); - check!("", [fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!("42195", [fix!(Nanosecond9NoDot)]; TOO_SHORT); - check!("421950803", [fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); - check!("000000003", [fix!(Nanosecond9NoDot)]; nanosecond: 3); - check!("42195080354", [fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 - check!("421950803547", [fix!(Nanosecond9NoDot)]; TOO_LONG); - check!("000000000", [fix!(Nanosecond9NoDot)]; nanosecond: 0); - check!("00000000x", [fix!(Nanosecond9NoDot)]; INVALID); - check!(" 4", [fix!(Nanosecond9NoDot)]; INVALID); - check!(".42100000", [fix!(Nanosecond9NoDot)]; INVALID); + check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); + check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); + check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); + check!("000000003", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 3); + check!("42195080354", [internal_fix!(Nanosecond9NoDot), num!(Second)]; nanosecond: 421_950_803, second: 54); // don't skip digits that come after the 9 + check!("421950803547", [internal_fix!(Nanosecond9NoDot)]; TOO_LONG); + check!("000000000", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 0); + check!("00000000x", [internal_fix!(Nanosecond9NoDot)]; INVALID); + check!(" 4", [internal_fix!(Nanosecond9NoDot)]; INVALID); + check!(".42100000", [internal_fix!(Nanosecond9NoDot)]; INVALID); // fixed: timezone offsets check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); @@ -637,7 +637,7 @@ fn test_parse() { minute: 37, second: 5, offset: 32400); check!("20150204143705567", [num!(Year), num!(Month), num!(Day), - num!(Hour), num!(Minute), num!(Second), fix!(Nanosecond3NoDot)]; + num!(Hour), num!(Minute), num!(Second), internal_fix!(Nanosecond3NoDot)]; year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, minute: 37, second: 5, nanosecond: 567000000); check!("Mon, 10 Jun 2013 09:32:37 GMT", diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 31f7ff1..7b66665 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -305,15 +305,15 @@ impl<'a> Iterator for StrftimeItems<'a> { _ => Item::Error, }, '3' => match next!() { - 'f' => fix!(Nanosecond3NoDot), + 'f' => internal_fix!(Nanosecond3NoDot), _ => Item::Error, }, '6' => match next!() { - 'f' => fix!(Nanosecond6NoDot), + 'f' => internal_fix!(Nanosecond6NoDot), _ => Item::Error, }, '9' => match next!() { - 'f' => fix!(Nanosecond9NoDot), + 'f' => internal_fix!(Nanosecond9NoDot), _ => Item::Error, }, '%' => lit!("%"), From df4912a33f440914c8f22087ca954eb48c66a8b7 Mon Sep 17 00:00:00 2001 From: Brandon W Maister Date: Wed, 13 Jun 2018 23:00:20 -0400 Subject: [PATCH 7/7] Test formatting with more precise nanoseconds There is some specific behavior around writing out truncated nanos that we want to be sure to preserve. --- src/format/strftime.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 7b66665..d7cf68c 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -408,9 +408,9 @@ fn test_strftime_items() { #[cfg(test)] #[test] fn test_strftime_docs() { - use {FixedOffset, TimeZone}; + use {FixedOffset, TimeZone, Timelike}; - let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_000); + let dt = FixedOffset::east(34200).ymd(2001, 7, 8).and_hms_nano(0, 34, 59, 1_026_490_708); // date specifiers assert_eq!(dt.format("%Y").to_string(), "2001"); @@ -449,14 +449,16 @@ fn test_strftime_docs() { assert_eq!(dt.format("%p").to_string(), "AM"); assert_eq!(dt.format("%M").to_string(), "34"); assert_eq!(dt.format("%S").to_string(), "60"); - assert_eq!(dt.format("%f").to_string(), "026490000"); - assert_eq!(dt.format("%.f").to_string(), ".026490"); + assert_eq!(dt.format("%f").to_string(), "026490708"); + assert_eq!(dt.format("%.f").to_string(), ".026490708"); + assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), + ".026490"); assert_eq!(dt.format("%.3f").to_string(), ".026"); assert_eq!(dt.format("%.6f").to_string(), ".026490"); - assert_eq!(dt.format("%.9f").to_string(), ".026490000"); + assert_eq!(dt.format("%.9f").to_string(), ".026490708"); assert_eq!(dt.format("%3f").to_string(), "026"); assert_eq!(dt.format("%6f").to_string(), "026490"); - assert_eq!(dt.format("%9f").to_string(), "026490000"); + assert_eq!(dt.format("%9f").to_string(), "026490708"); assert_eq!(dt.format("%R").to_string(), "00:34"); assert_eq!(dt.format("%T").to_string(), "00:34:60"); assert_eq!(dt.format("%X").to_string(), "00:34:60"); @@ -469,7 +471,9 @@ fn test_strftime_docs() { // date & time specifiers assert_eq!(dt.format("%c").to_string(), "Sun Jul 8 00:34:60 2001"); - assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490+09:30"); + assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30"); + assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(), + "2001-07-08T00:34:60.026490+09:30"); assert_eq!(dt.format("%s").to_string(), "994518299"); // special specifiers