Merge pull request #251 from emschwartz/master
Add nanoseconds without dots
This commit is contained in:
commit
7baafafc0c
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 =>
|
||||||
|
|
|
@ -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!(":"),
|
||||||
|
|
|
@ -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); }
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue