freeeの開発情報ポータルサイト

Rails の time_zone ヒヤリハット

こんにちは!freee の gon です。 freee Developers Advent Calendar 2024の2日目の記事です。

freee ではマイクロサービスを含め大小さまざまなサービスが稼働してますが、あるサービスで Rails の time_zone の設定をし忘れており、time_zone がデフォルトの UTC 扱いで動いていました(freee では Asia/Tokyo で揃っている)。開発中にバグが見つかったことで判明し、time_zone を設定し直すことで対応をしました。この記事では、実際に起こった問題とその際に調査したこと、具体的な対応、また同じ間違いを起こさないための提案について書き記しておこうと思います。

起きた問題

時刻の判定が9時間ずれる

当たり前に9時間時間がズレていたわけですが……。このサービスでは、freeeのライセンスの有効/無効を判定している箇所があり、その際に現在時刻を渡すのですが、その判定が9時間ズレていました。具体的には、その日付で有効になるはずライセンスが朝の9時まで無効になっていた、というような具合です。

is_valid = license.check_validity(Time.current) # 内部的には別のマイクロサービスに更に問い合わせる

ここで、Time.current を呼び出していたのですが、この Time.current は Rails で設定した time_zone を参照するため、UTCでの時刻を渡していました。しかし、問題が起きていなかった箇所もありました。なぜ……。

調査と対応

Rails の time_zone とシステムのタイムゾーンについて

Rails の time_zone は、Rails の設定ファイルである config/application.rb で設定します。デフォルトでは UTC が設定されていますが、例えば、日本時間に設定する場合は以下のように設定します。

config.time_zone = 'Tokyo'

この設定をすると、すべての時刻で日本時間が使われるようになります……だとうれしいのですが、実際にはそうではありません。Ruby の組み込みの Time.now などは、Rails の time_zone ではなく、システムのタイムゾーン /etc/localtime を参照するのです(と言うのはググるとたくさんでてきます)!

[1] pry(main)> Time.now.zone # ← これはシステムのタイムゾーンを見る
=> "JST"
[2] pry(main)> Time.current.zone # ← これは Rails の time_zone を見る
=> "UTC"

問題が起きたり起きなかったりする理由は、この違いによるものでした。

Rails で time_zone が使われる箇所

いくつかのドキュメントやRailsのコードを調査し、Rails で time_zone が使われる箇所を調べました。目立って使われてるのは下記の2箇所のようです。総じて ActiveSupport::TimeWithZone を Rails 側で作成するときに、time_zone が参照されるみたいでした。

(1) ActiveRecord の datetime, time 型のカラムの attribute

created_at, updated_at を含む ActiveRecord の datetime, time 型のカラムの attribute には、 time_zone で設定したタイムゾーンが指定された ActiveSupport::TimeWithZone クラスのオブジェクトが使われます。ただし、これはあくまで ActiveRecord 内での話で、データベースに保存されるときは必ず UTC に変換されます。詳しくは ActiveRecord のコメントに書かれています。データベース側に保存される値は変わらないので、アプリケーション内でこれらの値を触っている箇所をチェックすれば大丈夫そうでした。

(2) Date.current, Time.current, Time.zone.now

今回問題になった Date.current, Time.current では time_zone で設定したタイムゾーンが指定された ActiveSupport::TimeWithZone クラスのオブジェクトが使われます。

対応

意外と影響が少なそうなことがわかったので、気合(grep)で上記を影響箇所を洗い出してチェックし、Rails の time_zone を Tokyo に変更することにしました。とは言っても、多くの場合はタイムゾーン情報が付いてるので、そのままで問題ないことが多かったです。フロントエンドは new Date にちゃんと渡していれば安心!また、このヒヤリハットは社内でも共有され、社内の Rails テンプレートで config.time_zone が設定されるようになりました🎉

まとめ

  • Rails の time_zone はシステムのタイムゾーンと違うから気をつけよう
  • Rails で時刻を取得するときは、ActiveSupport::TimeWithZone のインスタンスが返ってくるオブジェクトを使おう

freee Developers Advent Calendar 2024、明日は bucyou による「ActiveSupport::CodeGenerator で遊ぼう」です!お楽しみに。