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]