Railsで複数タイムゾーンに対応

先日書いた記事 RailsのActiveRecordで今日・昨日など特定の日に保存されたレコードを取得 | EasyRamble の後半で少し触れた、Rails で世界中の複数のタイムゾーンに対応する場合の続き。

国際化する Rails アプリケーションの場合、User モデルに time_zone カラム等を持たせて、ユーザーごとに異なる複数のタイムゾーンに対応する場合もあるかと思いますが、そういったケースを対象とした話です。

— 環境 —
Rails 4.1.1
Ruby 2.1.1

スポンサーリンク

Rails が使用するタイムゾーン(config.time_zone/Time.zone)

まず、システムとは別に Rails によって設定されるタイムゾーンですが、これは config/application.rb の config.time_zone で設定する。

config/application.rb

上記のように書いてあり、config.time_zone のデフォルトは UTC。例えばここで、

のように日本時間を初期設定にしておくこともできる。

また、Rails の API ガイドに…

If you set config.time_zone in the Rails Application, you can access this TimeZone object via Time.zone:

とありますので、config.time_zone を設定したら、そのタイムゾーンが Time.zone を通して ActiveSupport::TimeZone のオブジェクトとして取得できます。config.time_zone が UTC の場合…

となる。Time.zone.now とすると Rails のタイムゾーン(ここでは UTC)が使われ、Time.now ではシステムのタイムゾーン(ここでは JST +0900)が使われる。この辺りややこしいのですが、以下の Qiita 記事が大変参考になります。

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

ApplicationController で current_user のタイムゾーンを設定

以上を踏まえまして、Rails アプリケーションにログイン中のユーザー(current_user)のタイムゾーンを使用するための実装コードです。

認証エンジンとして Devise を使っていると、ログイン中のユーザーを current_user で取得できます。なので、この記事中で current_user が指すのはログイン中ユーザーの意味です。また、User モデルに time_zone カラムがあり、そのカラムにユーザーごとのタイムゾーンが文字列として保存されている場合の例とします。

以下のように ApplicationController に書くことで、コントローラーの各アクションで current_user のタイムゾーンをほぼ自動で設定できる。

app/controllers/application_controller.rb

これにより、一時的に config.time_zone をログイン中ユーザーのタイムゾーン(current_user.time_zone)に変更する処理と同様になりまして、Time.zone が返す ActiveSupport::TimeZone オブジェクトが current_user.time_zone の値を反映したものとなる。

そして、上述の around_action により設定されたユーザー(current_user)のタイムゾーン(current_user.time_zone)での現在時刻を取得する場合、以下のように Time.zone.now とします。

app/controllers/some_controller.rb

Ruby の Time.now は、環境変数 ENV[‘TZ’] のタイムゾーン、またはシステムのタイムゾーンでの現在時刻を出力します。

これに対して、Time.zone.now は、Rails での config.time_zone の値を反映した Time.zone(ActiveSupport#TimeZone)オブジェクトに基づく時刻を出力します。Rails の ActiveSupport#TimeZone, ActiveSupport#TimeWithZone クラスによる機能です。

詳しくは以下にリンク掲載した API ガイド参照。

ActiveSupport::TimeZone
ActiveSupport::TimeWithZone

current_user 以外のユーザーに対応

current_user のタイムゾーンについては、上述の通り around_action で Time.zone(ActiveSupport::TimeZone クラスのオブジェクト)を一時的に変更して、利用時にも ActiveSupport::TimeWithZone を使うことで対応できます。

current_user 以外のユーザーのタイムゾーンを考慮する場合ですが、以下のようにやるのが良いかな〜と考えました。

app/controllers/some_controller.rb

Time#in_time_zone に user.time_zone を引数で渡してやる。こうすると Time が ActiveSupport::TimeWithZone に変更されて、ユーザーのタイムゾーンを反映した時刻を返してくれます。逐一 Time#in_time_zone を使わなきゃいけない面倒臭さはあるのですけれど…。

以上ですが、Rails で複数のタイムゾーンに対応する場合に、もっと上手い解決法がありましたらぜひ教えて下さいませ。

スポンサーリンク
パーフェクト Ruby on Rails は、最近読んだ Rails 本の中では一番役に立った本です。Chef や Capistrano など Rails と共によく使用される技術にも触れてあります。下の本はこれの Rails3 版の本を読みましたが、入門的な内容で Rails の機能全体を網羅されています。
 
Twitterを使っていますのでフォローお願いたします!ブログの更新情報もつぶやいてます^^
(英語学習用)

Leave Your Message!