Repository: time-rs/time (language: rust)

Issue:
Serde OffsetDateTime as Unix timestamp
I need to de/serialize `OffsetDateTime` from/to Unix timestamps.

For that purpose I wrote a module for use in combination with [serde's with-annotation](https://serde.rs/field-attrs.html#with).
Would such a module be in scope for this project? If yes, I would happily create a PR.


Discussion / hints:
As I'm sure you can imagine, different people have different needs for serialization. With time v0.2, a round-trip ser/de actually loses information, as the `UtcOffset` isn't encoded. This would also be the case with using Unix timestamps. The v0.3 branch has changed ser/de to use the defaults, essentially, which doesn't lose any information.

My recommendation would be to have a wrapper struct, which would allow you to implement `Serialize` and `Deserialize` as you need. Assuming you implement [`Deref`](https://doc.rust-lang.org/stable/std/ops/trait.Deref.html), it would also be mostly transparent. You'd also only need to use this when necessary, and could fall back to an `OffsetDateTime` for nearly all purposes.
Thanks for the fast response!

I just wanted to know if this clearly alternative approach to de/serialization would be useful for addition to this project. I did not intend to replace the default. Modules fulfilling the requirements of serde's with-annotation actually allow overriding the `Serialize` and `Deserialize` implementations per field without having to resort to a wrapper struct.

I am completely fine with this being out of scope of this project though.
I wasn't aware serde allowed for end-users to "override" the default, so to speak.

I'd be open to a pull request for the Unix timestamp on `OffsetDatetime` specifically, given it's probably one of the more common use cases. This can be based on master and not v0.3, as it would be safe to add as non-default.

Fix patch:
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 <the.z.cuber@gmail.com>"]
 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<S: Serializer>(
+    datetime: &OffsetDateTime,
+    serializer: S,
+) -> Result<S::Ok, S::Error> {
+    Wrapper(datetime.timestamp()).serialize(serializer)
+}
+
+#[allow(single_use_lifetimes)]
+pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> {
+    Wrapper::deserialize(deserializer)
+        .map(|Wrapper(timestamp)| timestamp)
+        .map(OffsetDateTime::from_unix_timestamp)
+}
+
+/// Treat an `Option<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::option")]
+///     datetime: Option<OffsetDateTime>,
+/// }
+///
+/// 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<S: Serializer>(
+        option: &Option<OffsetDateTime>,
+        serializer: S,
+    ) -> Result<S::Ok, S::Error> {
+        option.map(Wrapper).serialize(serializer)
+    }
+
+    #[allow(single_use_lifetimes)]
+    pub fn deserialize<'a, D: Deserializer<'a>>(
+        deserializer: D,
+    ) -> Result<Option<OffsetDateTime>, D::Error> {
+        Option::deserialize(deserializer).map(|opt| opt.map(|Wrapper(datetime)| datetime))
+    }
+}


Test patch: