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 46e0d62487..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,10 +529,13 @@ 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 { let year = dt.date().year(); if (0..=9999).contains(&year) { @@ -556,19 +565,28 @@ pub(crate) fn write_rfc3339( let sec = sec; write_hundreds(w, sec as u8)?; - 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)?; - } + 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)