diff --git a/CHANGELOG.md b/CHANGELOG.md index c7caaacc8..8d8dfdb70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ Versioning]. --- +## 0.2.16 [2020-05-12] + +### Added + +`OffsetDateTime`s can now be represented as Unix timestamps with serde. To do +this, you can use the `time::serde::timestamp` and +`time::serde::timestamp::option` modules. + ## 0.2.15 [2020-05-04] ### Fixed diff --git a/Cargo.toml b/Cargo.toml index c1c613a23..48be05b6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "time" -version = "0.2.15" +version = "0.2.16" authors = ["Jacob Pratt "] edition = "2018" repository = "https://github.com/time-rs/time" diff --git a/src/lib.rs b/src/lib.rs index 6e41b1a60..83bc155fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -373,7 +373,7 @@ mod primitive_date_time; mod rand; #[cfg(serde)] #[allow(missing_copy_implementations, missing_debug_implementations)] -mod serde; +pub mod serde; /// The `Sign` struct and its associated `impl`s. mod sign; /// The `Time` struct and its associated `impl`s. diff --git a/src/serde/mod.rs b/src/serde/mod.rs index 32dba5bce..c001d75dc 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -1,10 +1,12 @@ -//! Types with guaranteed stable serde representations. -//! -//! This allows for the ability to change the internal structure of a type while -//! maintaining backwards compatibility. -//! -//! Strings are avoided where possible to allow for optimal representations in -//! various binary forms. +//! Differential formats for serde. + +// Types with guaranteed stable serde representations. +// +// This allows for the ability to change the internal structure of a type while +// maintaining backwards compatibility. +// +// Strings are avoided where possible to allow for optimal representations in +// various binary forms. #![allow(clippy::missing_docs_in_private_items)] @@ -15,6 +17,7 @@ mod duration; mod primitive_date_time; mod sign; mod time; +pub mod timestamp; mod utc_offset; mod weekday; diff --git a/src/serde/timestamp.rs b/src/serde/timestamp.rs new file mode 100644 index 000000000..9427122ee --- /dev/null +++ b/src/serde/timestamp.rs @@ -0,0 +1,99 @@ +//! Treat an [`OffsetDateTime`] as a [Unix timestamp] 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. +//! +//! ```rust,ignore +//! use serde_json::json; +//! +//! #[derive(Serialize, Deserialize)] +//! struct S { +//! #[serde(with = "time::serde::timestamp")] +//! datetime: OffsetDateTime, +//! } +//! +//! let s = S { +//! datetime: date!(2019-01-01).midnight().assume_utc(), +//! }; +//! let v = json!({ "datetime": 1_546_300_800 }); +//! assert_eq!(v, serde_json::to_value(&s)?); +//! assert_eq!(s, serde_json::from_value(v)?); +//! ``` +//! +//! [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time +//! [with]: https://serde.rs/field-attrs.html#with + +use crate::OffsetDateTime; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[derive(Serialize, Deserialize)] +#[serde(transparent)] +struct Wrapper(i64); + +pub fn serialize( + datetime: &OffsetDateTime, + serializer: S, +) -> Result { + Wrapper(datetime.timestamp()).serialize(serializer) +} + +#[allow(single_use_lifetimes)] +pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result { + Wrapper::deserialize(deserializer) + .map(|Wrapper(timestamp)| timestamp) + .map(OffsetDateTime::from_unix_timestamp) +} + +/// Treat an `Option` as a [Unix timestamp] 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. +/// +/// ```rust,ignore +/// use serde_json::json; +/// +/// #[derive(Serialize, Deserialize)] +/// struct S { +/// #[serde(with = "time::serde::timestamp::option")] +/// datetime: Option, +/// } +/// +/// let s = S { +/// datetime: Some(date!(2019-01-01).midnight().assume_utc()), +/// }; +/// let v = json!({ "datetime": 1_546_300_800 }); +/// assert_eq!(v, serde_json::to_value(&s)?); +/// assert_eq!(s, serde_json::from_value(v)?); +/// +/// let s = S { datetime: None }; +/// let v = json!({ "datetime": null }); +/// assert_eq!(v, serde_json::to_value(&s)?); +/// assert_eq!(s, serde_json::from_value(v)?); +/// ``` +/// +/// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time +/// [with]: https://serde.rs/field-attrs.html#with +pub mod option { + use super::*; + + #[derive(Serialize, Deserialize)] + #[serde(transparent)] + struct Wrapper(#[serde(with = "super")] OffsetDateTime); + + pub fn serialize( + option: &Option, + serializer: S, + ) -> Result { + option.map(Wrapper).serialize(serializer) + } + + #[allow(single_use_lifetimes)] + pub fn deserialize<'a, D: Deserializer<'a>>( + deserializer: D, + ) -> Result, D::Error> { + Option::deserialize(deserializer).map(|opt| opt.map(|Wrapper(datetime)| datetime)) + } +}