From de95a5202e45904e124d15e93170c64b3cfcab9a Mon Sep 17 00:00:00 2001
From: Paul Dicker <paul@pitdicker.nl>
Date: Fri, 9 Jun 2023 09:08:58 +0200
Subject: [PATCH 1/2] Add `try_to_rfc2822`, deprecate `to_rfc2822`

---
 bench/benches/chrono.rs  |  4 +++-
 src/datetime/mod.rs      | 35 ++++++++++++++++++++++++++++++-----
 src/datetime/tests.rs    | 30 ++++++++++++++++--------------
 src/format/formatting.rs |  7 ++++++-
 src/format/parsed.rs     |  4 ++--
 src/lib.rs               |  4 ++--
 6 files changed, 59 insertions(+), 25 deletions(-)

diff --git a/bench/benches/chrono.rs b/bench/benches/chrono.rs
index 925c2939f1..541d82fcc0 100644
--- a/bench/benches/chrono.rs
+++ b/bench/benches/chrono.rs
@@ -56,7 +56,9 @@ fn bench_datetime_to_rfc2822(c: &mut Criterion) {
                 .unwrap(),
         )
         .unwrap();
-    c.bench_function("bench_datetime_to_rfc2822", |b| b.iter(|| black_box(dt).to_rfc2822()));
+    c.bench_function("bench_datetime_to_rfc2822", |b| {
+        b.iter(|| black_box(dt).try_to_rfc2822().unwrap())
+    });
 }
 
 fn bench_datetime_to_rfc3339(c: &mut Criterion) {
diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs
index bb157f836b..83cd5ef3bb 100644
--- a/src/datetime/mod.rs
+++ b/src/datetime/mod.rs
@@ -619,15 +619,40 @@ impl<Tz: TimeZone> DateTime<Tz> {
     ///
     /// # Panics
     ///
-    /// Panics if the date can not be represented in this format: the year may not be negative and
-    /// can not have more than 4 digits.
+    /// RFC 2822 is only defined on years 0 through 9999, and this method panics on dates outside
+    /// of that range.
     #[cfg(feature = "alloc")]
     #[must_use]
+    #[deprecated(
+        since = "0.4.36",
+        note = "Can panic on years outside of the range 0..=9999. Use `try_to_rfc2822()` instead."
+    )]
     pub fn to_rfc2822(&self) -> String {
+        self.try_to_rfc2822().expect("date outside of defined range for rfc2822")
+    }
+
+    /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`.
+    ///
+    /// # Errors
+    ///
+    /// RFC 2822 is only defined on years 0 through 9999, and this method returns an error on dates
+    /// outside of that range.
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// # use chrono::{TimeZone, Utc};
+    /// let dt = Utc.with_ymd_and_hms(2023, 6, 10, 9, 18, 25).unwrap();
+    /// assert_eq!(dt.try_to_rfc2822(), Some("Sat, 10 Jun 2023 09:18:25 +0000".to_owned()));
+    ///
+    /// let dt = Utc.with_ymd_and_hms(10_000, 1, 1, 0, 0, 0).unwrap();
+    /// assert_eq!(dt.try_to_rfc2822(), None);
+    /// ```
+    #[cfg(feature = "alloc")]
+    pub fn try_to_rfc2822(&self) -> Option<String> {
         let mut result = String::with_capacity(32);
-        write_rfc2822(&mut result, self.overflowing_naive_local(), self.offset.fix())
-            .expect("writing rfc2822 datetime to string should never fail");
-        result
+        write_rfc2822(&mut result, self.overflowing_naive_local(), self.offset.fix()).ok()?;
+        Some(result)
     }
 
     /// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`.
diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs
index 81e5c1bb59..dbf077dfbc 100644
--- a/src/datetime/tests.rs
+++ b/src/datetime/tests.rs
@@ -645,12 +645,12 @@ fn test_datetime_rfc2822() {
 
     // timezone 0
     assert_eq!(
-        Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc2822(),
-        "Wed, 18 Feb 2015 23:16:09 +0000"
+        Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().try_to_rfc2822().as_deref(),
+        Some("Wed, 18 Feb 2015 23:16:09 +0000")
     );
     assert_eq!(
-        Utc.with_ymd_and_hms(2015, 2, 1, 23, 16, 9).unwrap().to_rfc2822(),
-        "Sun, 1 Feb 2015 23:16:09 +0000"
+        Utc.with_ymd_and_hms(2015, 2, 1, 23, 16, 9).unwrap().try_to_rfc2822().as_deref(),
+        Some("Sun, 1 Feb 2015 23:16:09 +0000")
     );
     // timezone +05
     assert_eq!(
@@ -661,8 +661,9 @@ fn test_datetime_rfc2822() {
                 .unwrap()
         )
         .unwrap()
-        .to_rfc2822(),
-        "Wed, 18 Feb 2015 23:16:09 +0500"
+        .try_to_rfc2822()
+        .as_deref(),
+        Some("Wed, 18 Feb 2015 23:16:09 +0500")
     );
     assert_eq!(
         DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"),
@@ -696,8 +697,9 @@ fn test_datetime_rfc2822() {
                 .unwrap()
         )
         .unwrap()
-        .to_rfc2822(),
-        "Wed, 18 Feb 2015 23:59:60 +0500"
+        .try_to_rfc2822()
+        .as_deref(),
+        Some("Wed, 18 Feb 2015 23:59:60 +0500")
     );
 
     assert_eq!(
@@ -709,8 +711,8 @@ fn test_datetime_rfc2822() {
         Ok(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap())
     );
     assert_eq!(
-        ymdhms_micro(&edt, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc2822(),
-        "Wed, 18 Feb 2015 23:59:60 +0500"
+        ymdhms_micro(&edt, 2015, 2, 18, 23, 59, 59, 1_234_567).try_to_rfc2822().as_deref(),
+        Some("Wed, 18 Feb 2015 23:59:60 +0500")
     );
     assert_eq!(
         DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:58 +0500"),
@@ -1535,8 +1537,8 @@ fn test_min_max_getters() {
     let beyond_max = offset_max.from_utc_datetime(&NaiveDateTime::MAX);
 
     assert_eq!(format!("{:?}", beyond_min), "-262144-12-31T22:00:00-02:00");
-    // RFC 2822 doesn't support years with more than 4 digits.
-    // assert_eq!(beyond_min.to_rfc2822(), "");
+    #[cfg(feature = "alloc")]
+    assert_eq!(beyond_min.try_to_rfc2822(), None); // doesn't support years with more than 4 digits.
     #[cfg(feature = "alloc")]
     assert_eq!(beyond_min.to_rfc3339(), "-262144-12-31T22:00:00-02:00");
     #[cfg(feature = "alloc")]
@@ -1560,8 +1562,8 @@ fn test_min_max_getters() {
     assert_eq!(beyond_min.nanosecond(), 0);
 
     assert_eq!(format!("{:?}", beyond_max), "+262143-01-01T01:59:59.999999999+02:00");
-    // RFC 2822 doesn't support years with more than 4 digits.
-    // assert_eq!(beyond_max.to_rfc2822(), "");
+    #[cfg(feature = "alloc")]
+    assert_eq!(beyond_max.try_to_rfc2822(), None); // doesn't support years with more than 4 digits.
     #[cfg(feature = "alloc")]
     assert_eq!(beyond_max.to_rfc3339(), "+262143-01-01T01:59:59.999999999+02:00");
     #[cfg(feature = "alloc")]
diff --git a/src/format/formatting.rs b/src/format/formatting.rs
index f3448f94fe..bf28133c83 100644
--- a/src/format/formatting.rs
+++ b/src/format/formatting.rs
@@ -595,8 +595,13 @@ pub(crate) fn write_rfc3339(
     .format(w, off)
 }
 
+/// Write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z`
+///
+/// # Errors
+///
+/// RFC 2822 is only defined on years 0 through 9999, and this function returns an error on dates
+/// outside that range.
 #[cfg(feature = "alloc")]
-/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z`
 pub(crate) fn write_rfc2822(
     w: &mut impl Write,
     dt: NaiveDateTime,
diff --git a/src/format/parsed.rs b/src/format/parsed.rs
index 6f7253de4c..8f28d70c9f 100644
--- a/src/format/parsed.rs
+++ b/src/format/parsed.rs
@@ -72,7 +72,7 @@ use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday};
 /// parsed.set_second(40)?;
 /// parsed.set_offset(0)?;
 /// let dt = parsed.to_datetime()?;
-/// assert_eq!(dt.to_rfc2822(), "Wed, 31 Dec 2014 04:26:40 +0000");
+/// assert_eq!(dt.try_to_rfc2822().unwrap(), "Wed, 31 Dec 2014 04:26:40 +0000");
 ///
 /// let mut parsed = Parsed::new();
 /// parsed.set_weekday(Weekday::Thu)?; // changed to the wrong day
@@ -109,7 +109,7 @@ use crate::{DateTime, Datelike, TimeDelta, Timelike, Weekday};
 /// parse(&mut parsed, "Wed, 31 Dec 2014 04:26:40 +0000", rfc_2822.iter())?;
 /// let dt = parsed.to_datetime()?;
 ///
-/// assert_eq!(dt.to_rfc2822(), "Wed, 31 Dec 2014 04:26:40 +0000");
+/// assert_eq!(dt.try_to_rfc2822().unwrap(), "Wed, 31 Dec 2014 04:26:40 +0000");
 ///
 /// let mut parsed = Parsed::new();
 /// parse(&mut parsed, "Thu, 31 Dec 2014 04:26:40 +0000", rfc_2822.iter())?;
diff --git a/src/lib.rs b/src/lib.rs
index 917b39691b..c64b4c731b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -304,7 +304,7 @@
 //!
 //! assert_eq!(dt.format("%a %b %e %T %Y").to_string(), dt.format("%c").to_string());
 //! assert_eq!(dt.to_string(), "2014-11-28 12:00:09 UTC");
-//! assert_eq!(dt.to_rfc2822(), "Fri, 28 Nov 2014 12:00:09 +0000");
+//! assert_eq!(dt.try_to_rfc2822().unwrap(), "Fri, 28 Nov 2014 12:00:09 +0000");
 //! assert_eq!(dt.to_rfc3339(), "2014-11-28T12:00:09+00:00");
 //! assert_eq!(format!("{:?}", dt), "2014-11-28T12:00:09Z");
 //!
@@ -396,7 +396,7 @@
 //!
 //! // Construct a datetime from epoch:
 //! let dt: DateTime<Utc> = DateTime::from_timestamp(1_500_000_000, 0).unwrap();
-//! assert_eq!(dt.to_rfc2822(), "Fri, 14 Jul 2017 02:40:00 +0000");
+//! assert_eq!(dt.try_to_rfc2822(), Some("Fri, 14 Jul 2017 02:40:00 +0000".to_owned()));
 //!
 //! // Get epoch value from a datetime:
 //! let dt = DateTime::parse_from_rfc2822("Fri, 14 Jul 2017 02:40:00 +0000").unwrap();

From e980391a0566b3778f4aa069bb9fd9f357d3a634 Mon Sep 17 00:00:00 2001
From: Paul Dicker <paul@pitdicker.nl>
Date: Mon, 24 Jul 2023 09:51:35 +0200
Subject: [PATCH 2/2] Test panic in `to_rfc2822`

---
 src/datetime/tests.rs | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs
index dbf077dfbc..578b1ec8a6 100644
--- a/src/datetime/tests.rs
+++ b/src/datetime/tests.rs
@@ -640,6 +640,7 @@ fn test_datetime_with_timezone() {
 
 #[test]
 #[cfg(feature = "alloc")]
+#[allow(deprecated)]
 fn test_datetime_rfc2822() {
     let edt = FixedOffset::east_opt(5 * 60 * 60).unwrap();
 
@@ -764,6 +765,31 @@ fn test_datetime_rfc2822() {
     assert!(DateTime::parse_from_rfc2822("Wed. 18 Feb 2015 23:16:09 +0000").is_err());
     // *trailing* space causes failure
     assert!(DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000   ").is_err());
+
+    const RFC_2822_YEAR_MAX: i32 = 9999;
+    const RFC_2822_YEAR_MIN: i32 = 0;
+
+    let dt = Utc.with_ymd_and_hms(RFC_2822_YEAR_MAX, 1, 2, 3, 4, 5).unwrap();
+    assert_eq!(dt.to_rfc2822(), "Sat, 2 Jan 9999 03:04:05 +0000");
+
+    let dt = Utc.with_ymd_and_hms(RFC_2822_YEAR_MIN, 1, 2, 3, 4, 5).unwrap();
+    assert_eq!(dt.to_rfc2822(), "Sun, 2 Jan 0000 03:04:05 +0000");
+}
+
+#[test]
+#[should_panic]
+#[cfg(feature = "alloc")]
+#[allow(deprecated)]
+fn test_rfc_2822_year_range_panic_high() {
+    let _ = Utc.with_ymd_and_hms(10000, 1, 2, 3, 4, 5).unwrap().to_rfc2822();
+}
+
+#[test]
+#[should_panic]
+#[cfg(feature = "alloc")]
+#[allow(deprecated)]
+fn test_rfc_2822_year_range_panic_low() {
+    let _ = Utc.with_ymd_and_hms(-1, 1, 2, 3, 4, 5).unwrap().to_rfc2822();
 }
 
 #[test]