JavaScript
date
Firebase
Firestore

Firestore の Timestamp の仕様変更による警告と、その対処

firestore.Timestamp クラスが追加された

Firebase@4.13.0 から firestore.Timestamp クラスが追加された
オプションで有効化することで, Timestamp 型のフィールドは新しい Timestamp クラスのインスタンスとして取得できる(今までは Date 型で取得できた)
このオプションはのちにデフォルトで有効化されるそうなので, 対応する必要がある

[feature] Added a new Timestamp class to represent timestamp fields, currently supporting up to microsecond precision. It can be passed to API methods anywhere a JS Date object is currently accepted. To make DocumentSnapshots read timestamp fields back as Timestamps instead of Dates, you can set the newly added flag timestampsInSnapshots in FirestoreSettings to true. Note that the current behavior (DocumentSnapshots returning JS Date objects) will be removed in a future release. Timestamp supports higher precision than JS Date.

https://github.com/firebase/firebase-js-sdk/blob/firebase%404.13.0/packages/firestore/CHANGELOG.md

  • CHANGELOG には Unreleased と書かれているので分かりづらいが, この時点で npm install した firebase には新しい Timestamp の仕様が含まれている
  • このあと Firestore@0.4.0 で正式?に CHANGELOG に載ることになる

https://github.com/firebase/firebase-js-sdk/blob/master/packages/firestore/CHANGELOG.md#040

警告

今までと同じように Firestore に Date 型のデータを入れると次のような警告がコンソールに表示される

[Date 型の値]  @firebase/firestore: Firestore (4.13.0):
The behavior for Date objects stored in Firestore is going to change
AND YOUR APP MAY BREAK.
To hide this warning and ensure your app does not break, you need to add the
following code to your app before calling any other Cloud Firestore methods:

  const firestore = firebase.firestore();
  const settings = {/* your settings... */ timestampsInSnapshots: true};
  firestore.settings(settings);

With this change, timestamps stored in Cloud Firestore will be read back as
Firebase Timestamp objects instead of as system Date objects. So you will also
need to update code expecting a Date to instead expect a Timestamp. For example:

  // Old:
  const date = snapshot.get('created_at');
  // New:
  const timestamp = snapshot.get('created_at');
  const date = timestamp.toDate();

Please audit all existing usages of Date when you enable the new behavior. In a
future release, the behavior will change to the new behavior, so if you do not
follow these steps, YOUR APP MAY BREAK.

対処

指示通りこんな風に変更する

import firebase from 'firebase';
import 'firebase/firestore';

const config = {
    /* firebase config */
};
firebase.initializeApp(config);

const firestore = firebase.firestore();
const settings = { timestampsInSnapshots: true };
firestore.settings(settings);

あたらしい Timestamp クラスは Date のメソッドを持っている訳ではないので, 勝手に変更されるとアプリケーションが壊れてしまう. そこで, 明示的に Timestamp クラスに変換させるオプションが追加されている

次に Timestamp 型を扱っている箇所を すべて 次のように修正する

const timestamp = snapshot.get('created_at');

const date = timestamp.toDate(); // JS Date 型が欲しい場合
const seconds = timestamp.seconds; // 秒数が欲しい場合
const nanoseconds = timestamp.nanoseconds; // ナノ秒(?)が欲しい場合 (主にサーバ側で作ったタイムスタンプ用)

所感

サーバ側のタイムスタンプはマイクロ秒単位で保存されているらしい. しかし JS Date 型はミリ秒単位でしか値を保持できないので, 変換時に切り詰めが発生する. これでは不便だし, バグの原因にもなるかも知れない. だから JS Date を標準で使うのはやめて, みんなが使える Timestamp クラスを用意しよう, ということだと理解している

TypeScript を使っているケースでは嬉しいアップデートなんじゃないかと思う. 残念ながら flow-typed には未だ反映されていない. 頑張ろう

参照

https://firebase.google.com/docs/reference/js/firebase.firestore.Timestamp

https://github.com/firebase/firebase-js-sdk/pull/544