RailsとMySQLとでタイムゾーンが異なる場合の検索の仕方

RailsMySQLなどのDBとでタイムゾーンが異なる場合に、データ抽出や検索の際にミスが起こりそうなので自戒の意味を込めてメモします。
なおRailsコンソールで検索する方法について記載しています。

実行環境

ruby 2.6.6
Rails 6.0.3.6

まずはそれぞれにおけるタイムゾーンを確認します。

mysql でのタイムゾーン確認

UTCとなっています。

mysql> show variables like "%time_zone";
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | UTC    |
| time_zone        | SYSTEM |
+------------------+--------+
2 rows in set (0.00 sec)

mysql> select NOW();
+---------------------+
| NOW()               |
+---------------------+
| 2023-12-17 14:52:01 |
+---------------------+
1 row in set (0.00 sec)

Rails でのタイムゾーン確認

日本時間で検索しています。

pry(main)> Time.zone
=> #<ActiveSupport::TimeZone:0x000055555c991608 @name="Tokyo", @tzinfo=#<TZInfo::DataTimezone: Asia/Tokyo>, @utc_offset=nil>

Rubyコンテナサーバーのタイムゾーン

UTCとなっています。

pry(main)> Time.now
=> "2023-12-17T15:00:03.228+00:00"

Railsコンソールでの検索方法

いくつか方法があるのでそれぞれの違いを見ます。
なお現在時刻は日本時間で2023-12-18 00:00であるとします。

Time(RailsはTimeWithZone)クラスを使用する

Time.zone.now や TIme.zone.now.ago など TimeWithZone クラスのメソッドを使って検索します。

pry(main)> User.where("updated_at > ?", Time.zone.now).to_sql
=> "SELECT `users`.* FROM `users` WHERE (updated_at > '2023-12-17 15:00:47.921823')"

日本時間がUTC(9時間前)に変換されてデータベース検索を行なっていることがわかります。

日時の文字列で指定する

直接日時の文字列を指定して検索します。

pry(main)> User.where("updated_at > ?", '2023-12-18 00:00').to_sql
=> "SELECT `users`.* FROM `users` WHERE (updated_at > '2023-12-18 00:00')"

指定した時間からUTCには変換されていないことがわかります。
なので、UTCに変換してから指定しないと、日本時間に変換されると勘違いしていたら、期待した検索結果が得られないので注意が必要です。

Integerに変換する

Timeクラス(RailsならTimeWithZoneクラス)のオブジェクトを to_i でIntegerに変換したものを指定します。

# Time.zone.now → integer
pry(main)> User.where("updated_at > ?", Time.zone.now.to_i).to_sql
=> "SELECT `users`.* FROM `users` WHERE (updated_at > 1702825200)"

# Time.zone.parse → integer
pry(main)> User.where("updated_at > ?", Time.zone.parse("2023-12-18 00:00:00").to_i).to_sql
=> "SELECT `users`.* FROM `users` WHERE (updated_at > 1702825200)"

# 日本時間 2023-12-18 00:00 です
pry(main)> Time.zone.at(1702825200)
=> Mon, 18 Dec 2023 00:00:00 JST +09:00

Integerに変換しUnixTImeとして扱うことができるようになります。

pry(main)> User.where("updated_at > ?", Time.parse("2023-12-17 15:00:00").to_i).to_sql
=> "SELECT `users`.* FROM `users` WHERE (updated_at > 1702825200)"

上記のように、Time.parse でUTC時間のままTimeオブジェクトを to_i した時と同じ1702825200で検索されています。 そのため、タイムゾーンを気にすることなく検索することができます。

参考ページ

docs.ruby-lang.org