From 15d5d4dad51a04ae0bd37f1e35b88302ba837acc Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Mon, 24 Apr 2023 11:24:14 +0200 Subject: [PATCH] Do not use `Offset`'s `Debug` impl when serializing `DateTime` --- src/datetime/mod.rs | 4 +- src/datetime/rustc_serialize.rs | 4 +- src/datetime/serde.rs | 73 ++++++++++++++++++++++++++++----- 3 files changed, 68 insertions(+), 13 deletions(-) diff --git a/src/datetime/mod.rs b/src/datetime/mod.rs index d2a0c9b611..21a2b172ab 100644 --- a/src/datetime/mod.rs +++ b/src/datetime/mod.rs @@ -1418,7 +1418,9 @@ where &FixedOffset::east_opt(3650).unwrap().with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() ) .ok(), - Some(r#""2014-07-24T12:34:06+01:00:50""#.into()) + // An offset with seconds is not allowed by RFC 3339, so we round it to the nearest minute. + // In this case `+01:00:50` becomes `+01:01` + Some(r#""2014-07-24T12:34:06+01:01""#.into()) ); } diff --git a/src/datetime/rustc_serialize.rs b/src/datetime/rustc_serialize.rs index 8e75350d92..115ec9eb3f 100644 --- a/src/datetime/rustc_serialize.rs +++ b/src/datetime/rustc_serialize.rs @@ -1,6 +1,6 @@ #![cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))] -use super::DateTime; +use super::{DateTime, SecondsFormat}; #[cfg(feature = "clock")] use crate::offset::Local; use crate::offset::{FixedOffset, LocalResult, TimeZone, Utc}; @@ -10,7 +10,7 @@ use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; impl Encodable for DateTime { fn encode(&self, s: &mut S) -> Result<(), S::Error> { - format!("{:?}", self).encode(s) + self.to_rfc3339_opts(SecondsFormat::AutoSi, true).encode(s) } } diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 0e58d95ed6..61f7f68840 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -3,11 +3,12 @@ use core::fmt; use serde::{de, ser}; -use super::DateTime; +use super::{DateTime, SecondsFormat}; +use crate::format::write_rfc3339; use crate::naive::datetime::serde::serde_from; #[cfg(feature = "clock")] use crate::offset::Local; -use crate::offset::{FixedOffset, TimeZone, Utc}; +use crate::offset::{FixedOffset, Offset, TimeZone, Utc}; #[doc(hidden)] #[derive(Debug)] @@ -25,7 +26,7 @@ pub struct MicroSecondsTimestampVisitor; #[derive(Debug)] pub struct MilliSecondsTimestampVisitor; -/// Serialize into a rfc3339 time string +/// Serialize into an ISO 8601 formatted string. /// /// See [the `serde` module](./serde/index.html) for alternate /// serializations. @@ -34,18 +35,19 @@ impl ser::Serialize for DateTime { where S: ser::Serializer, { - struct FormatWrapped<'a, D: 'a> { - inner: &'a D, + struct FormatIso8601<'a, Tz: TimeZone> { + inner: &'a DateTime, } - impl<'a, D: fmt::Debug> fmt::Display for FormatWrapped<'a, D> { + impl<'a, Tz: TimeZone> fmt::Display for FormatIso8601<'a, Tz> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.inner.fmt(f) + let naive = self.inner.naive_local(); + let offset = self.inner.offset.fix(); + write_rfc3339(f, naive, offset, SecondsFormat::AutoSi, true) } } - // Debug formatting is correct RFC3339, and it allows Zulu. - serializer.collect_str(&FormatWrapped { inner: &self }) + serializer.collect_str(&FormatIso8601 { inner: self }) } } @@ -1154,7 +1156,8 @@ mod tests { #[cfg(feature = "clock")] use crate::datetime::test_decodable_json; use crate::datetime::test_encodable_json; - use crate::{DateTime, TimeZone, Utc}; + use crate::{DateTime, FixedOffset, TimeZone, Utc}; + use core::fmt; #[test] fn test_serde_serialize() { @@ -1183,4 +1186,54 @@ mod tests { assert_eq!(dt, decoded); assert_eq!(dt.offset(), decoded.offset()); } + + #[test] + fn test_serde_no_offset_debug() { + use crate::{LocalResult, NaiveDate, NaiveDateTime, Offset}; + use core::fmt::Debug; + + #[derive(Clone)] + struct TestTimeZone; + impl Debug for TestTimeZone { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TEST") + } + } + impl TimeZone for TestTimeZone { + type Offset = TestTimeZone; + fn from_offset(_state: &TestTimeZone) -> TestTimeZone { + TestTimeZone + } + fn offset_from_local_date(&self, _local: &NaiveDate) -> LocalResult { + LocalResult::Single(TestTimeZone) + } + fn offset_from_local_datetime( + &self, + _local: &NaiveDateTime, + ) -> LocalResult { + LocalResult::Single(TestTimeZone) + } + fn offset_from_utc_date(&self, _utc: &NaiveDate) -> TestTimeZone { + TestTimeZone + } + fn offset_from_utc_datetime(&self, _utc: &NaiveDateTime) -> TestTimeZone { + TestTimeZone + } + } + impl Offset for TestTimeZone { + fn fix(&self) -> FixedOffset { + FixedOffset::east_opt(15 * 60 * 60).unwrap() + } + } + + let tz = TestTimeZone; + assert_eq!(format!("{:?}", &tz), "TEST"); + + let dt = tz.with_ymd_and_hms(2023, 4, 24, 21, 10, 33).unwrap(); + let encoded = serde_json::to_string(&dt).unwrap(); + dbg!(&encoded); + let decoded: DateTime = serde_json::from_str(&encoded).unwrap(); + assert_eq!(dt, decoded); + assert_eq!(dt.offset().fix(), *decoded.offset()); + } }