RailsのTime.zone.nowとDateを比較するとfalseになる原因と解決法

4分で読める テック
最終更新:

この記事で伝えたいこと

Railsで日時比較をするときには日付の型を確認しようと言う話をします。

日時に応じて挙動が変わる処理の実装

WEBアプリケーションを開発していると、特定期間のみキャンペーンのバナーを表示させたいというような、期間などの時刻で動作する機能を開発する必要があります。 かつての自分は「その時間になったら手動でデプロイするのかな」と思っていたこともあったのですが、コードの中に時間による分岐を入れれば自動で機能がアクティブになることを教えてもらいました。

そんな自分が書いたコードが下記のような分岐です。

  Time.zone.now.between?(Date.new(2022, 12, 01), Date.new(2022, 12, 31))

しかし、2022年12月1日0:00(JST)になってもこれではうまく動作しませんでした。

[2] pry(main)> Time.zone.now.between?(Date.new(2022, 12, 01), Date.new(2022, 12, 31))
=> false

RubyとRailsぞれぞれの日時の型

どうしてこうなるのか少し調べてみると、RailsやRubyにはそれぞれたくさんの日時の型があることがわかりました。

Railsアプリケーションでは「システムまたは環境変数に設定されたタイムゾーン」と「application.rbに設定されたタイムゾーン」の2種類があります。 どちらも同じ設定になっている場合は問題が起きることは少ないですが、設定が異なっていると予期せぬ結果になる場合があります。

冒頭の処理で使ったTime.zone.nowDateの型をそれぞれ見てみると異なる型を使っていました。

[1] pry(main)> Time.zone.class
=> ActiveSupport::TimeZone
[2] pry(main)> Date.new(2022,12,01).class
=> Date

型を揃えた分岐にすることで解消

比較する型をどちらかにそろえれば解消できました。

Time.zone.localに合わせる

Time.zone.nowに合わせてActiveSupport::TimeWithZoneを使うようにすれば解消できました。

[1] pry(main)>  Time.zone.now.between?(Time.zone.local(2022, 12, 01), Time.zone.local(2022, 12, 31)
=> true
[2] pry(main)> Time.zone.local(2022, 12, 01).class
=> ActiveSupport::TimeWithZone

Dateに合わせる

またはDateに合わせる方法もあります。

[1] pry(main)> Time.zone.today.between?(Date.new(2022, 12, 01), Date.new(2022, 12, 31))
=> true
[2] pry(main)> Time.zone.today.class
=> Date

こうしてどちらかに揃えたことで意図するシステムの動作を担保することができました。

感想

今回はタイムゾーンがDate型に反映されなかったためエラーになってしまったようですが、どこでその分岐が起きたのかはまだ調査中になります。 仮説としてRailsのTimeWithZoneでのbetweenメソッド内部でutcによる比較になっている可能性がありそうでした。

ちょっとこのあたりだいぶ複雑でまだまだ理解ができていないと感じています。 もしもこのあたりご知見があるかたいらっしゃればコメントいただけると幸いですmm

参考

質問・リクエストを送る

記事についての質問や、取り上げてほしいテーマがあればお気軽にどうぞ。いただいた質問はブログ記事として回答し、Q&Aページで公開することがあります。