From 88854c01c22e638bba36395987857d9bef4673bf Mon Sep 17 00:00:00 2001 From: Mahmoud Al-Qudsi Date: Wed, 31 Aug 2022 15:57:57 -0500 Subject: [PATCH 1/2] Add `parse_from_xxx` to `DateTime` As discussed in #263. Note that this breaks existing code that did not explicitly specify the offset in calls to `DateTime::parse_from_*`, as they are now ambiguous. Relies on #271 for conversion back into `DateTime` from `DateTime` as the latter is used under the hood. --- src/datetime/mod.rs | 47 ++++++++++++++++++++++++++++++++++++++++-- src/datetime/tests.rs | 23 ++++++++++++--------- src/format/strftime.rs | 6 +++--- src/lib.rs | 10 ++++----- src/naive/time/mod.rs | 2 +- 5 files changed, 67 insertions(+), 21 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index 1ae0d9c42d..b69b62b638 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -533,7 +533,7 @@ impl DateTime { /// ``` /// # use chrono::{DateTime, FixedOffset, TimeZone}; /// assert_eq!( - /// DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 GMT").unwrap(), + /// DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 GMT").unwrap(), /// FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9) /// ); /// ``` @@ -574,7 +574,7 @@ impl DateTime { /// ```rust /// use chrono::{DateTime, FixedOffset, TimeZone}; /// - /// let dt = DateTime::parse_from_str( + /// let dt = DateTime::::parse_from_str( /// "1983 Apr 13 12:09:14.274 +0000", "%Y %b %d %H:%M:%S%.3f %z"); /// assert_eq!(dt, Ok(FixedOffset::east(0).ymd(1983, 4, 13).and_hms_milli(12, 9, 14, 274))); /// ``` @@ -585,6 +585,49 @@ impl DateTime { } } +impl DateTime { + /// Parses an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`, + /// then returns a new `DateTime` instance corresponding to the UTC date/time accounting + /// for the difference between UTC and the parsed timezone. + pub fn parse_from_rfc2822(s: &str) -> ParseResult> { + DateTime::::parse_from_rfc2822(s).map(|result| result.into()) + } + + /// Parses an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`, + /// then returns a new `DateTime` instance corresponding to the UTC date/time accounting + /// for the difference between UTC and the parsed timezone. + /// + /// Why isn't this named `parse_from_iso8601`? That's because ISO 8601 allows some freedom + /// over the syntax and RFC 3339 exercises that freedom to rigidly define a fixed format. + pub fn parse_from_rfc3339(s: &str) -> ParseResult> { + DateTime::::parse_from_rfc3339(s).map(|result| result.into()) + } + + /// Parses a string with the specified format string and + /// returns a new `DateTime` with a parsed `FixedOffset`. + /// See the [`format::strftime` module](./format/strftime/index.html) + /// on the supported escape sequences. + /// + /// See also `Offset::datetime_from_str` which gives a local `DateTime` on specific time zone. + /// + /// Note that this method *requires a timezone* in the string. See + /// [`NaiveDateTime::parse_from_str`](./naive/struct.NaiveDateTime.html#method.parse_from_str) + /// for a version that does not require a timezone in the to-be-parsed str. + /// + /// # Example + /// + /// ```rust + /// use chrono::{DateTime, TimeZone, Utc}; + /// + /// let dt = DateTime::::parse_from_str( + /// "1983 Apr 13 12:09:14.274 +0100", "%Y %b %d %H:%M:%S%.3f %z"); + /// assert_eq!(dt, Ok(Utc.ymd(1983, 4, 13).and_hms_milli(11, 9, 14, 274))); + /// ``` + pub fn parse_from_str(s: &str, fmt: &str) -> ParseResult> { + DateTime::::parse_from_str(s, fmt).map(|result| result.into()) + } +} + impl DateTime where Tz::Offset: fmt::Display, diff --git a/src/datetime/tests.rs b/src/datetime/tests.rs index 6c2b900f3a..21808e6372 100644 --- a/src/datetime/tests.rs +++ b/src/datetime/tests.rs @@ -111,24 +111,24 @@ fn test_datetime_rfc2822_and_rfc3339() { ); assert_eq!( - DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"), + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 +0000"), Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) ); assert_eq!( - DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 -0000"), + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:16:09 -0000"), Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) ); assert_eq!( - DateTime::parse_from_rfc3339("2015-02-18T23:16:09Z"), + DateTime::::parse_from_rfc3339("2015-02-18T23:16:09Z"), Ok(FixedOffset::east(0).ymd(2015, 2, 18).and_hms(23, 16, 9)) ); assert_eq!( - DateTime::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), + DateTime::::parse_from_rfc2822("Wed, 18 Feb 2015 23:59:60 +0500"), Ok(edt.ymd(2015, 2, 18).and_hms_milli(23, 59, 59, 1_000)) ); - assert!(DateTime::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); + assert!(DateTime::::parse_from_rfc2822("31 DEC 262143 23:59 -2359").is_err()); assert_eq!( - DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), + DateTime::::parse_from_rfc3339("2015-02-18T23:59:60.234567+05:00"), Ok(edt.ymd(2015, 2, 18).and_hms_micro(23, 59, 59, 1_234_567)) ); } @@ -209,12 +209,15 @@ fn test_datetime_from_str() { fn test_datetime_parse_from_str() { let ymdhms = |y, m, d, h, n, s, off| FixedOffset::east(off).ymd(y, m, d).and_hms(h, n, s); assert_eq!( - DateTime::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), + DateTime::::parse_from_str("2014-5-7T12:34:56+09:30", "%Y-%m-%dT%H:%M:%S%z"), Ok(ymdhms(2014, 5, 7, 12, 34, 56, 570 * 60)) ); // ignore offset - assert!(DateTime::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset - assert!(DateTime::parse_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT") - .is_err()); + assert!(DateTime::::parse_from_str("20140507000000", "%Y%m%d%H%M%S").is_err()); // no offset + assert!(DateTime::::parse_from_str( + "Fri, 09 Aug 2013 23:54:35 GMT", + "%a, %d %b %Y %H:%M:%S GMT" + ) + .is_err()); assert_eq!( Utc.datetime_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"), Ok(Utc.ymd(2013, 8, 9).and_hms(23, 54, 35)) diff --git a/src/format/strftime.rs b/src/format/strftime.rs index 31509a0ff4..c2d2e4d2fe 100644 --- a/src/format/strftime.rs +++ b/src/format/strftime.rs @@ -622,15 +622,15 @@ fn test_strftime_docs() { ); assert_eq!( dt.with_timezone(&Utc), - DateTime::parse_from_str("2001-07-07T15:04:60.026490708Z", "%+").unwrap() + DateTime::::parse_from_str("2001-07-07T15:04:60.026490708Z", "%+").unwrap() ); assert_eq!( dt.with_timezone(&Utc), - DateTime::parse_from_str("2001-07-07T15:04:60.026490708UTC", "%+").unwrap() + DateTime::::parse_from_str("2001-07-07T15:04:60.026490708UTC", "%+").unwrap() ); assert_eq!( dt.with_timezone(&Utc), - DateTime::parse_from_str("2001-07-07t15:04:60.026490708utc", "%+").unwrap() + DateTime::::parse_from_str("2001-07-07t15:04:60.026490708utc", "%+").unwrap() ); assert_eq!( diff --git a/src/lib.rs b/src/lib.rs index 4c3f086b25..4db2128d47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -268,11 +268,11 @@ //! assert_eq!("2014-11-28T21:00:09+09:00".parse::>(), Ok(fixed_dt.clone())); //! //! // method 2 -//! assert_eq!(DateTime::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z"), +//! assert_eq!(DateTime::::parse_from_str("2014-11-28 21:00:09 +09:00", "%Y-%m-%d %H:%M:%S %z"), //! Ok(fixed_dt.clone())); -//! assert_eq!(DateTime::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900"), +//! assert_eq!(DateTime::::parse_from_rfc2822("Fri, 28 Nov 2014 21:00:09 +0900"), //! Ok(fixed_dt.clone())); -//! assert_eq!(DateTime::parse_from_rfc3339("2014-11-28T21:00:09+09:00"), Ok(fixed_dt.clone())); +//! assert_eq!(DateTime::::parse_from_rfc3339("2014-11-28T21:00:09+09:00"), Ok(fixed_dt.clone())); //! //! // method 3 //! assert_eq!(Utc.datetime_from_str("2014-11-28 12:00:09", "%Y-%m-%d %H:%M:%S"), Ok(dt.clone())); @@ -302,14 +302,14 @@ //! //! ```rust //! // We need the trait in scope to use Utc::timestamp(). -//! use chrono::{DateTime, TimeZone, Utc}; +//! use chrono::{DateTime, FixedOffset, TimeZone, Utc}; //! //! // Construct a datetime from epoch: //! let dt = Utc.timestamp(1_500_000_000, 0); //! assert_eq!(dt.to_rfc2822(), "Fri, 14 Jul 2017 02:40:00 +0000"); //! //! // Get epoch value from a datetime: -//! let dt = DateTime::parse_from_rfc2822("Fri, 14 Jul 2017 02:40:00 +0000").unwrap(); +//! let dt = DateTime::::parse_from_rfc2822("Fri, 14 Jul 2017 02:40:00 +0000").unwrap(); //! assert_eq!(dt.timestamp(), 1_500_000_000); //! ``` //! diff --git a/src/naive/time/mod.rs b/src/naive/time/mod.rs index 84305bf8f5..646421a498 100644 --- a/src/naive/time/mod.rs +++ b/src/naive/time/mod.rs @@ -178,7 +178,7 @@ mod tests; /// /// let dt = Utc.ymd(2015, 6, 30).and_hms(23, 56, 5); /// assert_eq!(format!("{:?}", dt), "2015-06-30T23:56:05Z"); -/// assert_eq!(DateTime::parse_from_rfc3339("2015-06-30T23:56:05Z").unwrap(), dt); +/// assert_eq!(DateTime::::parse_from_rfc3339("2015-06-30T23:56:05Z").unwrap(), dt); /// ``` /// /// Since Chrono alone cannot determine any existence of leap seconds, From 9de268eeafc2131584e519b6f5ce98aa1bd48d98 Mon Sep 17 00:00:00 2001 From: Mahmoud Al-Qudsi Date: Thu, 8 Sep 2022 13:13:46 -0500 Subject: [PATCH 2/2] Update RFC 2822 and RFC 3339 docs Clarify the behavior of the parse methods, the relationship between ISO 8601 and RFC 3339, and use a brief description on the first line of each function's rustdoc to keep the resulting documentation pretty and concise. --- src/datetime/mod.rs | 102 ++++++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 41 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index b69b62b638..f4e5445ddb 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -524,11 +524,13 @@ where } impl DateTime { - /// Parses an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`, - /// then returns a new [`DateTime`] with a parsed [`FixedOffset`]. + /// Parses an RFC 2822 date-and-time string into a `DateTime` value. /// - /// RFC 2822 is the internet message standard that specifies the - /// representation of times in HTTP and email headers. + /// This parses valid RFC 2822 datetime strings (such as `Tue, 1 Jul 2003 10:52:37 +0200`) + /// and returns a new [`DateTime`] instance with the parsed timezone as the [`FixedOffset`]. + /// + /// RFC 2822 is the internet message standard that specifies the representation of times in HTTP + /// and email headers. /// /// ``` /// # use chrono::{DateTime, FixedOffset, TimeZone}; @@ -544,11 +546,19 @@ impl DateTime { parsed.to_datetime() } - /// Parses an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`, - /// then returns a new [`DateTime`] with a parsed [`FixedOffset`]. + /// Parses an RFC 3339 date-and-time string into a `DateTime` value. + /// + /// Parses all valid RFC 3339 values (as well as the subset of valid ISO 8601 values that are + /// also valid RFC 3339 date-and-time values) and returns a new [`DateTime`] with a + /// [`FixedOffset`] corresponding to the parsed timezone. While RFC 3339 values come in a wide + /// variety of shapes and sizes, `1996-12-19T16:39:57-08:00` is an example of the most commonly + /// encountered variety of RFC 3339 formats. /// - /// Why isn't this named `parse_from_iso8601`? That's because ISO 8601 allows some freedom - /// over the syntax and RFC 3339 exercises that freedom to rigidly define a fixed format. + /// Why isn't this named `parse_from_iso8601`? That's because ISO 8601 allows representing + /// values in a wide range of formats, only some of which represent actual date-and-time + /// instances (rather than periods, ranges, dates, or times). Some valid ISO 8601 values are + /// also simultaneously valid RFC 3339 values, but not all RFC 3339 values are valid ISO 8601 + /// values (or the other way around). pub fn parse_from_rfc3339(s: &str) -> ParseResult> { const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC3339)]; let mut parsed = Parsed::new(); @@ -556,18 +566,15 @@ impl DateTime { parsed.to_datetime() } - /// Parses a string with the specified format string and returns a new - /// [`DateTime`] with a parsed [`FixedOffset`]. + /// Parses a string from a user-specified format into a `DateTime` value. /// - /// See the [`crate::format::strftime`] module on the supported escape - /// sequences. - /// - /// See also [`TimeZone::datetime_from_str`] which gives a local - /// [`DateTime`] on specific time zone. + /// Note that this method *requires a timezone* in the input string. See + /// [`NaiveDateTime::parse_from_str`](./naive/struct.NaiveDateTime.html#method.parse_from_str) + /// for a version that does not require a timezone in the to-be-parsed str. The returned + /// [`DateTime`] value will have a [`FixedOffset`] reflecting the parsed timezone. /// - /// Note that this method *requires a timezone* in the string. See - /// [`NaiveDateTime::parse_from_str`] - /// for a version that does not require a timezone in the to-be-parsed str. + /// See the [`format::strftime` module](./format/strftime/index.html) for supported format + /// sequences. /// /// # Example /// @@ -586,33 +593,46 @@ impl DateTime { } impl DateTime { - /// Parses an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`, - /// then returns a new `DateTime` instance corresponding to the UTC date/time accounting - /// for the difference between UTC and the parsed timezone. + /// Parses an RFC 2822 date-and-time string into a `DateTime` value. + /// + /// This parses valid RFC 2822 datetime values (such as `Tue, 1 Jul 2003 10:52:37 +0200`) + /// and returns a new `DateTime` instance corresponding to the UTC date/time, accounting + /// for the difference between UTC and the parsed timezone, should they differ. + /// + /// RFC 2822 is the internet message standard that specifies the representation of times in HTTP + /// and email headers. pub fn parse_from_rfc2822(s: &str) -> ParseResult> { DateTime::::parse_from_rfc2822(s).map(|result| result.into()) } - /// Parses an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`, - /// then returns a new `DateTime` instance corresponding to the UTC date/time accounting - /// for the difference between UTC and the parsed timezone. + /// Parses an RFC 3339 date-and-time string into a `DateTime` value. /// - /// Why isn't this named `parse_from_iso8601`? That's because ISO 8601 allows some freedom - /// over the syntax and RFC 3339 exercises that freedom to rigidly define a fixed format. + /// Parses all valid RFC 3339 values (as well as the subset of valid ISO 8601 values that are + /// also valid RFC 3339 date-and-time values) and returns a new `DateTime` instance + /// corresponding to the matching UTC date/time, accounting for the difference between UTC and + /// the parsed input's timezone, should they differ. While RFC 3339 values come in a wide + /// variety of shapes and sizes, `1996-12-19T16:39:57-08:00` is an example of the most commonly + /// encountered variety of RFC 3339 formats. + /// + /// Why isn't this named `parse_from_iso8601`? That's because ISO 8601 allows representing + /// values in a wide range of formats, only some of which represent actual date-and-time + /// instances (rather than periods, ranges, dates, or times). Some valid ISO 8601 values are + /// also simultaneously valid RFC 3339 values, but not all RFC 3339 values are valid ISO 8601 + /// values (or the other way around). pub fn parse_from_rfc3339(s: &str) -> ParseResult> { DateTime::::parse_from_rfc3339(s).map(|result| result.into()) } - /// Parses a string with the specified format string and - /// returns a new `DateTime` with a parsed `FixedOffset`. - /// See the [`format::strftime` module](./format/strftime/index.html) - /// on the supported escape sequences. - /// - /// See also `Offset::datetime_from_str` which gives a local `DateTime` on specific time zone. + /// Parses a string from a user-specified format into a `DateTime` value. /// - /// Note that this method *requires a timezone* in the string. See + /// Note that this method *requires a timezone* in the input string. See /// [`NaiveDateTime::parse_from_str`](./naive/struct.NaiveDateTime.html#method.parse_from_str) - /// for a version that does not require a timezone in the to-be-parsed str. + /// for a version that does not require a timezone in the to-be-parsed str. The returned + /// `DateTime` value will reflect the difference in timezones between UTC and the parsed + /// time zone, should they differ. + /// + /// See the [`format::strftime` module](./format/strftime/index.html) for supported format + /// sequences. /// /// # Example /// @@ -649,10 +669,10 @@ where } /// Return an RFC 3339 and ISO 8601 date and time string with subseconds - /// formatted as per a `SecondsFormat`. + /// formatted as per `SecondsFormat`. /// - /// If passed `use_z` true and the timezone is UTC (offset 0), use 'Z', as - /// per [`Fixed::TimezoneOffsetColonZ`] If passed `use_z` false, use + /// If `use_z` is true and the timezone is UTC (offset 0), uses `Z` as + /// per [`Fixed::TimezoneOffsetColonZ`]. If `use_z` is false, uses /// [`Fixed::TimezoneOffsetColon`] /// /// # Examples @@ -729,9 +749,9 @@ where DelayedFormat::new_with_offset(Some(local.date()), Some(local.time()), &self.offset, items) } - /// Formats the combined date and time with the specified format string. - /// See the [`crate::format::strftime`] module - /// on the supported escape sequences. + /// Formats the combined date and time per the specified format string. + /// + /// See the [`crate::format::strftime`] module for the supported escape sequences. /// /// # Example /// ```rust @@ -771,7 +791,7 @@ where ) } - /// Formats the combined date and time with the specified format string and + /// Formats the combined date and time per the specified format string and /// locale. /// /// See the [`crate::format::strftime`] module on the supported escape