なぜRailsのall_dayなどが返すRangeは「以上・未満」ではなく「以上・以下」なのか?
Railsに限らず、期間を指定したいケースはたくさんある。
Rubyでは、期間を示す Range
クラスが用意されており
Railsでは、 Range
を利用して期間を取り扱いやすいように、その日の期間を示す all_day
だったり、その月の期間を示す all_month
だったり、標準で用意されている。
今回は、それらが、なぜ「以上・未満」ではなく、「以上・以下」で定義されているのか?
気になったので調べてみた。
個人的に、期間を示す際は「以上・未満」でなければ、 際どいタイミング(23時59分59秒999999..)で登録されたデータが漏れる可能性がある と思っていたのがキッカケ。
結論
RailsがDBの日時の精度を認識した上で、クエリを発行するので「以上・以下」でも、データの漏れは発生しない
本題
その日の期間を示す all_day
を見てみると、たしかに「以上・以下」で定義されている
補足:
(..) double-dot 「以上・以下」
(...) triple-dot 「以上・未満」
実際に、クエリを見てみる。
※以下、MySQLを想定
◆ 精度が0(長さ0で、秒以下の小数点は丸められる)場合
start_at: datetime(0)
# target_at = Time.zone.parse('2022-07-07')
# Hoge.where(start_at: target_at.all_day)
.. `start_at` BETWEEN '2022-07-06 15:00:00' AND '2022-07-07 14:59:59
=> 2022-07-07 14:59:59
と 2022-07-07 15:00:00
の間にはデータは入り得ない
◆ 精度が6(小数点以下6桁以下は丸められる)場合
created_at: datetime(6)
# target_at = Time.zone.parse('2022-07-07')
# Hoge.where(created_at: target_at.all_day)
.. `created_at` BETWEEN '2022-07-06 15:00:00' AND '2022-07-07 14:59:59.999999'
=> 2022-07-07 14:59:59.999999
と 2022-07-07 15:00:00.000000
の間にはデータは入り得ない
つまり、RailsがRangeを利用したクエリを発行する際は、その対象となるカラムの精度を考慮した上で、クエリを用意してくれるので、「以上・以下」でも問題ない。
⚠️ とはいえ、人間がクエリを書く際には精度を気にして書きたくないので「以上・未満」で。
余談
DBとは関係なしに、Rails上で表現できる日時の最大精度は小数点以下9桁までのようだ。
9桁を超えて表現しようと思うと ArgumentError
が発生する
irb(main):017:0> target_at.change(usec: Rational(999999999, 1000))
=> Thu, 07 Jul 2022 00:00:00.999999999 JST +09:00
irb(main):018:0> target_at.change(usec: Rational(9999999999, 1000))
/usr/local/bundle/gems/activesupport-7.0.3/lib/active_support/core_ext/time/calculations.rb:154:in `change': argument out of range (ArgumentError)
Discussion