diff --git a/src/zoned.rs b/src/zoned.rs index c551815..f13f306 100644 --- a/src/zoned.rs +++ b/src/zoned.rs @@ -17,10 +17,15 @@ use chrono::TimeZone; use chrono::Utc; use core::fmt; -pub trait FixedTimeZone: TimeZone + Copy + fmt::Debug {} +pub trait FixedTimeZone: TimeZone + Copy + fmt::Debug { + fn new() -> Self; +} -impl FixedTimeZone for Utc {} -impl FixedTimeZone for FixedOffset {} +impl FixedTimeZone for Utc { + fn new() -> Self { + Utc + } +} /// `Zoned` stores a `TimeResolution` representing the local time in the zone, plus the relevant /// offset and zone itself. This is intended to allow assertion that a given resolution is in a certain @@ -47,6 +52,42 @@ where zone: Z, } +#[cfg(feature = "serde")] +impl<'de, R, Z> serde::de::Deserialize<'de> for Zoned +where + R: SubDateResolution, + Z: FixedTimeZone, +{ + fn deserialize(deserializer: D) -> std::result::Result, D::Error> + where + D: serde::de::Deserializer<'de>, + { + let local = chrono::NaiveDateTime::deserialize(deserializer)?; + + // unwrap here is fine because by the rules of `FixedTimeZone` + // this operation must not fail + let zoned = local.and_local_timezone(Z::new()).unwrap(); + + Ok(zoned.into()) + } +} + +#[cfg(feature = "serde")] +impl<'de, R, Z> serde::Serialize for Zoned +where + R: SubDateResolution, + Z: FixedTimeZone, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.local_start_datetime() + .naive_local() + .serialize(serializer) + } +} + impl TimeResolution for Zoned where R: TimeResolution, @@ -355,7 +396,7 @@ mod tests { use crate::Minutes; use crate::Zoned; use alloc::vec::Vec; - use chrono::Offset; + use chrono::FixedOffset; #[test] fn test_subdate() { @@ -421,40 +462,79 @@ mod tests { Zoned::, _>::from(start_timestamp.clone()).local_resolution(), tz ), + Zoned::, _>::from(start_timestamp.clone()) + ); + + #[cfg(feature = "serde")] + assert_eq!( + serde_json::from_str::, _>>( + &serde_json::to_string(&Zoned::, _>::from( + start_timestamp.clone() + )) + .unwrap() + ) + .unwrap(), Zoned::, _>::from(start_timestamp) - ) + ); } } - for tz in [ - chrono::Utc.fix(), - chrono::FixedOffset::east_opt(60 * 60 * 3).unwrap(), - chrono::FixedOffset::west_opt(60 * 60 * 4).unwrap(), - ] { - subdate_fixed::<1, _>(tz); - subdate_fixed::<2, _>(tz); - subdate_fixed::<5, _>(tz); - subdate_fixed::<6, _>(tz); - subdate_fixed::<10, _>(tz); - subdate_fixed::<15, _>(tz); - subdate_fixed::<30, _>(tz); - subdate_fixed::<60, _>(tz); - subdate_fixed::<120, _>(tz); - subdate_fixed::<180, _>(tz); - subdate_fixed::<240, _>(tz); - - subdate_fixed::<1, _>(chrono::Utc); - subdate_fixed::<2, _>(chrono::Utc); - subdate_fixed::<5, _>(chrono::Utc); - subdate_fixed::<6, _>(chrono::Utc); - subdate_fixed::<10, _>(chrono::Utc); - subdate_fixed::<15, _>(chrono::Utc); - subdate_fixed::<30, _>(chrono::Utc); - subdate_fixed::<60, _>(chrono::Utc); - subdate_fixed::<120, _>(chrono::Utc); - subdate_fixed::<180, _>(chrono::Utc); - subdate_fixed::<240, _>(chrono::Utc); + #[derive(Debug, Clone, Copy)] + struct FixedEast; + + impl chrono::TimeZone for FixedEast { + type Offset = FixedOffset; + + fn from_offset(_: &Self::Offset) -> Self { + Self + } + + fn offset_from_local_date( + &self, + _: &chrono::prelude::NaiveDate, + ) -> chrono::MappedLocalTime { + unimplemented!() + } + + fn offset_from_local_datetime( + &self, + _: &chrono::prelude::NaiveDateTime, + ) -> chrono::MappedLocalTime { + chrono::MappedLocalTime::Single(chrono::FixedOffset::east_opt(N).unwrap()) + } + + fn offset_from_utc_date(&self, _: &chrono::prelude::NaiveDate) -> Self::Offset { + unimplemented!() + } + + fn offset_from_utc_datetime(&self, _: &chrono::prelude::NaiveDateTime) -> Self::Offset { + chrono::FixedOffset::east_opt(N).unwrap() + } + } + + impl FixedTimeZone for FixedEast { + fn new() -> Self { + FixedEast + } } + + fn test_for_zone() { + subdate_fixed::<1, _>(F::new()); + subdate_fixed::<2, _>(F::new()); + subdate_fixed::<5, _>(F::new()); + subdate_fixed::<6, _>(F::new()); + subdate_fixed::<10, _>(F::new()); + subdate_fixed::<15, _>(F::new()); + subdate_fixed::<30, _>(F::new()); + subdate_fixed::<60, _>(F::new()); + subdate_fixed::<120, _>(F::new()); + subdate_fixed::<180, _>(F::new()); + subdate_fixed::<240, _>(F::new()); + } + + test_for_zone::(); + test_for_zone::>(); + test_for_zone::>(); } #[test]