Merge pull request #358 from michalsrb/optimize-parsing

Optimize parsing
This commit is contained in:
Brandon W Maister 2019-11-23 19:09:12 -05:00 committed by GitHub
commit 46f8267c61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 270 additions and 166 deletions

View File

@ -8,6 +8,14 @@ Chrono obeys the principle of [Semantic Versioning](http://semver.org/).
There were/are numerous minor versions before 1.0 due to the language changes.
Versions with only mechanical changes will be omitted from the following list.
## next
### Features
* Functions that were accepting `Iterator` of `Item`s (for example
`format_with_items`) now accept `Iterator` of `Borrow<Item>`, so one can
use values or references.
## 0.4.9
### Fixes

View File

@ -29,6 +29,7 @@ alloc = []
std = []
clock = ["time", "std"]
wasmbind = ["wasm-bindgen", "js-sys"]
bench = ["std"]
[dependencies]
time = { version = "0.1.39", optional = true }

View File

@ -3,6 +3,7 @@
//! ISO 8601 calendar date with time zone.
use core::borrow::Borrow;
use core::{fmt, hash};
use core::cmp::Ordering;
use core::ops::{Add, Sub};
@ -258,8 +259,8 @@ impl<Tz: TimeZone> Date<Tz> where Tz::Offset: fmt::Display {
/// Formats the date with the specified formatting items.
#[cfg(any(feature = "alloc", feature = "std", test))]
#[inline]
pub fn format_with_items<'a, I>(&self, items: I) -> DelayedFormat<I>
where I: Iterator<Item=Item<'a>> + Clone {
pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat<I>
where I: Iterator<Item=B> + Clone, B: Borrow<Item<'a>> {
DelayedFormat::new_with_offset(Some(self.naive_local()), None, &self.offset, items)
}

View File

@ -25,6 +25,7 @@ use format::{Item, Numeric, Pad, Fixed};
use format::{parse, Parsed, ParseError, ParseResult, StrftimeItems};
#[cfg(any(feature = "alloc", feature = "std", test))]
use format::DelayedFormat;
use core::borrow::Borrow;
/// Specific formatting options for seconds. This may be extended in the
/// future, so exhaustive matching in external code is not recommended.
@ -326,7 +327,7 @@ impl DateTime<FixedOffset> {
pub fn parse_from_rfc2822(s: &str) -> ParseResult<DateTime<FixedOffset>> {
const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC2822)];
let mut parsed = Parsed::new();
try!(parse(&mut parsed, s, ITEMS.iter().cloned()));
try!(parse(&mut parsed, s, ITEMS.iter()));
parsed.to_datetime()
}
@ -338,7 +339,7 @@ impl DateTime<FixedOffset> {
pub fn parse_from_rfc3339(s: &str) -> ParseResult<DateTime<FixedOffset>> {
const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC3339)];
let mut parsed = Parsed::new();
try!(parse(&mut parsed, s, ITEMS.iter().cloned()));
try!(parse(&mut parsed, s, ITEMS.iter()));
parsed.to_datetime()
}
@ -374,14 +375,14 @@ impl<Tz: TimeZone> DateTime<Tz> where Tz::Offset: fmt::Display {
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn to_rfc2822(&self) -> String {
const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC2822)];
self.format_with_items(ITEMS.iter().cloned()).to_string()
self.format_with_items(ITEMS.iter()).to_string()
}
/// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`.
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn to_rfc3339(&self) -> String {
const ITEMS: &'static [Item<'static>] = &[Item::Fixed(Fixed::RFC3339)];
self.format_with_items(ITEMS.iter().cloned()).to_string()
self.format_with_items(ITEMS.iter()).to_string()
}
/// Return an RFC 3339 and ISO 8601 date and time string with subseconds
@ -450,11 +451,11 @@ impl<Tz: TimeZone> DateTime<Tz> where Tz::Offset: fmt::Display {
match ssitem {
None =>
self.format_with_items(
PREFIX.iter().chain([tzitem].iter()).cloned()
PREFIX.iter().chain([tzitem].iter())
).to_string(),
Some(s) =>
self.format_with_items(
PREFIX.iter().chain([s, tzitem].iter()).cloned()
PREFIX.iter().chain([s, tzitem].iter())
).to_string(),
}
}
@ -462,8 +463,8 @@ impl<Tz: TimeZone> DateTime<Tz> where Tz::Offset: fmt::Display {
/// Formats the combined date and time with the specified formatting items.
#[cfg(any(feature = "alloc", feature = "std", test))]
#[inline]
pub fn format_with_items<'a, I>(&self, items: I) -> DelayedFormat<I>
where I: Iterator<Item=Item<'a>> + Clone {
pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat<I>
where I: Iterator<Item=B> + Clone, B: Borrow<Item<'a>> {
let local = self.naive_local();
DelayedFormat::new_with_offset(Some(local.date()), Some(local.time()), &self.offset, items)
}
@ -621,24 +622,24 @@ impl str::FromStr for DateTime<FixedOffset> {
fn from_str(s: &str) -> ParseResult<DateTime<FixedOffset>> {
const ITEMS: &'static [Item<'static>] = &[
Item::Space(""), Item::Numeric(Numeric::Year, Pad::Zero),
Item::Numeric(Numeric::Year, Pad::Zero),
Item::Space(""), Item::Literal("-"),
Item::Space(""), Item::Numeric(Numeric::Month, Pad::Zero),
Item::Numeric(Numeric::Month, Pad::Zero),
Item::Space(""), Item::Literal("-"),
Item::Space(""), Item::Numeric(Numeric::Day, Pad::Zero),
Item::Numeric(Numeric::Day, Pad::Zero),
Item::Space(""), Item::Literal("T"), // XXX shouldn't this be case-insensitive?
Item::Space(""), Item::Numeric(Numeric::Hour, Pad::Zero),
Item::Numeric(Numeric::Hour, Pad::Zero),
Item::Space(""), Item::Literal(":"),
Item::Space(""), Item::Numeric(Numeric::Minute, Pad::Zero),
Item::Numeric(Numeric::Minute, Pad::Zero),
Item::Space(""), Item::Literal(":"),
Item::Space(""), Item::Numeric(Numeric::Second, Pad::Zero),
Item::Numeric(Numeric::Second, Pad::Zero),
Item::Fixed(Fixed::Nanosecond),
Item::Space(""), Item::Fixed(Fixed::TimezoneOffsetZ),
Item::Space(""),
];
let mut parsed = Parsed::new();
try!(parse(&mut parsed, s, ITEMS.iter().cloned()));
try!(parse(&mut parsed, s, ITEMS.iter()));
parsed.to_datetime()
}
}
@ -1140,7 +1141,7 @@ pub mod serde {
/// # fn main() { example().unwrap(); }
/// ```
pub mod ts_nanoseconds_option {
use std::fmt;
use core::fmt;
use serdelib::{ser, de};
use {DateTime, Utc};
@ -1431,7 +1432,7 @@ pub mod serde {
/// # fn main() { example().unwrap(); }
/// ```
pub mod ts_milliseconds_option {
use std::fmt;
use core::fmt;
use serdelib::{ser, de};
use {DateTime, Utc};
@ -1718,7 +1719,7 @@ pub mod serde {
/// # fn main() { example().unwrap(); }
/// ```
pub mod ts_seconds_option {
use std::fmt;
use core::fmt;
use serdelib::{ser, de};
use {DateTime, Utc};
@ -2213,4 +2214,53 @@ mod tests {
assert_eq!(format!("{} ", ymd_formatted), format!("{:<17}", ymd));
assert_eq!(format!(" {} ", ymd_formatted), format!("{:^17}", ymd));
}
#[cfg(feature = "bench")]
#[bench]
fn bench_datetime_parse_from_rfc2822(bh: &mut test::Bencher) {
bh.iter(|| {
let str = test::black_box("Wed, 18 Feb 2015 23:16:09 +0000");
DateTime::parse_from_rfc2822(str).unwrap()
});
}
#[cfg(feature = "bench")]
#[bench]
fn bench_datetime_parse_from_rfc3339(bh: &mut test::Bencher) {
bh.iter(|| {
let str = test::black_box("2015-02-18T23:59:60.234567+05:00");
DateTime::parse_from_rfc3339(str).unwrap()
});
}
#[cfg(feature = "bench")]
#[bench]
fn bench_datetime_from_str(bh: &mut test::Bencher) {
use std::str::FromStr;
bh.iter(|| {
let str = test::black_box("2019-03-30T18:46:57.193Z");
DateTime::<Utc>::from_str(str).unwrap()
});
}
#[cfg(feature = "bench")]
#[bench]
fn bench_datetime_to_rfc2822(bh: &mut test::Bencher) {
let pst = FixedOffset::east(8 * 60 * 60);
let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 084_660_000);
bh.iter(|| {
test::black_box(dt).to_rfc2822()
});
}
#[cfg(feature = "bench")]
#[bench]
fn bench_datetime_to_rfc3339(bh: &mut test::Bencher) {
let pst = FixedOffset::east(8 * 60 * 60);
let dt = pst.ymd(2018, 1, 11).and_hms_nano(10, 5, 13, 084_660_000);
bh.iter(|| {
test::black_box(dt).to_rfc3339()
});
}
}

View File

@ -17,6 +17,7 @@
#![allow(ellipsis_inclusive_range_patterns)]
use core::borrow::Borrow;
use core::fmt;
use core::str::FromStr;
#[cfg(any(feature = "std", test))]
@ -356,14 +357,14 @@ const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat);
/// Tries to format given arguments with given formatting items.
/// Internally used by `DelayedFormat`.
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn format<'a, I>(
pub fn format<'a, I, B>(
w: &mut fmt::Formatter,
date: Option<&NaiveDate>,
time: Option<&NaiveTime>,
off: Option<&(String, FixedOffset)>,
items: I,
) -> fmt::Result
where I: Iterator<Item=Item<'a>>
where I: Iterator<Item=B> + Clone, B: Borrow<Item<'a>>
{
// full and abbreviated month and weekday names
static SHORT_MONTHS: [&'static str; 12] =
@ -380,12 +381,12 @@ pub fn format<'a, I>(
let mut result = String::new();
for item in items {
match item {
Item::Literal(s) | Item::Space(s) => result.push_str(s),
match item.borrow() {
&Item::Literal(s) | &Item::Space(s) => result.push_str(s),
#[cfg(any(feature = "alloc", feature = "std", test))]
Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => result.push_str(s),
&Item::OwnedLiteral(ref s) | &Item::OwnedSpace(ref s) => result.push_str(s),
Item::Numeric(spec, pad) => {
&Item::Numeric(ref spec, ref pad) => {
use self::Numeric::*;
let week_from_sun = |d: &NaiveDate|
@ -394,31 +395,31 @@ pub fn format<'a, I>(
(d.ordinal() as i32 - d.weekday().num_days_from_monday() as i32 + 7) / 7;
let (width, v) = match spec {
Year => (4, date.map(|d| i64::from(d.year()))),
YearDiv100 => (2, date.map(|d| div_floor(i64::from(d.year()), 100))),
YearMod100 => (2, date.map(|d| mod_floor(i64::from(d.year()), 100))),
IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))),
IsoYearDiv100 => (2, date.map(|d| div_floor(
&Year => (4, date.map(|d| i64::from(d.year()))),
&YearDiv100 => (2, date.map(|d| div_floor(i64::from(d.year()), 100))),
&YearMod100 => (2, date.map(|d| mod_floor(i64::from(d.year()), 100))),
&IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))),
&IsoYearDiv100 => (2, date.map(|d| div_floor(
i64::from(d.iso_week().year()), 100))),
IsoYearMod100 => (2, date.map(|d| mod_floor(
&IsoYearMod100 => (2, date.map(|d| mod_floor(
i64::from(d.iso_week().year()), 100))),
Month => (2, date.map(|d| i64::from(d.month()))),
Day => (2, date.map(|d| i64::from(d.day()))),
WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))),
WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))),
IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))),
NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday()
&Month => (2, date.map(|d| i64::from(d.month()))),
&Day => (2, date.map(|d| i64::from(d.day()))),
&WeekFromSun => (2, date.map(|d| i64::from(week_from_sun(d)))),
&WeekFromMon => (2, date.map(|d| i64::from(week_from_mon(d)))),
&IsoWeek => (2, date.map(|d| i64::from(d.iso_week().week()))),
&NumDaysFromSun => (1, date.map(|d| i64::from(d.weekday()
.num_days_from_sunday()))),
WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday()
&WeekdayFromMon => (1, date.map(|d| i64::from(d.weekday()
.number_from_monday()))),
Ordinal => (3, date.map(|d| i64::from(d.ordinal()))),
Hour => (2, time.map(|t| i64::from(t.hour()))),
Hour12 => (2, time.map(|t| i64::from(t.hour12().1))),
Minute => (2, time.map(|t| i64::from(t.minute()))),
Second => (2, time.map(|t| i64::from(t.second() +
&Ordinal => (3, date.map(|d| i64::from(d.ordinal()))),
&Hour => (2, time.map(|t| i64::from(t.hour()))),
&Hour12 => (2, time.map(|t| i64::from(t.hour12().1))),
&Minute => (2, time.map(|t| i64::from(t.minute()))),
&Second => (2, time.map(|t| i64::from(t.second() +
t.nanosecond() / 1_000_000_000))),
Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))),
Timestamp => (1, match (date, time, off) {
&Nanosecond => (9, time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))),
&Timestamp => (1, match (date, time, off) {
(Some(d), Some(t), None) =>
Some(d.and_time(*t).timestamp()),
(Some(d), Some(t), Some(&(_, off))) =>
@ -427,24 +428,24 @@ pub fn format<'a, I>(
}),
// for the future expansion
Internal(ref int) => match int._dummy {},
&Internal(ref int) => match int._dummy {},
};
if let Some(v) = v {
try!(
if (spec == Year || spec == IsoYear) && !(0 <= v && v < 10_000) {
if (spec == &Year || spec == &IsoYear) && !(0 <= v && v < 10_000) {
// non-four-digit years require an explicit sign as per ISO 8601
match pad {
Pad::None => write!(result, "{:+}", v),
Pad::Zero => write!(result, "{:+01$}", v, width + 1),
Pad::Space => write!(result, "{:+1$}", v, width + 1),
&Pad::None => write!(result, "{:+}", v),
&Pad::Zero => write!(result, "{:+01$}", v, width + 1),
&Pad::Space => write!(result, "{:+1$}", v, width + 1),
}
} else {
match pad {
Pad::None => write!(result, "{}", v),
Pad::Zero => write!(result, "{:01$}", v, width),
Pad::Space => write!(result, "{:1$}", v, width),
&Pad::None => write!(result, "{}", v),
&Pad::Zero => write!(result, "{:01$}", v, width),
&Pad::Space => write!(result, "{:1$}", v, width),
}
}
)
@ -453,7 +454,7 @@ pub fn format<'a, I>(
}
},
Item::Fixed(spec) => {
&Item::Fixed(ref spec) => {
use self::Fixed::*;
/// Prints an offset from UTC in the format of `+HHMM` or `+HH:MM`.
@ -479,41 +480,41 @@ pub fn format<'a, I>(
}
let ret = match spec {
ShortMonthName =>
&ShortMonthName =>
date.map(|d| {
result.push_str(SHORT_MONTHS[d.month0() as usize]);
Ok(())
}),
LongMonthName =>
&LongMonthName =>
date.map(|d| {
result.push_str(LONG_MONTHS[d.month0() as usize]);
Ok(())
}),
ShortWeekdayName =>
&ShortWeekdayName =>
date.map(|d| {
result.push_str(
SHORT_WEEKDAYS[d.weekday().num_days_from_monday() as usize]
);
Ok(())
}),
LongWeekdayName =>
&LongWeekdayName =>
date.map(|d| {
result.push_str(
LONG_WEEKDAYS[d.weekday().num_days_from_monday() as usize]
);
Ok(())
}),
LowerAmPm =>
&LowerAmPm =>
time.map(|t| {
result.push_str(if t.hour12().0 {"pm"} else {"am"});
Ok(())
}),
UpperAmPm =>
&UpperAmPm =>
time.map(|t| {
result.push_str(if t.hour12().0 {"PM"} else {"AM"});
Ok(())
}),
Nanosecond =>
&Nanosecond =>
time.map(|t| {
let nano = t.nanosecond() % 1_000_000_000;
if nano == 0 {
@ -526,52 +527,52 @@ pub fn format<'a, I>(
write!(result, ".{:09}", nano)
}
}),
Nanosecond3 =>
&Nanosecond3 =>
time.map(|t| {
let nano = t.nanosecond() % 1_000_000_000;
write!(result, ".{:03}", nano / 1_000_000)
}),
Nanosecond6 =>
&Nanosecond6 =>
time.map(|t| {
let nano = t.nanosecond() % 1_000_000_000;
write!(result, ".{:06}", nano / 1_000)
}),
Nanosecond9 =>
&Nanosecond9 =>
time.map(|t| {
let nano = t.nanosecond() % 1_000_000_000;
write!(result, ".{:09}", nano)
}),
Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) =>
&Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) =>
time.map(|t| {
let nano = t.nanosecond() % 1_000_000_000;
write!(result, "{:03}", nano / 1_000_000)
}),
Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) =>
&Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) =>
time.map(|t| {
let nano = t.nanosecond() % 1_000_000_000;
write!(result, "{:06}", nano / 1_000)
}),
Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) =>
&Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) =>
time.map(|t| {
let nano = t.nanosecond() % 1_000_000_000;
write!(result, "{:09}", nano)
}),
TimezoneName =>
&TimezoneName =>
off.map(|&(ref name, _)| {
result.push_str(name);
Ok(())
}),
TimezoneOffsetColon =>
&TimezoneOffsetColon =>
off.map(|&(_, off)| write_local_minus_utc(&mut result, off, false, true)),
TimezoneOffsetColonZ =>
&TimezoneOffsetColonZ =>
off.map(|&(_, off)| write_local_minus_utc(&mut result, off, true, true)),
TimezoneOffset =>
&TimezoneOffset =>
off.map(|&(_, off)| write_local_minus_utc(&mut result, off, false, false)),
TimezoneOffsetZ =>
&TimezoneOffsetZ =>
off.map(|&(_, off)| write_local_minus_utc(&mut result, off, true, false)),
Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) =>
&Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) =>
panic!("Do not try to write %#z it is undefined"),
RFC2822 => // same to `%a, %e %b %Y %H:%M:%S %z`
&RFC2822 => // same to `%a, %e %b %Y %H:%M:%S %z`
if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
let sec = t.second() + t.nanosecond() / 1_000_000_000;
try!(write!(
@ -585,7 +586,7 @@ pub fn format<'a, I>(
} else {
None
},
RFC3339 => // same to `%Y-%m-%dT%H:%M:%S%.f%:z`
&RFC3339 => // same to `%Y-%m-%dT%H:%M:%S%.f%:z`
if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) {
// reuse `Debug` impls which already print ISO 8601 format.
// this is faster in this way.
@ -602,7 +603,7 @@ pub fn format<'a, I>(
}
},
Item::Error => return Err(fmt::Error),
&Item::Error => return Err(fmt::Error),
}
}
@ -633,7 +634,7 @@ pub struct DelayedFormat<I> {
}
#[cfg(any(feature = "alloc", feature = "std", test))]
impl<'a, I: Iterator<Item=Item<'a>> + Clone> DelayedFormat<I> {
impl<'a, I: Iterator<Item=B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
/// Makes a new `DelayedFormat` value out of local date and time.
pub fn new(date: Option<NaiveDate>, time: Option<NaiveTime>, items: I) -> DelayedFormat<I> {
DelayedFormat { date: date, time: time, off: None, items: items }
@ -649,7 +650,7 @@ impl<'a, I: Iterator<Item=Item<'a>> + Clone> DelayedFormat<I> {
}
#[cfg(any(feature = "alloc", feature = "std", test))]
impl<'a, I: Iterator<Item=Item<'a>> + Clone> fmt::Display for DelayedFormat<I> {
impl<'a, I: Iterator<Item=B> + Clone, B: Borrow<Item<'a>>> fmt::Display for DelayedFormat<I> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
format(f, self.date.as_ref(), self.time.as_ref(), self.off.as_ref(), self.items.clone())
}

View File

@ -6,6 +6,7 @@
#![allow(deprecated)]
use core::borrow::Borrow;
use core::usize;
use Weekday;
@ -204,64 +205,64 @@ fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st
/// so one can prepend any number of whitespace then any number of zeroes before numbers.
///
/// - (Still) obeying the intrinsic parsing width. This allows, for example, parsing `HHMMSS`.
pub fn parse<'a, I>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResult<()>
where I: Iterator<Item=Item<'a>> {
pub fn parse<'a, I, B>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResult<()>
where I: Iterator<Item=B>, B: Borrow<Item<'a>> {
macro_rules! try_consume {
($e:expr) => ({ let (s_, v) = try!($e); s = s_; v })
}
for item in items {
match item {
Item::Literal(prefix) => {
match item.borrow() {
&Item::Literal(prefix) => {
if s.len() < prefix.len() { return Err(TOO_SHORT); }
if !s.starts_with(prefix) { return Err(INVALID); }
s = &s[prefix.len()..];
}
#[cfg(any(feature = "alloc", feature = "std", test))]
Item::OwnedLiteral(ref prefix) => {
&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::Space(_) => {
s = s.trim_left();
}
#[cfg(any(feature = "alloc", feature = "std", test))]
Item::OwnedSpace(_) => {
&Item::OwnedSpace(_) => {
s = s.trim_left();
}
Item::Numeric(spec, _pad) => {
&Item::Numeric(ref spec, ref _pad) => {
use super::Numeric::*;
type Setter = fn(&mut Parsed, i64) -> ParseResult<()>;
let (width, signed, set): (usize, bool, Setter) = match spec {
Year => (4, true, Parsed::set_year),
YearDiv100 => (2, false, Parsed::set_year_div_100),
YearMod100 => (2, false, Parsed::set_year_mod_100),
IsoYear => (4, true, Parsed::set_isoyear),
IsoYearDiv100 => (2, false, Parsed::set_isoyear_div_100),
IsoYearMod100 => (2, false, Parsed::set_isoyear_mod_100),
Month => (2, false, Parsed::set_month),
Day => (2, false, Parsed::set_day),
WeekFromSun => (2, false, Parsed::set_week_from_sun),
WeekFromMon => (2, false, Parsed::set_week_from_mon),
IsoWeek => (2, false, Parsed::set_isoweek),
NumDaysFromSun => (1, false, set_weekday_with_num_days_from_sunday),
WeekdayFromMon => (1, false, set_weekday_with_number_from_monday),
Ordinal => (3, false, Parsed::set_ordinal),
Hour => (2, false, Parsed::set_hour),
Hour12 => (2, false, Parsed::set_hour12),
Minute => (2, false, Parsed::set_minute),
Second => (2, false, Parsed::set_second),
Nanosecond => (9, false, Parsed::set_nanosecond),
Timestamp => (usize::MAX, false, Parsed::set_timestamp),
&Year => (4, true, Parsed::set_year),
&YearDiv100 => (2, false, Parsed::set_year_div_100),
&YearMod100 => (2, false, Parsed::set_year_mod_100),
&IsoYear => (4, true, Parsed::set_isoyear),
&IsoYearDiv100 => (2, false, Parsed::set_isoyear_div_100),
&IsoYearMod100 => (2, false, Parsed::set_isoyear_mod_100),
&Month => (2, false, Parsed::set_month),
&Day => (2, false, Parsed::set_day),
&WeekFromSun => (2, false, Parsed::set_week_from_sun),
&WeekFromMon => (2, false, Parsed::set_week_from_mon),
&IsoWeek => (2, false, Parsed::set_isoweek),
&NumDaysFromSun => (1, false, set_weekday_with_num_days_from_sunday),
&WeekdayFromMon => (1, false, set_weekday_with_number_from_monday),
&Ordinal => (3, false, Parsed::set_ordinal),
&Hour => (2, false, Parsed::set_hour),
&Hour12 => (2, false, Parsed::set_hour12),
&Minute => (2, false, Parsed::set_minute),
&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 {},
&Internal(ref int) => match int._dummy {},
};
s = s.trim_left();
@ -281,31 +282,31 @@ pub fn parse<'a, I>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResult<(
try!(set(parsed, v));
}
Item::Fixed(spec) => {
&Item::Fixed(ref spec) => {
use super::Fixed::*;
match spec {
ShortMonthName => {
&ShortMonthName => {
let month0 = try_consume!(scan::short_month0(s));
try!(parsed.set_month(i64::from(month0) + 1));
}
LongMonthName => {
&LongMonthName => {
let month0 = try_consume!(scan::short_or_long_month0(s));
try!(parsed.set_month(i64::from(month0) + 1));
}
ShortWeekdayName => {
&ShortWeekdayName => {
let weekday = try_consume!(scan::short_weekday(s));
try!(parsed.set_weekday(weekday));
}
LongWeekdayName => {
&LongWeekdayName => {
let weekday = try_consume!(scan::short_or_long_weekday(s));
try!(parsed.set_weekday(weekday));
}
LowerAmPm | UpperAmPm => {
&LowerAmPm | &UpperAmPm => {
if s.len() < 2 { return Err(TOO_SHORT); }
let ampm = match (s.as_bytes()[0] | 32, s.as_bytes()[1] | 32) {
(b'a',b'm') => false,
@ -316,56 +317,56 @@ 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));
}
}
Internal(InternalFixed { val: InternalInternal::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));
}
Internal(InternalFixed { val: InternalInternal::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));
}
Internal(InternalFixed { val: InternalInternal::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));
}
TimezoneName => return Err(BAD_FORMAT),
&TimezoneName => return Err(BAD_FORMAT),
TimezoneOffsetColon | TimezoneOffset => {
&TimezoneOffsetColon | &TimezoneOffset => {
let offset = try_consume!(scan::timezone_offset(s.trim_left(),
scan::colon_or_space));
try!(parsed.set_offset(i64::from(offset)));
}
TimezoneOffsetColonZ | TimezoneOffsetZ => {
&TimezoneOffsetColonZ | &TimezoneOffsetZ => {
let offset = try_consume!(scan::timezone_offset_zulu(s.trim_left(),
scan::colon_or_space));
try!(parsed.set_offset(i64::from(offset)));
}
Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => {
&Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => {
let offset = try_consume!(scan::timezone_offset_permissive(
s.trim_left(), scan::colon_or_space));
try!(parsed.set_offset(i64::from(offset)));
}
RFC2822 => try_consume!(parse_rfc2822(parsed, s)),
RFC3339 => try_consume!(parse_rfc3339(parsed, s)),
&RFC2822 => try_consume!(parse_rfc2822(parsed, s)),
&RFC3339 => try_consume!(parse_rfc3339(parsed, s)),
}
}
Item::Error => {
&Item::Error => {
return Err(BAD_FORMAT);
}
}
@ -388,7 +389,7 @@ fn test_parse() {
// workaround for Rust issue #22255
fn parse_all(s: &str, items: &[Item]) -> ParseResult<Parsed> {
let mut parsed = Parsed::new();
try!(parse(&mut parsed, s, items.iter().cloned()));
try!(parse(&mut parsed, s, items.iter()));
Ok(parsed)
}
@ -699,12 +700,12 @@ fn test_rfc2822() {
fn rfc2822_to_datetime(date: &str) -> ParseResult<DateTime<FixedOffset>> {
let mut parsed = Parsed::new();
try!(parse(&mut parsed, date, [Item::Fixed(Fixed::RFC2822)].iter().cloned()));
try!(parse(&mut parsed, date, [Item::Fixed(Fixed::RFC2822)].iter()));
parsed.to_datetime()
}
fn fmt_rfc2822_datetime(dt: DateTime<FixedOffset>) -> String {
dt.format_with_items([Item::Fixed(Fixed::RFC2822)].iter().cloned()).to_string()
dt.format_with_items([Item::Fixed(Fixed::RFC2822)].iter()).to_string()
}
// Test against test data above
@ -780,12 +781,12 @@ fn test_rfc3339() {
fn rfc3339_to_datetime(date: &str) -> ParseResult<DateTime<FixedOffset>> {
let mut parsed = Parsed::new();
try!(parse(&mut parsed, date, [Item::Fixed(Fixed::RFC3339)].iter().cloned()));
try!(parse(&mut parsed, date, [Item::Fixed(Fixed::RFC3339)].iter()));
parsed.to_datetime()
}
fn fmt_rfc3339_datetime(dt: DateTime<FixedOffset>) -> String {
dt.format_with_items([Item::Fixed(Fixed::RFC3339)].iter().cloned()).to_string()
dt.format_with_items([Item::Fixed(Fixed::RFC3339)].iter()).to_string()
}
// Test against test data above

View File

@ -112,6 +112,7 @@ pub struct Parsed {
/// Checks if `old` is either empty or has the same value to `new` (i.e. "consistent"),
/// and if it is empty, set `old` to `new` as well.
#[inline]
fn set_if_consistent<T: PartialEq>(old: &mut Option<T>, new: T) -> ParseResult<()> {
if let Some(ref old) = *old {
if *old == new {Ok(())} else {Err(IMPOSSIBLE)}
@ -141,82 +142,97 @@ impl Parsed {
}
/// Tries to set the [`year`](#structfield.year) field from given value.
#[inline]
pub fn set_year(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.year, try!(value.to_i32().ok_or(OUT_OF_RANGE)))
}
/// Tries to set the [`year_div_100`](#structfield.year_div_100) field from given value.
#[inline]
pub fn set_year_div_100(&mut self, value: i64) -> ParseResult<()> {
if value < 0 { return Err(OUT_OF_RANGE); }
set_if_consistent(&mut self.year_div_100, try!(value.to_i32().ok_or(OUT_OF_RANGE)))
}
/// Tries to set the [`year_mod_100`](#structfield.year_mod_100) field from given value.
#[inline]
pub fn set_year_mod_100(&mut self, value: i64) -> ParseResult<()> {
if value < 0 { return Err(OUT_OF_RANGE); }
set_if_consistent(&mut self.year_mod_100, try!(value.to_i32().ok_or(OUT_OF_RANGE)))
}
/// Tries to set the [`isoyear`](#structfield.isoyear) field from given value.
#[inline]
pub fn set_isoyear(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.isoyear, try!(value.to_i32().ok_or(OUT_OF_RANGE)))
}
/// Tries to set the [`isoyear_div_100`](#structfield.isoyear_div_100) field from given value.
#[inline]
pub fn set_isoyear_div_100(&mut self, value: i64) -> ParseResult<()> {
if value < 0 { return Err(OUT_OF_RANGE); }
set_if_consistent(&mut self.isoyear_div_100, try!(value.to_i32().ok_or(OUT_OF_RANGE)))
}
/// Tries to set the [`isoyear_mod_100`](#structfield.isoyear_mod_100) field from given value.
#[inline]
pub fn set_isoyear_mod_100(&mut self, value: i64) -> ParseResult<()> {
if value < 0 { return Err(OUT_OF_RANGE); }
set_if_consistent(&mut self.isoyear_mod_100, try!(value.to_i32().ok_or(OUT_OF_RANGE)))
}
/// Tries to set the [`month`](#structfield.month) field from given value.
#[inline]
pub fn set_month(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.month, try!(value.to_u32().ok_or(OUT_OF_RANGE)))
}
/// Tries to set the [`week_from_sun`](#structfield.week_from_sun) field from given value.
#[inline]
pub fn set_week_from_sun(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.week_from_sun, try!(value.to_u32().ok_or(OUT_OF_RANGE)))
}
/// Tries to set the [`week_from_mon`](#structfield.week_from_mon) field from given value.
#[inline]
pub fn set_week_from_mon(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.week_from_mon, try!(value.to_u32().ok_or(OUT_OF_RANGE)))
}
/// Tries to set the [`isoweek`](#structfield.isoweek) field from given value.
#[inline]
pub fn set_isoweek(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.isoweek, try!(value.to_u32().ok_or(OUT_OF_RANGE)))
}
/// Tries to set the [`weekday`](#structfield.weekday) field from given value.
#[inline]
pub fn set_weekday(&mut self, value: Weekday) -> ParseResult<()> {
set_if_consistent(&mut self.weekday, value)
}
/// Tries to set the [`ordinal`](#structfield.ordinal) field from given value.
#[inline]
pub fn set_ordinal(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.ordinal, try!(value.to_u32().ok_or(OUT_OF_RANGE)))
}
/// Tries to set the [`day`](#structfield.day) field from given value.
#[inline]
pub fn set_day(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.day, try!(value.to_u32().ok_or(OUT_OF_RANGE)))
}
/// Tries to set the [`hour_div_12`](#structfield.hour_div_12) field from given value.
/// (`false` for AM, `true` for PM)
#[inline]
pub fn set_ampm(&mut self, value: bool) -> ParseResult<()> {
set_if_consistent(&mut self.hour_div_12, if value {1} else {0})
}
/// Tries to set the [`hour_mod_12`](#structfield.hour_mod_12) field from
/// given hour number in 12-hour clocks.
#[inline]
pub fn set_hour12(&mut self, value: i64) -> ParseResult<()> {
if value < 1 || value > 12 { return Err(OUT_OF_RANGE); }
set_if_consistent(&mut self.hour_mod_12, value as u32 % 12)
@ -224,6 +240,7 @@ impl Parsed {
/// Tries to set both [`hour_div_12`](#structfield.hour_div_12) and
/// [`hour_mod_12`](#structfield.hour_mod_12) fields from given value.
#[inline]
pub fn set_hour(&mut self, value: i64) -> ParseResult<()> {
let v = try!(value.to_u32().ok_or(OUT_OF_RANGE));
try!(set_if_consistent(&mut self.hour_div_12, v / 12));
@ -232,26 +249,31 @@ impl Parsed {
}
/// Tries to set the [`minute`](#structfield.minute) field from given value.
#[inline]
pub fn set_minute(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.minute, try!(value.to_u32().ok_or(OUT_OF_RANGE)))
}
/// Tries to set the [`second`](#structfield.second) field from given value.
#[inline]
pub fn set_second(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.second, try!(value.to_u32().ok_or(OUT_OF_RANGE)))
}
/// Tries to set the [`nanosecond`](#structfield.nanosecond) field from given value.
#[inline]
pub fn set_nanosecond(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.nanosecond, try!(value.to_u32().ok_or(OUT_OF_RANGE)))
}
/// Tries to set the [`timestamp`](#structfield.timestamp) field from given value.
#[inline]
pub fn set_timestamp(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.timestamp, value)
}
/// Tries to set the [`offset`](#structfield.offset) field from given value.
#[inline]
pub fn set_offset(&mut self, value: i64) -> ParseResult<()> {
set_if_consistent(&mut self.offset, try!(value.to_i32().ok_or(OUT_OF_RANGE)))
}

View File

@ -30,23 +30,35 @@ fn equals(s: &str, pattern: &str) -> bool {
/// The absence of digits at all is an unconditional error.
/// More than `max` digits are consumed up to the first `max` digits.
/// Any number that does not fit in `i64` is an error.
#[inline]
pub fn number(s: &str, min: usize, max: usize) -> ParseResult<(&str, i64)> {
assert!(min <= max);
// limit `s` to given number of digits
let mut window = s.as_bytes();
if window.len() > max { window = &window[..max]; }
// scan digits
let upto = window.iter().position(|&c| c < b'0' || b'9' < c)
.unwrap_or_else(|| window.len());
if upto < min {
return Err(if window.is_empty() {TOO_SHORT} else {INVALID});
// We are only interested in ascii numbers, so we can work with the `str` as bytes. We stop on
// the first non-numeric byte, which may be another ascii character or beginning of multi-byte
// UTF-8 character.
let bytes = s.as_bytes();
if bytes.len() < min {
return Err(TOO_SHORT);
}
// we can overflow here, which is the only possible cause of error from `parse`.
let v: i64 = try!(s[..upto].parse().map_err(|_| OUT_OF_RANGE));
Ok((&s[upto..], v))
let mut n = 0i64;
for (i, c) in bytes.iter().take(max).cloned().enumerate() { // cloned() = copied()
if c < b'0' || b'9' < c {
if i < min {
return Err(INVALID);
} else {
return Ok((&s[i..], n));
}
}
n = match n.checked_mul(10).and_then(|n| n.checked_add((c - b'0') as i64)) {
Some(n) => n,
None => return Err(OUT_OF_RANGE),
};
}
Ok((&s[::core::cmp::min(max, bytes.len())..], n))
}
/// Tries to consume at least one digits as a fractional second.

View File

@ -383,9 +383,10 @@
#![doc(html_root_url = "https://docs.rs/chrono/latest/")]
#![cfg_attr(bench, feature(test))] // lib stability features as per RFC #507
#![cfg_attr(feature = "bench", feature(test))] // lib stability features as per RFC #507
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#![allow(deprecated)]
#![cfg_attr(not(any(feature = "std", test)), no_std)]
@ -427,6 +428,8 @@ extern crate doc_comment;
extern crate wasm_bindgen;
#[cfg(all(target_arch = "wasm32", feature="wasmbind"))]
extern crate js_sys;
#[cfg(feature = "bench")]
extern crate test;
#[cfg(test)]
doctest!("../README.md");

View File

@ -3,6 +3,8 @@
//! ISO 8601 calendar date without timezone.
#[cfg(any(feature = "alloc", feature = "std", test))]
use core::borrow::Borrow;
use core::{str, fmt};
use core::ops::{Add, Sub, AddAssign, SubAssign};
use num_traits::ToPrimitive;
@ -920,8 +922,8 @@ impl NaiveDate {
/// ~~~~
#[cfg(any(feature = "alloc", feature = "std", test))]
#[inline]
pub fn format_with_items<'a, I>(&self, items: I) -> DelayedFormat<I>
where I: Iterator<Item=Item<'a>> + Clone {
pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat<I>
where I: Iterator<Item=B> + Clone, B: Borrow<Item<'a>> {
DelayedFormat::new(Some(*self), None, items)
}
@ -1507,16 +1509,16 @@ impl str::FromStr for NaiveDate {
fn from_str(s: &str) -> ParseResult<NaiveDate> {
const ITEMS: &'static [Item<'static>] = &[
Item::Space(""), Item::Numeric(Numeric::Year, Pad::Zero),
Item::Numeric(Numeric::Year, Pad::Zero),
Item::Space(""), Item::Literal("-"),
Item::Space(""), Item::Numeric(Numeric::Month, Pad::Zero),
Item::Numeric(Numeric::Month, Pad::Zero),
Item::Space(""), Item::Literal("-"),
Item::Space(""), Item::Numeric(Numeric::Day, Pad::Zero),
Item::Numeric(Numeric::Day, Pad::Zero),
Item::Space(""),
];
let mut parsed = Parsed::new();
try!(parse(&mut parsed, s, ITEMS.iter().cloned()));
try!(parse(&mut parsed, s, ITEMS.iter()));
parsed.to_naive_date()
}
}

View File

@ -3,6 +3,8 @@
//! ISO 8601 date and time without timezone.
#[cfg(any(feature = "alloc", feature = "std", test))]
use core::borrow::Borrow;
use core::{str, fmt, hash};
use core::ops::{Add, Sub, AddAssign, SubAssign};
use num_traits::ToPrimitive;
@ -649,8 +651,8 @@ impl NaiveDateTime {
/// ~~~~
#[cfg(any(feature = "alloc", feature = "std", test))]
#[inline]
pub fn format_with_items<'a, I>(&self, items: I) -> DelayedFormat<I>
where I: Iterator<Item=Item<'a>> + Clone {
pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat<I>
where I: Iterator<Item=B> + Clone, B: Borrow<Item<'a>> {
DelayedFormat::new(Some(self.date), Some(self.time), items)
}
@ -1472,22 +1474,22 @@ impl str::FromStr for NaiveDateTime {
fn from_str(s: &str) -> ParseResult<NaiveDateTime> {
const ITEMS: &'static [Item<'static>] = &[
Item::Space(""), Item::Numeric(Numeric::Year, Pad::Zero),
Item::Numeric(Numeric::Year, Pad::Zero),
Item::Space(""), Item::Literal("-"),
Item::Space(""), Item::Numeric(Numeric::Month, Pad::Zero),
Item::Numeric(Numeric::Month, Pad::Zero),
Item::Space(""), Item::Literal("-"),
Item::Space(""), Item::Numeric(Numeric::Day, Pad::Zero),
Item::Numeric(Numeric::Day, Pad::Zero),
Item::Space(""), Item::Literal("T"), // XXX shouldn't this be case-insensitive?
Item::Space(""), Item::Numeric(Numeric::Hour, Pad::Zero),
Item::Numeric(Numeric::Hour, Pad::Zero),
Item::Space(""), Item::Literal(":"),
Item::Space(""), Item::Numeric(Numeric::Minute, Pad::Zero),
Item::Numeric(Numeric::Minute, Pad::Zero),
Item::Space(""), Item::Literal(":"),
Item::Space(""), Item::Numeric(Numeric::Second, Pad::Zero),
Item::Numeric(Numeric::Second, Pad::Zero),
Item::Fixed(Fixed::Nanosecond), Item::Space(""),
];
let mut parsed = Parsed::new();
try!(parse(&mut parsed, s, ITEMS.iter().cloned()));
try!(parse(&mut parsed, s, ITEMS.iter()));
parsed.to_naive_datetime_with_offset(0)
}
}

View File

@ -470,7 +470,6 @@ impl fmt::Debug for Mdf {
#[cfg(test)]
mod tests {
#[cfg(test)] extern crate num_iter;
#[cfg(bench)] extern crate test;
use Weekday;
use super::{Of, Mdf};
@ -517,7 +516,7 @@ mod tests {
assert_eq!(GF.nisoweeks(), 52);
}
#[cfg(bench)]
#[cfg(feature = "bench")]
#[bench]
fn bench_year_flags_from_year(bh: &mut test::Bencher) {
bh.iter(|| {

View File

@ -3,6 +3,8 @@
//! ISO 8601 time without timezone.
#[cfg(any(feature = "alloc", feature = "std", test))]
use core::borrow::Borrow;
use core::{str, fmt, hash};
use core::ops::{Add, Sub, AddAssign, SubAssign};
use oldtime::Duration as OldDuration;
@ -727,8 +729,8 @@ impl NaiveTime {
/// ~~~~
#[cfg(any(feature = "alloc", feature = "std", test))]
#[inline]
pub fn format_with_items<'a, I>(&self, items: I) -> DelayedFormat<I>
where I: Iterator<Item=Item<'a>> + Clone {
pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat<I>
where I: Iterator<Item=B> + Clone, B: Borrow<Item<'a>> {
DelayedFormat::new(None, Some(*self), items)
}
@ -1303,16 +1305,16 @@ impl str::FromStr for NaiveTime {
fn from_str(s: &str) -> ParseResult<NaiveTime> {
const ITEMS: &'static [Item<'static>] = &[
Item::Space(""), Item::Numeric(Numeric::Hour, Pad::Zero),
Item::Numeric(Numeric::Hour, Pad::Zero),
Item::Space(""), Item::Literal(":"),
Item::Space(""), Item::Numeric(Numeric::Minute, Pad::Zero),
Item::Numeric(Numeric::Minute, Pad::Zero),
Item::Space(""), Item::Literal(":"),
Item::Space(""), Item::Numeric(Numeric::Second, Pad::Zero),
Item::Numeric(Numeric::Second, Pad::Zero),
Item::Fixed(Fixed::Nanosecond), Item::Space(""),
];
let mut parsed = Parsed::new();
try!(parse(&mut parsed, s, ITEMS.iter().cloned()));
try!(parse(&mut parsed, s, ITEMS.iter()));
parsed.to_naive_time()
}
}