Skip to content

Commit be932d8

Browse files
authored
Adds support to serialize and deserialize timestamps with different resolutions (#648)
1 parent bb397df commit be932d8

File tree

5 files changed

+284
-0
lines changed

5 files changed

+284
-0
lines changed

tests/serde/timestamps.rs

+93
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,24 @@ struct Test {
1010
dt: OffsetDateTime,
1111
}
1212

13+
#[derive(Serialize, Deserialize, Debug, PartialEq)]
14+
struct TestMilliseconds {
15+
#[serde(with = "timestamp::milliseconds")]
16+
dt: OffsetDateTime,
17+
}
18+
19+
#[derive(Serialize, Deserialize, Debug, PartialEq)]
20+
struct TestMicroseconds {
21+
#[serde(with = "timestamp::microseconds")]
22+
dt: OffsetDateTime,
23+
}
24+
25+
#[derive(Serialize, Deserialize, Debug, PartialEq)]
26+
struct TestNanoseconds {
27+
#[serde(with = "timestamp::nanoseconds")]
28+
dt: OffsetDateTime,
29+
}
30+
1331
#[test]
1432
fn serialize_timestamp() {
1533
let value = Test {
@@ -40,3 +58,78 @@ fn serialize_timestamp() {
4058
"invalid type: string \"bad\", expected i64",
4159
);
4260
}
61+
62+
#[test]
63+
fn serialize_timestamp_milliseconds() {
64+
let value_milliseconds = TestMilliseconds {
65+
dt: datetime!(2000-01-01 00:00:00.999 UTC),
66+
};
67+
assert_de_tokens_error::<TestMilliseconds>(
68+
&[
69+
Token::Struct {
70+
name: "TestMilliseconds",
71+
len: 1,
72+
},
73+
Token::Str("dt"),
74+
Token::Str("bad"),
75+
Token::StructEnd,
76+
],
77+
"invalid type: string \"bad\", expected i128",
78+
);
79+
// serde_test does not support I128, see: https://github.com/serde-rs/test/issues/18
80+
let milliseconds_str = r#"{"dt":946684800999}"#;
81+
let deserialized_milliseconds: TestMilliseconds = serde_json::from_str(milliseconds_str).unwrap();
82+
let serialized_milliseconds = serde_json::to_string(&value_milliseconds).unwrap();
83+
assert_eq!(value_milliseconds.dt, deserialized_milliseconds.dt);
84+
assert_eq!(serialized_milliseconds, milliseconds_str);
85+
}
86+
87+
#[test]
88+
fn serialize_timestamp_microseconds() {
89+
let value_microseconds = TestMicroseconds {
90+
dt: datetime!(2000-01-01 00:00:00.999_999 UTC),
91+
};
92+
assert_de_tokens_error::<TestMicroseconds>(
93+
&[
94+
Token::Struct {
95+
name: "TestMicroseconds",
96+
len: 1,
97+
},
98+
Token::Str("dt"),
99+
Token::Str("bad"),
100+
Token::StructEnd,
101+
],
102+
"invalid type: string \"bad\", expected i128",
103+
);
104+
// serde_test does not support I128, see: https://github.com/serde-rs/test/issues/18
105+
let microseconds_str = r#"{"dt":946684800999999}"#;
106+
let deserialized_microseconds: TestMicroseconds = serde_json::from_str(microseconds_str).unwrap();
107+
let serialized_microseconds = serde_json::to_string(&value_microseconds).unwrap();
108+
assert_eq!(value_microseconds.dt, deserialized_microseconds.dt);
109+
assert_eq!(serialized_microseconds, microseconds_str);
110+
}
111+
112+
#[test]
113+
fn serialize_timestamp_nanoseconds() {
114+
let value_nanoseconds = TestNanoseconds {
115+
dt: datetime!(2000-01-01 00:00:00.999_999_999 UTC),
116+
};
117+
assert_de_tokens_error::<TestNanoseconds>(
118+
&[
119+
Token::Struct {
120+
name: "TestNanoseconds",
121+
len: 1,
122+
},
123+
Token::Str("dt"),
124+
Token::Str("bad"),
125+
Token::StructEnd,
126+
],
127+
"invalid type: string \"bad\", expected i128",
128+
);
129+
// serde_test does not support I128, see: https://github.com/serde-rs/test/issues/18
130+
let nanoseconds_str = r#"{"dt":946684800999999999}"#;
131+
let deserialized_nanoseconds: TestNanoseconds = serde_json::from_str(nanoseconds_str).unwrap();
132+
let serialized_nanoseconds = serde_json::to_string(&value_nanoseconds).unwrap();
133+
assert_eq!(value_nanoseconds.dt, deserialized_nanoseconds.dt);
134+
assert_eq!(serialized_nanoseconds, nanoseconds_str);
135+
}
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//! Treat an [`OffsetDateTime`] as a [Unix timestamp] with microseconds for
2+
//! the purposes of serde.
3+
//!
4+
//! Use this module in combination with serde's [`#[with]`][with] attribute.
5+
//!
6+
//! When deserializing, the offset is assumed to be UTC.
7+
//!
8+
//! [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
9+
//! [with]: https://serde.rs/field-attrs.html#with
10+
11+
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
12+
13+
use crate::OffsetDateTime;
14+
15+
/// Serialize an `OffsetDateTime` as its Unix timestamp with microseconds
16+
pub fn serialize<S: Serializer>(
17+
datetime: &OffsetDateTime,
18+
serializer: S,
19+
) -> Result<S::Ok, S::Error> {
20+
let timestamp = datetime.unix_timestamp_nanos() / 1_000;
21+
timestamp.serialize(serializer)
22+
}
23+
24+
/// Deserialize an `OffsetDateTime` from its Unix timestamp with microseconds
25+
pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> {
26+
let value: i128 = <_>::deserialize(deserializer)?;
27+
OffsetDateTime::from_unix_timestamp_nanos(value * 1_000)
28+
.map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err))
29+
}
30+
31+
/// Treat an `Option<OffsetDateTime>` as a [Unix timestamp] with microseconds
32+
/// for the purposes of serde.
33+
///
34+
/// Use this module in combination with serde's [`#[with]`][with] attribute.
35+
///
36+
/// When deserializing, the offset is assumed to be UTC.
37+
///
38+
/// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
39+
/// [with]: https://serde.rs/field-attrs.html#with
40+
pub mod option {
41+
#[allow(clippy::wildcard_imports)]
42+
use super::*;
43+
44+
/// Serialize an `Option<OffsetDateTime>` as its Unix timestamp with microseconds
45+
pub fn serialize<S: Serializer>(
46+
option: &Option<OffsetDateTime>,
47+
serializer: S,
48+
) -> Result<S::Ok, S::Error> {
49+
option
50+
.map(|timestamp| timestamp.unix_timestamp_nanos() / 1_000)
51+
.serialize(serializer)
52+
}
53+
54+
/// Deserialize an `Option<OffsetDateTime>` from its Unix timestamp with microseconds
55+
pub fn deserialize<'a, D: Deserializer<'a>>(
56+
deserializer: D,
57+
) -> Result<Option<OffsetDateTime>, D::Error> {
58+
Option::deserialize(deserializer)?
59+
.map(|value: i128| OffsetDateTime::from_unix_timestamp_nanos(value * 1_000))
60+
.transpose()
61+
.map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err))
62+
}
63+
}
+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//! Treat an [`OffsetDateTime`] as a [Unix timestamp] with milliseconds for
2+
//! the purposes of serde.
3+
//!
4+
//! Use this module in combination with serde's [`#[with]`][with] attribute.
5+
//!
6+
//! When deserializing, the offset is assumed to be UTC.
7+
//!
8+
//! [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
9+
//! [with]: https://serde.rs/field-attrs.html#with
10+
11+
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
12+
13+
use crate::OffsetDateTime;
14+
15+
/// Serialize an `OffsetDateTime` as its Unix timestamp with milliseconds
16+
pub fn serialize<S: Serializer>(
17+
datetime: &OffsetDateTime,
18+
serializer: S,
19+
) -> Result<S::Ok, S::Error> {
20+
let timestamp = datetime.unix_timestamp_nanos() / 1_000_000;
21+
timestamp.serialize(serializer)
22+
}
23+
24+
/// Deserialize an `OffsetDateTime` from its Unix timestamp with milliseconds
25+
pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> {
26+
let value: i128 = <_>::deserialize(deserializer)?;
27+
OffsetDateTime::from_unix_timestamp_nanos(value * 1_000_000)
28+
.map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err))
29+
}
30+
31+
/// Treat an `Option<OffsetDateTime>` as a [Unix timestamp] with milliseconds
32+
/// for the purposes of serde.
33+
///
34+
/// Use this module in combination with serde's [`#[with]`][with] attribute.
35+
///
36+
/// When deserializing, the offset is assumed to be UTC.
37+
///
38+
/// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
39+
/// [with]: https://serde.rs/field-attrs.html#with
40+
pub mod option {
41+
#[allow(clippy::wildcard_imports)]
42+
use super::*;
43+
44+
/// Serialize an `Option<OffsetDateTime>` as its Unix timestamp with milliseconds
45+
pub fn serialize<S: Serializer>(
46+
option: &Option<OffsetDateTime>,
47+
serializer: S,
48+
) -> Result<S::Ok, S::Error> {
49+
option
50+
.map(|timestamp| timestamp.unix_timestamp_nanos() / 1_000_000)
51+
.serialize(serializer)
52+
}
53+
54+
/// Deserialize an `Option<OffsetDateTime>` from its Unix timestamp with milliseconds
55+
pub fn deserialize<'a, D: Deserializer<'a>>(
56+
deserializer: D,
57+
) -> Result<Option<OffsetDateTime>, D::Error> {
58+
Option::deserialize(deserializer)?
59+
.map(|value: i128| OffsetDateTime::from_unix_timestamp_nanos(value * 1_000_000))
60+
.transpose()
61+
.map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err))
62+
}
63+
}

time/src/serde/timestamp.rs time/src/serde/timestamp/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
//! [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
88
//! [with]: https://serde.rs/field-attrs.html#with
99
10+
pub mod microseconds;
11+
pub mod milliseconds;
12+
pub mod nanoseconds;
13+
1014
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
1115

1216
use crate::OffsetDateTime;
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//! Treat an [`OffsetDateTime`] as a [Unix timestamp] with nanoseconds for
2+
//! the purposes of serde.
3+
//!
4+
//! Use this module in combination with serde's [`#[with]`][with] attribute.
5+
//!
6+
//! When deserializing, the offset is assumed to be UTC.
7+
//!
8+
//! [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
9+
//! [with]: https://serde.rs/field-attrs.html#with
10+
11+
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
12+
13+
use crate::OffsetDateTime;
14+
15+
/// Serialize an `OffsetDateTime` as its Unix timestamp with nanoseconds
16+
pub fn serialize<S: Serializer>(
17+
datetime: &OffsetDateTime,
18+
serializer: S,
19+
) -> Result<S::Ok, S::Error> {
20+
datetime.unix_timestamp_nanos().serialize(serializer)
21+
}
22+
23+
/// Deserialize an `OffsetDateTime` from its Unix timestamp with nanoseconds
24+
pub fn deserialize<'a, D: Deserializer<'a>>(deserializer: D) -> Result<OffsetDateTime, D::Error> {
25+
OffsetDateTime::from_unix_timestamp_nanos(<_>::deserialize(deserializer)?)
26+
.map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err))
27+
}
28+
29+
/// Treat an `Option<OffsetDateTime>` as a [Unix timestamp] with nanoseconds
30+
/// for the purposes of serde.
31+
///
32+
/// Use this module in combination with serde's [`#[with]`][with] attribute.
33+
///
34+
/// When deserializing, the offset is assumed to be UTC.
35+
///
36+
/// [Unix timestamp]: https://en.wikipedia.org/wiki/Unix_time
37+
/// [with]: https://serde.rs/field-attrs.html#with
38+
pub mod option {
39+
#[allow(clippy::wildcard_imports)]
40+
use super::*;
41+
42+
/// Serialize an `Option<OffsetDateTime>` as its Unix timestamp with nanoseconds
43+
pub fn serialize<S: Serializer>(
44+
option: &Option<OffsetDateTime>,
45+
serializer: S,
46+
) -> Result<S::Ok, S::Error> {
47+
option
48+
.map(OffsetDateTime::unix_timestamp_nanos)
49+
.serialize(serializer)
50+
}
51+
52+
/// Deserialize an `Option<OffsetDateTime>` from its Unix timestamp with nanoseconds
53+
pub fn deserialize<'a, D: Deserializer<'a>>(
54+
deserializer: D,
55+
) -> Result<Option<OffsetDateTime>, D::Error> {
56+
Option::deserialize(deserializer)?
57+
.map(OffsetDateTime::from_unix_timestamp_nanos)
58+
.transpose()
59+
.map_err(|err| de::Error::invalid_value(de::Unexpected::Signed(err.value), &err))
60+
}
61+
}

0 commit comments

Comments
 (0)