Server Timestamp in Cloud Firestore with Unity

Fer Gamboa @ Prowess Games
5 min readOct 6, 2020

--

Cloud Firestore is part of Google’s serverless framework called Firebase. It is a NoSQL, document databased that was released not long ago. It is not the only database offer in Firebase, as there are now two: Realtime Database and Cloud Firestore. The latter is, as you have guessed, quite new.

The problem (and solution) I am going to describe is quite common, but most documentation I found was created for Realtime Database, and *not* for Cloud Firestore.

The Problem

A common situation when working with databases is trying to store a timestamp with the server time, without obtaining the date in the frontend.

There are plenty examples on how to do this with Cloud Firestore, but not so many made in Unity. I will showcase how to do it here. I will assume that you are already connected to the backend, and directly skip to the juicy part.

The Solution

There are two options to retrieve data stored in in Cloud Firestore: 1) using a <string, object> dictionary, or 2) creating a user-defined class. I am going to use the second one, but the code should be easily adaptable for the first case.

Step 1: User-Defined Type

To store a server timestamp your user-defined class, you need to create a property of the type object. Yes, it must be object, otherwise it won’t work correctly when reading the data back.

using Firebase.Firestore;
using System;
[FirestoreData]
public class MyData {
[FirestoreProperty]
public object myTimestamp { get; set; }
}

Notice the following important elements:

  • Your class must be tagged with [FirestoreData]
  • Your property field is an object (this is a must), and it is tagged with [FirestoreProperty].
  • Your property is always public and has a {get; set;} declaration.

Step 2: Writing Data

To write, you will need: (a) apre-initialised instance of your database, and (b) a reference to the document you want to write. Then, you will start your writing method as below, getting a reference to both of them:

using Firebase.Firestore;
using Firebase.Extensions;
public void writeMyData() { // Get the instance of your database
FirebaseFirestore db = FirebaseFirestore.DefaultInstance;
// Get a reference to the document you want to write
DocumentReference doc = db.Collection("collectionName")?.Document("docID").

Pay attention that:

  • The ? symbol ensures that you cannot create a reference to a collection that doesn’t exist already.
  • You need to replace the strings "collectionName" and "docID" for the correct names and ids of your collection and document, respectively.

After that, you need to initialise the data you want to write. You will need to indicate to Firestore that you want to store a server timestamp on a specific field. To do so, you will assign the placeholder FieldValue.ServerTimestamp.

The snippet below continues the method from above!

   // Continues from above, still inside writeMyData()
// Create the object with the empty data to read
MyData myData = new MyData {
// Now put a reference to the timestamp
myTimestamp = FieldValue.ServerTimestamp
};

Now, we need to actually write that data into Firestore. We continue with the same method, to write as we would do with any regular write:

   // Continues from above again!
// Trye to write...
doc?.SetAsync(myData, SetOptions.MergeAll);
}
// The above } closes the writeMyData() method

Important points from the above snippet:

  • Again, the ? operator prevents the writing call if the document is null. You can remove this if you want.
  • SetAsync has different options, but the example uses SetOptions.MergeAll , as it tends to be a common choice.
  • The writing is an asynchronous operation, and you should not attempt to read the file until the writing has completed. To do so, you can use .ContinueWith() right after the writing. If you are unfamiliar with threads in C#, I recommend you read a bit on that as well.

Now, you are ready to ready the data you just wrote.

Step 3: Read the Data Back

On this part, I will be assuming that: (a) you ensured that you finished writing the document, and (b) the references of step 2 are also initialised.

Just like writing, reading is also an asynchronous operation, so you cannot actually manipulate the result after the reading has completed. You can ensure this by concatenating a .ContinueWith() like I am doing below.

Then, your reading method will be as follows:

using Firebase.Firestore;
using Firebase.Extensions;
public void readMyData() { // Get the instance of your database
FirebaseFirestore db = FirebaseFirestore.DefaultInstance;
// Get a reference to the document you want to write
DocumentReference doc = db.Collection("collectionName")?.Document("docID").
// Attempt reading
doc.GetSnapshotAsync(Source.Default).ContinueWith((Task<DocumentSnapshot> t) => {
// Check if the document exists
if(t.Result.Exists) {
// Create a new custom object to load with the data
MyData readData = t.Result.ConvertTo<UserGameData>();
// Data is now loaded into your object! }
else {
// In this point, the document didn't exist
// ...and you'll need to handle this.
}
});
}

More important points:

  • Check that the .ContinueWith operation has an argument of a very specific type: a DocumentSnapshot . You can read more about that class in the official documentation.
  • The attribute t.Result.Exist will return true if the file existed and was retrieved, and false if it doesn’t exist. It won’t check for exceptions (such as loosing connectivity while reading).

But… we haven’t checked our data yet! And the gist is… that’s the main issue with the timestamp.

There are some problems with this:

  1. You need to have an attribute of the type object if you want to be able to put the placeholder FieldValue.ServerTimestamp to actually record the server time. If you try to make your attribute a Firebase.Firestore.Timestamp … it won’t work.
  2. The object that you retrieve from the database is actually a Firebase.Firestore.Timestamp .
  3. Because both values represent the same field, you can only have a single property in your user-defined class. And thus, it can only be of type object .

Step 4: Parse the Retrieved Data

Dispair not, you must. This can be easily solved by adding an extra method in your MyClass . This method will parse the object to a Timestamp and then to a DateTime so you can manipulate it in basic C#:

public DateTime parseMyTimestamp() {
// Return the time converted into UTC
return ((Timestamp)myTimestamp).ToDateTime().ToUniversalTime(); }

Let’s break that down. First, you cast the object into a Timestamp . Then, you convert that into a date time using .ToDateTime() . But the problem is that Firestore puts a servertime in the user’s timezone. Handling multiple timezones can be a mess, and you’ll probably be making this conversion several times, so we can just call .ToUniversalTime() in this same getter.

Then, you just need to remember to never use myDataInstance.myTimestamp , but instead call the parsed data using myDataInstance.parseMyTimestamp() .

Some Final Considerations

A common case when manipulating timestamps and dates, is converting them to strings. However, Timestamp and DateTime do not convert to strings in the same way.

What do I mean by this? Basically, if you do myTimestampData.ToString() , you will not be able to take that string and read it directly into a DateTime object. You need to actually parse it first:

string dateTime = myTimestampData.ToDateTime().ToString();

You can also read more about asychronous logic in Unity+Firebase in this great publication:

--

--

Fer Gamboa @ Prowess Games

Indie game dev based in Australia. Chasing big dreams, one mobile game at a time. Consider supporting me: https://ko-fi.com/prowessgames