From 9812e7d84a2ce1c4d8aef0390ff4a466e0e1afc9 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 21:07:58 +0200 Subject: [PATCH 1/7] Add new `Formatter` that has a generic `Offset` --- src/format/formatting.rs | 177 +++++++++++++++++++++++++-------------- src/format/mod.rs | 2 +- 2 files changed, 114 insertions(+), 65 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 8cb83c7521..602d96cc6e 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -31,24 +31,117 @@ use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric}; #[cfg(feature = "alloc")] use locales::*; -/// A *temporary* object which can be used as an argument to `format!` or others. -/// This is normally constructed via `format` methods of each date and time type. +/// A *temporary* object which can be used as an argument to [`format!`] or others. #[cfg(feature = "alloc")] #[derive(Debug)] -pub struct DelayedFormat { +pub struct Formatter { /// The date view, if any. date: Option, /// The time view, if any. time: Option, - /// The name and local-to-UTC difference for the offset (timezone), if any. - off: Option<(String, FixedOffset)>, + /// The offset from UTC, if any + offset: Option, /// An iterator returning formatting items. items: I, - /// Locale used for text. - // TODO: Only used with the locale feature. We should make this property - // only present when the feature is enabled. + /// Locale used for text + /// ZST if the `unstable-locales` feature is not enabled. + locale: Locale, +} + +#[cfg(feature = "alloc")] +impl<'a, I, B, Off> Formatter +where + I: Iterator + Clone, + B: Borrow>, + Off: Offset + Display, +{ + /// Makes a new `Formatter` value out of local date and time and UTC offset. + /// + /// # Errors/panics + /// + /// If the iterator given for `items` returns [`Item::Error`], the `Display` implementation of + /// `Formatter` will return an error, which causes a panic when used in combination with + /// [`to_string`](ToString::to_string), [`println!`] and [`format!`]. + #[must_use] + pub fn new( + date: Option, + time: Option, + offset: Option, + items: I, + ) -> Formatter { + Formatter { date, time, offset, items, locale: default_locale() } + } + + /// Makes a new `Formatter` value out of local date and time, UTC offset and locale. + /// + /// # Errors/panics + /// + /// If the iterator given for `items` returns [`Item::Error`], the `Display` implementation of + /// `Formatter` will return an error, which causes a panic when used in combination with + /// [`to_string`](ToString::to_string), [`println!`] and [`format!`]. #[cfg(feature = "unstable-locales")] - locale: Option, + #[must_use] + pub fn new_with_locale( + date: Option, + time: Option, + offset: Option, + items: I, + locale: Locale, + ) -> Formatter { + Formatter { date, time, offset, items, locale } + } +} + +#[cfg(feature = "alloc")] +impl<'a, I, B, Off> Display for Formatter +where + I: Iterator + Clone, + B: Borrow>, + Off: Offset + Display, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + #[cfg(feature = "unstable-locales")] + let locale = Some(self.locale); + #[cfg(not(feature = "unstable-locales"))] + let locale = None; + + let off = self.offset.as_ref().map(|off| (off.to_string(), off.fix())); + let mut result = String::new(); + for item in self.items.clone() { + format_inner(&mut result, self.date.as_ref(), self.time.as_ref(), off.as_ref(), item.borrow(), locale)?; + } + f.pad(&result) + } +} + +/// Only used to make `DelayedFormat` a wrapper around `Formatter`. +#[cfg(feature = "alloc")] +#[derive(Clone, Debug)] +struct OffsetFormatter { + offset: FixedOffset, + tz_name: String, +} + +#[cfg(feature = "alloc")] +impl Offset for OffsetFormatter { + fn fix(&self) -> FixedOffset { + self.offset + } +} + +#[cfg(feature = "alloc")] +impl Display for OffsetFormatter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(&self.tz_name) + } +} + +/// A *temporary* object which can be used as an argument to `format!` or others. +/// This is normally constructed via `format` methods of each date and time type. +#[cfg(feature = "alloc")] +#[derive(Debug)] +pub struct DelayedFormat { + inner: Formatter, } #[cfg(feature = "alloc")] @@ -56,14 +149,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { /// Makes a new `DelayedFormat` value out of local date and time. #[must_use] pub fn new(date: Option, time: Option, items: I) -> DelayedFormat { - DelayedFormat { - date, - time, - off: None, - items, - #[cfg(feature = "unstable-locales")] - locale: None, - } + DelayedFormat { inner: Formatter::new(date, time, None, items) } } /// Makes a new `DelayedFormat` value out of local date and time and UTC offset. @@ -77,15 +163,8 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { where Off: Offset + Display, { - let name_and_diff = (offset.to_string(), offset.fix()); - DelayedFormat { - date, - time, - off: Some(name_and_diff), - items, - #[cfg(feature = "unstable-locales")] - locale: None, - } + let offset = Some(OffsetFormatter { offset: offset.fix(), tz_name: offset.to_string() }); + DelayedFormat { inner: Formatter::new(date, time, offset, items) } } /// Makes a new `DelayedFormat` value out of local date and time and locale. @@ -97,7 +176,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { items: I, locale: Locale, ) -> DelayedFormat { - DelayedFormat { date, time, off: None, items, locale: Some(locale) } + DelayedFormat { inner: Formatter::new_with_locale(date, time, None, items, locale) } } /// Makes a new `DelayedFormat` value out of local date and time, UTC offset and locale. @@ -113,31 +192,15 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { where Off: Offset + Display, { - let name_and_diff = (offset.to_string(), offset.fix()); - DelayedFormat { date, time, off: Some(name_and_diff), items, locale: Some(locale) } + let offset = Some(OffsetFormatter { offset: offset.fix(), tz_name: offset.to_string() }); + DelayedFormat { inner: Formatter::new_with_locale(date, time, offset, items, locale) } } } #[cfg(feature = "alloc")] impl<'a, I: Iterator + Clone, B: Borrow>> Display for DelayedFormat { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - #[cfg(feature = "unstable-locales")] - let locale = self.locale; - #[cfg(not(feature = "unstable-locales"))] - let locale = None; - - let mut result = String::new(); - for item in self.items.clone() { - format_inner( - &mut result, - self.date.as_ref(), - self.time.as_ref(), - self.off.as_ref(), - item.borrow(), - locale, - )?; - } - f.pad(&result) + self.inner.fmt(f) } } @@ -156,15 +219,8 @@ where I: Iterator + Clone, B: Borrow>, { - DelayedFormat { - date: date.copied(), - time: time.copied(), - off: off.cloned(), - items, - #[cfg(feature = "unstable-locales")] - locale: None, - } - .fmt(w) + let offset = off.cloned().map(|(tz_name, offset)| OffsetFormatter { tz_name, offset }); + Formatter::new(date.copied(), time.copied(), offset, items).fmt(w) } /// Formats single formatting item @@ -177,15 +233,8 @@ pub fn format_item( off: Option<&(String, FixedOffset)>, item: &Item<'_>, ) -> fmt::Result { - DelayedFormat { - date: date.copied(), - time: time.copied(), - off: off.cloned(), - items: [item].into_iter(), - #[cfg(feature = "unstable-locales")] - locale: None, - } - .fmt(w) + let offset = off.cloned().map(|(tz_name, offset)| OffsetFormatter { tz_name, offset }); + Formatter::new(date.copied(), time.copied(), offset, [item].into_iter()).fmt(w) } #[cfg(feature = "alloc")] diff --git a/src/format/mod.rs b/src/format/mod.rs index df75b4ffbc..7de063c65f 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -60,7 +60,7 @@ pub(crate) use formatting::write_rfc2822; pub(crate) use formatting::write_rfc3339; #[cfg(feature = "alloc")] #[allow(deprecated)] -pub use formatting::{format, format_item, DelayedFormat}; +pub use formatting::{format, format_item, DelayedFormat, Formatter}; #[cfg(feature = "unstable-locales")] pub use locales::Locale; pub(crate) use parse::parse_rfc3339; From 0d25616b19ef658f64019611afa3dc2593b713c7 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 15:14:19 +0200 Subject: [PATCH 2/7] Move formatting to methods on `Formatter` --- src/format/formatting.rs | 480 +++++++++++++++++++-------------------- 1 file changed, 235 insertions(+), 245 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 602d96cc6e..aeb034ecde 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -90,6 +90,240 @@ where ) -> Formatter { Formatter { date, time, offset, items, locale } } + + #[inline] + fn format(&self, w: &mut impl Write) -> fmt::Result { + for item in self.items.clone() { + match *item.borrow() { + Item::Literal(s) | Item::Space(s) => w.write_str(s), + Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s), + Item::Numeric(ref spec, pad) => self.format_numeric(w, spec, pad), + Item::Fixed(ref spec) => self.format_fixed(w, spec), + Item::Error => Err(fmt::Error), + }?; + } + Ok(()) + } + + fn format_numeric(&self, w: &mut impl Write, spec: &Numeric, pad: Pad) -> fmt::Result { + use self::Numeric::*; + + let week_from_sun = |d: NaiveDate| d.weeks_from(Weekday::Sun); + let week_from_mon = |d: NaiveDate| d.weeks_from(Weekday::Mon); + + let (width, v) = match *spec { + Year => (4, self.date.map(|d| i64::from(d.year()))), + YearDiv100 => (2, self.date.map(|d| i64::from(d.year()).div_euclid(100))), + YearMod100 => (2, self.date.map(|d| i64::from(d.year()).rem_euclid(100))), + IsoYear => (4, self.date.map(|d| i64::from(d.iso_week().year()))), + IsoYearDiv100 => (2, self.date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))), + IsoYearMod100 => (2, self.date.map(|d| i64::from(d.iso_week().year()).rem_euclid(100))), + Month => (2, self.date.map(|d| i64::from(d.month()))), + Day => (2, self.date.map(|d| i64::from(d.day()))), + WeekFromSun => (2, self.date.map(|d| i64::from(week_from_sun(d)))), + WeekFromMon => (2, self.date.map(|d| i64::from(week_from_mon(d)))), + IsoWeek => (2, self.date.map(|d| i64::from(d.iso_week().week()))), + NumDaysFromSun => (1, self.date.map(|d| i64::from(d.weekday().num_days_from_sunday()))), + WeekdayFromMon => (1, self.date.map(|d| i64::from(d.weekday().number_from_monday()))), + Ordinal => (3, self.date.map(|d| i64::from(d.ordinal()))), + Hour => (2, self.time.map(|t| i64::from(t.hour()))), + Hour12 => (2, self.time.map(|t| i64::from(t.hour12().1))), + Minute => (2, self.time.map(|t| i64::from(t.minute()))), + Second => { + (2, self.time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))) + } + Nanosecond => (9, self.time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))), + Timestamp => ( + 1, + match (self.date, self.time, self.offset.as_ref()) { + (Some(d), Some(t), None) => Some(d.and_time(t).timestamp()), + (Some(d), Some(t), Some(off)) => { + Some(d.and_time(t).timestamp() - i64::from(off.fix().local_minus_utc())) + } + (_, _, _) => None, + }, + ), + + // for the future expansion + Internal(ref int) => match int._dummy {}, + }; + + if let Some(v) = v { + if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { + // non-four-digit years require an explicit sign as per ISO 8601 + match pad { + Pad::None => write!(w, "{:+}", v), + Pad::Zero => write!(w, "{:+01$}", v, width + 1), + Pad::Space => write!(w, "{:+1$}", v, width + 1), + } + } else { + match pad { + Pad::None => write!(w, "{}", v), + Pad::Zero => write!(w, "{:01$}", v, width), + Pad::Space => write!(w, "{:1$}", v, width), + } + } + } else { + Err(fmt::Error) // insufficient arguments for given format + } + } + + fn format_fixed(&self, w: &mut impl Write, spec: &Fixed) -> fmt::Result { + use self::Fixed::*; + + let ret = match *spec { + ShortMonthName => self.date.map(|d| { + w.write_str(short_months(self.locale)[d.month0() as usize])?; + Ok(()) + }), + LongMonthName => self.date.map(|d| { + w.write_str(long_months(self.locale)[d.month0() as usize])?; + Ok(()) + }), + ShortWeekdayName => self.date.map(|d| { + w.write_str( + short_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize], + )?; + Ok(()) + }), + LongWeekdayName => self.date.map(|d| { + w.write_str( + long_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize], + )?; + Ok(()) + }), + LowerAmPm => self.time.map(|t| { + let ampm = if t.hour12().0 { am_pm(self.locale)[1] } else { am_pm(self.locale)[0] }; + for c in ampm.chars().flat_map(|c| c.to_lowercase()) { + w.write_char(c)? + } + Ok(()) + }), + UpperAmPm => self.time.map(|t| { + w.write_str(if t.hour12().0 { + am_pm(self.locale)[1] + } else { + am_pm(self.locale)[0] + }) + }), + Nanosecond => self.time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + if nano == 0 { + Ok(()) + } else { + w.write_str(decimal_point(self.locale))?; + 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) + } + } + }), + Nanosecond3 => self.time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + w.write_str(decimal_point(self.locale))?; + write!(w, "{:03}", nano / 1_000_000) + }), + Nanosecond6 => self.time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + w.write_str(decimal_point(self.locale))?; + write!(w, "{:06}", nano / 1_000) + }), + Nanosecond9 => self.time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + w.write_str(decimal_point(self.locale))?; + write!(w, "{:09}", nano) + }), + Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { + self.time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:03}", nano / 1_000_000) + }) + } + Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { + self.time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:06}", nano / 1_000) + }) + } + Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { + self.time.map(|t| { + let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:09}", nano) + }) + } + TimezoneName => self.offset.as_ref().map(|off| write!(w, "{}", off)), + TimezoneOffset | TimezoneOffsetZ => self.offset.as_ref().map(|off| { + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Maybe, + allow_zulu: *spec == TimezoneOffsetZ, + padding: Pad::Zero, + } + .format(w, off.fix()) + }), + TimezoneOffsetColon | TimezoneOffsetColonZ => self.offset.as_ref().map(|off| { + OffsetFormat { + precision: OffsetPrecision::Minutes, + colons: Colons::Colon, + allow_zulu: *spec == TimezoneOffsetColonZ, + padding: Pad::Zero, + } + .format(w, off.fix()) + }), + TimezoneOffsetDoubleColon => self.offset.as_ref().map(|off| { + OffsetFormat { + precision: OffsetPrecision::Seconds, + colons: Colons::Colon, + allow_zulu: false, + padding: Pad::Zero, + } + .format(w, off.fix()) + }), + TimezoneOffsetTripleColon => self.offset.as_ref().map(|off| { + OffsetFormat { + precision: OffsetPrecision::Hours, + colons: Colons::None, + allow_zulu: false, + padding: Pad::Zero, + } + .format(w, off.fix()) + }), + Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { + return Err(fmt::Error); + } + RFC2822 => + // same as `%a, %d %b %Y %H:%M:%S %z` + { + if let (Some(d), Some(t), Some(off)) = (self.date, self.time, self.offset.as_ref()) + { + Some(write_rfc2822_inner(w, d, t, off.fix(), self.locale)) + } else { + None + } + } + RFC3339 => + // same as `%Y-%m-%dT%H:%M:%S%.f%:z` + { + if let (Some(d), Some(t), Some(off)) = (self.date, self.time, self.offset.as_ref()) + { + Some(write_rfc3339( + w, + crate::NaiveDateTime::new(d, t), + off.fix(), + SecondsFormat::AutoSi, + false, + )) + } else { + None + } + } + }; + + ret.unwrap_or(Err(fmt::Error)) // insufficient arguments for given format + } } #[cfg(feature = "alloc")] @@ -100,16 +334,8 @@ where Off: Offset + Display, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - #[cfg(feature = "unstable-locales")] - let locale = Some(self.locale); - #[cfg(not(feature = "unstable-locales"))] - let locale = None; - - let off = self.offset.as_ref().map(|off| (off.to_string(), off.fix())); let mut result = String::new(); - for item in self.items.clone() { - format_inner(&mut result, self.date.as_ref(), self.time.as_ref(), off.as_ref(), item.borrow(), locale)?; - } + self.format(&mut result)?; f.pad(&result) } } @@ -238,242 +464,6 @@ pub fn format_item( } #[cfg(feature = "alloc")] -fn format_inner( - w: &mut impl Write, - date: Option<&NaiveDate>, - time: Option<&NaiveTime>, - off: Option<&(String, FixedOffset)>, - item: &Item<'_>, - locale: Option, -) -> fmt::Result { - let locale = locale.unwrap_or(default_locale()); - - match *item { - Item::Literal(s) | Item::Space(s) => w.write_str(s), - #[cfg(feature = "alloc")] - Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s), - - Item::Numeric(ref spec, ref pad) => { - use self::Numeric::*; - - let week_from_sun = |d: &NaiveDate| d.weeks_from(Weekday::Sun); - let week_from_mon = |d: &NaiveDate| d.weeks_from(Weekday::Mon); - - let (width, v) = match *spec { - Year => (4, date.map(|d| i64::from(d.year()))), - YearDiv100 => (2, date.map(|d| i64::from(d.year()).div_euclid(100))), - YearMod100 => (2, date.map(|d| i64::from(d.year()).rem_euclid(100))), - IsoYear => (4, date.map(|d| i64::from(d.iso_week().year()))), - IsoYearDiv100 => (2, date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))), - IsoYearMod100 => (2, date.map(|d| i64::from(d.iso_week().year()).rem_euclid(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().num_days_from_sunday()))), - 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() + 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) { - (Some(d), Some(t), None) => Some(d.and_time(*t).timestamp()), - (Some(d), Some(t), Some(&(_, off))) => { - Some(d.and_time(*t).timestamp() - i64::from(off.local_minus_utc())) - } - (_, _, _) => None, - }, - ), - - // for the future expansion - Internal(ref int) => match int._dummy {}, - }; - - if let Some(v) = v { - if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { - // non-four-digit years require an explicit sign as per ISO 8601 - match *pad { - Pad::None => write!(w, "{:+}", v), - Pad::Zero => write!(w, "{:+01$}", v, width + 1), - Pad::Space => write!(w, "{:+1$}", v, width + 1), - } - } else { - match *pad { - Pad::None => write!(w, "{}", v), - Pad::Zero => write!(w, "{:01$}", v, width), - Pad::Space => write!(w, "{:1$}", v, width), - } - } - } else { - Err(fmt::Error) // insufficient arguments for given format - } - } - - Item::Fixed(ref spec) => { - use self::Fixed::*; - - let ret = match *spec { - ShortMonthName => date.map(|d| { - w.write_str(short_months(locale)[d.month0() as usize])?; - Ok(()) - }), - LongMonthName => date.map(|d| { - w.write_str(long_months(locale)[d.month0() as usize])?; - Ok(()) - }), - ShortWeekdayName => date.map(|d| { - w.write_str( - short_weekdays(locale)[d.weekday().num_days_from_sunday() as usize], - )?; - Ok(()) - }), - LongWeekdayName => date.map(|d| { - w.write_str( - long_weekdays(locale)[d.weekday().num_days_from_sunday() as usize], - )?; - Ok(()) - }), - LowerAmPm => time.map(|t| { - let ampm = if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] }; - for c in ampm.chars().flat_map(|c| c.to_lowercase()) { - w.write_char(c)? - } - Ok(()) - }), - UpperAmPm => time.map(|t| { - w.write_str(if t.hour12().0 { am_pm(locale)[1] } else { am_pm(locale)[0] })?; - Ok(()) - }), - Nanosecond => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - if nano == 0 { - Ok(()) - } else { - w.write_str(decimal_point(locale))?; - 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) - } - } - }), - Nanosecond3 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - w.write_str(decimal_point(locale))?; - write!(w, "{:03}", nano / 1_000_000) - }), - Nanosecond6 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - w.write_str(decimal_point(locale))?; - write!(w, "{:06}", nano / 1_000) - }), - Nanosecond9 => time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - w.write_str(decimal_point(locale))?; - write!(w, "{:09}", nano) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { - time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:03}", nano / 1_000_000) - }) - } - Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { - time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:06}", nano / 1_000) - }) - } - Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { - time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:09}", nano) - }) - } - TimezoneName => off.map(|(name, _)| { - w.write_str(name)?; - Ok(()) - }), - TimezoneOffset | TimezoneOffsetZ => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Minutes, - colons: Colons::Maybe, - allow_zulu: *spec == TimezoneOffsetZ, - padding: Pad::Zero, - } - .format(w, off) - }), - TimezoneOffsetColon | TimezoneOffsetColonZ => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Minutes, - colons: Colons::Colon, - allow_zulu: *spec == TimezoneOffsetColonZ, - padding: Pad::Zero, - } - .format(w, off) - }), - TimezoneOffsetDoubleColon => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Seconds, - colons: Colons::Colon, - allow_zulu: false, - padding: Pad::Zero, - } - .format(w, off) - }), - TimezoneOffsetTripleColon => off.map(|&(_, off)| { - OffsetFormat { - precision: OffsetPrecision::Hours, - colons: Colons::None, - allow_zulu: false, - padding: Pad::Zero, - } - .format(w, off) - }), - Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { - return Err(fmt::Error); - } - RFC2822 => - // same as `%a, %d %b %Y %H:%M:%S %z` - { - if let (Some(d), Some(t), Some(&(_, off))) = (date, time, off) { - Some(write_rfc2822_inner(w, *d, *t, off, locale)) - } else { - None - } - } - RFC3339 => - // 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, - crate::NaiveDateTime::new(*d, *t), - off.fix(), - SecondsFormat::AutoSi, - false, - )) - } else { - None - } - } - }; - - ret.unwrap_or(Err(fmt::Error)) // insufficient arguments for given format - } - - Item::Error => Err(fmt::Error), - } -} - -#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] impl OffsetFormat { /// Writes an offset from UTC with the format defined by `self`. fn format(&self, w: &mut impl Write, off: FixedOffset) -> fmt::Result { From 25ec62feb3679e6c52e4dacecd1ef3485c2934f1 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 15:33:42 +0200 Subject: [PATCH 3/7] Match on tuples --- src/format/formatting.rs | 269 +++++++++++++++------------------------ 1 file changed, 106 insertions(+), 163 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index aeb034ecde..b1ce73c76e 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -108,105 +108,80 @@ where fn format_numeric(&self, w: &mut impl Write, spec: &Numeric, pad: Pad) -> fmt::Result { use self::Numeric::*; - let week_from_sun = |d: NaiveDate| d.weeks_from(Weekday::Sun); - let week_from_mon = |d: NaiveDate| d.weeks_from(Weekday::Mon); - - let (width, v) = match *spec { - Year => (4, self.date.map(|d| i64::from(d.year()))), - YearDiv100 => (2, self.date.map(|d| i64::from(d.year()).div_euclid(100))), - YearMod100 => (2, self.date.map(|d| i64::from(d.year()).rem_euclid(100))), - IsoYear => (4, self.date.map(|d| i64::from(d.iso_week().year()))), - IsoYearDiv100 => (2, self.date.map(|d| i64::from(d.iso_week().year()).div_euclid(100))), - IsoYearMod100 => (2, self.date.map(|d| i64::from(d.iso_week().year()).rem_euclid(100))), - Month => (2, self.date.map(|d| i64::from(d.month()))), - Day => (2, self.date.map(|d| i64::from(d.day()))), - WeekFromSun => (2, self.date.map(|d| i64::from(week_from_sun(d)))), - WeekFromMon => (2, self.date.map(|d| i64::from(week_from_mon(d)))), - IsoWeek => (2, self.date.map(|d| i64::from(d.iso_week().week()))), - NumDaysFromSun => (1, self.date.map(|d| i64::from(d.weekday().num_days_from_sunday()))), - WeekdayFromMon => (1, self.date.map(|d| i64::from(d.weekday().number_from_monday()))), - Ordinal => (3, self.date.map(|d| i64::from(d.ordinal()))), - Hour => (2, self.time.map(|t| i64::from(t.hour()))), - Hour12 => (2, self.time.map(|t| i64::from(t.hour12().1))), - Minute => (2, self.time.map(|t| i64::from(t.minute()))), - Second => { - (2, self.time.map(|t| i64::from(t.second() + t.nanosecond() / 1_000_000_000))) + let (width, v) = match (spec, self.date, self.time) { + (Year, Some(d), _) => (4, i64::from(d.year())), + (YearDiv100, Some(d), _) => (2, i64::from(d.year()).div_euclid(100)), + (YearMod100, Some(d), _) => (2, i64::from(d.year()).rem_euclid(100)), + (IsoYear, Some(d), _) => (4, i64::from(d.iso_week().year())), + (IsoYearDiv100, Some(d), _) => (2, i64::from(d.iso_week().year()).div_euclid(100)), + (IsoYearMod100, Some(d), _) => (2, i64::from(d.iso_week().year()).rem_euclid(100)), + (Month, Some(d), _) => (2, i64::from(d.month())), + (Day, Some(d), _) => (2, i64::from(d.day())), + (WeekFromSun, Some(d), _) => (2, i64::from(d.weeks_from(Weekday::Sun))), + (WeekFromMon, Some(d), _) => (2, i64::from(d.weeks_from(Weekday::Mon))), + (IsoWeek, Some(d), _) => (2, i64::from(d.iso_week().week())), + (NumDaysFromSun, Some(d), _) => (1, i64::from(d.weekday().num_days_from_sunday())), + (WeekdayFromMon, Some(d), _) => (1, i64::from(d.weekday().number_from_monday())), + (Ordinal, Some(d), _) => (3, i64::from(d.ordinal())), + (Hour, _, Some(t)) => (2, i64::from(t.hour())), + (Hour12, _, Some(t)) => (2, i64::from(t.hour12().1)), + (Minute, _, Some(t)) => (2, i64::from(t.minute())), + (Second, _, Some(t)) => (2, i64::from(t.second() + t.nanosecond() / 1_000_000_000)), + (Nanosecond, _, Some(t)) => (9, i64::from(t.nanosecond() % 1_000_000_000)), + (Timestamp, Some(d), Some(t)) => { + let offset = self.offset.as_ref().map(|o| i64::from(o.fix().local_minus_utc())); + let timestamp = d.and_time(t).timestamp() - offset.unwrap_or(0); + (1, timestamp) } - Nanosecond => (9, self.time.map(|t| i64::from(t.nanosecond() % 1_000_000_000))), - Timestamp => ( - 1, - match (self.date, self.time, self.offset.as_ref()) { - (Some(d), Some(t), None) => Some(d.and_time(t).timestamp()), - (Some(d), Some(t), Some(off)) => { - Some(d.and_time(t).timestamp() - i64::from(off.fix().local_minus_utc())) - } - (_, _, _) => None, - }, - ), - - // for the future expansion - Internal(ref int) => match int._dummy {}, + (Internal(_), _, _) => return Ok(()), // for future expansion + _ => return Err(fmt::Error), // insufficient arguments for given format }; - if let Some(v) = v { - if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { - // non-four-digit years require an explicit sign as per ISO 8601 - match pad { - Pad::None => write!(w, "{:+}", v), - Pad::Zero => write!(w, "{:+01$}", v, width + 1), - Pad::Space => write!(w, "{:+1$}", v, width + 1), - } - } else { - match pad { - Pad::None => write!(w, "{}", v), - Pad::Zero => write!(w, "{:01$}", v, width), - Pad::Space => write!(w, "{:1$}", v, width), - } + if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { + // non-four-digit years require an explicit sign as per ISO 8601 + match pad { + Pad::None => write!(w, "{:+}", v), + Pad::Zero => write!(w, "{:+01$}", v, width + 1), + Pad::Space => write!(w, "{:+1$}", v, width + 1), } } else { - Err(fmt::Error) // insufficient arguments for given format + match pad { + Pad::None => write!(w, "{}", v), + Pad::Zero => write!(w, "{:01$}", v, width), + Pad::Space => write!(w, "{:1$}", v, width), + } } } fn format_fixed(&self, w: &mut impl Write, spec: &Fixed) -> fmt::Result { - use self::Fixed::*; + use Fixed::*; + use InternalInternal::*; - let ret = match *spec { - ShortMonthName => self.date.map(|d| { - w.write_str(short_months(self.locale)[d.month0() as usize])?; - Ok(()) - }), - LongMonthName => self.date.map(|d| { - w.write_str(long_months(self.locale)[d.month0() as usize])?; - Ok(()) - }), - ShortWeekdayName => self.date.map(|d| { - w.write_str( - short_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize], - )?; - Ok(()) - }), - LongWeekdayName => self.date.map(|d| { - w.write_str( - long_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize], - )?; - Ok(()) - }), - LowerAmPm => self.time.map(|t| { + match (spec, self.date, self.time, self.offset.as_ref()) { + (ShortMonthName, Some(d), _, _) => { + w.write_str(short_months(self.locale)[d.month0() as usize]) + } + (LongMonthName, Some(d), _, _) => { + w.write_str(long_months(self.locale)[d.month0() as usize]) + } + (ShortWeekdayName, Some(d), _, _) => w.write_str( + short_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize], + ), + (LongWeekdayName, Some(d), _, _) => { + w.write_str(long_weekdays(self.locale)[d.weekday().num_days_from_sunday() as usize]) + } + (LowerAmPm, _, Some(t), _) => { let ampm = if t.hour12().0 { am_pm(self.locale)[1] } else { am_pm(self.locale)[0] }; for c in ampm.chars().flat_map(|c| c.to_lowercase()) { w.write_char(c)? } Ok(()) - }), - UpperAmPm => self.time.map(|t| { - w.write_str(if t.hour12().0 { - am_pm(self.locale)[1] - } else { - am_pm(self.locale)[0] - }) - }), - Nanosecond => self.time.map(|t| { + } + (UpperAmPm, _, Some(t), _) => { + let ampm = if t.hour12().0 { am_pm(self.locale)[1] } else { am_pm(self.locale)[0] }; + w.write_str(ampm) + } + (Nanosecond, _, Some(t), _) => { let nano = t.nanosecond() % 1_000_000_000; if nano == 0 { Ok(()) @@ -220,109 +195,77 @@ where write!(w, "{:09}", nano) } } - }), - Nanosecond3 => self.time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; + } + (Nanosecond3, _, Some(t), _) => { w.write_str(decimal_point(self.locale))?; - write!(w, "{:03}", nano / 1_000_000) - }), - Nanosecond6 => self.time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:03}", t.nanosecond() % 1_000_000_000 / 1_000_000) + } + (Nanosecond6, _, Some(t), _) => { w.write_str(decimal_point(self.locale))?; - write!(w, "{:06}", nano / 1_000) - }), - Nanosecond9 => self.time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; + write!(w, "{:06}", t.nanosecond() % 1_000_000_000 / 1_000) + } + (Nanosecond9, _, Some(t), _) => { w.write_str(decimal_point(self.locale))?; - write!(w, "{:09}", nano) - }), - Internal(InternalFixed { val: InternalInternal::Nanosecond3NoDot }) => { - self.time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:03}", nano / 1_000_000) - }) + write!(w, "{:09}", t.nanosecond() % 1_000_000_000) + } + (Internal(InternalFixed { val: Nanosecond3NoDot }), _, Some(t), _) => { + write!(w, "{:03}", t.nanosecond() % 1_000_000_000 / 1_000_000) } - Internal(InternalFixed { val: InternalInternal::Nanosecond6NoDot }) => { - self.time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:06}", nano / 1_000) - }) + (Internal(InternalFixed { val: Nanosecond6NoDot }), _, Some(t), _) => { + write!(w, "{:06}", t.nanosecond() % 1_000_000_000 / 1_000) } - Internal(InternalFixed { val: InternalInternal::Nanosecond9NoDot }) => { - self.time.map(|t| { - let nano = t.nanosecond() % 1_000_000_000; - write!(w, "{:09}", nano) - }) + (Internal(InternalFixed { val: Nanosecond9NoDot }), _, Some(t), _) => { + write!(w, "{:09}", t.nanosecond() % 1_000_000_000) } - TimezoneName => self.offset.as_ref().map(|off| write!(w, "{}", off)), - TimezoneOffset | TimezoneOffsetZ => self.offset.as_ref().map(|off| { - OffsetFormat { + (TimezoneName, _, _, Some(off)) => write!(w, "{}", off), + (TimezoneOffset | TimezoneOffsetZ, _, _, Some(off)) => { + let offset_format = OffsetFormat { precision: OffsetPrecision::Minutes, colons: Colons::Maybe, allow_zulu: *spec == TimezoneOffsetZ, padding: Pad::Zero, - } - .format(w, off.fix()) - }), - TimezoneOffsetColon | TimezoneOffsetColonZ => self.offset.as_ref().map(|off| { - OffsetFormat { + }; + offset_format.format(w, off.fix()) + } + (TimezoneOffsetColon | TimezoneOffsetColonZ, _, _, Some(off)) => { + let offset_format = OffsetFormat { precision: OffsetPrecision::Minutes, colons: Colons::Colon, allow_zulu: *spec == TimezoneOffsetColonZ, padding: Pad::Zero, - } - .format(w, off.fix()) - }), - TimezoneOffsetDoubleColon => self.offset.as_ref().map(|off| { - OffsetFormat { + }; + offset_format.format(w, off.fix()) + } + (TimezoneOffsetDoubleColon, _, _, Some(off)) => { + let offset_format = OffsetFormat { precision: OffsetPrecision::Seconds, colons: Colons::Colon, allow_zulu: false, padding: Pad::Zero, - } - .format(w, off.fix()) - }), - TimezoneOffsetTripleColon => self.offset.as_ref().map(|off| { - OffsetFormat { + }; + offset_format.format(w, off.fix()) + } + (TimezoneOffsetTripleColon, _, _, Some(off)) => { + let offset_format = OffsetFormat { precision: OffsetPrecision::Hours, colons: Colons::None, allow_zulu: false, padding: Pad::Zero, - } - .format(w, off.fix()) - }), - Internal(InternalFixed { val: InternalInternal::TimezoneOffsetPermissive }) => { - return Err(fmt::Error); + }; + offset_format.format(w, off.fix()) } - RFC2822 => - // same as `%a, %d %b %Y %H:%M:%S %z` - { - if let (Some(d), Some(t), Some(off)) = (self.date, self.time, self.offset.as_ref()) - { - Some(write_rfc2822_inner(w, d, t, off.fix(), self.locale)) - } else { - None - } - } - RFC3339 => - // same as `%Y-%m-%dT%H:%M:%S%.f%:z` - { - if let (Some(d), Some(t), Some(off)) = (self.date, self.time, self.offset.as_ref()) - { - Some(write_rfc3339( - w, - crate::NaiveDateTime::new(d, t), - off.fix(), - SecondsFormat::AutoSi, - false, - )) - } else { - None - } + (RFC2822, Some(d), Some(t), Some(off)) => { + write_rfc2822_inner(w, d, t, off.fix(), self.locale) } - }; - - ret.unwrap_or(Err(fmt::Error)) // insufficient arguments for given format + (RFC3339, Some(d), Some(t), Some(off)) => write_rfc3339( + w, + crate::NaiveDateTime::new(d, t), + off.fix(), + SecondsFormat::AutoSi, + false, + ), + _ => Err(fmt::Error), // insufficient arguments for given format + } } } From 09366d1585f06a04c963c268b411b22f81d55d7a Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 15:43:41 +0200 Subject: [PATCH 4/7] Optimize number formatting --- src/format/formatting.rs | 124 ++++++++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 42 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index b1ce73c76e..7c9ec4e3d0 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -108,48 +108,88 @@ where fn format_numeric(&self, w: &mut impl Write, spec: &Numeric, pad: Pad) -> fmt::Result { use self::Numeric::*; - let (width, v) = match (spec, self.date, self.time) { - (Year, Some(d), _) => (4, i64::from(d.year())), - (YearDiv100, Some(d), _) => (2, i64::from(d.year()).div_euclid(100)), - (YearMod100, Some(d), _) => (2, i64::from(d.year()).rem_euclid(100)), - (IsoYear, Some(d), _) => (4, i64::from(d.iso_week().year())), - (IsoYearDiv100, Some(d), _) => (2, i64::from(d.iso_week().year()).div_euclid(100)), - (IsoYearMod100, Some(d), _) => (2, i64::from(d.iso_week().year()).rem_euclid(100)), - (Month, Some(d), _) => (2, i64::from(d.month())), - (Day, Some(d), _) => (2, i64::from(d.day())), - (WeekFromSun, Some(d), _) => (2, i64::from(d.weeks_from(Weekday::Sun))), - (WeekFromMon, Some(d), _) => (2, i64::from(d.weeks_from(Weekday::Mon))), - (IsoWeek, Some(d), _) => (2, i64::from(d.iso_week().week())), - (NumDaysFromSun, Some(d), _) => (1, i64::from(d.weekday().num_days_from_sunday())), - (WeekdayFromMon, Some(d), _) => (1, i64::from(d.weekday().number_from_monday())), - (Ordinal, Some(d), _) => (3, i64::from(d.ordinal())), - (Hour, _, Some(t)) => (2, i64::from(t.hour())), - (Hour12, _, Some(t)) => (2, i64::from(t.hour12().1)), - (Minute, _, Some(t)) => (2, i64::from(t.minute())), - (Second, _, Some(t)) => (2, i64::from(t.second() + t.nanosecond() / 1_000_000_000)), - (Nanosecond, _, Some(t)) => (9, i64::from(t.nanosecond() % 1_000_000_000)), - (Timestamp, Some(d), Some(t)) => { - let offset = self.offset.as_ref().map(|o| i64::from(o.fix().local_minus_utc())); - let timestamp = d.and_time(t).timestamp() - offset.unwrap_or(0); - (1, timestamp) + fn write_one(w: &mut impl Write, v: u8) -> fmt::Result { + w.write_char((b'0' + v) as char) + } + + fn write_two(w: &mut impl Write, v: u8, pad: Pad) -> fmt::Result { + let ones = b'0' + v % 10; + match (v / 10, pad) { + (0, Pad::None) => {} + (0, Pad::Space) => w.write_char(' ')?, + (tens, _) => w.write_char((b'0' + tens) as char)?, } - (Internal(_), _, _) => return Ok(()), // for future expansion - _ => return Err(fmt::Error), // insufficient arguments for given format - }; + w.write_char(ones as char) + } - if (spec == &Year || spec == &IsoYear) && !(0..10_000).contains(&v) { - // non-four-digit years require an explicit sign as per ISO 8601 - match pad { - Pad::None => write!(w, "{:+}", v), - Pad::Zero => write!(w, "{:+01$}", v, width + 1), - Pad::Space => write!(w, "{:+1$}", v, width + 1), + #[inline] + fn write_year(w: &mut impl Write, year: i32, pad: Pad) -> fmt::Result { + if (1000..=9999).contains(&year) { + // fast path + write_hundreds(w, (year / 100) as u8)?; + write_hundreds(w, (year % 100) as u8) + } else { + write_n(w, 4, year as i64, pad, !(0..10_000).contains(&year)) } - } else { - match pad { - Pad::None => write!(w, "{}", v), - Pad::Zero => write!(w, "{:01$}", v, width), - Pad::Space => write!(w, "{:1$}", v, width), + } + + fn write_n( + w: &mut impl Write, + n: usize, + v: i64, + pad: Pad, + always_sign: bool, + ) -> fmt::Result { + if always_sign { + match pad { + Pad::None => write!(w, "{:+}", v), + Pad::Zero => write!(w, "{:+01$}", v, n + 1), + Pad::Space => write!(w, "{:+1$}", v, n + 1), + } + } else { + match pad { + Pad::None => write!(w, "{}", v), + Pad::Zero => write!(w, "{:01$}", v, n), + Pad::Space => write!(w, "{:1$}", v, n), + } + } + } + + match (spec, self.date, self.time) { + (Year, Some(d), _) => write_year(w, d.year(), pad), + (YearDiv100, Some(d), _) => write_two(w, d.year().div_euclid(100) as u8, pad), + (YearMod100, Some(d), _) => write_two(w, d.year().rem_euclid(100) as u8, pad), + (IsoYear, Some(d), _) => write_year(w, d.iso_week().year(), pad), + (IsoYearDiv100, Some(d), _) => { + write_two(w, d.iso_week().year().div_euclid(100) as u8, pad) + } + (IsoYearMod100, Some(d), _) => { + write_two(w, d.iso_week().year().rem_euclid(100) as u8, pad) + } + (Month, Some(d), _) => write_two(w, d.month() as u8, pad), + (Day, Some(d), _) => write_two(w, d.day() as u8, pad), + (WeekFromSun, Some(d), _) => write_two(w, d.weeks_from(Weekday::Sun) as u8, pad), + (WeekFromMon, Some(d), _) => write_two(w, d.weeks_from(Weekday::Mon) as u8, pad), + (IsoWeek, Some(d), _) => write_two(w, d.iso_week().week() as u8, pad), + (NumDaysFromSun, Some(d), _) => write_one(w, d.weekday().num_days_from_sunday() as u8), + (WeekdayFromMon, Some(d), _) => write_one(w, d.weekday().number_from_monday() as u8), + (Ordinal, Some(d), _) => write_n(w, 3, d.ordinal() as i64, pad, false), + (Hour, _, Some(t)) => write_two(w, t.hour() as u8, pad), + (Hour12, _, Some(t)) => write_two(w, t.hour12().1 as u8, pad), + (Minute, _, Some(t)) => write_two(w, t.minute() as u8, pad), + (Second, _, Some(t)) => { + write_two(w, (t.second() + t.nanosecond() / 1_000_000_000) as u8, pad) + } + (Nanosecond, _, Some(t)) => { + write_n(w, 9, (t.nanosecond() % 1_000_000_000) as i64, pad, false) + } + (Timestamp, Some(d), Some(t)) => { + let offset = self.offset.as_ref().map(|o| i64::from(o.fix().local_minus_utc())); + let timestamp = d.and_time(t).timestamp() - offset.unwrap_or(0); + write_n(w, 9, timestamp, pad, false) } + (Internal(_), _, _) => Ok(()), // for future expansion + _ => Err(fmt::Error), // insufficient arguments for given format } } @@ -198,21 +238,21 @@ where } (Nanosecond3, _, Some(t), _) => { w.write_str(decimal_point(self.locale))?; - write!(w, "{:03}", t.nanosecond() % 1_000_000_000 / 1_000_000) + write!(w, "{:03}", t.nanosecond() / 1_000_000 % 1000) } (Nanosecond6, _, Some(t), _) => { w.write_str(decimal_point(self.locale))?; - write!(w, "{:06}", t.nanosecond() % 1_000_000_000 / 1_000) + write!(w, "{:06}", t.nanosecond() / 1_000 % 1_000_000) } (Nanosecond9, _, Some(t), _) => { w.write_str(decimal_point(self.locale))?; write!(w, "{:09}", t.nanosecond() % 1_000_000_000) } (Internal(InternalFixed { val: Nanosecond3NoDot }), _, Some(t), _) => { - write!(w, "{:03}", t.nanosecond() % 1_000_000_000 / 1_000_000) + write!(w, "{:03}", t.nanosecond() / 1_000_000 % 1_000) } (Internal(InternalFixed { val: Nanosecond6NoDot }), _, Some(t), _) => { - write!(w, "{:06}", t.nanosecond() % 1_000_000_000 / 1_000) + write!(w, "{:06}", t.nanosecond() / 1_000 % 1_000_000) } (Internal(InternalFixed { val: Nanosecond9NoDot }), _, Some(t), _) => { write!(w, "{:09}", t.nanosecond() % 1_000_000_000) From abbf253879128bd1d98be55c6e93ab488b0ac959 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 1 Jul 2023 15:48:54 +0200 Subject: [PATCH 5/7] Don't require `alloc` feature for `Formatter` --- src/format/formatting.rs | 48 +++++++++++++++++----------------------- src/format/mod.rs | 9 ++++---- 2 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 7c9ec4e3d0..94f9b9c9de 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -5,34 +5,22 @@ #[cfg(all(not(feature = "std"), feature = "alloc"))] use alloc::string::{String, ToString}; -#[cfg(feature = "alloc")] use core::borrow::Borrow; -#[cfg(feature = "alloc")] -use core::fmt::Display; -use core::fmt::{self, Write}; +use core::fmt::{self, Display, Write}; -#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] use crate::datetime::SecondsFormat; -#[cfg(feature = "alloc")] -use crate::offset::Offset; -#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] -use crate::{Datelike, FixedOffset, NaiveDateTime, Timelike}; -#[cfg(feature = "alloc")] -use crate::{NaiveDate, NaiveTime, Weekday}; +use crate::{ + Datelike, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Offset, Timelike, Weekday, +}; -#[cfg(feature = "alloc")] use super::locales; -#[cfg(all(feature = "unstable-locales", feature = "alloc"))] -use super::Locale; -#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] -use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; -#[cfg(feature = "alloc")] -use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric}; -#[cfg(feature = "alloc")] +use super::{ + Colons, Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric, OffsetFormat, + OffsetPrecision, Pad, +}; use locales::*; /// A *temporary* object which can be used as an argument to [`format!`] or others. -#[cfg(feature = "alloc")] #[derive(Debug)] pub struct Formatter { /// The date view, if any. @@ -48,7 +36,6 @@ pub struct Formatter { locale: Locale, } -#[cfg(feature = "alloc")] impl<'a, I, B, Off> Formatter where I: Iterator + Clone, @@ -96,6 +83,7 @@ where for item in self.items.clone() { match *item.borrow() { Item::Literal(s) | Item::Space(s) => w.write_str(s), + #[cfg(any(feature = "alloc", feature = "std"))] Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s), Item::Numeric(ref spec, pad) => self.format_numeric(w, spec, pad), Item::Fixed(ref spec) => self.format_fixed(w, spec), @@ -309,7 +297,6 @@ where } } -#[cfg(feature = "alloc")] impl<'a, I, B, Off> Display for Formatter where I: Iterator + Clone, @@ -317,9 +304,17 @@ where Off: Offset + Display, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut result = String::new(); - self.format(&mut result)?; - f.pad(&result) + #[cfg(any(feature = "alloc", feature = "std"))] + if f.width().is_some() || f.precision().is_some() { + // Justify/pad/truncate the formatted result by rendering it to a temporary `String` + // first. + // We skip this step if there are no 'external' formatting specifiers. + // This is the only formatting functionality that is missing without `alloc`. + let mut result = String::new(); + self.format(&mut result)?; + return f.pad(&result); + } + self.format(f) } } @@ -446,7 +441,6 @@ pub fn format_item( Formatter::new(date.copied(), time.copied(), offset, [item].into_iter()).fmt(w) } -#[cfg(feature = "alloc")] impl OffsetFormat { /// Writes an offset from UTC with the format defined by `self`. fn format(&self, w: &mut impl Write, off: FixedOffset) -> fmt::Result { @@ -528,7 +522,6 @@ impl OffsetFormat { /// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` #[inline] -#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] pub(crate) fn write_rfc3339( w: &mut impl Write, dt: NaiveDateTime, @@ -601,7 +594,6 @@ pub(crate) fn write_rfc2822( write_rfc2822_inner(w, dt.date(), dt.time(), off, default_locale()) } -#[cfg(feature = "alloc")] /// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z` fn write_rfc2822_inner( w: &mut impl Write, diff --git a/src/format/mod.rs b/src/format/mod.rs index 7de063c65f..22ccc8b9f2 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -48,9 +48,6 @@ pub(crate) mod scan; pub mod strftime; -#[allow(unused)] -// TODO: remove '#[allow(unused)]' once we use this module for parsing or something else that does -// not require `alloc`. pub(crate) mod locales; pub(crate) use formatting::write_hundreds; @@ -58,11 +55,15 @@ pub(crate) use formatting::write_hundreds; pub(crate) use formatting::write_rfc2822; #[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))] pub(crate) use formatting::write_rfc3339; +pub use formatting::Formatter; +#[allow(deprecated)] #[cfg(feature = "alloc")] #[allow(deprecated)] -pub use formatting::{format, format_item, DelayedFormat, Formatter}; +pub use formatting::{format, format_item, DelayedFormat}; #[cfg(feature = "unstable-locales")] pub use locales::Locale; +#[cfg(not(feature = "unstable-locales"))] +pub(crate) use locales::Locale; pub(crate) use parse::parse_rfc3339; pub use parse::{parse, parse_and_remainder}; pub use parsed::Parsed; From 0507da4d670bee9140ffd83f1c9c6ffca8700730 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sat, 8 Jul 2023 12:49:04 +0200 Subject: [PATCH 6/7] Padding and truncation without allocating --- src/format/formatting.rs | 111 +++++++++++++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 9 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 94f9b9c9de..97182d91cf 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -93,6 +93,50 @@ where Ok(()) } + #[cfg(any(feature = "alloc", feature = "std"))] + fn format_with_parameters(&self, f: &mut fmt::Formatter) -> fmt::Result { + // Justify/pad/truncate the formatted result by rendering it to a temporary `String` + // first. + let mut result = String::new(); + self.format(&mut result)?; + f.pad(&result) + } + + #[cfg(not(any(feature = "alloc", feature = "std")))] + fn format_with_parameters(&self, f: &mut fmt::Formatter) -> fmt::Result { + // We have to replicate the `fmt::Formatter:pad` method without allocating. + let mut counter = CountingSink::new(); + self.format(&mut counter)?; + let chars_count = counter.chars_written(); + + if let Some(max_width) = f.precision() { + if chars_count > max_width { + let mut truncating_writer = TruncatingWriter::new(f, max_width); + return self.format(&mut truncating_writer); + } + } + if let Some(min_width) = f.width() { + if chars_count < min_width { + let padding = min_width - chars_count; + let (before, after) = match f.align().unwrap_or(fmt::Alignment::Left) { + fmt::Alignment::Left => (0, padding), + fmt::Alignment::Right => (padding, 0), + fmt::Alignment::Center => (padding / 2, (padding + 1) / 2), + }; + let fill = f.fill(); + for _ in 0..before { + f.write_char(fill)?; + } + self.format(f)?; + for _ in 0..after { + f.write_char(fill)?; + } + return Ok(()); + } + } + self.format(f) + } + fn format_numeric(&self, w: &mut impl Write, spec: &Numeric, pad: Pad) -> fmt::Result { use self::Numeric::*; @@ -304,17 +348,11 @@ where Off: Offset + Display, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - #[cfg(any(feature = "alloc", feature = "std"))] if f.width().is_some() || f.precision().is_some() { - // Justify/pad/truncate the formatted result by rendering it to a temporary `String` - // first. - // We skip this step if there are no 'external' formatting specifiers. - // This is the only formatting functionality that is missing without `alloc`. - let mut result = String::new(); - self.format(&mut result)?; - return f.pad(&result); + self.format_with_parameters(f) + } else { + self.format(f) } - self.format(f) } } @@ -652,6 +690,61 @@ pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result { w.write_char(ones as char) } +/// Sink that counts the number of bytes written to it. +#[cfg(not(any(feature = "alloc", feature = "std")))] +struct CountingSink { + written: usize, +} + +#[cfg(not(any(feature = "alloc", feature = "std")))] +impl CountingSink { + fn new() -> Self { + Self { written: 0 } + } + + fn chars_written(&self) -> usize { + self.written + } +} + +#[cfg(not(any(feature = "alloc", feature = "std")))] +impl Write for CountingSink { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.written = self.written.checked_add(s.chars().count()).ok_or(fmt::Error)?; + Ok(()) + } +} + +// `Write` adaptor that only emits up to `max` characters. +#[cfg(not(any(feature = "alloc", feature = "std")))] +struct TruncatingWriter<'a, 'b> { + formatter: &'a mut fmt::Formatter<'b>, + chars_remaining: usize, +} + +#[cfg(not(any(feature = "alloc", feature = "std")))] +impl<'a, 'b> TruncatingWriter<'a, 'b> { + fn new(formatter: &'a mut fmt::Formatter<'b>, max: usize) -> Self { + Self { formatter, chars_remaining: max } + } +} + +#[cfg(not(any(feature = "alloc", feature = "std")))] +impl<'a, 'b> Write for TruncatingWriter<'a, 'b> { + fn write_str(&mut self, s: &str) -> fmt::Result { + let max = self.chars_remaining; + let char_count = s.chars().count(); + let (s, count) = if char_count <= max { + (s, char_count) + } else { + let (i, _) = s.char_indices().nth(max).unwrap(); + (&s[..i], max) + }; + self.chars_remaining -= count; + self.formatter.write_str(s) + } +} + #[cfg(test)] #[cfg(feature = "alloc")] mod tests { From cef3f54a73b7631ef95aeab502aa3c80307c1a52 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 11 Aug 2023 21:07:58 +0200 Subject: [PATCH 7/7] Add new `Formatter` that has a generic `Offset` --- src/format/formatting.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 97182d91cf..e93ffee3e9 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -83,7 +83,7 @@ where for item in self.items.clone() { match *item.borrow() { Item::Literal(s) | Item::Space(s) => w.write_str(s), - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] Item::OwnedLiteral(ref s) | Item::OwnedSpace(ref s) => w.write_str(s), Item::Numeric(ref spec, pad) => self.format_numeric(w, spec, pad), Item::Fixed(ref spec) => self.format_fixed(w, spec), @@ -93,7 +93,7 @@ where Ok(()) } - #[cfg(any(feature = "alloc", feature = "std"))] + #[cfg(feature = "alloc")] fn format_with_parameters(&self, f: &mut fmt::Formatter) -> fmt::Result { // Justify/pad/truncate the formatted result by rendering it to a temporary `String` // first. @@ -102,7 +102,7 @@ where f.pad(&result) } - #[cfg(not(any(feature = "alloc", feature = "std")))] + #[cfg(not(feature = "alloc"))] fn format_with_parameters(&self, f: &mut fmt::Formatter) -> fmt::Result { // We have to replicate the `fmt::Formatter:pad` method without allocating. let mut counter = CountingSink::new(); @@ -691,12 +691,12 @@ pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result { } /// Sink that counts the number of bytes written to it. -#[cfg(not(any(feature = "alloc", feature = "std")))] +#[cfg(not(feature = "alloc"))] struct CountingSink { written: usize, } -#[cfg(not(any(feature = "alloc", feature = "std")))] +#[cfg(not(feature = "alloc"))] impl CountingSink { fn new() -> Self { Self { written: 0 } @@ -707,7 +707,7 @@ impl CountingSink { } } -#[cfg(not(any(feature = "alloc", feature = "std")))] +#[cfg(not(feature = "alloc"))] impl Write for CountingSink { fn write_str(&mut self, s: &str) -> fmt::Result { self.written = self.written.checked_add(s.chars().count()).ok_or(fmt::Error)?; @@ -716,20 +716,20 @@ impl Write for CountingSink { } // `Write` adaptor that only emits up to `max` characters. -#[cfg(not(any(feature = "alloc", feature = "std")))] +#[cfg(not(feature = "alloc"))] struct TruncatingWriter<'a, 'b> { formatter: &'a mut fmt::Formatter<'b>, chars_remaining: usize, } -#[cfg(not(any(feature = "alloc", feature = "std")))] +#[cfg(not(feature = "alloc"))] impl<'a, 'b> TruncatingWriter<'a, 'b> { fn new(formatter: &'a mut fmt::Formatter<'b>, max: usize) -> Self { Self { formatter, chars_remaining: max } } } -#[cfg(not(any(feature = "alloc", feature = "std")))] +#[cfg(not(feature = "alloc"))] impl<'a, 'b> Write for TruncatingWriter<'a, 'b> { fn write_str(&mut self, s: &str) -> fmt::Result { let max = self.chars_remaining;