Cloud Firestore: Storing and querying for today's date over multiple UTC offsets?

saricden picture saricden · Jan 31, 2018 · Viewed 7.1k times · Source

I'm writing a web app using Firestore that needs to be able to show "Today's Popular Posts" and I'm having trouble getting the queries right when considering users in different timezones.

The dates are stored in the DB as UTC 0, then adjusted to the current user's UTC offset in the client via Moment.js. This works correctly.

When adding a new post I use firebase.firestore.FieldValue.serverTimestamp() to store the current server timestamp in a field called timestamp, like so:

const collectionRef = db.collection('posts');
collectionRef.add({
  name: "Test Post",
  content: "Blah blah blah",
  timestamp: firebase.firestore.FieldValue.serverTimestamp(),
  likeCount: 0
});

Then on the server I have a Cloud Function that runs on create and adds another field to the document called datestamp which is the the UTC 0 timestamp, but adjusted so that the time is the beginning of the day. The function looks like this:

exports.updatePostDate = functions.firestore
  .document('posts/{postID}')
  .onCreate((event) => {
    const db = admin.firestore();
    const postRef = db.doc('post/'+event.params.postID);
    const postData = event.data.data();

    const startOfDay = moment(postData.timestamp).startOf('day').toDate();

    return postRef.update({
      datestamp: startOfDay
    });
  });

Storing a timestamp where the time is always the beginning of the day enables me to write a query like this for finding all posts and ordering by popularity on a given day:

const startOfDayUTC = moment.utc().startOf('day').toDate();
const postQuery = db.collection('posts')
                    .orderBy('likeCount', 'desc')
                    .orderBy('timestamp', 'desc')
                    .where('datestamp', '==', startOfDayUTC)
                    .limit(25);

The problem is, depending on the user's UTC offset, this can display posts with two different dates when parsing the post's timestamp field. So even though the query is correctly fetching all the posts where the datestamp is say, 2018-01-30T00:00:00Z, the timestamp's date might not be the same once parsed. Here's an example of two posts:

Post 2:
likeCount: 1
timestamp (UTC 0): 2018-01-30T06:41:58Z
timestamp (parsed to UTC-8): 2018-01-29T22:41:58-08:00
datestamp (UTC 0): 2018-01-30T00:00:00Z

Post 1:
likeCount: 0
timestamp (UTC 0): 2018-01-30T10:44:35Z
timestamp (parsed to UTC-8): 2018-01-30T02:44:35-08:00
datestamp (UTC 0): 2018-01-30T00:00:00Z

So you can see, while the posts have the same datestamp, after adjusting the timestamp to the local UTC, the timestamp fields can end up being on two different days.

If anyone has a solution to this I would be very grateful.

Answer

TheeBen picture TheeBen · Jan 31, 2018

I think it is better to avoid functions in this case as you can perform compound queries now. You can simply use

query.where(date > lastMidnight).where(data < now).get().then(...)

so to speak to limit data which only belongs to one day and try to keep all your time variables in UTC 0 and just find the start point and the current time both client side and convert them to UTC0.

//get local time from midnight to now (local)
const now = new Date();
const lastMidnight = now.setHours(0,0,0,0);

//then convert those to UTC0 to pass on in your query to firestore
const lastMidNightUTC = new Date(lastMidnight + now.getTimezoneOffset() * 60000).toString();
const nowInUTC = new Date(now + now.getTimezoneOffset() * 60000).toString();

and you can get your data (remember you need to make an index or just run the query once and firebase SDK will generate a link to create the index in dev tools -> console , for you)

    query.where(date > lastMidNightUTC).where(data < now).get().then(...)