1use core::marker::PhantomData;
2
3use serde::{Deserializer, Serializer, de};
4use serde_with::{DeserializeAs, Same, SerializeAs};
5
6use crate::{Overflow, Timestamp};
7
8macro_rules! serde_as {
9 ($($vis:vis struct $name:ident: $int:ty {
10 $as:ident,
11 $from:ident,
12 })*) => {$(
13 $vis struct $name<F: ?Sized = Same>(PhantomData<F>);
14
15 impl<F> SerializeAs<Timestamp> for $name<F>
16 where
17 F: SerializeAs<$int> + ?Sized,
18 {
19 #[inline]
20 fn serialize_as<S>(source: &Timestamp, serializer: S) -> Result<S::Ok, S::Error>
21 where
22 S: Serializer,
23 {
24 let timestamp: $int = source.$as();
25 <F as SerializeAs<$int>>::serialize_as(×tamp, serializer)
26 }
27 }
28
29 impl<'de, F> DeserializeAs<'de, Timestamp> for $name<F>
30 where
31 F: DeserializeAs<'de, $int> + ?Sized,
32 {
33 #[inline]
34 fn deserialize_as<D>(deserializer: D) -> Result<Timestamp, D::Error>
35 where
36 D: Deserializer<'de>,
37 {
38 let timestamp = <F as DeserializeAs<'de, $int>>::deserialize_as(deserializer)?;
39 Timestamp::$from(timestamp).ok_or(Overflow).map_err( de::Error::custom)
40 }
41 }
42
43 #[cfg(feature = "schemars-v0_8")]
44 const _: () = {
45 use schemars::{SchemaGenerator, schema::Schema};
46 use serde_with::schemars_0_8::JsonSchemaAs;
47
48 impl<F> JsonSchemaAs<Timestamp> for $name<F>
49 where
50 F: JsonSchemaAs<$int> + ?Sized,
51 {
52 #[inline]
53 fn is_referenceable() -> bool {
54 false
55 }
56
57 #[inline]
58 fn schema_name() -> String {
59 stringify!($name<F>).into()
60 }
61
62 #[inline]
63 fn json_schema(generator: &mut SchemaGenerator) -> Schema {
64 <F as JsonSchemaAs<$int>>::json_schema(generator)
65 }
66 }
67 };
68 )*};
69}
70
71serde_as! {
72 pub struct TimestampSeconds: i64 {
73 as_secs,
74 from_secs,
75 }
76
77 pub struct TimestampMilliSeconds: i64 {
78 as_millis,
79 from_millis,
80 }
81
82 pub struct TimestampMicroSeconds: i128 {
83 as_micros,
84 from_micros,
85 }
86
87 pub struct TimestampNanoSeconds: i128 {
88 as_nanos,
89 from_nanos,
90 }
91}
92
93#[cfg(test)]
94#[allow(clippy::inconsistent_digit_grouping)]
95mod tests {
96 use std::fmt::Debug;
97
98 use rstest::rstest;
99
100 use serde_json::json;
101 use serde_with::{DisplayFromStr, de::DeserializeAsWrap, ser::SerializeAsWrap};
102
103 use super::*;
104
105 #[rstest]
106 fn timestamp_secs_roundtrip(#[values(0, 1782395622, -1782395622)] secs: i64) {
107 let ts = Timestamp::from_secs(secs).unwrap();
108 roundtrip_as::<_, TimestampSeconds>(&ts);
109 roundtrip_as::<_, TimestampSeconds<DisplayFromStr>>(&ts);
110 }
111
112 #[rstest]
113 fn timestamp_millis_roundtrip(#[values(0, 1782395622_123, -1782395622_123)] millis: i64) {
114 let ts = Timestamp::from_millis(millis).unwrap();
115 roundtrip_as::<_, TimestampMilliSeconds>(&ts);
116 roundtrip_as::<_, TimestampMilliSeconds<DisplayFromStr>>(&ts);
117 }
118
119 #[rstest]
120 fn timestamp_micros_roundtrip(
121 #[values(0, 1782395622_123456, -1782395622_123456)] micros: i128,
122 ) {
123 let ts = Timestamp::from_micros(micros).unwrap();
124 roundtrip_as::<_, TimestampMicroSeconds>(&ts);
125 roundtrip_as::<_, TimestampMicroSeconds<DisplayFromStr>>(&ts);
126 }
127
128 #[rstest]
129 fn timestamp_nanos_roundtrip(
130 #[values(0, 1782395622_123456789, -1782395622_123456789)] nanos: i128,
131 ) {
132 let ts = Timestamp::from_nanos(nanos).unwrap();
133 roundtrip_as::<_, TimestampNanoSeconds>(&ts);
134 roundtrip_as::<_, TimestampNanoSeconds<DisplayFromStr>>(&ts);
135 }
136
137 #[track_caller]
139 fn roundtrip_as<T, As>(orig: &T)
140 where
141 for<'de> As: SerializeAs<T> + DeserializeAs<'de, T>,
142 T: PartialEq + Debug,
143 {
144 let serialized =
145 serde_json::to_string(&SerializeAsWrap::<T, As>::new(orig)).expect("JSON: serialize");
146 let deserialized: T = serde_json::from_str::<DeserializeAsWrap<T, As>>(&serialized)
147 .expect("JSON: deserialize")
148 .into_inner();
149 assert_eq!(
150 &deserialized, orig,
151 "deserialized value differs from the original one"
152 );
153 }
154
155 #[rstest]
156 #[case(json!("1970-01-01T00:00:00Z"), None)]
157 #[case(json!("1970-01-01T00:00:00+00:00"), json!("1970-01-01T00:00:00Z"))]
158 #[case(json!("1970-01-01T10:00:00+04:00"), json!("1970-01-01T06:00:00Z"))]
159 #[case(json!("1970-01-01T10:00:00-02:30"), json!("1970-01-01T12:30:00Z"))]
160 #[case(json!("2026-06-25T13:53:42.123456789Z"), None)]
161 #[case(
162 json!("2026-06-25T13:53:42.123456789+00:00"),
163 json!("2026-06-25T13:53:42.123456789Z"),
164 )]
165 #[case(
166 json!("2026-06-25T13:53:42.123456789+02:30"),
167 json!("2026-06-25T11:23:42.123456789Z"),
168 )]
169 #[case(
170 json!("2026-06-25T13:53:42.123456789-05:00"),
171 json!("2026-06-25T18:53:42.123456789Z"),
172 )]
173 fn semi_roundtrip(
174 #[case] orig: serde_json::Value,
175 #[case] expected: impl Into<Option<serde_json::Value>>,
176 ) {
177 let ts: Timestamp = serde_json::from_value(orig.clone()).expect("JSON: deserialize");
178 let got = serde_json::to_value(ts).expect("JSON: serialize");
179 assert_eq!(got, expected.into().unwrap_or(orig));
180 }
181}