Skip to content

Commit

Permalink
Do not use Offset's Debug impl when serializing DateTime
Browse files Browse the repository at this point in the history
  • Loading branch information
pitdicker committed Aug 7, 2023
1 parent 5f80aaf commit 15d5d4d
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 13 deletions.
4 changes: 3 additions & 1 deletion src/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
);
}

Expand Down
4 changes: 2 additions & 2 deletions src/datetime/rustc_serialize.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -10,7 +10,7 @@ use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};

impl<Tz: TimeZone> Encodable for DateTime<Tz> {
fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
format!("{:?}", self).encode(s)
self.to_rfc3339_opts(SecondsFormat::AutoSi, true).encode(s)
}
}

Expand Down
73 changes: 63 additions & 10 deletions src/datetime/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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.
Expand All @@ -34,18 +35,19 @@ impl<Tz: TimeZone> ser::Serialize for DateTime<Tz> {
where
S: ser::Serializer,
{
struct FormatWrapped<'a, D: 'a> {
inner: &'a D,
struct FormatIso8601<'a, Tz: TimeZone> {
inner: &'a DateTime<Tz>,
}

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 })
}
}

Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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<TestTimeZone> {
LocalResult::Single(TestTimeZone)
}
fn offset_from_local_datetime(
&self,
_local: &NaiveDateTime,
) -> LocalResult<TestTimeZone> {
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<FixedOffset> = serde_json::from_str(&encoded).unwrap();
assert_eq!(dt, decoded);
assert_eq!(dt.offset().fix(), *decoded.offset());
}
}

0 comments on commit 15d5d4d

Please sign in to comment.