概要
RubyでTimeオブジェクトを扱ってると境界問題にぶち当たる。
普通にやってるとRangeオブジェクトなりを使ってそこまで問題になることはないが、普通じゃないやり方をしていて躓いた。
環境
Ruby: 2.6.2
Rails: 5.1.2
そもそもハマった問題
Time.now.end_of_day
=> 2020-05-15 23:59:59 +0900
これの次の値を取りたい。
※ end_of_dayはわかりやすいから使ってるだけであって、とある日の先端、終端が欲しいわけではない。飽くまでもTimeオブジェクトの次の値
真っ先に思いついたのがこれ
Time.now.end_of_day + 1
=> 2020-05-16 00:00:00 +0900
ただ、これは間違い
(Time.now.end_of_day + 1).iso8601(3)
=> "2020-05-16T00:00:00.999+09:00"
+ 1
は飽くまでも1秒加算であって、次の値に移行するわけではないので使えない。
Timeクラスを漁ってるとすごくそれっぽいメソッドを見つけた
https://docs.ruby-lang.org/ja/latest/method/Time/i/succ.html
Time.now.end_of_day.succ
(pry):109: warning: Time#succ is obsolete; use time + 1
=> 2020-05-16 00:00:00 +0900
しかし、時代遅れだから使うなって怒られる上 + 1
と同じことしてるだけらしい。だめ。
解決方法
結局良さげな方法はわからなかったからゴリ押し
例の問題だと少数9桁までしか見ていない
(Time.now.end_of_day + 1).iso8601(10)
=> "2020-05-16T00:00:00.9999999990+09:00"
つまり、1ナノ秒加算してやればいいわけだ
(Time.now.end_of_day + 1/1000000000.0).iso8601(9)
=> "2020-05-16T00:00:00.000000000+09:00"
できた!
おまけ
Time.now.iso8601(10)
=> "2020-05-15T19:56:55.4979370000+09:00"
Time.new(2020, 5, 16, 16, 59, 59.3).iso8601(9)
=> "2020-05-16T16:59:59.299999999+09:00"
Time.parse("2020-05-16T16:59:59.3+09:00").iso8601(9)
=> "2020-05-16T16:59:59.300000000+09:00"
Time.parse("2020-05-16T16:59:59.35555555555555555555+09:00").iso8601(20)
=> "2020-05-16T16:59:59.35555555555555555555+09:00"
だめだった
結論
- Timeオブジェクト内の小数秒の桁数は無限に持てる
- そのため次の値という概念がそもそも無い
- ただ、Timeオブジェクトの生成の仕方に依存するので、DBから引っ張ってくるならその有効桁数を考慮してやれば無理やり作れなくはない
- そもそも素直にRangeオブジェクトを使うなりして比較すべき