Use `Range#cover?` for `this_week?`/`this_month?`/`this_year?`
this_week?、this_month?、this_year? が内部で Range#include? を使っていたため、日付範囲を走査するコストが高くなっていました。この PR では Range#cover? に置き換えることで、同等の判定結果を保ちつつ実行速度を大幅に向上させます。
背景
これらのメソッドは現在日の属する週・月・年を判定する際に、Date.current.all_week などが返す Date 範囲 に対して include? を呼び出していました。include? は範囲の各要素を #succ で順に走査するため、this_year? では約 365 回の比較が毎回発生します。
実際に this_year? が 365 回のイテレーションを行うことは、頻繁に呼び出される日時判定において非効率です。パフォーマンス改善のためには、範囲の端点だけを比較すればよいケースであることが判明しました。
同様の問題は this_quarter? でも指摘され、PR #57963 で既に Range#cover? が導入されています。本変更はその成果を this_week?、this_month?、this_year? にも適用し、メソッド群を一貫させることを目的としています。
技術的な変更
activesupport/lib/active_support/core_ext/date_and_time/calculations.rb において、4 つのメソッドが include? から cover? へ置き換えられました。変更は次の通りです。
変更前:
def this_week?(start_day = Date.beginning_of_week)
::Date.current.all_week(start_day).include?(to_date)
end
def this_month?
::Date.current.all_month.include?(to_date)
end
def this_year?
::Date.current.all_year.include?(to_date)
end
変更後:
def this_week?(start_day = Date.beginning_of_week)
::Date.current.all_week(start_day).cover?(to_date)
end
def this_month?
::Date.current.all_month.cover?(to_date)
end
def this_year?
::Date.current.all_year.cover?(to_date)
end
all_week、all_month、all_year が返す Date 範囲 は端点だけで包含判定可能です。cover? はその端点比較のみを行うため、走査コストが不要となり、実行速度が 10〜100 倍向上すると報告されています。機能的な振る舞いは変わらず、同一の真偽値が返ります。
設計判断
この変更は Range#cover? という既存メソッドの活用を選択した点が特徴です。新たな API を追加することなく、既存のメソッドシグネチャを保ちつつ内部実装を最適化しています。
this_quarter? がすでに cover? を使用していたことから、一貫性の確保 が設計上の主要な判断材料となりました。コード量の増加を抑え、後方互換性を維持しながらパフォーマンス改善を実現しています。
まとめ
this_week?、this_month?、this_year? が Range#include? から Range#cover? に置き換わり、走査コストが排除されました。挙動はそのままで、呼び出し頻度が高い日時判定に対して大幅な速度向上が期待できます。これにより、同系列メソッド全体が 端点比較ベース に統一され、Rails コアの一貫性と性能が同時に改善されました。