This article is an translation of a Japanese article I posted earlier.

Original article


One of the Firestore data types is the Timestamp type. This is a proprietary type of Firestore.

Timestamp has a toDate() method that can be called to convert data to Date type of JavaScript. After getting data on the client side, it is a common practice to call toDate() first when using the data.

If all Timestamps in a document can be treated as Date type on the client side, it is useful to make a function that execute toDate() on all Timestamp fields of the getting document.

I’ve created a function to do this, so I will introduce here.

TL;DR

Here is the code. I’ll explain later.

import firebase from 'firebase/app';

const { Timestamp } = firebase.firestore;

type Datefied<T> = {
  [K in keyof T]: T[K] extends typeof Timestamp
    ? Date
    : T[K] extends typeof Timestamp[]
    ? Date[]
    : T[K] extends unknown[] | Record<string, unknown>
    ? Datefied<T[K]>
    : T[K];
};

const datefy = <T extends Record<string, unknown>>(document: T) => {
  Object.entries(document).forEach(([key, value]) => {
    if (typeof value !== 'object' || value === null) return;
    if (value instanceof Timestamp) {
      // @ts-ignore
      document[key] = value.toDate();
      return;
    }
    // @ts-ignore
    document[key] = datefy(value);
  });
  return document as Datefied<T>;
};

How to use

Just pass the data getting from Firestore to datefy().

datefy() converts all fields of Timestamp values in the document to values after toDate() and returns them with the Date type.

const snapshot = await db.collection('users').doc('***').get();
const data = snapshot.data();

console.log(data);
// The following <Timestamp> is assumed to contain the actual Timestamp value.
// {
//   name: 'Alice',
//   birthday: <Timestamp>,
//   friends: [
//     { name: bob, meetAt: <Timestamp> },
//     { name: charlie, meetAt: <Timestamp> },
//   ],
//   favoriteDates: [<Timestamp>, <Timestamp>, <Timestamp>]
// }

type Timestamp = firebase.firestore.Timestamp;

type Data = {
  name: string;
  birthday: Timestamp;
  friends: { name: string; meetAt: Timestamp }[];
  favoriteDates: Timestamp[];
};

const datefiedData = datefy<Data>(data);

console.log(datefiedData);
// The following <Date> contains the value obtained by toDate() of Timestamp.
// {
//   name: 'Alice',
//   birthday: <Date>,
//   friends: [
//     { name: bob, meetAt: <Date> },
//     { name: charlie, meetAt: <Date> },
//   ],
//   favoriteDates: [<Date>, <Date>, <Date>]
// }

// The type of datefiedData is as follows.
// {
//   name: string;
//   birthday: Date;
//   friends: { name: string; meetAt: Date }[];
//   favoriteDates: Date[];
// };

Explanation

As for the internal implementation of datefy(), it checks the value of the object accepted as an argument, and if it is a timestamp, it executes toDate(). If the value is an object or an array, the same process is recursively execute on its content.

The same applies to Datefied<T>. If the type is Timestamp, change it to Date, and if it is an array or object, do the same recursively to its contents. If it is any other type, leave it as it is without conversion.

Thank you for reading.

I hope this article was helpful!