From fd20e8438cb0fe8797f36fef42748d87aefd57e1 Mon Sep 17 00:00:00 2001
From: Steven Tang <sttang@blackberry.com>
Date: Tue, 21 Jan 2025 14:21:02 -0500
Subject: [PATCH] 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 | 69 +++++++++++++++++++++++++++++++++++++---
 2 files changed, 93 insertions(+), 4 deletions(-)

diff --git a/bench/benches/chrono.rs b/bench/benches/chrono.rs
index 925c2939f1..91ca557307 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..61a17dbdc1 100644
--- a/src/format/formatting.rs
+++ b/src/format/formatting.rs
@@ -97,7 +97,34 @@ impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
         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<W: std::io::Write> {
+            writer: W,
+        }
+        impl<W: std::io::Write> fmt::Write for IoWriter<W> {
+            #[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 +348,7 @@ impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
 impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> Display for DelayedFormat<I> {
     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 +380,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>,
@@ -609,7 +636,41 @@ mod tests {
     use super::{Colons, OffsetFormat, OffsetPrecision, Pad};
     use crate::FixedOffset;
     #[cfg(feature = "alloc")]
-    use crate::{NaiveDate, NaiveTime, TimeZone, Timelike, Utc};
+    use crate::{NaiveDate, NaiveTime, TimeZone, Timelike, Utc, DateTime};
+
+    #[cfg(feature = "std")]
+    #[test]
+    fn test_delayed_format_into_io_matches_format_into() {
+        let dt = 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;
+
+        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")]