diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c8b941..9cc0c24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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`, so one can + use values or references. + ## 0.4.9 ### Fixes diff --git a/Cargo.toml b/Cargo.toml index 6fbb591..a572dc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ alloc = [] std = [] clock = ["time", "std"] wasmbind = ["wasm-bindgen", "js-sys"] +bench = ["std"] [dependencies] time = { version = "0.1.39", optional = true } diff --git a/src/date.rs b/src/date.rs index d449009..7026c41 100644 --- a/src/date.rs +++ b/src/date.rs @@ -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 Date 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 - where I: Iterator> + Clone { + pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat + where I: Iterator + Clone, B: Borrow> { DelayedFormat::new_with_offset(Some(self.naive_local()), None, &self.offset, items) } diff --git a/src/datetime.rs b/src/datetime.rs index 3bd6c77..92cf178 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -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 { pub fn parse_from_rfc2822(s: &str) -> ParseResult> { 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 { pub fn parse_from_rfc3339(s: &str) -> ParseResult> { 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 DateTime 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 DateTime 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 DateTime 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 - where I: Iterator> + Clone { + pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat + where I: Iterator + Clone, B: Borrow> { 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 { fn from_str(s: &str) -> ParseResult> { 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::::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() + }); + } } diff --git a/src/format/mod.rs b/src/format/mod.rs index b8e6458..dfd8627 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -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> + where I: Iterator + Clone, B: Borrow> { // 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 { } #[cfg(any(feature = "alloc", feature = "std", test))] -impl<'a, I: Iterator> + Clone> DelayedFormat { +impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { /// Makes a new `DelayedFormat` value out of local date and time. pub fn new(date: Option, time: Option, items: I) -> DelayedFormat { DelayedFormat { date: date, time: time, off: None, items: items } @@ -649,7 +650,7 @@ impl<'a, I: Iterator> + Clone> DelayedFormat { } #[cfg(any(feature = "alloc", feature = "std", test))] -impl<'a, I: Iterator> + Clone> fmt::Display for DelayedFormat { +impl<'a, I: Iterator + Clone, B: Borrow>> fmt::Display for DelayedFormat { 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()) } diff --git a/src/format/parse.rs b/src/format/parse.rs index e8e0d25..0a7d6a7 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -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> { +pub fn parse<'a, I, B>(parsed: &mut Parsed, mut s: &str, items: I) -> ParseResult<()> + where I: Iterator, B: Borrow> { 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 { 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> { 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) -> 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> { 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) -> 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 diff --git a/src/format/parsed.rs b/src/format/parsed.rs index 7b9708d..a09dd19 100644 --- a/src/format/parsed.rs +++ b/src/format/parsed.rs @@ -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(old: &mut Option, 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))) } diff --git a/src/format/scan.rs b/src/format/scan.rs index c311175..e776fee 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -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. diff --git a/src/lib.rs b/src/lib.rs index bbc4324..84831db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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"); diff --git a/src/naive/date.rs b/src/naive/date.rs index f67b670..f0e63e2 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -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 - where I: Iterator> + Clone { + pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat + where I: Iterator + Clone, B: Borrow> { DelayedFormat::new(Some(*self), None, items) } @@ -1507,16 +1509,16 @@ impl str::FromStr for NaiveDate { fn from_str(s: &str) -> ParseResult { 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() } } diff --git a/src/naive/datetime.rs b/src/naive/datetime.rs index 2049782..50ba8b0 100644 --- a/src/naive/datetime.rs +++ b/src/naive/datetime.rs @@ -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 - where I: Iterator> + Clone { + pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat + where I: Iterator + Clone, B: Borrow> { DelayedFormat::new(Some(self.date), Some(self.time), items) } @@ -1472,22 +1474,22 @@ impl str::FromStr for NaiveDateTime { fn from_str(s: &str) -> ParseResult { 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) } } diff --git a/src/naive/internals.rs b/src/naive/internals.rs index a1cd1f6..d043163 100644 --- a/src/naive/internals.rs +++ b/src/naive/internals.rs @@ -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(|| { diff --git a/src/naive/time.rs b/src/naive/time.rs index c0272c8..4ce520c 100644 --- a/src/naive/time.rs +++ b/src/naive/time.rs @@ -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 - where I: Iterator> + Clone { + pub fn format_with_items<'a, I, B>(&self, items: I) -> DelayedFormat + where I: Iterator + Clone, B: Borrow> { DelayedFormat::new(None, Some(*self), items) } @@ -1303,16 +1305,16 @@ impl str::FromStr for NaiveTime { fn from_str(s: &str) -> ParseResult { 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() } }