読者です 読者をやめる 読者になる 読者になる

Rails Webook

自社のECを開発している会社で働いています。Rails情報やサービスを成長させる方法を書いていきます

Railsのタイムゾーンや時刻処理のまとめ

Rails初級 Rails ActiveSupport

Rubyでは日付(yyyymmdd)を扱うためにDateクラス、時刻(yyyymmdd hh:mm:ss)を扱うためにTimeクラスが定義さています。
これらを扱いやすくするためにRailsのActiveSupportでタイムゾーンなどいくつか機能が拡張されているので、それらをまとめました。

時刻関連のテストはTimecopの使い方を参照してください。

動作確認

  • Rails 4.1
  • ActiveSupport 4.1.7

目次

  1. 本ページの動作の前提条件
  2. Rails4アプリ全体でタイムゾーンを設定
  3. ユーザー単位でタイムゾーンを設定
  4. タイムゾーン一覧とOSのタイムゾーンを確認
  5. 現在の日付(Date)を取得(Date.today, Date.current)
  6. 現在の時刻(Time)を取得する(Time.now)
  7. 指定した日時(Time)を作成する(Time.local)
  8. 昨日、明日を取得する(yesterday, tomorrow)
  9. 昨月、昨年、翌月、翌年を取得する(prev_xxx, next_xxx)
  10. 相対的な時刻(Time)を返す
  11. 開始時間と終了時間を取得する(beginning_of_xxx, end_of_xxx)


1. 本ページの動作の前提条件

  • OS(Mac)のタイムゾーン: JST (+9:00) = Tokyo
  • Railsのタイムゾーン : CST (-5:00/-6:00) = US & Canada(application.rbconfig.time_zone = 'Central Time (US & Canada)'を設定)

アメリカなどの場合、サマータイムがあるため日付に応じて自動的にタイムゾーンが変わります。

Time.zone.local(2014, 11, 1) #=> Sat, 01 Nov 2014 00:00:00 CDT -05:00
Time.zone.local(2014, 11, 29) #=> Sat, 29 Nov 2014 00:00:00 CST -06:00


2. Rails4アプリ全体でタイムゾーンを設定

Rails全体のタイムゾーンを設定するにはapplication.rbconfig.time_zoneにタイムゾーンを設定します。

# config/application.rb
# デフォルトはUTC (0:00)

# タイムゾーンをセントラルタイム(CDT -5:00 / CST -6:00)に設定
# config.time_zone = 'Central Time (US & Canada)'

# タイムゾーンを東京(JST +9:00)に設定
# config.time_zone = 'Tokyo'

設定可能なタイムゾーンは「タイムゾーン一覧とOSのタイムゾーンを確認」を参照してください。




3. ユーザー単位でタイムゾーンを設定

ユーザー単位でタイムゾーンを設定するには、ユーザーにタイムゾーンを保持するカラムを追加し、コントローラーで設定する必要があります。

rails g migration add_time_zone_to_users time_zone
rake db:migrate


モデルにバリデーションをつける。

# app/models/user.rb
valications :time_zone, inclusion: { in: ActiveSupport::TimeZone.zones_map(&:name) }


ビューで更新するようにする。

# app/views/users/_form.html.erb
<div class="field">
  <%= f.label :time_zone %><br />
  <%= f.time_zone_select :time_zone %>
</div>


StringParamterにtime_zoneを追加する。

# app/controllers/users_controller.rb

private
  def users_params
    params.require(:user).permite(..., :time_zone)
  end


コントローラーでタイムゾーンを設定する。

# app/controllers/application_controller.rb
around_action :user_time_zone, if: :current_user

private
  def user_time_zone(&block)
    Time.use_zone(current_user.time_zone, &block)
  end
end


4. タイムゾーン一覧とOSのタイムゾーンを確認

rake time:zones:allですべてのタイムゾーンを確認できます。

rake time:zones:all
* UTC -11:00 *
American Samoa
International Date Line West
Midway Island
Samoa

* UTC -10:00 *
Hawaii

* UTC -09:00 *
Alaska
...


OSのタイムゾーンはrake time:zones:localで確認できます。

rake time:zones:local
* UTC +09:00 *
Osaka
Sapporo
Seoul
Tokyo
Yakutsk


5. 現在の日付(Date)を取得(Date.today, Date.current)

  • Date.todayで、現在の日付を取得する
  • Date.currentで、タイムゾーンに基づいた現在の日付を取得する
# JST (+ 9:00)
Date.today   # => Sat, 29 Nov 2014
# CST (- 5:00)
Date.current #=> Sat, 29 Nov 2014
# Time.zoneかconfig.time_zoneがセットされている場合、Time.zone.todayの結果を返す
# セットされていない場合、Date.todayの結果を返す


6. 現在の時刻(Time)を取得する(Time.now)

  • Time.nowで、現在の日時を取得する
  • Time.zone.nowで、タイムゾーンに基づいた現在の日時を取得する
Time.now      #=> 2014-11-29 17:00:46 +0900
Time.zone.now #=> Sat, 29 Nov 2014 02:01:41 CST -06:00

Time.current  #=> Sat, 29 Nov 2014 02:01:41 CST -06:00
# Time.zonかconfig.time_zoneがセットされている場合、Time.zone.nowの結果を返す
# セットされていない場合、Tiem.nowの結果を返す


7. 指定した日時(Time)を作成する(Time.local)

  • Time.localで、指定した日時を取得する
  • Time.zone.localで、タイムゾーンに基づいた指定した日時を取得する
Time.local(2014, 11, 29, 11, 22, 33)      #=> 2014-11-29 11:22:33 +0900
Time.zone.local(2014, 11, 29, 11, 22, 33) #=> Sat, 29 Nov 2014 11:22:33 CST -06:00


8. 昨日、明日を取得する(yesterday, tomorrow)

  • Time#yesterdayで、前日の日時を取得する
  • Time#tomorrowで、翌日の日時を取得する
t = Time.local(2014, 11, 30) #=> 2014-11-30 00:00:00 +0900
t.yesterday                  #=> 2014-11-29 00:00:00 +0900
t.tomorrow                   #=> 2014-12-01 00:00:00 +0900

# タイムゾーンは呼び出し元のTimeオブジェクトに依存する
t2 = Time.zone.local(2014, 11, 29) #=> Sat, 29 Nov 2014 00:00:00 CST -06:00
t2.yesterday                       #=> Fri, 28 Nov 2014 00:00:00 CST -06:00
t2.tomorrow                        #=> Sun, 30 Nov 2014 00:00:00 CST -06:00


9. 昨月、昨年、翌月、翌年を取得する(prev_xxx, next_xxx)

次のメソッドが定義されています。

  • Time#prev_month, Date#prev_month
  • Time#next_month, Date#next_month
  • Time#prev_year, Date#prev_year
  • Time#next_year, Date#next_year

また、タイムゾーンは呼び出し元のオブジェクトに依存します。




10. 相対的な時刻(Time)を返す

ActiveSupportによりNumericクラスには次のメソッドが拡張されている。

  • years (年)
  • mounths (月)
  • weeks (週)
  • days (日)
  • hours (時間)
  • minutes (分)
  • seconds (秒)

そして、次のメソッドをメソッドチェインすることで相対的な日付を返す

  • 後: from_now or since
  • 前: until or ago(time = :Time.current)

デフォルトで引数にTime.currentが指定されているため、Railsのタイムゾーンに基づいた現在日時からの相対時間を返す

Time.current    # => Sat, 29 Nov 2014 02:20:08 CST -06:00
# 1週間前
1.weeks.ago     # => Sat, 22 Nov 2014 02:21:23 CST -06:00
# 1日後
1.days.from_now # => Sun, 30 Nov 2014 02:20:32 CST -06:00

# 引数を指定することで、その日からの相対的な時間を指定できる
t = Time.local(2014, 11, 1) #=> 2014-11-01 00:00:00 +0900
1.months.ago(t)             #=> 2014-10-01 00:00:00 +0900


11. 開始時間と終了時間を取得する(beginning_of_xxx, end_of_xxx)

DateクラスとTimeクラスには、開始時刻や終了時刻を取得するために次のメソッドが定義されています。

  • beginning_of_hour, end_of_hour
  • beginning_of_day, end_of_day
  • beginning_of_week, end_of_week
  • beginning_of_month, end_of_month
  • beginning_of_quarter, end_of_quarter
  • beginning_of_year, end_of_year

次のようにすることで、ActiveRecordで月単位などでレコードを取得ができる

now = Time.current #=> Sat, 29 Nov 2014 02:26:53 CST -06:00
Order.where(order_at: now.beginning_of_month..now.end_of_month)


以上です。

参考文献