1use crate::FirestoreInstant;
2use gcloud_sdk::google::firestore::v1::value;
3use rvstruct::ValueStruct;
4use serde::{Deserialize, Serialize, Serializer};
5
6use crate::{
7 errors::FirestoreSerializationError, timestamp_utils::to_timestamp, FirestoreError,
8 FirestoreValue,
9};
10
11#[derive(
19 Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Default, ValueStruct,
20)]
21pub struct FirestoreTimestamp(pub FirestoreInstant);
22
23impl FirestoreTimestamp {
24 #[inline]
26 pub fn now() -> Self {
27 FirestoreTimestamp(FirestoreInstant::now()).truncated_to_firestore_precision()
28 }
29
30 #[inline]
53 pub fn truncated_to_firestore_precision(self) -> Self {
54 let ts = crate::timestamp_utils::to_timestamp(self.0);
59
60 crate::timestamp_utils::from_timestamp(gcloud_sdk::prost_types::Timestamp {
61 seconds: ts.seconds,
62 nanos: (ts.nanos / 1_000) * 1_000,
63 })
64 .map(FirestoreTimestamp)
65 .unwrap_or(self)
66 }
67}
68
69impl std::fmt::Display for FirestoreTimestamp {
70 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
71 self.0.fmt(f)
72 }
73}
74
75impl std::str::FromStr for FirestoreTimestamp {
76 type Err = crate::errors::FirestoreError;
77
78 fn from_str(s: &str) -> Result<Self, Self::Err> {
79 Ok(FirestoreTimestamp(s.parse::<FirestoreInstant>()?).truncated_to_firestore_precision())
80 }
81}
82
83impl From<FirestoreTimestamp> for FirestoreInstant {
84 fn from(ts: FirestoreTimestamp) -> Self {
85 ts.0
86 }
87}
88
89impl From<FirestoreTimestamp> for std::time::SystemTime {
92 fn from(ts: FirestoreTimestamp) -> Self {
93 ts.0.into()
94 }
95}
96
97impl TryFrom<std::time::SystemTime> for FirestoreTimestamp {
100 type Error = crate::errors::FirestoreError;
101
102 fn try_from(system_time: std::time::SystemTime) -> Result<Self, Self::Error> {
103 Ok(FirestoreTimestamp(FirestoreInstant::try_from(system_time)?)
104 .truncated_to_firestore_precision())
105 }
106}
107
108impl TryFrom<gcloud_sdk::prost_types::Timestamp> for FirestoreTimestamp {
110 type Error = crate::errors::FirestoreError;
111
112 fn try_from(ts: gcloud_sdk::prost_types::Timestamp) -> Result<Self, Self::Error> {
113 Ok(
114 FirestoreTimestamp(crate::timestamp_utils::from_timestamp(ts)?)
115 .truncated_to_firestore_precision(),
116 )
117 }
118}
119
120impl From<FirestoreTimestamp> for gcloud_sdk::prost_types::Timestamp {
122 fn from(ts: FirestoreTimestamp) -> Self {
123 crate::timestamp_utils::to_timestamp(ts.0)
124 }
125}
126
127pub(crate) const FIRESTORE_TS_TYPE_TAG_TYPE: &str = "FirestoreTimestamp";
128
129pub(crate) const FIRESTORE_TS_NULL_TYPE_TAG_TYPE: &str = "FirestoreTimestampAsNull";
130
131pub mod serialize_as_timestamp {
132 use crate::FirestoreInstant;
133 use serde::{Deserialize, Deserializer, Serializer};
134
135 pub fn serialize<S>(ts: &FirestoreInstant, serializer: S) -> Result<S::Ok, S::Error>
136 where
137 S: Serializer,
138 {
139 serializer.serialize_newtype_struct(crate::firestore_serde::FIRESTORE_TS_TYPE_TAG_TYPE, &ts)
140 }
141
142 pub fn deserialize<'de, D>(deserializer: D) -> Result<FirestoreInstant, D::Error>
143 where
144 D: Deserializer<'de>,
145 {
146 FirestoreInstant::deserialize(deserializer)
147 }
148}
149
150pub mod serialize_as_optional_timestamp {
151 use crate::FirestoreInstant;
152 use serde::{Deserialize, Deserializer, Serializer};
153
154 pub fn serialize<S>(ts: &Option<FirestoreInstant>, serializer: S) -> Result<S::Ok, S::Error>
155 where
156 S: Serializer,
157 {
158 match ts {
159 Some(v) => serializer
160 .serialize_newtype_struct(crate::firestore_serde::FIRESTORE_TS_TYPE_TAG_TYPE, v),
161 None => serializer.serialize_none(),
162 }
163 }
164
165 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<FirestoreInstant>, D::Error>
166 where
167 D: Deserializer<'de>,
168 {
169 Option::<FirestoreInstant>::deserialize(deserializer)
170 }
171}
172
173pub mod serialize_as_null_timestamp {
174 use crate::FirestoreInstant;
175 use serde::{Deserialize, Deserializer, Serializer};
176
177 pub fn serialize<S>(ts: &Option<FirestoreInstant>, serializer: S) -> Result<S::Ok, S::Error>
178 where
179 S: Serializer,
180 {
181 serializer
182 .serialize_newtype_struct(crate::firestore_serde::FIRESTORE_TS_NULL_TYPE_TAG_TYPE, ts)
183 }
184
185 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<FirestoreInstant>, D::Error>
186 where
187 D: Deserializer<'de>,
188 {
189 Option::<FirestoreInstant>::deserialize(deserializer)
190 }
191}
192
193pub fn serialize_timestamp_for_firestore<T: ?Sized + Serialize>(
194 value: &T,
195 none_as_null: bool,
196) -> Result<FirestoreValue, FirestoreError> {
197 struct TimestampSerializer {
198 none_as_null: bool,
199 }
200
201 impl Serializer for TimestampSerializer {
202 type Ok = FirestoreValue;
203 type Error = FirestoreError;
204 type SerializeSeq = crate::firestore_serde::serializer::SerializeVec;
205 type SerializeTuple = crate::firestore_serde::serializer::SerializeVec;
206 type SerializeTupleStruct = crate::firestore_serde::serializer::SerializeVec;
207 type SerializeTupleVariant = crate::firestore_serde::serializer::SerializeTupleVariant;
208 type SerializeMap = crate::firestore_serde::serializer::SerializeMap;
209 type SerializeStruct = crate::firestore_serde::serializer::SerializeMap;
210 type SerializeStructVariant = crate::firestore_serde::serializer::SerializeStructVariant;
211
212 fn serialize_bool(self, _v: bool) -> Result<Self::Ok, Self::Error> {
213 Err(FirestoreError::SerializeError(
214 FirestoreSerializationError::from_message(
215 "Timestamp serializer doesn't support this type",
216 ),
217 ))
218 }
219
220 fn serialize_i8(self, _v: i8) -> Result<Self::Ok, Self::Error> {
221 Err(FirestoreError::SerializeError(
222 FirestoreSerializationError::from_message(
223 "Timestamp serializer doesn't support this type",
224 ),
225 ))
226 }
227
228 fn serialize_i16(self, _v: i16) -> Result<Self::Ok, Self::Error> {
229 Err(FirestoreError::SerializeError(
230 FirestoreSerializationError::from_message(
231 "Timestamp serializer doesn't support this type",
232 ),
233 ))
234 }
235
236 fn serialize_i32(self, _v: i32) -> Result<Self::Ok, Self::Error> {
237 Err(FirestoreError::SerializeError(
238 FirestoreSerializationError::from_message(
239 "Timestamp serializer doesn't support this type",
240 ),
241 ))
242 }
243
244 fn serialize_i64(self, _v: i64) -> Result<Self::Ok, Self::Error> {
245 Err(FirestoreError::SerializeError(
246 FirestoreSerializationError::from_message(
247 "Timestamp serializer doesn't support this type",
248 ),
249 ))
250 }
251
252 fn serialize_u8(self, _v: u8) -> Result<Self::Ok, Self::Error> {
253 Err(FirestoreError::SerializeError(
254 FirestoreSerializationError::from_message(
255 "Timestamp serializer doesn't support this type",
256 ),
257 ))
258 }
259
260 fn serialize_u16(self, _v: u16) -> Result<Self::Ok, Self::Error> {
261 Err(FirestoreError::SerializeError(
262 FirestoreSerializationError::from_message(
263 "Timestamp serializer doesn't support this type",
264 ),
265 ))
266 }
267
268 fn serialize_u32(self, _v: u32) -> Result<Self::Ok, Self::Error> {
269 Err(FirestoreError::SerializeError(
270 FirestoreSerializationError::from_message(
271 "Timestamp serializer doesn't support this type",
272 ),
273 ))
274 }
275
276 fn serialize_u64(self, _v: u64) -> Result<Self::Ok, Self::Error> {
277 Err(FirestoreError::SerializeError(
278 FirestoreSerializationError::from_message(
279 "Timestamp serializer doesn't support this type",
280 ),
281 ))
282 }
283
284 fn serialize_f32(self, _v: f32) -> Result<Self::Ok, Self::Error> {
285 Err(FirestoreError::SerializeError(
286 FirestoreSerializationError::from_message(
287 "Timestamp serializer doesn't support this type",
288 ),
289 ))
290 }
291
292 fn serialize_f64(self, _v: f64) -> Result<Self::Ok, Self::Error> {
293 Err(FirestoreError::SerializeError(
294 FirestoreSerializationError::from_message(
295 "Timestamp serializer doesn't support this type",
296 ),
297 ))
298 }
299
300 fn serialize_char(self, _v: char) -> Result<Self::Ok, Self::Error> {
301 Err(FirestoreError::SerializeError(
302 FirestoreSerializationError::from_message(
303 "Timestamp serializer doesn't support this type",
304 ),
305 ))
306 }
307
308 fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
309 let ts = v.parse::<FirestoreInstant>()?;
310 Ok(FirestoreValue::from(
311 gcloud_sdk::google::firestore::v1::Value {
312 value_type: Some(value::ValueType::TimestampValue(to_timestamp(ts))),
313 },
314 ))
315 }
316
317 fn serialize_bytes(self, _v: &[u8]) -> Result<Self::Ok, Self::Error> {
318 Err(FirestoreError::SerializeError(
319 FirestoreSerializationError::from_message(
320 "Timestamp serializer doesn't support this type",
321 ),
322 ))
323 }
324
325 fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
326 if self.none_as_null {
327 Ok(FirestoreValue::from(
328 gcloud_sdk::google::firestore::v1::Value {
329 value_type: Some(value::ValueType::NullValue(0)),
330 },
331 ))
332 } else {
333 Ok(FirestoreValue::from(
334 gcloud_sdk::google::firestore::v1::Value { value_type: None },
335 ))
336 }
337 }
338
339 fn serialize_some<T: ?Sized + Serialize>(self, value: &T) -> Result<Self::Ok, Self::Error> {
340 value.serialize(self)
341 }
342
343 fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
344 Ok(FirestoreValue::from(
345 gcloud_sdk::google::firestore::v1::Value { value_type: None },
346 ))
347 }
348
349 fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Self::Error> {
350 self.serialize_unit()
351 }
352
353 fn serialize_unit_variant(
354 self,
355 _name: &'static str,
356 _variant_index: u32,
357 variant: &'static str,
358 ) -> Result<Self::Ok, Self::Error> {
359 self.serialize_str(variant)
360 }
361
362 fn serialize_newtype_struct<T: ?Sized + Serialize>(
363 self,
364 _name: &'static str,
365 value: &T,
366 ) -> Result<Self::Ok, Self::Error> {
367 value.serialize(self)
368 }
369
370 fn serialize_newtype_variant<T: ?Sized + Serialize>(
371 self,
372 _name: &'static str,
373 _variant_index: u32,
374 _variant: &'static str,
375 _value: &T,
376 ) -> Result<Self::Ok, Self::Error> {
377 Err(FirestoreError::SerializeError(
378 FirestoreSerializationError::from_message(
379 "Timestamp serializer doesn't support this type",
380 ),
381 ))
382 }
383
384 fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
385 Err(FirestoreError::SerializeError(
386 FirestoreSerializationError::from_message(
387 "Timestamp serializer doesn't support this type",
388 ),
389 ))
390 }
391
392 fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Self::Error> {
393 Err(FirestoreError::SerializeError(
394 FirestoreSerializationError::from_message(
395 "Timestamp serializer doesn't support this type",
396 ),
397 ))
398 }
399
400 fn serialize_tuple_struct(
401 self,
402 _name: &'static str,
403 _len: usize,
404 ) -> Result<Self::SerializeTupleStruct, Self::Error> {
405 Err(FirestoreError::SerializeError(
406 FirestoreSerializationError::from_message(
407 "Timestamp serializer doesn't support this type",
408 ),
409 ))
410 }
411
412 fn serialize_tuple_variant(
413 self,
414 _name: &'static str,
415 _variant_index: u32,
416 _variant: &'static str,
417 _len: usize,
418 ) -> Result<Self::SerializeTupleVariant, Self::Error> {
419 Err(FirestoreError::SerializeError(
420 FirestoreSerializationError::from_message(
421 "Timestamp serializer doesn't support this type",
422 ),
423 ))
424 }
425
426 fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
427 Err(FirestoreError::SerializeError(
428 FirestoreSerializationError::from_message(
429 "Timestamp serializer doesn't support this type",
430 ),
431 ))
432 }
433
434 fn serialize_struct(
435 self,
436 _name: &'static str,
437 _len: usize,
438 ) -> Result<Self::SerializeStruct, Self::Error> {
439 Err(FirestoreError::SerializeError(
440 FirestoreSerializationError::from_message(
441 "Timestamp serializer doesn't support this type",
442 ),
443 ))
444 }
445
446 fn serialize_struct_variant(
447 self,
448 _name: &'static str,
449 _variant_index: u32,
450 _variant: &'static str,
451 _len: usize,
452 ) -> Result<Self::SerializeStructVariant, Self::Error> {
453 Err(FirestoreError::SerializeError(
454 FirestoreSerializationError::from_message(
455 "Timestamp serializer doesn't support this type",
456 ),
457 ))
458 }
459 }
460
461 value.serialize(TimestampSerializer { none_as_null })
462}
463
464#[cfg(test)]
465mod tests {
466 use crate::{
467 firestore_document_from_serializable, firestore_document_to_serializable, FirestoreInstant,
468 FirestoreTimestamp,
469 };
470 use rvstruct::ValueStruct;
471 use serde::{Deserialize, Serialize};
472
473 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
474 struct TestStructure {
475 #[serde(with = "crate::serialize_as_timestamp")]
476 created_at: FirestoreInstant,
477 #[serde(default)]
478 #[serde(with = "crate::serialize_as_optional_timestamp")]
479 updated_at: Option<FirestoreInstant>,
480 wrapped_at: FirestoreTimestamp,
481 }
482
483 #[test]
484 fn test_timestamp_serde_roundtrip() {
485 let created_at: FirestoreInstant = "2022-12-02T16:53:20.123456789Z".parse().unwrap();
486 let wrapped_at: FirestoreInstant = "2018-01-01T00:00:00Z".parse().unwrap();
487
488 let test_structure = TestStructure {
489 created_at,
490 updated_at: Some(created_at),
491 wrapped_at: FirestoreTimestamp(wrapped_at),
492 };
493
494 let document =
495 firestore_document_from_serializable("test/doc-id", &test_structure).unwrap();
496
497 assert!(matches!(
499 document.fields["created_at"].value_type,
500 Some(gcloud_sdk::google::firestore::v1::value::ValueType::TimestampValue(_))
501 ));
502
503 let deserialized: TestStructure =
504 firestore_document_to_serializable(&document).expect("Unable to deserialize");
505
506 assert_eq!(deserialized, test_structure);
507 assert_eq!(deserialized.created_at.subsec_nanosecond(), 123_456_789);
509 }
510
511 #[test]
512 fn test_firestore_precision_truncation() {
513 use std::str::FromStr;
514 use std::time::{Duration, SystemTime, UNIX_EPOCH};
515
516 assert_eq!(
518 FirestoreTimestamp::from_str("2022-12-02T16:53:20.123456789Z")
519 .unwrap()
520 .to_string(),
521 "2022-12-02T16:53:20.123456Z"
522 );
523
524 assert_eq!(
527 FirestoreTimestamp::from_str("1969-12-31T23:59:59.542758875Z")
528 .unwrap()
529 .to_string(),
530 "1969-12-31T23:59:59.542758Z"
531 );
532
533 let from_system_time: FirestoreTimestamp = (UNIX_EPOCH + Duration::new(1, 542_758_875))
535 .try_into()
536 .unwrap();
537 assert_eq!(from_system_time.to_string(), "1970-01-01T00:00:01.542758Z");
538
539 let from_proto: FirestoreTimestamp = gcloud_sdk::prost_types::Timestamp {
540 seconds: 1,
541 nanos: 542_758_875,
542 }
543 .try_into()
544 .unwrap();
545 assert_eq!(from_proto.to_string(), "1970-01-01T00:00:01.542758Z");
546
547 assert_eq!(FirestoreTimestamp::now().0.subsec_nanosecond() % 1_000, 0);
548
549 let already_truncated =
551 FirestoreTimestamp::from_str("2022-12-02T16:53:20.123456Z").unwrap();
552 assert_eq!(
553 already_truncated.truncated_to_firestore_precision(),
554 already_truncated
555 );
556
557 let exact = FirestoreTimestamp(
559 FirestoreInstant::from_str("2022-12-02T16:53:20.123456789Z").unwrap(),
560 );
561 assert_eq!(exact.0.subsec_nanosecond(), 123_456_789);
562
563 let _ = SystemTime::from(exact);
564 }
565
566 #[test]
567 fn test_timestamp_conversions() {
568 use std::str::FromStr;
569 use std::time::SystemTime;
570
571 let ts = FirestoreTimestamp::from_str("2022-12-02T16:53:20.123456Z").unwrap();
573
574 assert_eq!(FirestoreTimestamp::from_str(&ts.to_string()).unwrap(), ts);
576
577 let system_time: SystemTime = ts.into();
579 assert_eq!(FirestoreTimestamp::try_from(system_time).unwrap(), ts);
580
581 let proto: gcloud_sdk::prost_types::Timestamp = ts.into();
583 assert_eq!(proto.seconds, 1670000000);
584 assert_eq!(proto.nanos, 123_456_000);
585 assert_eq!(FirestoreTimestamp::try_from(proto).unwrap(), ts);
586
587 let instant: FirestoreInstant = ts.into();
589 assert_eq!(FirestoreTimestamp::from(instant), ts);
590 assert_eq!(ts.value(), &instant);
591
592 assert_eq!(
593 FirestoreTimestamp::try_from(SystemTime::UNIX_EPOCH).unwrap(),
594 FirestoreTimestamp::default()
595 );
596 }
597
598 #[test]
601 fn test_all_documented_timestamp_forms() {
602 use gcloud_sdk::google::firestore::v1::value::ValueType;
603 use std::time::SystemTime;
604
605 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
606 struct ExampleStructure {
607 some_id: String,
608 created_at_system_time: std::time::SystemTime,
609 created_at: FirestoreTimestamp,
610 updated_at: Option<FirestoreTimestamp>,
611 updated_at_always_none: Option<FirestoreTimestamp>,
612 #[serde(with = "crate::serialize_as_timestamp")]
613 created_at_attr: FirestoreInstant,
614 #[serde(default)]
615 #[serde(with = "crate::serialize_as_optional_timestamp")]
616 updated_at_attr: Option<FirestoreInstant>,
617 #[serde(default)]
618 #[serde(with = "crate::serialize_as_optional_timestamp")]
619 updated_at_attr_always_none: Option<FirestoreInstant>,
620 }
621
622 let from_system_time: FirestoreTimestamp = SystemTime::now().try_into().unwrap();
623
624 let example = ExampleStructure {
625 some_id: "test-1".to_string(),
626 created_at_system_time: std::time::SystemTime::now(),
627 created_at: FirestoreTimestamp::now(),
628 updated_at: Some(from_system_time),
629 updated_at_always_none: None,
630 created_at_attr: FirestoreInstant::now(),
631 updated_at_attr: Some(FirestoreInstant::now()),
632 updated_at_attr_always_none: None,
633 };
634
635 let document = firestore_document_from_serializable("test/doc-id", &example).unwrap();
636
637 for field in [
639 "created_at",
640 "updated_at",
641 "created_at_attr",
642 "updated_at_attr",
643 ] {
644 assert!(
645 matches!(
646 document.fields[field].value_type,
647 Some(ValueType::TimestampValue(_))
648 ),
649 "{field} must serialize as a Firestore timestamp"
650 );
651 }
652
653 let deserialized: ExampleStructure =
654 firestore_document_to_serializable(&document).expect("Unable to deserialize");
655 assert_eq!(deserialized, example);
656 }
657
658 #[test]
659 fn test_optional_timestamp_none_roundtrip() {
660 let created_at: FirestoreInstant = "2022-12-02T16:53:20Z".parse().unwrap();
661
662 let test_structure = TestStructure {
663 created_at,
664 updated_at: None,
665 wrapped_at: FirestoreTimestamp(created_at),
666 };
667
668 let document =
669 firestore_document_from_serializable("test/doc-id", &test_structure).unwrap();
670
671 let deserialized: TestStructure =
672 firestore_document_to_serializable(&document).expect("Unable to deserialize");
673
674 assert_eq!(deserialized, test_structure);
675 }
676}