- 公開日
[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 さん)