LoginSignup
1
0

More than 3 years have passed since last update.

【Rails】日時の文字列をTime.zone.parseしてDBのレコードと一致させる方法

Posted at

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は次の状態だとします。

[*] pry(#<ProductsController>)> Time.zone
=> #<ActiveSupport::TimeZone:0x000055cc45b31408
 @name="Tokyo",
 @tzinfo=#<TZInfo::DataTimezone: Asia/Tokyo>,
 @utc_offset=nil>

例題

[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してました。

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
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0