Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add try_to_rfc3339 and to_iso8601, deprecate to_rfc3339 #1331

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion bench/benches/chrono.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ fn bench_datetime_to_rfc3339(c: &mut Criterion) {
.unwrap(),
)
.unwrap();
c.bench_function("bench_datetime_to_rfc3339", |b| b.iter(|| black_box(dt).to_rfc3339()));
c.bench_function("bench_datetime_to_rfc3339", |b| {
b.iter(|| black_box(dt).try_to_rfc3339().unwrap())
});
}

fn bench_datetime_to_rfc3339_opts(c: &mut Criterion) {
Expand Down
80 changes: 73 additions & 7 deletions src/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -630,11 +630,74 @@ impl<Tz: TimeZone> DateTime<Tz> {
result
}

/// Returns an RFC 3339 and ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`.
/// Returns an RFC 3339 date and time string such as `1996-12-19T16:39:57-08:00`.
/// This is also valid ISO 8601.
///
/// # Warning
///
/// RFC 3339 is only defined on years 0 through 9999. This method switches to an ISO 8601
/// representation on dates outside of that range, which is not supported by conforming RFC 3339
/// parsers.
#[cfg(feature = "alloc")]
#[must_use]
#[deprecated(
since = "0.4.36",
note = "Produces invalid data on years outside of the range 0..=9999. Use `try_to_rfc3339()` or `to_iso8601` instead."
)]
pub fn to_rfc3339(&self) -> String {
// For some reason a string with a capacity less than 32 is ca 20% slower when benchmarking.
self.to_iso8601()
}

/// Returns an RFC 3339 date and time string such as `1996-12-19T16:39:57-08:00`.
/// This is also valid ISO 8601.
///
/// # Errors
///
/// RFC 3339 is only defined on years 0 through 9999. This method returns `None` on dates
/// outside of this 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_rfc3339(), Some("2023-06-10T09:18:25+00:00".to_owned()));
///
/// let dt = Utc.with_ymd_and_hms(10_000, 1, 1, 0, 0, 0).unwrap();
/// assert_eq!(dt.try_to_rfc3339(), None);
/// ```
#[cfg(feature = "alloc")]
#[must_use]
pub fn try_to_rfc3339(&self) -> Option<String> {
let year = self.year();
if !(0..=9999).contains(&year) {
return None;
}
Some(self.to_iso8601())
}

/// Returns an ISO 8601 date and time string such as `1996-12-19T16:39:57-08:00`.
///
/// Note that although the standard supports many different formats, we choose one that is
/// compatible with the RFC 3339 format for most common cases.
/// This format supports years outside of the range 0 through 9999, which RFC 3339 does not.
///
/// # Example
///
/// ```rust
/// # use chrono::{TimeZone, Utc};
/// let dt = Utc.with_ymd_and_hms(2023, 6, 10, 9, 18, 25).unwrap();
/// assert_eq!(dt.to_iso8601(), "2023-06-10T09:18:25+00:00");
///
/// let dt = Utc.with_ymd_and_hms(10_000, 1, 1, 0, 0, 0).unwrap();
/// assert_eq!(dt.to_iso8601(), "+10000-01-01T00:00:00+00:00");
///
/// let dt = Utc.with_ymd_and_hms(-537, 6, 10, 9, 18, 25).unwrap();
/// assert_eq!(dt.to_iso8601(), "-0537-06-10T09:18:25+00:00");
/// ```
#[cfg(feature = "alloc")]
#[must_use]
pub fn to_iso8601(&self) -> String {
let mut result = String::with_capacity(32);
let naive = self.overflowing_naive_local();
let offset = self.offset.fix();
Expand All @@ -643,12 +706,15 @@ impl<Tz: TimeZone> DateTime<Tz> {
result
}

/// Return an RFC 3339 and ISO 8601 date and time string with subseconds
/// formatted as per `SecondsFormat`.
/// Return an RFC 3339 and ISO 8601 date and time string with subseconds formatted as per
/// `SecondsFormat`.
///
/// If `use_z` is `false` and the time zone is UTC the offset will be formatted as `+00:00`.
/// If `use_z` is `true` the offset will be formatted as `Z` instead.
///
/// 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`]
/// Note that if the year of the `DateTime` is outside of the range 0 through 9999 then the date
/// while be formatted as an expanded representation according to ISO 8601. This makes the
/// string incompatible with RFC 3339.
///
/// # Examples
///
Expand Down
39 changes: 23 additions & 16 deletions src/datetime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -772,8 +772,8 @@ fn test_datetime_rfc3339() {

// timezone 0
assert_eq!(
Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().to_rfc3339(),
"2015-02-18T23:16:09+00:00"
Utc.with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap().try_to_rfc3339().as_deref(),
Some("2015-02-18T23:16:09+00:00")
);
// timezone +05
assert_eq!(
Expand All @@ -784,18 +784,18 @@ fn test_datetime_rfc3339() {
.unwrap()
)
.unwrap()
.to_rfc3339(),
"2015-02-18T23:16:09.150+05:00"
.try_to_rfc3339()
.as_deref(),
Some("2015-02-18T23:16:09.150+05:00")
);

assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00");
assert_eq!(
ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).to_rfc3339(),
"2015-02-18T23:16:09.150+05:00"
ymdhms_utc(2015, 2, 18, 23, 16, 9).try_to_rfc3339().as_deref(),
Some("2015-02-18T23:16:09+00:00")
);
assert_eq!(
ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(),
"2015-02-18T23:59:60.234567+05:00"
ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).try_to_rfc3339().as_deref(),
Some("2015-02-18T23:59:60.234567+05:00")
);
assert_eq!(
DateTime::parse_from_rfc3339("2015-02-18T23:59:59.123+05:00"),
Expand All @@ -815,12 +815,12 @@ fn test_datetime_rfc3339() {
);

assert_eq!(
ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).to_rfc3339(),
"2015-02-18T23:59:60.234567+05:00"
ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567).try_to_rfc3339().as_deref(),
Some("2015-02-18T23:59:60.234567+05:00")
);
assert_eq!(
ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).to_rfc3339(),
"2015-02-18T23:16:09.150+05:00"
ymdhms_milli(&edt5, 2015, 2, 18, 23, 16, 9, 150).try_to_rfc3339().as_deref(),
Some("2015-02-18T23:16:09.150+05:00")
);
assert_eq!(
DateTime::parse_from_rfc3339("2015-02-18T00:00:00.234567+05:00"),
Expand All @@ -834,7 +834,10 @@ fn test_datetime_rfc3339() {
DateTime::parse_from_rfc3339("2015-02-18 23:59:60.234567+05:00"),
Ok(ymdhms_micro(&edt5, 2015, 2, 18, 23, 59, 59, 1_234_567))
);
assert_eq!(ymdhms_utc(2015, 2, 18, 23, 16, 9).to_rfc3339(), "2015-02-18T23:16:09+00:00");
assert_eq!(
ymdhms_utc(2015, 2, 18, 23, 16, 9).try_to_rfc3339().as_deref(),
Some("2015-02-18T23:16:09+00:00")
);

assert!(DateTime::parse_from_rfc3339("2015-02-18T23:59:60.234567 +05:00").is_err());
assert!(DateTime::parse_from_rfc3339("2015-02-18T23:059:60.234567+05:00").is_err());
Expand Down Expand Up @@ -1538,7 +1541,9 @@ fn test_min_max_getters() {
// RFC 2822 doesn't support years with more than 4 digits.
// assert_eq!(beyond_min.to_rfc2822(), "");
#[cfg(feature = "alloc")]
assert_eq!(beyond_min.to_rfc3339(), "-262144-12-31T22:00:00-02:00");
assert_eq!(beyond_min.try_to_rfc3339(), None); // doesn't support years with more than 4 digits.
#[cfg(feature = "alloc")]
assert_eq!(beyond_min.to_iso8601(), "-262144-12-31T22:00:00-02:00");
#[cfg(feature = "alloc")]
assert_eq!(
beyond_min.format("%Y-%m-%dT%H:%M:%S%:z").to_string(),
Expand All @@ -1563,7 +1568,9 @@ fn test_min_max_getters() {
// RFC 2822 doesn't support years with more than 4 digits.
// assert_eq!(beyond_max.to_rfc2822(), "");
#[cfg(feature = "alloc")]
assert_eq!(beyond_max.to_rfc3339(), "+262143-01-01T01:59:59.999999999+02:00");
assert_eq!(beyond_max.try_to_rfc3339(), None); // doesn't support years with more than 4 digits.
#[cfg(feature = "alloc")]
assert_eq!(beyond_max.to_iso8601(), "+262143-01-01T01:59:59.999999999+02:00");
#[cfg(feature = "alloc")]
assert_eq!(
beyond_max.format("%Y-%m-%dT%H:%M:%S%.9f%:z").to_string(),
Expand Down
4 changes: 4 additions & 0 deletions src/format/formatting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,10 @@ pub enum SecondsFormat {
}

/// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z`
///
/// This does not always output a valid RFC 3339 string. RFC 3339 is only defined on years
/// 0 through 9999. Instead we output an ISO 8601 string with a format that for common dates is
/// compatible with RFC 3339.
#[inline]
#[cfg(any(feature = "alloc", feature = "serde", feature = "rustc-serialize"))]
pub(crate) fn write_rfc3339(
Expand Down
4 changes: 4 additions & 0 deletions src/format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@ pub enum Fixed {
/// RFC 2822 date and time syntax. Commonly used for email and MIME date and time.
RFC2822,
/// RFC 3339 & ISO 8601 date and time syntax.
///
/// Note that if the year of the `DateTime` is outside of the range 0 through 9999 then the date
/// while be formatted as an expanded representation according to ISO 8601. These dates are not
/// supported by, and incompatible with, RFC 3339.
RFC3339,

/// Internal uses only.
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,8 @@
//! 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.to_rfc3339(), "2014-11-28T12:00:09+00:00");
//! assert_eq!(dt.try_to_rfc3339().unwrap(), "2014-11-28T12:00:09+00:00");
//! assert_eq!(dt.to_iso8601(), "2014-11-28T12:00:09+00:00");
//! assert_eq!(format!("{:?}", dt), "2014-11-28T12:00:09Z");
//!
//! // Note that milli/nanoseconds are only printed if they are non-zero
Expand Down
4 changes: 2 additions & 2 deletions src/offset/fixed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl FixedOffset {
/// let hour = 3600;
/// let datetime =
/// FixedOffset::east_opt(5 * hour).unwrap().with_ymd_and_hms(2016, 11, 08, 0, 0, 0).unwrap();
/// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00+05:00")
/// assert_eq!(&datetime.to_iso8601(), "2016-11-08T00:00:00+05:00")
/// ```
#[must_use]
pub const fn east_opt(secs: i32) -> Option<FixedOffset> {
Expand Down Expand Up @@ -89,7 +89,7 @@ impl FixedOffset {
/// let hour = 3600;
/// let datetime =
/// FixedOffset::west_opt(5 * hour).unwrap().with_ymd_and_hms(2016, 11, 08, 0, 0, 0).unwrap();
/// assert_eq!(&datetime.to_rfc3339(), "2016-11-08T00:00:00-05:00")
/// assert_eq!(&datetime.to_iso8601(), "2016-11-08T00:00:00-05:00")
/// ```
#[must_use]
pub const fn west_opt(secs: i32) -> Option<FixedOffset> {
Expand Down