Time.zoneが一致しなくて検索できなかった問題
筆者がRailsのアプリを開発している時に、
日付を一致させる時にひと悶着あったので、備忘録として残しておきます。
今回使用する例
productsテーブル
id | date (datetime型) |
---|---|
1 | 2020-11-01 15:00:00 |
2 | 2020-11-02 00:00:00 |
DBの時刻はUTC とします。 |
問題
下記の例題で、true
or false
の値と、なぜそれを返す理由が説明できるでしょうか?
RailsアプリのTime.zoneは次の状態だとします。
.js
[*] pry(#<ProductsController>)> Time.zone
=> #<ActiveSupport::TimeZone:0x000055cc45b31408
@name="Tokyo",
@tzinfo=#<TZInfo::DataTimezone: Asia/Tokyo>,
@utc_offset=nil>
例題
.rb
[1] pry(#<ProductsController>)>
Product.find(1).date == "2020-11-01 15:00:00"
[2] pry(#<ProductsController>)>
Product.find(1).date == "2020-11-02 00:00:00"
[3] pry(#<ProductsController>)>
Product.find(1).date == Time.zone.parse("2020-11-01 15:00:00")
[4] pry(#<ProductsController>)>
Product.find(1).date == Time.zone.parse("2020-11-02 00:00:00")
[5] pry(#<ProductsController>)>
Product.find(2).date == "2020-11-01 15:00:00"
[6] pry(#<ProductsController>)>
Product.find(2).date == "2020-11-02 00:00:00"
[7] pry(#<ProductsController>)>
Product.find(1).date == Time.zone.parse("2020-11-01 15:00:00")
[8] pry(#<ProductsController>)>
Product.find(1).date == Time.zone.parse("2020-11-02 00:00:00")
[9] pry(#<ProductsController>)>
Product.find(1).date == Time.zone.parse("2020-11-02 09:00:00")
回答
[1] pry(#<ProductsController>)>
Product.find(1).date == "2020-11-01 15:00:00"
=> true
# 文字列で検索した場合、DBのレコードと一致するものを検索します。
# つまり、2020-11-01 15:00:00という値が一致するのでtrueとなります。
[2] pry(#<ProductsController>)>
Product.find(1).date == "2020-11-02 00:00:00"
=> false
# 純粋な文字列を比較してるので、
# 2020-11-01 15:00:00 == 2020-11-02 00:00:00はfalseとなります。
[3] pry(#<ProductsController>)>
Product.find(1).date == Time.zone.parse("2020-11-01 15:00:00")
=> false
# 今回のRailsアプリのTime.zoneはTokyoでした。
# Time.zone.parseとは、文字通りアプリのタイムゾーンに合わせて変換することです。
# UTCとTokyoの時間は9時間ずれてる(Tokyoが遅れてる)
# DBにsaveする時、2020-11-02 00:00:00 ➡️ 2020-11-01 15:00:00に変換されます。
# RailsがDBからレコードを取り出す時、+9時間して返します。
[4] pry(#<ProductsController>)>
Product.find(1).date == Time.zone.parse("2020-11-02 00:00:00")
=> true
# 上記で説明したとおり、Railsのタイムゾーンと同じ値を検索&parseしたので、trueを返します。
[5] pry(#<ProductsController>)>
Product.find(2).date == "2020-11-01 15:00:00"
=> false
# 文字列で検索した場合、DBと一致する値を返すため
[6] pry(#<ProductsController>)>
Product.find(2).date == "2020-11-02 00:00:00"
=> true
# 文字列で検索した場合、DBと一致する値を返すため
[7] pry(#<ProductsController>)>
Product.find(1).date == Time.zone.parse("2020-11-01 15:00:00")
=> false
# parseした後はDBの値 - 9 されるためfalse
# parseした後は、2020-11-01 06:00:00になる
[8] pry(#<ProductsController>)>
Product.find(1).date == Time.zone.parse("2020-11-02 00:00:00")
=> false
# parseした後はDBの値 - 9 されるためfalse
# parseした後は、2020-11-01 15:00:00になる
[9] pry(#<ProductsController>)>
Product.find(1).date == Time.zone.parse("2020-11-02 09:00:00")
=> true
# parseした後は、2020-11-02 00:00:00になるのでtrue
言いたかったこと
今回のRailsのTime.zoneはTokyoなので、
バックエンドのデータに-9時間してsaveしてる。
それに合わせて、Time.zoneをTokyoにparseしたら、
欲しかった情報を取得できました!
参考
ようやく:こうしきよめ
https://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html
助けられたteratail
https://teratail.com/questions/148819
RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い
https://qiita.com/jnchito/items/cae89ee43c30f5d6fa2c
おまけ
ふと疑問に思ったので、
ransackのコード見たら、Time.zone.parseしてました。
.rb
def cast_to_time(val)
if val.is_a?(Array)
Time.zone.local(*val) rescue nil
else
unless val.acts_like?(:time)
val = val.is_a?(String) ? Time.zone.parse(val) : val.to_time rescue val
end
val.in_time_zone rescue nil
end
end