From bfdd4d74f0fd9a225d9e2d4d4dde89767efc816e Mon Sep 17 00:00:00 2001 From: Steven Tang Date: Tue, 21 Jan 2025 14:21:02 -0500 Subject: [PATCH 1/2] Expose `format_into` and `format_into_io` for `DelayedFormat` Request for #1649 - renamed format to format_into and made it public. - added format_into_io to allow format into a `std::io::Write`. - added unittests - added benchmarks for the 3 methods, `Display`, `format_into`, and `format_into_io`. --- bench/benches/chrono.rs | 28 ++++++++++++++++++ src/format/formatting.rs | 64 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/bench/benches/chrono.rs b/bench/benches/chrono.rs index 925c2939f1..3ec133ed32 100644 --- a/bench/benches/chrono.rs +++ b/bench/benches/chrono.rs @@ -185,6 +185,33 @@ fn bench_format_with_items(c: &mut Criterion) { }); } +fn benches_delayed_format(c: &mut Criterion) { + let mut group = c.benchmark_group("delayed_format"); + let dt = Local::now(); + group.bench_function(BenchmarkId::new("with_display", dt), |b| { + b.iter_batched( + || dt.format("%Y-%m-%dT%H:%M:%S%.f%:z"), + |df| black_box(df).to_string(), + criterion::BatchSize::SmallInput, + ) + }); + group.bench_function(BenchmarkId::new("with_string_buffer", dt), |b| { + b.iter_batched( + || (dt.format("%Y-%m-%dT%H:%M:%S%.f%:z"), String::with_capacity(256)), + |(df, string)| black_box(df).format_into(&mut black_box(string)), + criterion::BatchSize::SmallInput, + ) + }); + group.bench_function(BenchmarkId::new("with_vec_buffer", dt), |b| { + b.iter_batched( + || (dt.format("%Y-%m-%dT%H:%M:%S%.f%:z"), String::with_capacity(256)), + |(df, string)| black_box(df).format_into(&mut black_box(string)), + criterion::BatchSize::SmallInput, + ) + }); + group.finish(); +} + fn bench_format_manual(c: &mut Criterion) { let dt = Local::now(); c.bench_function("bench_format_manual", |b| { @@ -237,6 +264,7 @@ criterion_group!( bench_format, bench_format_with_items, bench_format_manual, + benches_delayed_format, bench_naivedate_add_signed, bench_datetime_with, ); diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 967f2d3a68..0c297993ca 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -97,7 +97,30 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { DelayedFormat { date, time, off: Some(name_and_diff), items, locale } } - fn format(&self, w: &mut impl Write) -> fmt::Result { + /// Formats `DelayedFormat` into an `std::io::Write` instance. + /// # Errors + /// This function returns an error if formatting into the `std::io::Write` instance fails. + #[cfg(feature = "std")] + pub fn format_into_io(self, w: &mut impl std::io::Write) -> fmt::Result { + // wrapper to allow reuse of the existing string based + // writers + struct IoWriter { + writer: W, + } + impl fmt::Write for IoWriter { + #[inline] + fn write_str(&mut self, s: &str) -> fmt::Result { + self.writer.write_all(s.as_bytes()).map_err(|_| fmt::Error) + } + } + let mut writer = IoWriter { writer: w }; + self.format_into(&mut writer) + } + + /// Formats `DelayedFormat` into a `core::fmt::Write` instance. + /// # Errors + /// This function returns an error if formatting into the `core::fmt::Write` instance fails. + pub fn format_into(&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), @@ -321,7 +344,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { impl<'a, I: Iterator + Clone, B: Borrow>> Display for DelayedFormat { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut result = String::new(); - self.format(&mut result)?; + self.format_into(&mut result)?; f.pad(&result) } } @@ -353,7 +376,7 @@ where /// Formats single formatting item #[cfg(feature = "alloc")] -#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt instead")] +#[deprecated(since = "0.4.32", note = "Use DelayedFormat::fmt or DelayedFormat::format instead")] pub fn format_item( w: &mut fmt::Formatter, date: Option<&NaiveDate>, @@ -611,6 +634,41 @@ mod tests { #[cfg(feature = "alloc")] use crate::{NaiveDate, NaiveTime, TimeZone, Timelike, Utc}; + #[cfg(all(feature = "std", feature = "alloc"))] + #[test] + fn test_delayed_format_into_io_matches_format_into() { + let dt = crate::DateTime::from_timestamp(1643723400, 123456789).unwrap(); + let df = dt.format("%Y-%m-%d %H:%M:%S%.9f"); + + let mut dt_str = String::new(); + let mut dt_vec_str = Vec::new(); + + df.format_into(&mut dt_str).unwrap(); + df.format_into_io(&mut dt_vec_str).unwrap(); + + assert_eq!(dt_str, String::from_utf8(dt_vec_str).unwrap()); + assert_eq!(dt_str, "2022-02-01 13:50:00.123456789"); + } + + #[cfg(all(feature = "std", feature = "unstable-locales", feature = "alloc"))] + #[test] + fn test_with_locale_delayed_format_into_io_matches_format_into() { + use crate::format::locales::Locale; + use crate::DateTime; + + let dt = DateTime::from_timestamp(1643723400, 123456789).unwrap(); + let df = dt.format_localized("%A, %B %d, %Y", Locale::ja_JP); + + let mut dt_str = String::new(); + let mut dt_vec_str = Vec::new(); + + df.format_into(&mut dt_str).unwrap(); + df.format_into_io(&mut dt_vec_str).unwrap(); + + assert_eq!(dt_str, String::from_utf8(dt_vec_str).unwrap()); + assert_eq!(dt_str, "火曜日, 2月 01, 2022"); + } + #[test] #[cfg(feature = "alloc")] fn test_date_format() { From b93c9af46f1b642ccbaf49b2a0855d20333abc02 Mon Sep 17 00:00:00 2001 From: Steven Tang Date: Wed, 22 Jan 2025 09:56:31 -0500 Subject: [PATCH 2/2] Expose `write_to` for `DelayedFormat` Request for #1649 - renamed format to write-to and made it public. - added unittests - added benchmarks to compare and show `Display` is slower than `write_to` in that specific use case. --- bench/benches/chrono.rs | 10 +----- src/format/formatting.rs | 71 ++++++++++++++++++++-------------------- 2 files changed, 37 insertions(+), 44 deletions(-) diff --git a/bench/benches/chrono.rs b/bench/benches/chrono.rs index 3ec133ed32..91c4e45520 100644 --- a/bench/benches/chrono.rs +++ b/bench/benches/chrono.rs @@ -198,18 +198,10 @@ fn benches_delayed_format(c: &mut Criterion) { group.bench_function(BenchmarkId::new("with_string_buffer", dt), |b| { b.iter_batched( || (dt.format("%Y-%m-%dT%H:%M:%S%.f%:z"), String::with_capacity(256)), - |(df, string)| black_box(df).format_into(&mut black_box(string)), + |(df, string)| black_box(df).write_to(&mut black_box(string)), criterion::BatchSize::SmallInput, ) }); - group.bench_function(BenchmarkId::new("with_vec_buffer", dt), |b| { - b.iter_batched( - || (dt.format("%Y-%m-%dT%H:%M:%S%.f%:z"), String::with_capacity(256)), - |(df, string)| black_box(df).format_into(&mut black_box(string)), - criterion::BatchSize::SmallInput, - ) - }); - group.finish(); } fn bench_format_manual(c: &mut Criterion) { diff --git a/src/format/formatting.rs b/src/format/formatting.rs index 0c297993ca..3ab56204e7 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -97,30 +97,38 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { DelayedFormat { date, time, off: Some(name_and_diff), items, locale } } - /// Formats `DelayedFormat` into an `std::io::Write` instance. - /// # Errors - /// This function returns an error if formatting into the `std::io::Write` instance fails. - #[cfg(feature = "std")] - pub fn format_into_io(self, w: &mut impl std::io::Write) -> fmt::Result { - // wrapper to allow reuse of the existing string based - // writers - struct IoWriter { - writer: W, - } - impl fmt::Write for IoWriter { - #[inline] - fn write_str(&mut self, s: &str) -> fmt::Result { - self.writer.write_all(s.as_bytes()).map_err(|_| fmt::Error) - } - } - let mut writer = IoWriter { writer: w }; - self.format_into(&mut writer) - } - /// Formats `DelayedFormat` into a `core::fmt::Write` instance. /// # Errors - /// This function returns an error if formatting into the `core::fmt::Write` instance fails. - pub fn format_into(&self, w: &mut impl Write) -> fmt::Result { + /// This function returns a `core::fmt::Error` if formatting into the `core::fmt::Write` instance fails. + /// + /// # Example + /// ### Writing to a String + /// ``` + /// let dt = chrono::DateTime::from_timestamp(1643723400, 123456789).unwrap(); + /// let df = dt.format("%Y-%m-%d %H:%M:%S%.9f"); + /// let mut buffer = String::new(); + /// let _ = df.write_to(&mut buffer); + /// ``` + /// ### Writing to a Vec + /// ``` + /// // wrapper to allow reuse of the existing string based + /// // writers + /// struct IoWriter { + /// writer: W, + /// } + /// impl std::fmt::Write for IoWriter { + /// #[inline] + /// fn write_str(&mut self, s: &str) -> std::fmt::Result { + /// self.writer.write_all(s.as_bytes()).map_err(|_| std::fmt::Error) + /// } + /// } + /// let dt = chrono::DateTime::from_timestamp(1643723400, 123456789).unwrap(); + /// let df = dt.format("%Y-%m-%d %H:%M:%S%.9f"); + /// let w: Vec = Vec::new(); + /// let mut writer = IoWriter { writer: w }; + /// let _ = df.write_to(&mut writer); + /// ``` + pub fn write_to(&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), @@ -344,7 +352,7 @@ impl<'a, I: Iterator + Clone, B: Borrow>> DelayedFormat { impl<'a, I: Iterator + Clone, B: Borrow>> Display for DelayedFormat { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut result = String::new(); - self.format_into(&mut result)?; + self.write_to(&mut result)?; f.pad(&result) } } @@ -634,25 +642,21 @@ mod tests { #[cfg(feature = "alloc")] use crate::{NaiveDate, NaiveTime, TimeZone, Timelike, Utc}; - #[cfg(all(feature = "std", feature = "alloc"))] + #[cfg(feature = "alloc")] #[test] - fn test_delayed_format_into_io_matches_format_into() { + fn test_delayed_write_to() { let dt = crate::DateTime::from_timestamp(1643723400, 123456789).unwrap(); let df = dt.format("%Y-%m-%d %H:%M:%S%.9f"); let mut dt_str = String::new(); - let mut dt_vec_str = Vec::new(); - - df.format_into(&mut dt_str).unwrap(); - df.format_into_io(&mut dt_vec_str).unwrap(); - assert_eq!(dt_str, String::from_utf8(dt_vec_str).unwrap()); + df.write_to(&mut dt_str).unwrap(); assert_eq!(dt_str, "2022-02-01 13:50:00.123456789"); } #[cfg(all(feature = "std", feature = "unstable-locales", feature = "alloc"))] #[test] - fn test_with_locale_delayed_format_into_io_matches_format_into() { + fn test_with_locale_delayed_write_to() { use crate::format::locales::Locale; use crate::DateTime; @@ -660,12 +664,9 @@ mod tests { let df = dt.format_localized("%A, %B %d, %Y", Locale::ja_JP); let mut dt_str = String::new(); - let mut dt_vec_str = Vec::new(); - df.format_into(&mut dt_str).unwrap(); - df.format_into_io(&mut dt_vec_str).unwrap(); + df.write_to(&mut dt_str).unwrap(); - assert_eq!(dt_str, String::from_utf8(dt_vec_str).unwrap()); assert_eq!(dt_str, "火曜日, 2月 01, 2022"); }