今の現場で、以前から9時前にテストを走らせると落ちる問題があり、
不意に原因が分かったので対応しました。
理解が若干曖昧だったので、改めて再現させて、ベストプラクティスを考えてみました。
先ずは、現象を再現させる
前提: 9時前に実行するか、PCの時間を戻して実行
% date 2023年 8月 6日 日曜日 08時37分07秒 JST
ユーザー(何でも良い)を作成して、今日以降に作成したのを取得してみるが、
取れない(想定通り)
% rails c > FactoryBot.create(:user) created_at: 2023-08-05 23:37:35.960766 > User.where(created_at: Date.today..) > User.where(created_at: Time.zone.today..) > User.where(created_at: Time.zone.today.to_datetime..) User Load (0.7ms) SELECT `users`.* FROM `users` WHERE `users`.`created_at` >= '2023-08-06 00:00:00' => []
原因
DBにはUTCで入っている為、日付だけ見ると前日になっている。
% rails db > SELECT * FROM users ORDER BY id DESC LIMIT 1\G created_at: 2023-08-05 23:37:35.960766
対策
.to_datetimeがタイムゾーンを持たない(+0000になる)のが原因の為、
.in_time_zoneを使うようにすればOK。
.to_timeでも挙動は問題ないけど、タイムゾーン持ちが明確になる。
% rails c > User.where(created_at: Time.zone.today.in_time_zone..) User Load (1.3ms) SELECT `users`.* FROM `users` WHERE `users`.`created_at` >= '2023-08-05 15:00:00' => [#<User <省略> created_at: "2023-08-06 08:37:35.960766000 +0900" <省略>>]
ベストプラクティス
.to_datetime → ※あえて、+0000にしたい場合は除く
(日時型と比較する場合).in_time_zone
(日付型と比較する場合).to_date
.to_time → .in_time_zone
Date.today, Date.current, Time.current.to_date → Time.zone.today
Date.yesterday → Time.zone.yesterday
Date.tomorrow → Time.zone.tomorrow
Time.now, Time.new(パラメータなし) → Time.current
Time.new(パラメータあり) → Time.zone.local