firestore_serde_timestamp/
lib.rs1pub mod timestamp {
2 use chrono::{DateTime, TimeZone, Timelike, Utc};
3 use prost::Message;
4 use prost_types::Timestamp;
5 use serde::{Deserialize, Deserializer, Serializer};
6 use serde_bytes::ByteBuf;
7
8 pub const DATE_MAGIC: &str = "$TimestampValue";
9
10 pub fn serialize<S>(date: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
11 where
12 S: Serializer,
13 {
14 #[allow(clippy::cast_possible_wrap)]
15 let c = Timestamp {
16 seconds: date.timestamp(),
17 nanos: date.nanosecond() as i32,
18 };
19
20 let v = ByteBuf::from(c.encode_to_vec());
21
22 serializer.serialize_newtype_struct(DATE_MAGIC, &v)
23 }
24
25 pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
26 where
27 D: Deserializer<'de>,
28 {
29 let buf = ByteBuf::deserialize(deserializer)?;
30 let timestamp = Timestamp::decode(buf.as_slice())
31 .expect("Should always be able to decode timestamp because we just encoded it.");
32 #[allow(clippy::cast_sign_loss)]
33 let datetime = Utc.timestamp(timestamp.seconds, timestamp.nanos as u32);
34
35 Ok(datetime)
36 }
37}
38
39#[cfg(test)]
40mod test {
41 use chrono::{DateTime, Utc, TimeZone};
42 use firestore_serde::{from_grpc_value, to_grpc_value, ValueDeserializer, ValueSerializer};
43 use googapis::google::firestore::v1::{value::ValueType, MapValue, Value};
44 use prost_types::Timestamp;
45 use serde::{Deserialize, Serialize};
46
47 use crate::timestamp::{deserialize, serialize};
48
49 #[derive(Serialize, Deserialize, PartialEq, Debug)]
50 struct StructWithDate {
51 #[serde(with = "crate::timestamp")]
52 date: DateTime<Utc>,
53 }
54
55 #[test]
56 fn test_serialize_date() {
57 let date = Utc.timestamp(150, 200);
58
59 let result = serialize(&date, ValueSerializer).unwrap();
60
61 assert_eq!(
62 Value {
63 value_type: Some(ValueType::TimestampValue(Timestamp {
64 seconds: 150,
65 nanos: 200
66 }))
67 },
68 result
69 );
70
71 assert_eq!(date, deserialize(&mut ValueDeserializer(&result)).unwrap());
72 }
73
74 #[test]
75 fn test_serialize_in_struct() {
76 let st = StructWithDate {
77 date: Utc.timestamp(150, 200),
78 };
79
80 let result = to_grpc_value(&st).unwrap();
81
82 assert_eq!(
83 Value {
84 value_type: Some(ValueType::MapValue(MapValue {
85 fields: vec![(
86 "date".to_string(),
87 Value {
88 value_type: Some(ValueType::TimestampValue(Timestamp {
89 seconds: 150,
90 nanos: 200
91 }))
92 }
93 )]
94 .into_iter()
95 .collect()
96 }))
97 },
98 result
99 );
100
101 assert_eq!(st, from_grpc_value(&result).unwrap());
102 }
103}