- 2016年1月15日
- Rails
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
1 2 3 |
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. # config.time_zone = 'Central Time (US & Canada)' |
上記のように書いてあり、config.time_zone のデフォルトは UTC。例えばここで、
1 |
config.time_zone = 'Tokyo' |
のように日本時間を初期設定にしておくこともできる。
また、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 の場合…
1 2 3 4 5 6 7 8 9 10 11 |
$ bundle exec rails c pry(main)> Time.zone.class => ActiveSupport::TimeZone pry(main)> Time.zone.name => "UTC" pry(main)> Time.zone.now => Fri, 15 Jan 2016 08:25:37 UTC +00:00 pry(main)> Time.now => 2016-01-15 17:25:40 +0900 |
となる。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
1 2 3 4 5 6 7 8 9 10 11 12 |
class ApplicationController < ActionController::Base around_action :set_time_zone, if: :user_time_zone_present? private def set_time_zone(&block) Time.use_zone(current_user.time_zone, &block) end def user_time_zone_present? current_user.try(:time_zone).present? end end |
これにより、一時的に 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
1 2 3 4 5 6 7 8 9 10 |
class SomeController < ActionController::Base def some_action # current_user のタイムゾーン(time_zone)での現在の時刻を取得 @time = Time.zone.now # または Time#in_time_zone や Time#current を使う # @time = Time.now.in_time_zone # @time = Time.current end end |
Ruby の Time.now は、環境変数 ENV[‘TZ’] のタイムゾーン、またはシステムのタイムゾーンでの現在時刻を出力します。
これに対して、Time.zone.now は、Rails での config.time_zone の値を反映した Time.zone(ActiveSupport#TimeZone)オブジェクトに基づく時刻を出力します。Rails の ActiveSupport#TimeZone, ActiveSupport#TimeWithZone クラスによる機能です。
1 2 3 4 5 6 7 8 9 10 11 12 |
pry(main)> Time.now.class => Time pry(main)> Time.zone.class => ActiveSupport::TimeZone pry(main)> Time.zone.now.class => ActiveSupport::TimeWithZone pry(main)> Time.current.class => ActiveSupport::TimeWithZone pry(main)> Time.now.in_time_zone.class => 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
1 2 3 4 5 6 |
class SomeController < ActionController::Base def other_action user = User.find(params[:id]) @time = Time.now.in_time_zone(user.time_zone) end end |
Time#in_time_zone に user.time_zone を引数で渡してやる。こうすると Time が ActiveSupport::TimeWithZone に変更されて、ユーザーのタイムゾーンを反映した時刻を返してくれます。逐一 Time#in_time_zone を使わなきゃいけない面倒臭さはあるのですけれど…。
以上ですが、Rails で複数のタイムゾーンに対応する場合に、もっと上手い解決法がありましたらぜひ教えて下さいませ。
- – 参考リンク –
- Working with time zones in Ruby on Rails ????Elabs
- Railsのタイムゾーンや時刻処理のまとめ – Rails Webook
- RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い – Qiita
- ruby on rails – Time.use_zone is not working as expected – Stack Overflow
- - 関連記事 -
- RailsのN+1問題をBulletで検出する
- RailsのActiveRecordで今日・昨日など特定の日に保存されたレコードを取得
- Railsで複数テーブルのjoins/includesとwhere検索
- RailsのActiveRecordでSQLite3/PostgreSQLのidをリセットする拡張
- Rails/jQuery UI AutocompleteでDBデータからオートコンプリート
- warning: toplevel constant/RuntimeError: Circular dependency detectedエラー
- Railsのvalidates :numericalityのバリデーションでnilを許可
- View Helperの動作をrails consoleのpryで確認
- Railsでoptgroup付きプルダウンメニューを配列・ハッシュから作成
- RailsのSTIでコントローラーをまとめ、Strong Parametersを動的に設定
Leave Your Message!