diff --git a/src/datetime/serde.rs b/src/datetime/serde.rs index 1492fe3f0..480341e64 100644 --- a/src/datetime/serde.rs +++ b/src/datetime/serde.rs @@ -23,6 +23,22 @@ pub struct MicroSecondsTimestampVisitor; #[derive(Debug)] pub struct MilliSecondsTimestampVisitor; +#[doc(hidden)] +#[derive(Debug)] +pub struct SecondsTimeDeltaVisitor; + +#[doc(hidden)] +#[derive(Debug)] +pub struct NanoSecondsTimeDeltaVisitor; + +#[doc(hidden)] +#[derive(Debug)] +pub struct MicroSecondsTimeDeltaVisitor; + +#[doc(hidden)] +#[derive(Debug)] +pub struct MilliSecondsTimeDeltaVisitor; + /// Serialize to an RFC 3339 formatted string /// /// As an extension to RFC 3339 this can serialize `DateTime`s outside the range of 0-9999 years @@ -1206,154 +1222,1200 @@ pub mod ts_seconds_option { } } -#[cfg(test)] -mod tests { - #[cfg(feature = "clock")] - use crate::Local; - use crate::{DateTime, FixedOffset, TimeZone, Utc}; +/// Ser/de to/from [`crate::TimeDelta`]s in nanoseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::TimeDelta; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::td_nanoseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "td_nanoseconds")] +/// delta: TimeDelta, +/// } +/// +/// let delta = TimeDelta::nanoseconds(2018517); +/// let my_s = S { delta: delta.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"delta":2018517}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.delta, delta); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod td_nanoseconds { use core::fmt; + use serde::{de, ser}; - #[test] - fn test_serde_serialize() { - assert_eq!( - serde_json::to_string(&Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()).ok(), - Some(r#""2014-07-24T12:34:06Z""#.to_owned()) - ); - assert_eq!( - serde_json::to_string( - &FixedOffset::east_opt(3660) - .unwrap() - .with_ymd_and_hms(2014, 7, 24, 12, 34, 6) - .unwrap() - ) - .ok(), - Some(r#""2014-07-24T12:34:06+01:01""#.to_owned()) - ); - assert_eq!( - serde_json::to_string( - &FixedOffset::east_opt(3650) - .unwrap() - .with_ymd_and_hms(2014, 7, 24, 12, 34, 6) - .unwrap() - ) - .ok(), - // 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""#.to_owned()) - ); + use crate::TimeDelta; + use crate::serde::invalid_time_delta; + + use super::NanoSecondsTimeDeltaVisitor; + + /// Serialize a [`TimeDelta`] into an integer number of nanoseconds. + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Errors + /// + /// This function returns an error on an out of range [`TimeDelta`]. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::TimeDelta; + /// # use serde_derive::Serialize; + /// use chrono::serde::td_nanoseconds::serialize as to_nano_td; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_nano_td")] + /// delta: TimeDelta, + /// } + /// + /// let my_s = S { + /// delta: TimeDelta::nanoseconds(2018517), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"delta":2018517}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(td: &TimeDelta, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(td.num_nanoseconds().ok_or(ser::Error::custom( + "value out of range for a TimeDelta with nanosecond precision", + ))?) } - #[test] - fn test_serde_deserialize() { - // should check against the offset as well (the normal DateTime comparison will ignore them) - fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { - dt.as_ref().map(|dt| (dt, dt.offset())) - } + /// Deserialize a [`TimeDelta`] from a nanosecond integer number. + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::TimeDelta; + /// # use serde_derive::Deserialize; + /// use chrono::serde::td_nanoseconds::deserialize as from_nano_td; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_nano_td")] + /// delta: TimeDelta, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "delta": 2018517 }"#)?; + /// assert_eq!(my_s, S { delta: TimeDelta::nanoseconds(2018517) }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(NanoSecondsTimeDeltaVisitor) + } - let dt: Option> = serde_json::from_str(r#""2014-07-24T12:34:06Z""#).ok(); - assert_eq!(norm(&dt), norm(&Some(Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()))); - let dt: Option> = serde_json::from_str(r#""2014-07-24T13:57:06+01:23""#).ok(); - assert_eq!(norm(&dt), norm(&Some(Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()))); + impl de::Visitor<'_> for NanoSecondsTimeDeltaVisitor { + type Value = TimeDelta; - let dt: Option> = - serde_json::from_str(r#""2014-07-24T12:34:06Z""#).ok(); - assert_eq!( - norm(&dt), - norm(&Some( - FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() - )) - ); - let dt: Option> = - serde_json::from_str(r#""2014-07-24T13:57:06+01:23""#).ok(); - assert_eq!( - norm(&dt), - norm(&Some( - FixedOffset::east_opt(60 * 60 + 23 * 60) - .unwrap() - .with_ymd_and_hms(2014, 7, 24, 13, 57, 6) - .unwrap() - )) - ); + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a TimeDelta in nanoseconds") + } - // we don't know the exact local offset but we can check that - // the conversion didn't change the instant itself - #[cfg(feature = "clock")] + /// Deserialize a [`TimeDelta`] in nanoseconds + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, { - let dt: DateTime = - serde_json::from_str(r#""2014-07-24T12:34:06Z""#).expect("local should parse"); - assert_eq!(dt, Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()); - - let dt: DateTime = serde_json::from_str(r#""2014-07-24T13:57:06+01:23""#) - .expect("local should parse with offset"); - assert_eq!(dt, Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()); + Ok(TimeDelta::nanoseconds(value)) } - assert!(serde_json::from_str::>(r#""2014-07-32T12:34:06Z""#).is_err()); - assert!( - serde_json::from_str::>(r#""2014-07-32T12:34:06Z""#).is_err() - ); + /// Deserialize a [`TimeDelta`] in nanoseconds + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Ok(TimeDelta::nanoseconds(value.try_into().map_err(|_| invalid_time_delta(value))?)) + } } +} - #[test] - fn test_serde_bincode() { - // Bincode is relevant to test separately from JSON because - // it is not self-describing. - use bincode::{deserialize, serialize}; +/// Ser/de to/from optional [`crate::TimeDelta`]s in nanoseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::TimeDelta; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::td_nanoseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "td_nanoseconds_option")] +/// delta: Option, +/// } +/// +/// let delta = Some(TimeDelta::nanoseconds(201817)); +/// let my_s = S { delta: delta.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"delta":201817}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.delta, delta); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod td_nanoseconds_option { + use core::fmt; + use serde::{de, ser}; - let dt = Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap(); - let encoded = serialize(&dt).unwrap(); - let decoded: DateTime = deserialize(&encoded).unwrap(); - assert_eq!(dt, decoded); - assert_eq!(dt.offset(), decoded.offset()); - } + use crate::TimeDelta; - #[test] - fn test_serde_no_offset_debug() { - use crate::{MappedLocalTime, NaiveDate, NaiveDateTime, Offset}; - use core::fmt::Debug; + use super::NanoSecondsTimeDeltaVisitor; - #[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) -> MappedLocalTime { - MappedLocalTime::Single(TestTimeZone) - } - fn offset_from_local_datetime( - &self, - _local: &NaiveDateTime, - ) -> MappedLocalTime { - MappedLocalTime::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() - } + /// Serialize a UTC datetime into an integer number of nanoseconds or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Errors + /// + /// This function returns an error on an out of range `DateTime`. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::TimeDelta; + /// # use serde_derive::Serialize; + /// use chrono::serde::td_nanoseconds_option::serialize as to_nano_tdopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_nano_tdopt")] + /// delta: Option, + /// } + /// + /// let my_s = S { + /// delta: Some(TimeDelta::nanoseconds(2018517)), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"delta":2018517}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref td) => serializer.serialize_some(&td.num_nanoseconds().ok_or( + ser::Error::custom("value out of range for a TimeDelta with nanosecond precision"), + )?), + None => serializer.serialize_none(), } + } - 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); + /// Deserialize a `DateTime` from a nanosecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::TimeDelta; + /// # use serde_derive::Deserialize; + /// use chrono::serde::td_nanoseconds_option::deserialize as from_nano_tdopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_nano_tdopt")] + /// delta: Option, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "delta": 2018517 }"#)?; + /// assert_eq!(my_s, S { delta: Some(TimeDelta::nanoseconds(2018517)) }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionNanoSecondsTimeDeltaVisitor) + } + + struct OptionNanoSecondsTimeDeltaVisitor; + + impl<'de> de::Visitor<'de> for OptionNanoSecondsTimeDeltaVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a TimeDelta in nanoseconds or none") + } + + /// Deserialize a [`TimeDelta`] in nanoseconds + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(NanoSecondsTimeDeltaVisitor).map(Some) + } + + /// Deserialize a [`TimeDelta`] in nanoseconds + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a [`TimeDelta`] in nanoseconds + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Ser/de to/from [`crate::TimeDelta`]s in microseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::TimeDelta; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::td_microseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "td_microseconds")] +/// delta: TimeDelta, +/// } +/// +/// let delta = TimeDelta::microseconds(2018517); +/// let my_s = S { delta: delta.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"delta":2018517}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.delta, delta); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod td_microseconds { + use core::fmt; + use serde::{de, ser}; + + use crate::TimeDelta; + use crate::serde::invalid_time_delta; + + use super::MicroSecondsTimeDeltaVisitor; + + /// Serialize a [`TimeDelta`] into an integer number of microseconds. + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Errors + /// + /// This function returns an error on an out of range [`TimeDelta`]. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::TimeDelta; + /// # use serde_derive::Serialize; + /// use chrono::serde::td_microseconds::serialize as to_micro_td; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_micro_td")] + /// delta: TimeDelta, + /// } + /// + /// let my_s = S { + /// delta: TimeDelta::microseconds(2018517), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"delta":2018517}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(td: &TimeDelta, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(td.num_microseconds().ok_or(ser::Error::custom( + "value out of range for a TimeDelta with microsecond precision", + ))?) + } + + /// Deserialize a [`TimeDelta`] from a microsecond integer number. + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::TimeDelta; + /// # use serde_derive::Deserialize; + /// use chrono::serde::td_microseconds::deserialize as from_micro_td; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_micro_td")] + /// delta: TimeDelta, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "delta": 2018517 }"#)?; + /// assert_eq!(my_s, S { delta: TimeDelta::microseconds(2018517) }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MicroSecondsTimeDeltaVisitor) + } + + impl de::Visitor<'_> for MicroSecondsTimeDeltaVisitor { + type Value = TimeDelta; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a TimeDelta in microseconds") + } + + /// Deserialize a [`TimeDelta`] in microseconds + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + Ok(TimeDelta::microseconds(value)) + } + + /// Deserialize a [`TimeDelta`] in microseconds + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Ok(TimeDelta::microseconds(value.try_into().map_err(|_| invalid_time_delta(value))?)) + } + } +} + +/// Ser/de to/from optional [`crate::TimeDelta`]s in microseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::TimeDelta; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::td_microseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "td_microseconds_option")] +/// delta: Option, +/// } +/// +/// let delta = Some(TimeDelta::microseconds(201817)); +/// let my_s = S { delta: delta.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"delta":201817}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.delta, delta); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod td_microseconds_option { + use core::fmt; + use serde::{de, ser}; + + use crate::TimeDelta; + + use super::MicroSecondsTimeDeltaVisitor; + + /// Serialize a UTC datetime into an integer number of microseconds or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Errors + /// + /// This function returns an error on an out of range `DateTime`. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::TimeDelta; + /// # use serde_derive::Serialize; + /// use chrono::serde::td_microseconds_option::serialize as to_micro_tdopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_micro_tdopt")] + /// delta: Option, + /// } + /// + /// let my_s = S { + /// delta: Some(TimeDelta::microseconds(2018517)), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"delta":2018517}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref td) => serializer.serialize_some(&td.num_microseconds().ok_or( + ser::Error::custom("value out of range for a TimeDelta with microsecond precision"), + )?), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `DateTime` from a microsecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::TimeDelta; + /// # use serde_derive::Deserialize; + /// use chrono::serde::td_microseconds_option::deserialize as from_micro_tdopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_micro_tdopt")] + /// delta: Option, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "delta": 2018517 }"#)?; + /// assert_eq!(my_s, S { delta: Some(TimeDelta::microseconds(2018517)) }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionmicroSecondsTimeDeltaVisitor) + } + + struct OptionmicroSecondsTimeDeltaVisitor; + + impl<'de> de::Visitor<'de> for OptionmicroSecondsTimeDeltaVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a TimeDelta in microseconds or none") + } + + /// Deserialize a [`TimeDelta`] in microseconds + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MicroSecondsTimeDeltaVisitor).map(Some) + } + + /// Deserialize a [`TimeDelta`] in microseconds + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a [`TimeDelta`] in microseconds + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Ser/de to/from [`crate::TimeDelta`]s in milliseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::TimeDelta; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::td_milliseconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "td_milliseconds")] +/// delta: TimeDelta, +/// } +/// +/// let delta = TimeDelta::milliseconds(2018517); +/// let my_s = S { delta: delta.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"delta":2018517}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.delta, delta); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod td_milliseconds { + use core::fmt; + use serde::{de, ser}; + + use crate::TimeDelta; + use crate::serde::invalid_time_delta; + + use super::MilliSecondsTimeDeltaVisitor; + + /// Serialize a [`TimeDelta`] into an integer number of milliseconds. + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Errors + /// + /// This function returns an error on an out of range [`TimeDelta`]. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::TimeDelta; + /// # use serde_derive::Serialize; + /// use chrono::serde::td_milliseconds::serialize as to_milli_td; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_milli_td")] + /// delta: TimeDelta, + /// } + /// + /// let my_s = S { + /// delta: TimeDelta::milliseconds(2018517), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"delta":2018517}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(td: &TimeDelta, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(td.num_milliseconds()) + } + + /// Deserialize a [`TimeDelta`] from a millisecond integer number. + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::TimeDelta; + /// # use serde_derive::Deserialize; + /// use chrono::serde::td_milliseconds::deserialize as from_milli_td; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_milli_td")] + /// delta: TimeDelta, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "delta": 2018517 }"#)?; + /// assert_eq!(my_s, S { delta: TimeDelta::milliseconds(2018517) }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MilliSecondsTimeDeltaVisitor) + } + + impl de::Visitor<'_> for MilliSecondsTimeDeltaVisitor { + type Value = TimeDelta; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a TimeDelta in milliseconds") + } + + /// Deserialize a [`TimeDelta`] in milliseconds + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + Ok(TimeDelta::milliseconds(value)) + } + + /// Deserialize a [`TimeDelta`] in milliseconds + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Ok(TimeDelta::milliseconds(value.try_into().map_err(|_| invalid_time_delta(value))?)) + } + } +} + +/// Ser/de to/from optional [`crate::TimeDelta`]s in milliseconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::TimeDelta; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::td_milliseconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "td_milliseconds_option")] +/// delta: Option, +/// } +/// +/// let delta = Some(TimeDelta::milliseconds(201817)); +/// let my_s = S { delta: delta.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"delta":201817}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.delta, delta); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod td_milliseconds_option { + use core::fmt; + use serde::{de, ser}; + + use crate::TimeDelta; + + use super::MilliSecondsTimeDeltaVisitor; + + /// Serialize a UTC datetime into an integer number of milliseconds or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Errors + /// + /// This function returns an error on an out of range `DateTime`. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::TimeDelta; + /// # use serde_derive::Serialize; + /// use chrono::serde::td_milliseconds_option::serialize as to_milli_tdopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to_milli_tdopt")] + /// delta: Option, + /// } + /// + /// let my_s = S { + /// delta: Some(TimeDelta::milliseconds(2018517)), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"delta":2018517}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref td) => serializer.serialize_some(&td.num_milliseconds()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `DateTime` from a millisecond timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::TimeDelta; + /// # use serde_derive::Deserialize; + /// use chrono::serde::td_milliseconds_option::deserialize as from_milli_tdopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from_milli_tdopt")] + /// delta: Option, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "delta": 2018517 }"#)?; + /// assert_eq!(my_s, S { delta: Some(TimeDelta::milliseconds(2018517)) }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionmilliSecondsTimeDeltaVisitor) + } + + struct OptionmilliSecondsTimeDeltaVisitor; + + impl<'de> de::Visitor<'de> for OptionmilliSecondsTimeDeltaVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a TimeDelta in milliseconds or none") + } + + /// Deserialize a [`TimeDelta`] in milliseconds + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(MilliSecondsTimeDeltaVisitor).map(Some) + } + + /// Deserialize a [`TimeDelta`] in milliseconds + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a [`TimeDelta`] in milliseconds + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} + +/// Ser/de to/from [`crate::TimeDelta`]s in seconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::TimeDelta; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::td_seconds; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "td_seconds")] +/// delta: TimeDelta, +/// } +/// +/// let delta = TimeDelta::seconds(2018517); +/// let my_s = S { delta: delta.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"delta":2018517}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.delta, delta); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod td_seconds { + use core::fmt; + use serde::{de, ser}; + + use crate::TimeDelta; + use crate::serde::invalid_time_delta; + + use super::SecondsTimeDeltaVisitor; + + /// Serialize a [`TimeDelta`] into an integer number of seconds. + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Errors + /// + /// This function returns an error on an out of range [`TimeDelta`]. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::TimeDelta; + /// # use serde_derive::Serialize; + /// use chrono::serde::td_seconds::serialize as to__td; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to__td")] + /// delta: TimeDelta, + /// } + /// + /// let my_s = S { + /// delta: TimeDelta::seconds(2018517), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"delta":2018517}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(td: &TimeDelta, serializer: S) -> Result + where + S: ser::Serializer, + { + serializer.serialize_i64(td.num_seconds()) + } + + /// Deserialize a [`TimeDelta`] from a second integer number. + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::TimeDelta; + /// # use serde_derive::Deserialize; + /// use chrono::serde::td_seconds::deserialize as from__td; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from__td")] + /// delta: TimeDelta, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "delta": 2018517 }"#)?; + /// assert_eq!(my_s, S { delta: TimeDelta::seconds(2018517) }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(SecondsTimeDeltaVisitor) + } + + impl de::Visitor<'_> for SecondsTimeDeltaVisitor { + type Value = TimeDelta; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a TimeDelta in seconds") + } + + /// Deserialize a [`TimeDelta`] in seconds + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + Ok(TimeDelta::seconds(value)) + } + + /// Deserialize a [`TimeDelta`] in seconds + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Ok(TimeDelta::seconds(value.try_into().map_err(|_| invalid_time_delta(value))?)) + } + } +} + +/// Ser/de to/from optional [`crate::TimeDelta`]s in seconds +/// +/// Intended for use with `serde`'s `with` attribute. +/// +/// # Example: +/// +/// ```rust +/// # use chrono::TimeDelta; +/// # use serde_derive::{Deserialize, Serialize}; +/// use chrono::serde::td_seconds_option; +/// #[derive(Deserialize, Serialize)] +/// struct S { +/// #[serde(with = "td_seconds_option")] +/// delta: Option, +/// } +/// +/// let delta = Some(TimeDelta::seconds(201817)); +/// let my_s = S { delta: delta.clone() }; +/// +/// let as_string = serde_json::to_string(&my_s)?; +/// assert_eq!(as_string, r#"{"delta":201817}"#); +/// let my_s: S = serde_json::from_str(&as_string)?; +/// assert_eq!(my_s.delta, delta); +/// # Ok::<(), serde_json::Error>(()) +/// ``` +pub mod td_seconds_option { + use core::fmt; + use serde::{de, ser}; + + use crate::TimeDelta; + + use super::SecondsTimeDeltaVisitor; + + /// Serialize a UTC datetime into an integer number of seconds or none + /// + /// Intended for use with `serde`s `serialize_with` attribute. + /// + /// # Errors + /// + /// This function returns an error on an out of range `DateTime`. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::TimeDelta; + /// # use serde_derive::Serialize; + /// use chrono::serde::td_seconds_option::serialize as to__tdopt; + /// #[derive(Serialize)] + /// struct S { + /// #[serde(serialize_with = "to__tdopt")] + /// delta: Option, + /// } + /// + /// let my_s = S { + /// delta: Some(TimeDelta::seconds(2018517)), + /// }; + /// let as_string = serde_json::to_string(&my_s)?; + /// assert_eq!(as_string, r#"{"delta":2018517}"#); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn serialize(opt: &Option, serializer: S) -> Result + where + S: ser::Serializer, + { + match *opt { + Some(ref td) => serializer.serialize_some(&td.num_seconds()), + None => serializer.serialize_none(), + } + } + + /// Deserialize a `DateTime` from a second timestamp or none + /// + /// Intended for use with `serde`s `deserialize_with` attribute. + /// + /// # Example: + /// + /// ```rust + /// # use chrono::TimeDelta; + /// # use serde_derive::Deserialize; + /// use chrono::serde::td_seconds_option::deserialize as from__tdopt; + /// #[derive(Debug, PartialEq, Deserialize)] + /// struct S { + /// #[serde(deserialize_with = "from__tdopt")] + /// delta: Option, + /// } + /// + /// let my_s: S = serde_json::from_str(r#"{ "delta": 2018517 }"#)?; + /// assert_eq!(my_s, S { delta: Some(TimeDelta::seconds(2018517)) }); + /// # Ok::<(), serde_json::Error>(()) + /// ``` + pub fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: de::Deserializer<'de>, + { + d.deserialize_option(OptionSecondsTimeDeltaVisitor) + } + + struct OptionSecondsTimeDeltaVisitor; + + impl<'de> de::Visitor<'de> for OptionSecondsTimeDeltaVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a TimeDelta in seconds or none") + } + + /// Deserialize a [`TimeDelta`] in seconds + fn visit_some(self, d: D) -> Result + where + D: de::Deserializer<'de>, + { + d.deserialize_i64(SecondsTimeDeltaVisitor).map(Some) + } + + /// Deserialize a [`TimeDelta`] in seconds + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + /// Deserialize a [`TimeDelta`] in seconds + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } +} +#[cfg(test)] +mod tests { + #[cfg(feature = "clock")] + use crate::Local; + use crate::{DateTime, FixedOffset, TimeDelta, TimeZone, Utc}; + use core::fmt; + + #[test] + fn test_serde_serialize() { + assert_eq!( + serde_json::to_string(&Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()).ok(), + Some(r#""2014-07-24T12:34:06Z""#.to_owned()) + ); + assert_eq!( + serde_json::to_string( + &FixedOffset::east_opt(3660) + .unwrap() + .with_ymd_and_hms(2014, 7, 24, 12, 34, 6) + .unwrap() + ) + .ok(), + Some(r#""2014-07-24T12:34:06+01:01""#.to_owned()) + ); + assert_eq!( + serde_json::to_string( + &FixedOffset::east_opt(3650) + .unwrap() + .with_ymd_and_hms(2014, 7, 24, 12, 34, 6) + .unwrap() + ) + .ok(), + // 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""#.to_owned()) + ); + } + + #[test] + fn test_serde_deserialize() { + // should check against the offset as well (the normal DateTime comparison will ignore them) + fn norm(dt: &Option>) -> Option<(&DateTime, &Tz::Offset)> { + dt.as_ref().map(|dt| (dt, dt.offset())) + } + + let dt: Option> = serde_json::from_str(r#""2014-07-24T12:34:06Z""#).ok(); + assert_eq!(norm(&dt), norm(&Some(Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()))); + let dt: Option> = serde_json::from_str(r#""2014-07-24T13:57:06+01:23""#).ok(); + assert_eq!(norm(&dt), norm(&Some(Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()))); + + let dt: Option> = + serde_json::from_str(r#""2014-07-24T12:34:06Z""#).ok(); + assert_eq!( + norm(&dt), + norm(&Some( + FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap() + )) + ); + let dt: Option> = + serde_json::from_str(r#""2014-07-24T13:57:06+01:23""#).ok(); + assert_eq!( + norm(&dt), + norm(&Some( + FixedOffset::east_opt(60 * 60 + 23 * 60) + .unwrap() + .with_ymd_and_hms(2014, 7, 24, 13, 57, 6) + .unwrap() + )) + ); + + // we don't know the exact local offset but we can check that + // the conversion didn't change the instant itself + #[cfg(feature = "clock")] + { + let dt: DateTime = + serde_json::from_str(r#""2014-07-24T12:34:06Z""#).expect("local should parse"); + assert_eq!(dt, Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()); + + let dt: DateTime = serde_json::from_str(r#""2014-07-24T13:57:06+01:23""#) + .expect("local should parse with offset"); + assert_eq!(dt, Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap()); + } + + assert!(serde_json::from_str::>(r#""2014-07-32T12:34:06Z""#).is_err()); + assert!( + serde_json::from_str::>(r#""2014-07-32T12:34:06Z""#).is_err() + ); + } + + #[test] + fn test_serde_bincode() { + // Bincode is relevant to test separately from JSON because + // it is not self-describing. + use bincode::{deserialize, serialize}; + + let dt = Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap(); + let encoded = serialize(&dt).unwrap(); + let decoded: DateTime = deserialize(&encoded).unwrap(); + assert_eq!(dt, decoded); + assert_eq!(dt.offset(), decoded.offset()); + } + + #[test] + fn test_serde_no_offset_debug() { + use crate::{MappedLocalTime, 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) -> MappedLocalTime { + MappedLocalTime::Single(TestTimeZone) + } + fn offset_from_local_datetime( + &self, + _local: &NaiveDateTime, + ) -> MappedLocalTime { + MappedLocalTime::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()); } + + #[test] + fn test_serde_td_nanoseconds() { + let delta1 = TimeDelta::nanoseconds(2018517); + + let mut bytes = Vec::new(); + let mut serializer = serde_json::Serializer::new(&mut bytes); + + assert!(super::td_nanoseconds::serialize(&delta1, &mut serializer).is_ok()); + + let delta2 = + super::td_nanoseconds::deserialize(&mut serde_json::Deserializer::from_slice(&bytes)); + + assert_eq!(Some(delta1), delta2.ok()); + } + + #[test] + fn test_serde_td_microseconds_zero() { + let delta = + super::td_microseconds::deserialize(&mut serde_json::Deserializer::from_str("0")); + + assert_eq!(Some(TimeDelta::zero()), delta.ok()); + } + + #[test] + fn test_serde_td_seconds_option_null() { + let delta = + super::td_seconds_option::deserialize(&mut serde_json::Deserializer::from_str("null")); + + assert_eq!(Some(None), delta.ok()); + } } diff --git a/src/lib.rs b/src/lib.rs index 80a70d464..23166a8f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -626,16 +626,29 @@ pub mod serde { E::custom(SerdeError::InvalidTimestamp(value)) } + /// Create a custom `de::Error` with `SerdeError::InvalidTimestamp`. + pub(crate) fn invalid_time_delta(value: T) -> E + where + E: de::Error, + T: fmt::Display, + { + E::custom(SerdeError::InvalidTimeDelta(value)) + } + enum SerdeError { InvalidTimestamp(T), + InvalidTimeDelta(T), } impl fmt::Display for SerdeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - SerdeError::InvalidTimestamp(ts) => { + Self::InvalidTimestamp(ts) => { write!(f, "value is not a legal timestamp: {}", ts) } + Self::InvalidTimeDelta(td) => { + write!(f, "value is not a legal TimeDelta: {}", td) + } } } }