おはようございます。今日はRubyやRailsでの日付の扱いについて書いていきたいと思います。この記事を読むと日時データを扱う際のベストプラクティスが分かります。
筆者は駆け出しなので間違っていることも多々あるかとは思いますが、分かり次第随時更新していきたいと思います。
タイムゾーンについて
まずは、日付を扱う話をするのに必須なタイムゾーンについてです。
コンピュータの世界では標準をUTC (協定世界時:Coordinated Universal Time)
としています。日本時間はJST (日本標準時:Japan Standard Time)
で表され、UTCより9時間進んでいます。
素のRubyの場合
素のRubyでは、タイムゾーンは以下によって決まります。環境変数が設定されている場合、そちらが優先されます。
- OSのタイムゾーン
- 環境変数ENV[‘TZ’]の値
環境変数が設定されているかどうかはprintenv
コマンドで確認ができます。
printenv TZ
Railsの場合
Railsでは、上記とは別にapplication.rb
でデフォルトのタイムゾーンを設定できます。
config.time_zone = 'Tokyo'
それでは、次は日付を扱うクラスについて見ていきます。
クラスの種類
純粋なRubyには3種類のクラスが存在します。
- Timeクラス
- Dateクラス
- DateTimeクラス
Railsでは、上記クラスが拡張されていて様々なメソッドが追加されます。また、上記3つとは別にActive::Support::TimeWithZone
クラスが使えるようになります。
Timeクラスについて
Time
クラスで使えるメソッドを見ていきます。
純粋なRubyのTimeクラス
Time.now
=> 2021-03-22 07:51:16 +0900
Time.new(2021, 3, 22, 7, 53, 0)
=> 2021-03-22 07:53:00 +0900
Time.new(2021,1,1)
=> 2021-01-01 00:00:00 +090
Time.now.zone
=> "JST"
time = Time.now
time.year
=> 2021
time.month
=> 3
time.day
=> 22
time.min # minuteじゃなくminでした
=> 34
time.sec
=> 59
time.monday?
=> true
time.strftime("%Y/%m/%d")
=> "2021/03/22"
Railsによって拡張されたTimeクラスのメソッド
time = Time.now
time + 10
=> 2021-03-22 09:33:59 +0900
time + 10.second
=> 2021-03-22 09:33:59 +0900
time + 10.seconds
=> 2021-03-22 09:33:59 +0900
time + 10.minute
=> 2021-03-22 09:43:49 +0900
time + 10.hours
=> 2021-03-22 19:33:49 +0900
time + 10.days
=> 2021-04-01 09:33:49 +0900
time + 1.month
=> 2021-04-22 09:33:49 +0900
time + 3.years
=> 2024-03-22 09:33:49 +0900
time.yesterday
=> 2021-03-21 08:54:35 +0900
time.tomorrow
=> 2021-03-23 09:32:11 +0900
time.next_month
=> 2021-04-22 09:33:49 +0900
time.prev_month
=> 2021-02-22 09:33:49 +0900
time.next_year
=> 2022-03-22 09:33:49 +0900
time.prev_year
=> 2020-03-22 09:33:49 +0900
time.since(2.months)
=> 2021-05-22 09:33:49 +0900
time.ago(2.months)
=> 2021-01-22 09:33:49 +0900
time.since(2.years)
=> 2023-03-22 09:33:49 +0900
time.ago(2.years)
=> 2019-03-22 09:33:49 +0900
time.beginning_of_month
=> 2021-03-01 00:00:00 +0900
time.end_of_month
=> 2021-03-31 23:59:59 +0900
time.beginning_of_year
=> 2021-01-01 00:00:00 +0900
time.end_of_year
=> 2021-12-31 23:59:59 +0900
time.next_occurring(:wednesday)
=> 2021-03-24 09:33:49 +0900
time.next_occurring(:sunday)
=> 2021-03-28 09:33:49 +0900
time.prev_occurring(:sunday)
=> 2021-03-21 09:33:49 +0900
time.next_week(:wednesday)
=> 2021-03-31 00:00:00 +0900
time.prev_week(:wednesday)
=> 2021-03-17 00:00:00 +0900
time.beginning_of_day
=> 2023-02-26 00:00:00 +0000
# https://github.com/rails/rails/blob/HEAD/activesupport/lib/active_support/core_ext/time/calculations.rb#L241
time.end_of_day
=> 2023-02-26 23:59:59.999999999 +0000
# https://github.com/rails/rails/blob/HEAD/activesupport/lib/active_support/core_ext/time/calculations.rb#L259
自分で調べてみてこんなにもメソッドがたくさんあるのかと思いました。Time
クラスを使えばほとんどのことは表現できそうですね。
Dateクラス
時間情報を持ちません。また、require 'date'
としないと使えないので注意です。
require 'date'
Date.today
=> #<Date: 2021-03-22 ((2459296j,0s,0n),+0s,2299161j)>
Date.new(2021,3,22)
=> #<Date: 2021-03-22 ((2459296j,0s,0n),+0s,2299161j)>
Time
クラスで日付を扱えるので使う機会はそこまで多くないでしょう。
DateTimeクラス
Dateクラスのサブクラスで時間情報を扱えるようにしたものです。公式のリファレンスで下のように非推奨とされているので、Timeクラスを使ったほうがよいでしょう。
DateTime は deprecated とされているため、 Timeを使うことを推奨します。
https://docs.ruby-lang.org/ja/latest/class/DateTime.html
require 'date'
DateTime.now
=> #<DateTime: 2021-03-22T07:57:10+09:00 ((2459295j,82630s,374058000n),+32400s,2299161j)>
Rubyで日付や時間情報を扱う場合はTimeクラスを使う
Active::Support::TimeWithZoneクラス
Railsで使える日付を扱うクラスです。これは次のような特徴を持っています。
- Rails独自のクラスで、Timeクラスと完全な互換性がある。なので、Timeクラスで使用できるメソッドはすべて使える
- 実装はTimeクラスを継承しているのではなく親クラスはObject
- タイムゾーンはapplication.rbの設定が適用される
RailsでTimeWithZoneクラスを使ったほうがいい理由
国をまたいだ開発をする際、Aさんの環境変数TZはUTC、Bさんの環境変数TZはJSTとなっているとします。そうした場合に、AさんとBさんで表示される日時が異なってきます。TimeWithZone
クラスを使えばapplication.rb
の設定が適用されるので、環境による差はなくなります。なので、Railsを使って日付を扱う場合は、TimeWithZone
クラスを使うのがよいでしょう。
TimeWithZoneの使い方
current
メソッドやzone.now
メソッドを使うとTime
クラスをTimeWithZone
クラスに変換できます。これにより、Time
クラスを使っていると思っていても実はTimeWithZone
クラスを使っていることも多いです。また、おなじみのcreated_at
の中身もTimeWithZone
クラスです。
Time.now
=> 2021-03-22 08:47:56 +0900
Time.now.class
=> Time
Time.current
=> Mon, 22 Mar 2021 08:46:06 JST +09:00 # 曜日やタイムゾーンに関する情報まで出力されている
Time.current.class
=> ActiveSupport::TimeWithZone
Time.zone.now
=> Mon, 22 Mar 2021 08:49:45 JST +09:00 # Time.zone.nowとTime.currentは同等
User.first.created_at.class # created_atの中身もActive::Support::TimeWithZone
=> ActiveSupport::TimeWithZone
Railsで使える便利メソッド
最後にRailsで使える便利なメソッドを紹介しておこうと思います。
- to_timeメソッド
Timeクラスのオブジェクトに変換します。 - in_time_zoneメソッド
TimeWithZoneクラスのオブジェクトに変換します。
"2020-03-02".to_time
=> 2020-03-02 00:00:00 +0900
"2020-03-02".in_time_zone
=> Mon, 02 Mar 2020 00:00:00 JST +09:00
"2020".in_time_zone
ArgumentError: argument out of range # 一定の規則を満たさないとエラーになります。
time = Time.now
=> 2021-03-22 10:55:07 +0900
time.in_time_zone
=> Mon, 22 Mar 2021 10:55:07 JST +09:00 # もちろん、Timeクラスのオブジェクトでも変換できます。
これらはTime
クラス → TimeWithZone
クラスだけじゃなく、String
クラス → TimeWithZone
クラスへの変換もできるので、開発で使う機会も多いのではないかと思います。
- RailsではTimeWithZoneクラスを使う
- どの地域のタイムゾーンを使うかはapplication.rbで設定ができる
まとめ
2021年3月時点でのRubyとRailsで日付を扱うベストプラクティスはRubyがTimeクラス、RailsがTimeWithZoneクラスです。これらを意識して使うといいかと思います。
ここまで読んでくださりありがとうございました。