Rubyなら Time.now
、Railsなら Time.zone.now
などで使う Time
や TimeWithZone
意識して使わないと思わぬバグを生む原因なのでその事例を箸休め的にご紹介。
環境
$ ruby -v
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-darwin18]
$ bundle exec rails -v
Rails 5.2.1
例
ISO8601の形式に沿う形式出力がRubyにあり、オプションとして引数に値を入れると小数点以下の値も付随してくれるので、そこで確認することができる。
require 'time'
=> true
now_time = Time.now
#=> 2018-12-01 11:42:01 +0900
now_time.iso8601
#=> "2018-12-01T11:42:01+09:00"
# .iso8601({{小数点以下の桁数}})で確認できる
now_time.iso8601(5)
#=> "2018-12-01T11:42:01.69147+09:00"
11:42:01.69147
となり小数点以下の秒数を保持していることがわかる
起こりうるケース
日時だけ保存するデータ(主にMySQLなどのRDSのDateTime等)は小数点以下の秒数をもっていないことが多い。そのため範囲比較をするときに目に見えにくい範囲も比較してしまい。予期せぬ挙動をすることがある。
例
期間の終了を term_end
として人が設定した値として持ち、それに対して特定の月の最終日と合致するかを確認する。
## 期間の終了日(今回はStringから起こす形)
term_end = "2018/12/31 23:59:59".in_time_zone
# => Mon, 31 Dec 2018 23:59:59 JST +09:00
## 特定の月
month_start_at = "2018/12/01 00:00:00".in_time_zone
# => Sat, 01 Dec 2018 00:00:00 JST +09:00
## 特定の月の最終日
month_start_at.end_of_month
# => Mon, 31 Dec 2018 23:59:59 JST +09:00
irb上で出力される見た目は同じだからイコールになりそうだが…
month_start_at.end_of_month == term_end
=> false
小数点のレベルで相違があるのでそうはならないのでそうはならない。
month_start_at.end_of_month.iso8601(5)
# => "2018-12-31T23:59:59.99999+09:00"
term_end.iso8601(5)
# => "2018-12-31T23:59:59.00000+09:00"