Server Timestamps #
In distributed systems, relying on the client's clock can lead to inconsistencies. Firestore provides a FieldValue.serverTimestamp()
to ensure that timestamps are generated consistently on Google's servers. This ODM provides a convenient and type-safe way to use this feature.
The FirestoreODM.serverTimestamp
Constant #
The ODM exposes a special static constant, FirestoreODM.serverTimestamp
. You should use this constant whenever you want to set a server-generated timestamp on a DateTime
field.
This constant acts as a sentinel value. When you use it in an update operation, the ODM's internal logic detects this special value and replaces it with the actual FieldValue.serverTimestamp()
before sending the data to Firestore.
Usage with modify
and incrementalModify
#
When using modify
or incrementalModify
, assign FirestoreODM.serverTimestamp
directly to your DateTime
field within the copyWith
method.
final userDoc = odm.users('jane-doe');
// The ODM will detect the special constant and convert it
// to a proper server timestamp.
await userDoc.modify((user) => user.copyWith(
lastLogin: FirestoreODM.serverTimestamp,
));
// incrementalModify also supports server timestamps
await userDoc.incrementalModify((user) => user.copyWith(
age: user.age + 1,
lastLogin: FirestoreODM.serverTimestamp,
));
Usage with patch
#
When using the patch
method, you get access to a dedicated .serverTimestamp()
method on any DateTime
field. This is the recommended way to set server timestamps within a patch operation as it's more explicit.
final userDoc = odm.users('jane-doe');
// The .serverTimestamp() method directly creates the correct update operation.
await userDoc.patch(($) => [
$.lastLogin.serverTimestamp(),
$.profile.lastActivity.serverTimestamp(),
]);
By providing these two mechanisms, the ODM ensures you can easily and safely use server-generated timestamps, whether you prefer working with model objects (modify
) or explicit update operations (patch
).
⚠️ Important: Server Timestamp Arithmetic #
Warning: You cannot perform arithmetic operations on FirestoreODM.serverTimestamp
.
// ❌ This will NOT work as expected
FirestoreODM.serverTimestamp + Duration(days: 1) // Results in a regular DateTime, not server timestamp
// ❌ This will also NOT work
FirestoreODM.serverTimestamp.add(Duration(hours: 1)) // Results in a regular DateTime
The FirestoreODM.serverTimestamp
constant is a sentinel value that gets replaced with FieldValue.serverTimestamp()
only when used exactly as-is. Any arithmetic operations will create a regular DateTime
object instead of a server timestamp.
If you need "server timestamp + offset": #
Use client-side calculation:
dart// Set to current time + 1 day (client time) DateTime.now().add(Duration(days: 1))
Model design with computed getter (recommended): Store the base timestamp and offset separately, then compute the final value:
dart@freezed class User with _$User { const factory User({ @DocumentIdField() required String id, required String name, DateTime? vipStartedAt, // When VIP started (server timestamp) Duration? vipExpiryOffset, // How long VIP lasts }) = _User; // Computed getter for VIP expiry date DateTime? get vipExpiryDate { if (vipStartedAt == null || vipExpiryOffset == null) return null; return vipStartedAt!.add(vipExpiryOffset!); } // Computed getter to check if VIP is still active bool get isVipActive { final expiryDate = vipExpiryDate; if (expiryDate == null) return false; return DateTime.now().isBefore(expiryDate); } factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json); }
Usage:
dart// Set server timestamp and Duration offset in one operation await userDoc.patch(($) => [ $.vipStartedAt.serverTimestamp(), // Server sets exact start time $.vipExpiryOffset(Duration(days: 30)), // VIP lasts 30 days ]); // Access computed values final user = await userDoc.get(); print('VIP expires at: ${user?.vipExpiryDate}'); // Automatically calculated print('Is VIP active: ${user?.isVipActive}'); // true/false
Note: Firestore ODM supports
Duration
type natively. Duration values are serialized to/from Firestore as microseconds (int64), ensuring precision and compatibility.Two-step approach (separate operations):
dart// Step 1: Set server timestamp await userDoc.patch(($) => [$.createdAt.serverTimestamp()]); // Step 2: Read and calculate offset final user = await userDoc.get(); final expiryDate = user!.createdAt.add(Duration(days: 30)); await userDoc.patch(($) => [$.expiryDate(expiryDate)]);
Note: This approach uses separate operations and may have potential race conditions. Unfortunately, you cannot use
patch
operations with server timestamps inside transactions, so this is the only viable approach for this pattern.Use Firestore Rules or Cloud Functions for server-side calculations.
The key point: FirestoreODM.serverTimestamp
must be used exactly as provided to work as a server timestamp.