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()); + } } diff --git a/src/format/formatting.rs b/src/format/formatting.rs index c0a877cd09..429b9658b0 100644 --- a/src/format/formatting.rs +++ b/src/format/formatting.rs @@ -10,19 +10,17 @@ use core::borrow::Borrow; use core::fmt; use core::fmt::Write; +use crate::datetime::SecondsFormat; #[cfg(any(feature = "alloc", feature = "std"))] -use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; +use crate::offset::Offset; +use crate::{Datelike, FixedOffset, NaiveDateTime, Timelike}; #[cfg(any(feature = "alloc", feature = "std"))] -use crate::offset::{FixedOffset, Offset}; -#[cfg(any(feature = "alloc", feature = "std"))] -use crate::{Datelike, SecondsFormat, Timelike, Weekday}; +use crate::{NaiveDate, NaiveTime, Weekday}; use super::locales; +use super::{Colons, OffsetFormat, OffsetPrecision, Pad}; #[cfg(any(feature = "alloc", feature = "std"))] -use super::{ - Colons, Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric, OffsetFormat, - OffsetPrecision, Pad, -}; +use super::{Fixed, InternalFixed, InternalInternal, Item, Locale, Numeric}; use locales::*; /// A *temporary* object which can be used as an argument to `format!` or others. @@ -447,7 +445,6 @@ fn format_inner( } } -#[cfg(any(feature = "alloc", feature = "std"))] impl OffsetFormat { /// Writes an offset from UTC with the format defined by `self`. fn format(&self, w: &mut impl Write, off: FixedOffset) -> fmt::Result { @@ -528,7 +525,6 @@ impl OffsetFormat { } /// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z` -#[cfg(any(feature = "alloc", feature = "std"))] #[inline] pub(crate) fn write_rfc3339( w: &mut impl Write, diff --git a/src/format/mod.rs b/src/format/mod.rs index 65598187e6..42ee3c2fb2 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -52,11 +52,12 @@ pub(crate) mod locales; pub(crate) use formatting::write_hundreds; #[cfg(any(feature = "alloc", feature = "std"))] +pub(crate) use formatting::write_rfc2822; +pub(crate) use formatting::write_rfc3339; +#[cfg(any(feature = "alloc", feature = "std"))] pub use formatting::{format, format_item, DelayedFormat}; #[cfg(feature = "unstable-locales")] pub use formatting::{format_item_localized, format_localized}; -#[cfg(any(feature = "alloc", feature = "std"))] -pub(crate) use formatting::{write_rfc2822, write_rfc3339}; #[cfg(feature = "unstable-locales")] pub use locales::Locale; #[cfg(not(feature = "unstable-locales"))]