Tech Hotoke Blog

IT観音とは私のことです。

システムタイムゾーンを考慮したDB保存にハマっているRails開発者へ

これは何?

Railsアプリ→ MySQLのTIME STAMPの保存でハマったのでメモ

前提

Ruby on Rails: 7.0.2 MySQL: 8系

Tips

  • 事象
    • 以下のようにscheduled_atをTime.zoneで更新をかけたところ、保存されている値が scheduled_at= '2024-04-03 03:00:00UTC時刻で保存されてしまっていた。
    • scheduled_at は DATETIME型
    scheduled_time = if Time.zone.now >= Time.zone.now.beginning_of_day + 12.hours + 1.minute
                         Time.zone.tomorrow.beginning_of_day + 12.hours
                       else
                         Time.zone.today.beginning_of_day + 12.hours
                       end
      hoge.update!(scheduled_at: scheduled_time)

Rails側の確認

irb(main):001:0> Time.now
=> 2024-04-04 02:56:00.07041709 +0000
irb(main):002:0> Time.current
=> Thu, 04 Apr 2024 11:56:11.926433860 JST +09:00
irb(main):003:0> Time.zone.now
=> Thu, 04 Apr 2024 11:56:26.882822349 JST +09:00

上記のことから、RailsタイムゾーンJSTになっていることがわかる。 Time.nowだけUTC時刻になっているのはシステムのゾーンがUTCのママだからだとわかる。

補足
` config/application.rb に config.time_zone = 'Tokyo' ` を記載していても、Time.nowには反映されません。
なぜなら、TimeはRubyの組み込みのクラスなので、Railsの設定の影響を受けないためです。
Rubyのタイムゾーンは以下の2つによって決まります。

■ システムのタイムゾーン
■ 環境変数 ENV['TZ'] の値

環境変数が設定されていればそちらが優先されます。
環境変数が設定されていなければシステムのタイムゾーンが使われます。

例
Asia/Tokyo
US/Central

⚠️ 無効なタイムゾーン('Tokyo'や'Hoge'など)が設定されている場合は ** 特にエラーにならず、世界標準時(UTC)がデフォルトになる ** そうなので注意が必要(システムのタイムゾーン設定にもなりません)

MySQLタイムゾーンの設定を確認

  • 以下のコマンドを実行すると、system_time_zoneがUTCになっていた。

SHOW VARIABLES LIKE '%time_zone%';

補足

■システムタイムゾーン
ホストマシンのタイムゾーンです。
サーバ起動時にホストマシンのタイムゾーンを特定して、system_time_zone システム変数に設定されます。

■サーバタイムゾーン
MySQLサーバの現在のタイムゾーンです。
time_zone システム変数に設定され、現在動作しているタイムゾーンを示します。time_zone の初期値は 'SYSTEM' となっており、サーバーのタイムゾーンがシステムタイムゾーンと同じであることを示します。

まとめ

これらを踏まえると、Rails側の設定の問題でDBの保存処理実行時にUTC時間に変換されてしまっている様子。 MySQLの設定は関係ない。。。?

config.active_record.default_timezoneの設定はDBを読み書きする際に、DBに記録されている時間をTime.utcで読むかTime.localで読むかを設定する。 :utcの場合DBに記録されている時間はUTC扱いで、この時DBサーバのタイムゾーン設定は考慮しない。

ActiveRecordインスタンスが持っているTimeWithZoneの値をUTCに変換し、その時刻をDBに書き込む。 :localの場合は、DBに記録されている時間はシステムのタイムゾーンとして扱う。

ActiveRecordインスタンスが持っているTimeWithZoneの値をシステムのタイムゾーンに変換し、その時刻をDBに書き込む。

参考

RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い #Ruby - Qiita

Rails アプリケーションの設定項目 - Railsガイド

Railsと周辺のTimeZone設定を整理する (active_record.default_timezoneの罠) #Ruby - Qiita

https://ruby-doc.org/3.0.6/Time.html