Timestamps support
By default, date/time values serialize as a string to Firestore (while deserialization works from Timestamps and Strings). To store them as native Firestore timestamps there are three options.
- Using
std::time::SystemTimedirectly, with no attribute and no wrapping type, since it is recognised automatically:
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
#[derive(Debug, Clone, Deserialize, Serialize)]
struct MyTestStructure {
created_at: SystemTime,
updated_at: Option<SystemTime>,
}
It works in the queries as well:
use firestore::*;
use std::time::SystemTime;
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
struct MyTestStructure { created_at: SystemTime }
const TEST_COLLECTION_NAME: &str = "test";
async fn example(db: FirestoreDb) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
db.fluent().select().from(TEST_COLLECTION_NAME).filter(|q| { q.for_all([
q.field(path!(MyTestStructure::created_at))
.less_than_or_equal(SystemTime::now())
]) }).obj::<MyTestStructure>().query().await?;
Ok(())
}
Note that SystemTime cannot carry the instants before the Unix epoch, since
serde itself refuses them.
- Using the type
FirestoreTimestamp, which needs no attributes:
use firestore::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
struct MyTestStructure {
created_at: FirestoreTimestamp,
updated_at: Option<FirestoreTimestamp>,
}
It can be created with FirestoreTimestamp::now(), parsed from a string, and
converted from/to std::time::SystemTime:
use firestore::*;
use std::time::SystemTime;
fn example() -> Result<(), Box<dyn std::error::Error>> {
let now = FirestoreTimestamp::now();
let from_system_time: FirestoreTimestamp = SystemTime::now().try_into()?;
let back_to_system_time: SystemTime = now.into();
let _ = (from_system_time, back_to_system_time);
Ok(())
}
Use it in your queries as well, for example:
use firestore::*;
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
struct MyTestStructure { created_at: FirestoreTimestamp }
const TEST_COLLECTION_NAME: &str = "test";
async fn example(db: FirestoreDb) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
db.fluent().select().from(TEST_COLLECTION_NAME).filter(|q| { q.for_all([
q.field(path!(MyTestStructure::created_at))
.less_than_or_equal(FirestoreTimestamp::now())
]) }).obj::<MyTestStructure>().query().await?;
Ok(())
}
- Or, if you prefer to keep a plain instant in your model, use
FirestoreInstant(an alias forjiff::Timestamp) with#[serde(with)]attributes:
use firestore::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
struct MyTestStructure {
#[serde(with = "firestore::serialize_as_timestamp")]
created_at: FirestoreInstant,
#[serde(default)]
#[serde(with = "firestore::serialize_as_optional_timestamp")]
updated_at: Option<FirestoreInstant>,
}
Firestore stores the timestamps with microsecond precision and discards the
nanoseconds on write. FirestoreTimestamp truncates to that precision in all of
its constructors and conversions, so its values survive a round trip unchanged.
A FirestoreInstant or a SystemTime carrying nanoseconds does not, since those
are the standard types this library cannot change.
All of them change the representation only for Firestore serialization.
FirestoreTimestamp and FirestoreInstant still serialize as a string to JSON,
so the same model can be reused for JSON and Firestore, while a plain SystemTime
keeps the default serde representation.