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 Jul 1, 2023
1 parent 613ae41 commit c20ec93
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 14 deletions.
4 changes: 3 additions & 1 deletion src/datetime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1448,7 +1448,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())
// Seconds are not allowed by RFC 3339, offset should be rounded 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
18 changes: 16 additions & 2 deletions src/datetime/rustc_serialize.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
#![cfg_attr(docsrs, doc(cfg(feature = "rustc-serialize")))]

use super::DateTime;
use crate::format::{Colons, OffsetFormat, OffsetPrecision, Pad};
#[cfg(feature = "clock")]
use crate::offset::Local;
use crate::offset::{FixedOffset, LocalResult, TimeZone, Utc};
use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone, Utc};
use core::fmt;
use core::ops::Deref;
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)
// Don't use DateTime::to_rfc3339 because we want to use a more compact
// representation (`Z`) for UTC datetimes.
let mut offset = String::new();
OffsetFormat {
precision: OffsetPrecision::Minutes,
colons: Colons::Colon,
allow_zulu: true,
padding: Pad::Zero,
}
.format(&mut offset, (*self.offset()).fix())
.unwrap();

// Reuse the `Debug` impl of `NaiveDateTime`.
format!("{:?}{}", self.naive_local(), offset).encode(s)
}
}

Expand Down
34 changes: 24 additions & 10 deletions src/datetime/serde.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#![cfg_attr(docsrs, doc(cfg(feature = "serde")))]

use core::fmt;
use core::fmt::{self, Debug};
use serde::{de, ser};

use super::DateTime;
use crate::format::{Colons, OffsetFormat, OffsetPrecision, Pad};
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 a RFC 3339 time string.
///
/// See [the `serde` module](./serde/index.html) for alternate
/// serializations.
Expand All @@ -34,18 +35,31 @@ impl<Tz: TimeZone> ser::Serialize for DateTime<Tz> {
where
S: ser::Serializer,
{
struct FormatWrapped<'a, D: 'a> {
inner: &'a D,
struct DisplayRfc3339<'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 DisplayRfc3339<'a, Tz> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.inner.fmt(f)
// Don't use DateTime::to_rfc3339 because we want to use a more compact
// representation (`Z`) for UTC datetimes.

// Reuse the `Debug` impl of `NaiveDateTime`.
Debug::fmt(&self.inner.naive_local(), f)?;
let mut offset = String::new();
OffsetFormat {
precision: OffsetPrecision::Minutes,
colons: Colons::Colon,
allow_zulu: true,
padding: Pad::Zero,
}
.format(&mut offset, (*self.inner.offset()).fix())
.unwrap();
write!(f, "{}", offset)
}
}

// Debug formatting is correct RFC3339, and it allows Zulu.
serializer.collect_str(&FormatWrapped { inner: &self })
serializer.collect_str(&DisplayRfc3339 { inner: self })
}
}

Expand Down Expand Up @@ -1130,11 +1144,11 @@ pub mod ts_seconds_option {

#[cfg(test)]
mod tests {
use core::fmt;
#[cfg(feature = "clock")]
use crate::datetime::test_decodable_json;
use crate::datetime::test_encodable_json;
use crate::{DateTime, FixedOffset, TimeZone, Utc};
use core::fmt;

#[test]
fn test_serde_serialize() {
Expand Down
2 changes: 1 addition & 1 deletion src/format/formatting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ fn format_inner(
#[cfg(any(feature = "alloc", feature = "std"))]
impl OffsetFormat {
/// Writes an offset from UTC to `result` with the format defined by `self`.
fn format(&self, result: &mut String, off: FixedOffset) -> fmt::Result {
pub(crate) fn format(&self, result: &mut String, off: FixedOffset) -> fmt::Result {
let off = off.local_minus_utc();
if self.allow_zulu && off == 0 {
result.push('Z');
Expand Down

0 comments on commit c20ec93

Please sign in to comment.