この記事で伝えたいこと
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.now
とDate
の型をそれぞれ見てみると異なる型を使っていました。
[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
参考