diff --git a/benches/chrono.rs b/benches/chrono.rs index 1cc5eb0659..3f74923af2 100644 --- a/benches/chrono.rs +++ b/benches/chrono.rs @@ -63,6 +63,21 @@ fn bench_datetime_to_rfc3339(c: &mut Criterion) { c.bench_function("bench_datetime_to_rfc3339", |b| b.iter(|| black_box(dt).to_rfc3339())); } +fn bench_datetime_to_rfc3339_opts(c: &mut Criterion) { + let pst = FixedOffset::east_opt(8 * 60 * 60).unwrap(); + let dt = pst + .from_local_datetime( + &NaiveDate::from_ymd_opt(2018, 1, 11) + .unwrap() + .and_hms_nano_opt(10, 5, 13, 84_660_000) + .unwrap(), + ) + .unwrap(); + c.bench_function("bench_datetime_to_rfc3339_opts", |b| { + b.iter(|| black_box(dt).to_rfc3339_opts(SecondsFormat::Nanos, true)) + }); +} + fn bench_year_flags_from_year(c: &mut Criterion) { c.bench_function("bench_year_flags_from_year", |b| { b.iter(|| { @@ -188,6 +203,7 @@ criterion_group!( bench_datetime_from_str, bench_datetime_to_rfc2822, bench_datetime_to_rfc3339, + bench_datetime_to_rfc3339_opts, bench_year_flags_from_year, bench_num_days_from_ce, bench_get_local_time, diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 8cf3814a82..d2a0c9b611 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -4,22 +4,20 @@ //! ISO 8601 date and time with time zone. #[cfg(all(not(feature = "std"), feature = "alloc"))] -use alloc::string::{String, ToString}; +use alloc::string::String; use core::borrow::Borrow; use core::cmp::Ordering; use core::fmt::Write; use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::{fmt, hash, str}; #[cfg(feature = "std")] -use std::string::ToString; -#[cfg(feature = "std")] use std::time::{SystemTime, UNIX_EPOCH}; -#[cfg(any(feature = "alloc", feature = "std"))] -use crate::format::DelayedFormat; #[cfg(feature = "unstable-locales")] use crate::format::Locale; use crate::format::{parse, parse_and_remainder, ParseError, ParseResult, Parsed, StrftimeItems}; +#[cfg(any(feature = "alloc", feature = "std"))] +use crate::format::{write_rfc3339, DelayedFormat}; use crate::format::{Fixed, Item}; use crate::naive::{Days, IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(feature = "clock")] @@ -48,6 +46,7 @@ mod tests; /// /// See the `TimeZone::to_rfc3339_opts` function for usage. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +#[allow(clippy::manual_non_exhaustive)] pub enum SecondsFormat { /// Format whole seconds only, with no decimal point nor subseconds. Secs, @@ -508,8 +507,11 @@ impl DateTime { #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[must_use] pub fn to_rfc3339(&self) -> String { + // For some reason a string with a capacity less than 32 is ca 20% slower when benchmarking. let mut result = String::with_capacity(32); - crate::format::write_rfc3339(&mut result, self.naive_local(), self.offset.fix()) + let naive = self.naive_local(); + let offset = self.offset.fix(); + write_rfc3339(&mut result, naive, offset, SecondsFormat::AutoSi, false) .expect("writing rfc3339 datetime to string should never fail"); result } @@ -542,46 +544,10 @@ impl DateTime { #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] #[must_use] pub fn to_rfc3339_opts(&self, secform: SecondsFormat, use_z: bool) -> String { - use crate::format::Numeric::*; - use crate::format::Pad::Zero; - use crate::SecondsFormat::*; - - debug_assert!(secform != __NonExhaustive, "Do not use __NonExhaustive!"); - - const PREFIX: &[Item<'static>] = &[ - Item::Numeric(Year, Zero), - Item::Literal("-"), - Item::Numeric(Month, Zero), - Item::Literal("-"), - Item::Numeric(Day, Zero), - Item::Literal("T"), - Item::Numeric(Hour, Zero), - Item::Literal(":"), - Item::Numeric(Minute, Zero), - Item::Literal(":"), - Item::Numeric(Second, Zero), - ]; - - let ssitem = match secform { - Secs => None, - Millis => Some(Item::Fixed(Fixed::Nanosecond3)), - Micros => Some(Item::Fixed(Fixed::Nanosecond6)), - Nanos => Some(Item::Fixed(Fixed::Nanosecond9)), - AutoSi => Some(Item::Fixed(Fixed::Nanosecond)), - __NonExhaustive => unreachable!(), - }; - - let tzitem = Item::Fixed(if use_z { - Fixed::TimezoneOffsetColonZ - } else { - Fixed::TimezoneOffsetColon - }); - - let dt = self.fixed_offset(); - match ssitem { - None => dt.format_with_items(PREFIX.iter().chain([tzitem].iter())).to_string(), - Some(s) => dt.format_with_items(PREFIX.iter().chain([s, tzitem].iter())).to_string(), - } + let mut result = String::with_capacity(38); + write_rfc3339(&mut result, self.naive_local(), self.offset.fix(), secform, use_z) + .expect("writing rfc3339 datetime to string should never fail"); + result } /// The minimum possible `DateTime`. diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 1df5939ab8..c0a877cd09 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -15,7 +15,7 @@ use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; #[cfg(any(feature = "alloc", feature = "std"))] use crate::offset::{FixedOffset, Offset}; #[cfg(any(feature = "alloc", feature = "std"))] -use crate::{Datelike, Timelike, Weekday}; +use crate::{Datelike, SecondsFormat, Timelike, Weekday}; use super::locales; #[cfg(any(feature = "alloc", feature = "std"))] @@ -427,7 +427,13 @@ fn format_inner( // same as `%Y-%m-%dT%H:%M:%S%.f%:z` { if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc3339(w, NaiveDateTime::new(*d, *t), off)) + Some(write_rfc3339( + w, + crate::NaiveDateTime::new(*d, *t), + off.fix(), + SecondsFormat::AutoSi, + false, + )) } else { None } @@ -523,18 +529,64 @@ impl OffsetFormat { /// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` #[cfg(any(feature = "alloc", feature = "std"))] +#[inline] pub(crate) fn write_rfc3339( w: &mut impl Write, dt: NaiveDateTime, off: FixedOffset, + secform: SecondsFormat, + use_z: bool, ) -> fmt::Result { - // reuse `Debug` impls which already print ISO 8601 format. - // this is faster in this way. - write!(w, "{:?}", dt)?; + let year = dt.date().year(); + if (0..=9999).contains(&year) { + write_hundreds(w, (year / 100) as u8)?; + write_hundreds(w, (year % 100) as u8)?; + } else { + // ISO 8601 requires the explicit sign for out-of-range years + write!(w, "{:+05}", year)?; + } + w.write_char('-')?; + write_hundreds(w, dt.date().month() as u8)?; + w.write_char('-')?; + write_hundreds(w, dt.date().day() as u8)?; + + w.write_char('T')?; + + let (hour, min, mut sec) = dt.time().hms(); + let mut nano = dt.nanosecond(); + if nano >= 1_000_000_000 { + sec += 1; + nano -= 1_000_000_000; + } + write_hundreds(w, hour as u8)?; + w.write_char(':')?; + write_hundreds(w, min as u8)?; + w.write_char(':')?; + let sec = sec; + write_hundreds(w, sec as u8)?; + + match secform { + SecondsFormat::Secs => {} + SecondsFormat::Millis => write!(w, ".{:03}", nano / 1_000_000)?, + SecondsFormat::Micros => write!(w, ".{:06}", nano / 1000)?, + SecondsFormat::Nanos => write!(w, ".{:09}", nano)?, + SecondsFormat::AutoSi => { + if nano == 0 { + } else if nano % 1_000_000 == 0 { + write!(w, ".{:03}", nano / 1_000_000)? + } else if nano % 1_000 == 0 { + write!(w, ".{:06}", nano / 1_000)? + } else { + write!(w, ".{:09}", nano)? + } + } + SecondsFormat::__NonExhaustive => unreachable!(), + }; + OffsetFormat { precision: OffsetPrecision::Minutes, colons: Colons::Colon, - allow_zulu: false, + allow_zulu: use_z, padding: Pad::Zero, } .format(w, off) @@ -574,11 +626,13 @@ fn write_rfc2822_inner( write_hundreds(w, (year / 100) as u8)?; write_hundreds(w, (year % 100) as u8)?; w.write_char(' ')?; - write_hundreds(w, t.hour() as u8)?; + + let (hour, min, sec) = t.hms(); + write_hundreds(w, hour as u8)?; w.write_char(':')?; - write_hundreds(w, t.minute() as u8)?; + write_hundreds(w, min as u8)?; w.write_char(':')?; - let sec = t.second() + t.nanosecond() / 1_000_000_000; + let sec = sec + t.nanosecond() / 1_000_000_000; write_hundreds(w, sec as u8)?; w.write_char(' ')?; OffsetFormat { diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index b0f730feb6..c376a9a11d 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -801,7 +801,7 @@ impl NaiveTime { } /// Returns a triple of the hour, minute and second numbers. - fn hms(&self) -> (u32, u32, u32) { + pub(crate) fn hms(&self) -> (u32, u32, u32) { let sec = self.secs % 60; let mins = self.secs / 60; let min = mins % 60;