RubyのTimeの比較は同じ日付・時・分・秒でもfalseが返ることがある
まとめ
- Timeの比較は1秒以下をみるので、パッと見同じ日付・時・分・秒でもfalseが返ることがある。
- 同じ日付・時・分・秒をtrueにするには1秒以下をto_iで切り捨てる
本筋
Railsで時刻を比較する機会はなかなか多いと思う。
時刻の比較で、テストを書いていたときに同じケースを比較してtrueが返ってきてほしいところをfalseが返ってくるケースがあった。
最初ActiveSupport::TimeWithZoneと比較してる中で何かあるのかなと思った。
github.com
こういう挙動を調べるのにTraceLocationはとても手っ取り早くてとても助かる。
Generated by trace_location at 2019-11-03 16:48:00 +0900
~/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/time_with_zone.rb:225
ActiveSupport::TimeWithZone#<=>
def <=>(other) utc <=> other end # called from (pry):10
~/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/time_with_zone.rb:64
ActiveSupport::TimeWithZone#utc
def utc @utc ||= period.to_utc(@time) end # called from ~/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/time_with_zone.rb:226
~/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/core_ext/time/calculations.rb:292
Time#compare_with_coercion
def compare_with_coercion(other) # we're avoiding Time#to_datetime and Time#to_time because they're expensive if other.class == Time compare_without_coercion(other) elsif other.is_a?(Time) compare_without_coercion(other.to_time) else to_datetime <=> other end end alias_method :compare_without_coercion, :<=> alias_method :<=>, :compare_with_coercion # called from ~/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activesupport-6.0.0/lib/active_support/time_with_zone.rb:226
alias_methodで最終的に<=>メソッドが走るので、要は最終的にActiveSupport::TimeWithZoneであれ、Timeに変換して比較しているだけであって、そこは関係ないと。
で、なのでTimeの比較を調べてみる。
丁寧に同一時刻の比較を書いてくれている。
Timeの比較は1秒以下の値を見てそこの大小を比較していることがわかる。
なので、パッと見同じ日付・時・分・秒でもfalseが返ることがあるということだ。
これをtrueにしたい場合、ミリ秒以下を切り捨てればよいので、to_iとかで比較してあげると良い。