DBに登録したと思った日付が、いざ取得すると9時間ずれてるとかいうよくある話で悩んだのでメモ。
ちなみに利用していたデータベースはMySQLである。
コード例
例としてMySQLにsample_timesというテーブルがあり、そこにtimestamp型のtest_timeという名前のカラムがあると想定する。
now_time = DateTime.now SampleTime.create(:test_time => now_time)
上記のようなコードで今の時間を作成とする。
そしていざ登録した時間を取得すると9時間ずれている。
17:00に登録したはずなのに、データ上は8:00になっているというよくあるタイプ。
ずれが”9時間”ということで、すぐにタイムゾーンの設定が原因だとは分かるけれどどうすればいいのか。
9時間ずれた要因
結構ややこしくて頭がパンクしそうになったのだけれど、ざっくりと下記のような感じだったらしい。
now_time = DateTime.now
サーバーのシステム時間を取得。使用していたサーバーはJST(日本時間)。17:00 JSTが取得できる。
SampleTime.create(:test_time => now_time)
TimeWithZone登場。
ModelなんかのActiveRecordを経由して時間をやり取りする場合はActiveSupport::TimeWithZoneが使用されるらしいです。
config.time_zoneに左右されるらしく、time_zoneを指定していなかった場合、デフォルトのUTCに変換されるみたいですね。
というわけで、私はtime_zoneを指定していなかったので、ここで17:00 JSTを渡したけれど8:00 UTCに変換されます。
MySQLに登録
8:00 UTCがMySQLに渡されて登録されます。
ただし、MySQLのdatetime型はタイムゾーンを保持しません。
なので、データ上は単なる”8:00″となります。
SampleTime.first.test_time
作成したデータをActiveRecord経由して取得します。
MySQLから取得したデータは”8:00″。タイムゾーンも特にないのでUTCと判断してActiveSupport::TimeWithZoneは何もせず、8:00 UTCを返してくれる……というわけです。
直した方法
今回発覚したこの現象、すでにリリースされたプロジェクトであったため、configを変えるのは危険ということで力技を使用しました。
追加機能以外は特に日付時間まで確認するようなものがないためでもあります。
SampleTime.first.test_time.in_time_zone('Tokyo')
in_time_zoneを指定して明示的に日本時間に変換をかけます。
この方法はJSTがUTCに変換されたデータが登録されてUTCで受け取るという前提です。
力技なのでリリースしていないプロジェクトならば、素直にtime_zoneを設定した方が賢明かと思われます。
※参考記事
Railsと周辺のTimeZone設定を整理する (active_record.default_timezoneの罠)
http://qiita.com/joker1007/items/2c277cca5bd50e4cce5e