Rails3 + ActiveRecord で正しくtimezoneを設定する

アプリケーションのI18N対応をする中で出てくるタイムゾーン問題。今回はRails3環境で
ActiveRecordのTimezoneの設定をどのようにすべきかまとめてみました。


photo credit: Myxi via photo pin cc

Timezoneの問題はOS、DB、Frameworkといろいろ絡みうるので、一つ一つ行きたいと思います。

OSのTimezone

Amazon EC2などだったらそれぞれの地域のタイムゾーンで運用しているケースも
あるかと思いますが、JST(日本標準時)で運用しているところが多いのではないでしょうか。

$ date
Tue Jul  3 12:54:53 JST 2012
$ ls -l /etc/localtime
lrwxrwxrwx 1 root root 30 Jul  3 10:51 /etc/localtime -> /usr/share/zoneinfo/Asia/Tokyo

DB(MySQL)のTimezone

MySQLの場合、システムのタイムゾーンはローカル時間(JST)に、MySQLタイムゾーン
SYSTEM(つまりJST)になっています

$ mysql
> show variables like '%time_zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | JST    |
| time_zone        | SYSTEM |
+------------------+--------+

OSのタイムゾーンを変更してDBを再起動するとタイムゾーンがOSにあわせて変更されている様子が分かります

# rm /etc/localtime; ln -s /usr/share/zoneinfo/UTC /etc/localtime
# date
Tue Jul  3 04:03:05 UTC 2012
# service mysqld restart
# mysql
> show variables like '%time_zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | UTC    |
| time_zone        | SYSTEM |
+------------------+--------+

Rails3 + ActiveRecordのTimezone

結論からいうとDB自体のタイムゾーンは関係ありません。MySQLのtime_zoneがUTCであれ、
JSTであれActiveRecordで指定したタイムゾーンに変換された時刻が保存されます。
DBのNOW()を呼ぶのではなく、アプリーケーション側でタイムゾーン変換をしてるのでしょうね。

データ保存に利用される設定
  1. MyApp::Application.config.active_record.default_timezone
  2. ActiveRecord::Base.default_timezone
$ rails console
> MyApp::Application.config.active_record.default_timezone
=> nil
> ActiveRecord::Base.default_timezone
=> :utc

config/application.rbにて、何もTimezoneの設定をしていない場合、
MyApp::Application.config.active_record.default_timezoneがnilとなり、
ActiveRecord::Base.default_timezoneが:utcとなります。

最終的にはActiveRecord::Base.default_timezoneの値でDBに保存されるため、
この場合は:utcで保存されます。保存に利用するタイムゾーンを変更するには
config/application.rbにて、config.active_record.default_timezoneに
値を設定するとよいでしょう

# config/application.rb
config.active_record.default_timezone = :local

config.active_record.default_timezoneは:local もしくは :utcのいずれかを
設定することができます:localを指定するとOSのローカル時間で保存されます

$ rails console
> ActiveRecord::Base.default_timezone
=> :local

config/application.rbを修正したところです。 :localが設定されていることが分かります。
この状態でdatetimeを入力するとDBにはJSTで保存されていることが確認できます。
データの保存時のタイムゾーンはアプリケーションで統一せねばならず、かつその基準はUTCもしくは
OSのローカル時間であろうというとても合理的な設計になっています。

# config/application.rb
config.active_record.default_timezone = :utc
データ取得時に利用される設定
  1. MyApp::Application.config.time_zone
  2. Time.zone

データ取得時にはTime.zoneの値にしたがってデータが変換されます。
変換されるタイミングは、そのアトリビュートにアクセスしたタイミングになるため、注意してください
(変換時の基準となるDBのタイムゾーンには
MyApp::Application.config.active_record.default_timezoneが利用される)。
Time.zoneのデフォルト値を変更したい時は、config/application.rbのconfig.time_zoneを変更します

# config/application.rb
config.time_zone = 'Tokyo'

アクセスするユーザによってタイムゾーンを変更したい場合などは、before_filterなどで
セッション等からユーザ固有の設定タイムゾーンを取り出した上でTime.zoneを上書きするとよいと思います

結論:推奨設定

I18N対応のアプリケーション

以下の設定をした上で、ユーザごとにTime.zoneを設定する

# config/application.rb
config.time_zone = 'UTC'
config.active_record.default_timezone = :utc
日本のみで利用されるアプリケーション
# config/application.rb
config.time_zone = 'Tokyo'
config.active_record.default_timezone = :local