diff --git a/time/src/serde/timestamp/milliseconds_i64.rs b/time/src/serde/timestamp/milliseconds_i64.rs new file mode 100644 index 000000000..f9c90f2e2 --- /dev/null +++ b/time/src/serde/timestamp/milliseconds_i64.rs @@ -0,0 +1,64 @@ +//! Treat an [`OffsetDateTime`] as a [Unix timestamp] with milliseconds for +//! the purposes of serde. +//! +//! Use this module in combination with serde's [`#[with]`][with] attribute. +//! +//! When deserializing, the offset is assumed to be UTC. +//! +//! [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time +//! [with]: https://serde.rs/field-attrs.html#with + +use num_conv::Truncate; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + +use crate::OffsetDateTime; + +/// Serialize an `OffsetDateTime` as its Unix timestamp with milliseconds +pub fn serialize( + datetime: &OffsetDateTime, + serializer: S, +) -> Result { + let timestamp = (datetime.unix_timestamp_nanos() / 1_000_000).truncate::(); + timestamp.serialize(serializer) +} + +/// Deserialize an `OffsetDateTime` from its Unix timestamp with milliseconds +pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result { + let value: i128 = <_>::deserialize(deserializer)?; + OffsetDateTime::from_unix_timestamp_nanos(value * 1_000_000) + .map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err)) +} + +/// Treat an `Option` as a [Unix timestamp] with milliseconds +/// for the purposes of serde. +/// +/// Use this module in combination with serde's [`#[with]`][with] attribute. +/// +/// When deserializing, the offset is assumed to be UTC. +/// +/// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time +/// [with]: https://serde.rs/field-attrs.html#with +pub mod option { + #[allow(clippy::wildcard_imports)] + use super::*; + + /// Serialize an `Option` as its Unix timestamp with milliseconds + pub fn serialize( + option: &Option, + serializer: S, + ) -> Result { + option + .map(|timestamp| (timestamp.unix_timestamp_nanos() / 1_000_000).truncate::()) + .serialize(serializer) + } + + /// Deserialize an `Option` from its Unix timestamp with milliseconds + pub fn deserialize<'a, D: Deserializer<'a>>( + deserializer: D, + ) -> Result, D::Error> { + Option::deserialize(deserializer)? + .map(|value: i128| OffsetDateTime::from_unix_timestamp_nanos(value * 1_000_000)) + .transpose() + .map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err)) + } +} diff --git a/time/src/serde/timestamp/mod.rs b/time/src/serde/timestamp/mod.rs index 6dd0db03c..d27836b6d 100644 --- a/time/src/serde/timestamp/mod.rs +++ b/time/src/serde/timestamp/mod.rs @@ -9,6 +9,7 @@ pub mod microseconds; pub mod milliseconds; +pub mod milliseconds_i64; pub mod nanoseconds; use serde::{de, Deserialize, Deserializer, Serialize, Serializer};