time_util/
serde.rs

1// Copyright (C) 2020-2021 Daniel Mueller <deso@posteo.net>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4use std::time::Duration;
5use std::time::SystemTime;
6use std::time::UNIX_EPOCH;
7
8#[cfg(feature = "chrono-tz")]
9use chrono::{
10  offset::TimeZone as _,
11  DateTime,
12  Utc,
13};
14#[cfg(feature = "chrono-tz")]
15use chrono_tz::America::New_York;
16
17use serde::de::Deserializer;
18use serde::de::Error;
19use serde::de::Unexpected;
20use serde::ser::Serializer;
21use serde::Deserialize;
22
23use crate::parse::parse_system_time_from_str_impl;
24use crate::parse::DATE_PARSE_FNS;
25use crate::parse::TIME_PARSE_FNS;
26use crate::print::print_system_time_to_rfc3339;
27use crate::print::print_system_time_to_rfc3339_with_nanos;
28
29
30/// Deserialize a time stamp as a `SystemTime`.
31pub fn system_time_from_str<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
32where
33  D: Deserializer<'de>,
34{
35  let time = String::deserialize(deserializer)?;
36  parse_system_time_from_str_impl(&time, &TIME_PARSE_FNS)
37    .ok_or_else(|| Error::invalid_value(Unexpected::Str(&time), &"a time stamp string"))
38}
39
40
41/// Deserialize an optional time stamp.
42pub fn optional_system_time_from_str<'de, D>(
43  deserializer: D,
44) -> Result<Option<SystemTime>, D::Error>
45where
46  D: Deserializer<'de>,
47{
48  match Option::<String>::deserialize(deserializer)? {
49    Some(time) => parse_system_time_from_str_impl(&time, &TIME_PARSE_FNS)
50      .ok_or_else(|| Error::invalid_value(Unexpected::Str(&time), &"an optional time stamp string"))
51      .map(Option::Some),
52    None => Ok(None),
53  }
54}
55
56
57/// Deserialize a `SystemTime` from a date.
58pub fn system_time_from_date_str<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
59where
60  D: Deserializer<'de>,
61{
62  let date = String::deserialize(deserializer)?;
63  parse_system_time_from_str_impl(&date, &DATE_PARSE_FNS)
64    .ok_or_else(|| Error::invalid_value(Unexpected::Str(&date), &"a date string"))
65}
66
67
68/// Deserialize a `SystemTime` from a UNIX time stamp.
69pub fn system_time_from_secs<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
70where
71  D: Deserializer<'de>,
72{
73  let seconds = u64::deserialize(deserializer)?;
74  let time = UNIX_EPOCH + Duration::new(seconds, 0);
75  Ok(time)
76}
77
78
79/// Deserialize a `SystemTime` from a timestamp containing the
80/// milliseconds since 1970-01-01.
81pub fn system_time_from_millis<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
82where
83  D: Deserializer<'de>,
84{
85  let ms = u64::deserialize(deserializer)?;
86  let time = UNIX_EPOCH + Duration::from_millis(ms);
87  Ok(time)
88}
89
90
91/// Deserialize a `SystemTime` from a timestamp containing the
92/// milliseconds since 1970-01-01 in the New York time zone.
93#[cfg(feature = "chrono-tz")]
94pub fn system_time_from_millis_in_new_york<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
95where
96  D: Deserializer<'de>,
97{
98  let time = system_time_from_millis(deserializer)?;
99  let naive_time = DateTime::<Utc>::from(time).naive_local();
100  let ny_time = New_York.from_utc_datetime(&naive_time);
101  let utc_time = Utc.from_local_datetime(&ny_time.naive_local()).unwrap();
102
103  Ok(SystemTime::from(utc_time))
104}
105
106
107/// Serialize a `SystemTime` into a RFC3339 time stamp.
108pub fn system_time_to_rfc3339<S>(time: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
109where
110  S: Serializer,
111{
112  let string = print_system_time_to_rfc3339(time);
113  serializer.serialize_str(&string)
114}
115
116
117/// Serialize a `SystemTime` into a RFC3339 time stamp.
118pub fn system_time_to_rfc3339_with_nanos<S>(
119  time: &SystemTime,
120  serializer: S,
121) -> Result<S::Ok, S::Error>
122where
123  S: Serializer,
124{
125  let string = print_system_time_to_rfc3339_with_nanos(time);
126  serializer.serialize_str(&string)
127}
128
129
130/// Serialize an optional `SystemTime` into a RFC3339 time stamp.
131pub fn optional_system_time_to_rfc3339<S>(
132  time: &Option<SystemTime>,
133  serializer: S,
134) -> Result<S::Ok, S::Error>
135where
136  S: Serializer,
137{
138  match time {
139    Some(time) => system_time_to_rfc3339(time, serializer),
140    None => serializer.serialize_none(),
141  }
142}
143
144
145/// Serialize an optional `SystemTime` into a RFC3339 time stamp.
146pub fn optional_system_time_to_rfc3339_with_nanos<S>(
147  time: &Option<SystemTime>,
148  serializer: S,
149) -> Result<S::Ok, S::Error>
150where
151  S: Serializer,
152{
153  match time {
154    Some(time) => system_time_to_rfc3339_with_nanos(time, serializer),
155    None => serializer.serialize_none(),
156  }
157}
158
159
160/// Serialize a `SystemTime` into a timestamp containing the
161/// milliseconds since 1970-01-01.
162pub fn system_time_to_millis<S>(time: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
163where
164  S: Serializer,
165{
166  // It should be safe to unwrap here given that there is absolutely no
167  // way for a time stamp to ever point to a time before `UNIX_EPOCH`
168  // and that the only (documented) error case for `duration_since`.
169  let millis = time.duration_since(UNIX_EPOCH).unwrap().as_millis();
170  serializer.serialize_u128(millis)
171}
172
173
174/// Serialize a `SystemTime` into a timestamp containing the
175/// milliseconds since 1970-01-01 in New York.
176#[cfg(feature = "chrono-tz")]
177pub fn system_time_to_millis_in_new_york<S>(
178  time: &SystemTime,
179  serializer: S,
180) -> Result<S::Ok, S::Error>
181where
182  S: Serializer,
183{
184  let utc_time = DateTime::<Utc>::from(*time);
185  let ny_time = New_York.from_local_datetime(&utc_time.naive_utc()).unwrap();
186  system_time_to_millis(&SystemTime::from(ny_time), serializer)
187}
188
189
190#[cfg(test)]
191mod tests {
192  use super::*;
193
194  use std::time::SystemTime;
195
196  use serde::Deserialize;
197  use serde::Serialize;
198  use serde_json::from_str as from_json;
199  use serde_json::to_string as to_json;
200
201  #[cfg(feature = "chrono-tz")]
202  use crate::parse::parse_system_time_from_str;
203
204
205  #[derive(Debug, Deserialize)]
206  struct Time {
207    #[serde(deserialize_with = "system_time_from_str")]
208    time: SystemTime,
209  }
210
211  #[test]
212  fn deserialize_system_time_from_str() {
213    let times = [
214      r#"{"time": "2018-04-01T12:00:00Z"}"#,
215      r#"{"time": "2018-04-01T12:00:00.000Z"}"#,
216      r#"{"time": "2018-04-01T08:00:00.000-04:00"}"#,
217    ];
218
219    for json in &times {
220      let time = from_json::<Time>(json).unwrap();
221      assert_eq!(time.time, UNIX_EPOCH + Duration::from_secs(1_522_584_000));
222    }
223  }
224
225  #[derive(Debug, Deserialize)]
226  struct Date {
227    #[serde(deserialize_with = "system_time_from_date_str")]
228    date: SystemTime,
229  }
230
231  #[test]
232  fn deserialize_system_time_from_date_str() {
233    let dates = [r#"{"date": "2019-08-01"}"#];
234
235    for json in &dates {
236      let date = from_json::<Date>(json).unwrap();
237      assert_eq!(date.date, UNIX_EPOCH + Duration::from_secs(1_564_617_600));
238    }
239  }
240
241
242  #[derive(Debug, Deserialize, Serialize)]
243  struct OtherTime {
244    #[serde(
245      deserialize_with = "system_time_from_secs",
246      serialize_with = "system_time_to_rfc3339",
247    )]
248    time: SystemTime,
249  }
250
251  #[test]
252  fn deserialize_system_time_from_secs() {
253    let time = from_json::<OtherTime>(r#"{"time": 1544129220}"#).unwrap();
254    assert_eq!(time.time, UNIX_EPOCH + Duration::from_secs(1_544_129_220));
255  }
256
257  #[test]
258  fn serialize_system_time_to_rfc3339() {
259    let time = OtherTime {
260      time: UNIX_EPOCH + Duration::from_secs(1_544_129_220),
261    };
262    let json = to_json(&time).unwrap();
263    assert_eq!(json, r#"{"time":"2018-12-06T20:47:00.000Z"}"#);
264  }
265
266  #[derive(Debug, Deserialize, Serialize)]
267  struct MsTime {
268    #[serde(
269      deserialize_with = "system_time_from_millis",
270      serialize_with = "system_time_to_rfc3339",
271    )]
272    time: SystemTime,
273  }
274
275  #[test]
276  fn deserialize_system_time_from_millis() {
277    let time = from_json::<MsTime>(r#"{"time": 1517461200000}"#).unwrap();
278    assert_eq!(time.time, UNIX_EPOCH + Duration::from_secs(1_517_461_200));
279  }
280
281
282  #[derive(Debug, Deserialize, Serialize)]
283  #[cfg(feature = "chrono-tz")]
284  struct MsTimeNY {
285    #[serde(
286      deserialize_with = "system_time_from_millis_in_new_york",
287      serialize_with = "system_time_to_millis_in_new_york",
288    )]
289    time: SystemTime,
290  }
291
292  #[test]
293  #[cfg(feature = "chrono-tz")]
294  fn deserialize_serialize_system_time_millis_in_new_york() {
295    // This time stamp represents 2018-02-01T00:00:00-05:00:
296    // $ date --date='2018-02-01T00:00:00-05:00' +'%s'
297    let time = from_json::<MsTimeNY>(r#"{"time": 1517461200000}"#).unwrap();
298    let expected = parse_system_time_from_str("2018-02-01T00:00:00.000Z").unwrap();
299    assert_eq!(time.time, expected);
300
301    let json = to_json::<MsTimeNY>(&time).unwrap();
302    let time = from_json::<MsTimeNY>(&json).unwrap();
303    assert_eq!(time.time, expected);
304  }
305
306  #[test]
307  #[cfg(feature = "chrono-tz")]
308  fn deserialize_serialize_system_time_millis_in_new_york_daylight_savings() {
309    let time = from_json::<MsTimeNY>(r#"{"time": 1599537600000}"#).unwrap();
310    let expected = parse_system_time_from_str("2020-09-08T00:00:00.000Z").unwrap();
311    assert_eq!(time.time, expected);
312
313    let json = to_json::<MsTimeNY>(&time).unwrap();
314    let time = from_json::<MsTimeNY>(&json).unwrap();
315    assert_eq!(time.time, expected);
316  }
317}