公開日

[Ruby]日時が特定日時の範囲内にあるかのチェック

(image)[Ruby]日時が特定日時の範囲内にあるかのチェック

TimeWithZone#between? で範囲内判定

特定日付が範囲内にあるかの判定を行うには ActiveSupport::TimeWithZone#between? が使える。

# 日時.between?(始端, 終端)
Time.now.between?(Date.yesterday, Date.tomorrow)

実際に範囲内チェックを行うとこんな感じ。

> Time.now.between?(Date.yesterday, Date.tomorrow)
=> true

> Time.now.between?(1.week.ago, Date.yesterday)
=> false

> Time.now.between?(Date.tomorrow, 1.week.since)
=> false

ただ上記の書き方の場合、始端もしくは終端が nil の場合にエラーが出てしまう。

Date.today.between?(Date.yesterday, nil)
ArgumentError: comparison of Date with nil failed
from (pry):9:in `between?`

Range#cover? で範囲内判定

上述の問題が回避するには、Rubyの Range#cover? が使える。

# (始端..終端).cover? 日時
> (Date.yesterday..Date.tomorrow).cover? Time.now
=> true

始端/終端が nil の場合

# 始端がnil
> (nil..Date.tomorrow).cover? Time.now
=> true

# 終端がnil
> (Date.yesterday..nil).cover? Time.now
=> true

下記の通り範囲内から外れた場合は false が帰ってくる。

> (nil..Date.yesterday).cover? Time.now
=> false
> (Date.tomorrow..nil).cover? Time.now
=> false

include? との違い

Range#include? と異なり <=> メソッドによる演算により範囲内かどうかを判定します。 Range#include? は原則として離散値を扱い、 Range#cover? は連続値を扱います。

ref. Range#cover? (Ruby 3.3 リファレンスマニュアル)

と書いてあるとおり、下記のような挙動の違いがある。

require 'date'
(Date.today - 365 .. Date.today + 365).include?(Date.today)    #=> true
(Date.today - 365 .. Date.today + 365).include?(DateTime.now)  #=> false
(Date.today - 365 .. Date.today + 365).cover?(Date.today)      #=> true
(Date.today - 365 .. Date.today + 365).cover?(DateTime.now)    #=> true

おまけ:between? のオリジナル定義

between?メソッドはオリジナル定義を辿ると module Comparable に存在します。

比較演算子 <=> をもとに self が min と max の範囲内(min, max を含みます)にあるかを判断します。

# Not Rails Project
> Time.now.class.included_modules
=> [Comparable, PP::ObjectMixin, Kernel]
# Rails Project
rails8(dev)> Time.zone.class.included_modules
=>
[Comparable,
 ActiveSupport::Dependencies::RequireDependency,
 PP::ObjectMixin,
 ActiveSupport::ToJsonWithActiveSupportEncoder,
 ActiveSupport::Tryable,
 JSON::Ext::Generator::GeneratorMethods::Object,
 Kernel]

(Special thanks: @knu さん)