Cloud FunctionsでFirestoreのTimestamp取得すると時間がずれる

2021/03/07に公開2

Cloud Functions内でFirestoreに登録した日時データを取得したら、どういうわけか日本標準時ではなく、Fri Mar 05 2021 15:00:00 GMT+0000 (Coordinated Universal Time) のように協定世界時の標準時となってしまう現象に遭遇しました。その解決に1日時間を潰してしまったのでこちらで供養したいと思います。

事象

現在、非常食管理アプリを作成しています。
その中で登録したデータに「通知日」をもたせておき、今日が通知日であるデータをfunctionsでユーザーに通知するという処理をつくっていました。

ところが、Firestoreに登録したデータをCloud Functionsで取得してログに表示すると、通知日が日本標準時(JST)ではなくグリニッジ標準時(GMT)で9時間前の時間になってしまって、うまくデータを取得できない事象が発生しました。

Firestoreに登録された値(UTC+9で日本標準時になっている)
Image from Gyazo

Cloud Functionsで上記データを取得してログに出したとき(GMT+0000で協定世界時の標準時になっている)
Image from Gyazo

最終的に有効だった解決策

  • Cloud FunctionsのGCP側のコンソールでランタイム環境変数にTZ=Asia/Tokyoを設定してあげる

調べたこと

実は他にもいろいろDateの使い方が間違ってないかとか、Firestoreからの取得が間違ってないかとか、GMTとかUTCってなんなん?等も調べたんですが、結果的に解決につながったのは以下の3つを調べていったのが大きかったです。

時間のズレが問題なので、Firebase側のリージョンやタイムゾーン設定、もしくはFunctionsの関数の書き方あたりに原因がありそうだと検討をつけて調査を進めました。

  1. Cloud FunctionsのソースでタイムゾーンをAsia/Tokyoに設定されているか?
  2. Firestore, Cloud Functionsのロケーションがasia-northeast1になっているか?
  3. Cloud FunctionsのタイムゾーンがAsia/Tokyoになっているか?

Cloud FunctionsのソースでタイムゾーンをAsia/Tokyoに設定されているか?

最初に「Firestore functions 時間」でググったときにでてきた解決策で、functionsの関数を書くときにリージョンやタイムゾーンを指定する必要があるとのことでした。
当初、Cloud Functionsの関数処理にちゃんとリージョンやタイムゾーンをうっかり指定していなかった
ので、公式ドキュメントを参考に以下のように修正を加えました。(一部省略)

functions/src/index.ts
[...]
exports.dailyNotificationSender = functions
   // リージョンを指定
  .region('asia-northeast1')
  .pubsub.schedule('0 10 * * *')
  // タイムゾーンを指定
  .timeZone('Asia/Tokyo')
  .onRun(async (context) => {
    const jst = new Date().toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' });
    const todayStart = new Date(jst);
[...]

ただ、修正したあと現在デプロイされている関数を削除したうえで、再デプロイし実行したのですがうまくいきませんでした。

Firestore, Cloud Functionsのロケーションがasia-northeast1になっているか?

次にあんまり初期設定等でミスをしていてロケーションがasia-northeast1になっていないのでは?と思いコンソールを確認しましたが、以下の通りちゃんと東京リージョンになっていました。

Firestore側
Image from Gyazo

Cloud Functions側
Image from Gyazo

Cloud FunctionsのタイムゾーンがAsia/Tokyoになっているか?

で、最終的に有効だったのがCloud Functionsのタイムゾーンの設定をすることでした。ただ、これGCP側のコンソールから設定する必要があります。

  1. GCPのコンソールでCloud Functionsを開く
    Image from Gyazo

  2. 対象の関数をクリックして以下の画面で「編集」を押す
    Image from Gyazo

  3. ランタイム環境変数にTZ=Asia/Tokyoを足して保存
    Image from Gyazo

この方法でちゃんと日本標準時で取得できるようになったのですが、一つ問題点があってどうもローカルからfirebase deployするとTZ=Asia/Tokyoが消えてしまうようです。

その解決策としては、関数の中でprocess.env.TZ = 'Asia/Tokyo'を設定してあげればいけました。

functions/src/index.ts
import * as sgMail from '@sendgrid/mail';
import { format } from 'date-fns';
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';

const db = admin.initializeApp().firestore();
sgMail.setApiKey(functions.config().sendgrid.key);
// ソース内で環境変数としてセットすればOK
const timezone = 'Asia/Tokyo';
process.env.TZ = timezone;

exports.dailyNotificationSender = functions
  .region('asia-northeast1')
  .pubsub.schedule('0 10 * * *')
  .timeZone(timezone)
  .onRun(async (context) => {
    const jst = new Date().toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' });
    const todayStart = new Date(jst);
[...]

参考

Cloud Functions のロケーション  |  Firebase
協定世界時 - Wikipedia
グリニッジ標準時 - Wikipedia
GMT/UTC +0 hours - Time Now | GMT
firebase functionsでDate()使ったときに9時間ずれてしまうのを直す的なお話 ~ 適当な感じでプログラミングとか!
https://www.yuulinux.tokyo/18671/

Discussion

いわたちゃんいわたちゃん

firestoreのTimestampにはタイムゾーンの情報は無くて、ブラウザで見るときはブラウザのタイムゾーンで表示してるだけみたいです(ブラウザのタイムゾーンを変えたら表示が変わった)

参考 https://stackoverflow.com/questions/55714631/firestore-timestamp-fromdate-not-utc

ぶるーぶるー

コメントありがとうございます!
返信気づかず大変失礼しました・・・

firestoreのTimestampにはタイムゾーンの情報は無くて、ブラウザで見るときはブラウザのタイムゾーンで表示してるだけみたいです(ブラウザのタイムゾーンを変えたら表示が変わった)

これは知りませんでした。ありがとうございます。