Merge pull request #251 from emschwartz/master

Add nanoseconds without dots
This commit is contained in:
Brandon W Maister 2018-06-17 12:30:37 -04:00 committed by GitHub
commit 7baafafc0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 129 additions and 9 deletions

View File

@ -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 a serde serialise/deserialise module for nanosecond timestamps. (@harkonenbade #247)
* Added "Permissive" timezone parsing which allows a numeric timezone to * Added "Permissive" timezone parsing which allows a numeric timezone to
be specified without minutes. (@quodlibetor #242) be specified without minutes. (@quodlibetor #242)
* Added support for parsing nanoseconds without the leading dot (@emschwartz #251)
## 0.4.2 ## 0.4.2

View File

@ -229,6 +229,12 @@ enum InternalInternal {
/// ///
/// [iso8601]: https://en.wikipedia.org/wiki/ISO_8601#Time_offsets_from_UTC /// [iso8601]: https://en.wikipedia.org/wiki/ISO_8601#Time_offsets_from_UTC
TimezoneOffsetPermissive, 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. /// A single formatting item. This is used for both formatting and parsing.
@ -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; let nano = t.nanosecond() % 1_000_000_000;
write!(w, ".{:09}", nano) write!(w, ".{:09}", nano)
}), }),
Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) =>
time.map(|t| {
let nano = t.nanosecond() % 1_000_000_000;
write!(w, "{:03}", nano / 1_000_000)
}),
Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) =>
time.map(|t| {
let nano = t.nanosecond() % 1_000_000_000;
write!(w, "{:06}", nano / 1_000)
}),
Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) =>
time.map(|t| {
let nano = t.nanosecond() % 1_000_000_000;
write!(w, "{:09}", nano)
}),
TimezoneName => TimezoneName =>
off.map(|&(ref name, _)| write!(w, "{}", *name)), off.map(|&(ref name, _)| write!(w, "{}", *name)),
TimezoneOffsetColon => TimezoneOffsetColon =>

View File

@ -308,13 +308,31 @@ pub fn parse<'a, I>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResult<(
s = &s[2..]; s = &s[2..];
} }
Nanosecond | Nanosecond3 | Nanosecond6 | Nanosecond9=> { Nanosecond | Nanosecond3 | Nanosecond6 | Nanosecond9 => {
if s.starts_with('.') { if s.starts_with('.') {
let nano = try_consume!(scan::nanosecond(&s[1..])); let nano = try_consume!(scan::nanosecond(&s[1..]));
try!(parsed.set_nanosecond(nano)); try!(parsed.set_nanosecond(nano));
} }
} }
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));
}
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));
}
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));
}
TimezoneName => return Err(BAD_FORMAT), TimezoneName => return Err(BAD_FORMAT),
TimezoneOffsetColon | TimezoneOffset => { TimezoneOffsetColon | TimezoneOffset => {
@ -534,6 +552,39 @@ fn test_parse() {
check!(". 4", [fix!(Nanosecond)]; INVALID); check!(". 4", [fix!(Nanosecond)]; INVALID);
check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming
// fixed: nanoseconds without the dot
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!("", [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!("", [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 // fixed: timezone offsets
check!("+00:00", [fix!(TimezoneOffset)]; offset: 0); check!("+00:00", [fix!(TimezoneOffset)]; offset: 0);
check!("-00:00", [fix!(TimezoneOffset)]; offset: 0); check!("-00:00", [fix!(TimezoneOffset)]; offset: 0);
@ -584,6 +635,11 @@ fn test_parse() {
num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)]; num!(Hour), lit!(":"), num!(Minute), lit!(":"), num!(Second), fix!(TimezoneOffset)];
year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2, year: 2015, month: 2, day: 4, hour_div_12: 1, hour_mod_12: 2,
minute: 37, second: 5, offset: 32400); minute: 37, second: 5, offset: 32400);
check!("20150204143705567",
[num!(Year), num!(Month), num!(Day),
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", check!("Mon, 10 Jun 2013 09:32:37 GMT",
[fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "), [fix!(ShortWeekdayName), lit!(","), sp!(" "), num!(Day), sp!(" "),
fix!(ShortMonthName), sp!(" "), num!(Year), sp!(" "), num!(Hour), lit!(":"), fix!(ShortMonthName), sp!(" "), num!(Year), sp!(" "), num!(Hour), lit!(":"),

View File

@ -66,6 +66,20 @@ pub fn nanosecond(s: &str) -> ParseResult<(&str, i64)> {
Ok((s, v)) 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. /// Tries to parse the month index (0 through 11) with the first three ASCII letters.
pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> { pub fn short_month0(s: &str) -> ParseResult<(&str, u8)> {
if s.len() < 3 { return Err(TOO_SHORT); } if s.len() < 3 { return Err(TOO_SHORT); }

View File

@ -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] | | `%.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] | | `%.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] | | `%.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`. | | `%R` | `00:34` | Hour-minute format. Same to `%H:%M`. |
| `%T` | `00:34:60` | Hour-minute-second format. Same to `%H:%M:%S`. | | `%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 For the purpose of Chrono, it only accounts for non-leap seconds
so it slightly differs from ISO C `strftime` behavior. 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 The default `%f` is right-aligned and always zero-padded to 9 digits
for the compatibility with glibc and others, 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 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 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}; use super::{Item, Numeric, Fixed, InternalFixed, InternalInternal, Pad};
@ -295,6 +304,18 @@ impl<'a> Iterator for StrftimeItems<'a> {
'f' => fix!(Nanosecond), 'f' => fix!(Nanosecond),
_ => Item::Error, _ => Item::Error,
}, },
'3' => match next!() {
'f' => internal_fix!(Nanosecond3NoDot),
_ => Item::Error,
},
'6' => match next!() {
'f' => internal_fix!(Nanosecond6NoDot),
_ => Item::Error,
},
'9' => match next!() {
'f' => internal_fix!(Nanosecond9NoDot),
_ => Item::Error,
},
'%' => lit!("%"), '%' => lit!("%"),
_ => Item::Error, // no such specifier _ => Item::Error, // no such specifier
}; };
@ -387,9 +408,9 @@ fn test_strftime_items() {
#[cfg(test)] #[cfg(test)]
#[test] #[test]
fn test_strftime_docs() { 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 // date specifiers
assert_eq!(dt.format("%Y").to_string(), "2001"); assert_eq!(dt.format("%Y").to_string(), "2001");
@ -428,11 +449,16 @@ fn test_strftime_docs() {
assert_eq!(dt.format("%p").to_string(), "AM"); assert_eq!(dt.format("%p").to_string(), "AM");
assert_eq!(dt.format("%M").to_string(), "34"); assert_eq!(dt.format("%M").to_string(), "34");
assert_eq!(dt.format("%S").to_string(), "60"); assert_eq!(dt.format("%S").to_string(), "60");
assert_eq!(dt.format("%f").to_string(), "026490000"); assert_eq!(dt.format("%f").to_string(), "026490708");
assert_eq!(dt.format("%.f").to_string(), ".026490"); 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("%.3f").to_string(), ".026");
assert_eq!(dt.format("%.6f").to_string(), ".026490"); 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(), "026490708");
assert_eq!(dt.format("%R").to_string(), "00:34"); assert_eq!(dt.format("%R").to_string(), "00:34");
assert_eq!(dt.format("%T").to_string(), "00:34:60"); assert_eq!(dt.format("%T").to_string(), "00:34:60");
assert_eq!(dt.format("%X").to_string(), "00:34:60"); assert_eq!(dt.format("%X").to_string(), "00:34:60");
@ -445,7 +471,9 @@ fn test_strftime_docs() {
// date & time specifiers // date & time specifiers
assert_eq!(dt.format("%c").to_string(), "Sun Jul 8 00:34:60 2001"); 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"); assert_eq!(dt.format("%s").to_string(), "994518299");
// special specifiers // special specifiers