今回の記事ではDockerを使ったRailsの開発環境で、MySQLに対してJST(日本時間)でレコードを入れる方法を書いて行きます。
RailsではデフォルトのタイムゾーンがUTCになっているため、日本時間だと思ってdb:seedで初期データを投入したりすると、想定していた時間とは違う値でレコードがインサートされてしまいます。
今回紹介する方法はとても簡単なので、TimeZoneをJSTで固定したい方は是非参考にしてみてください。
開発環境とバージョン
- Docker
- Rails 6.0.1
- MySQL Ver 8.0.18
今回利用する環境ではDockerを使っていますが、Dockerを使っていない場合でも今回紹介する方法でタイムゾーンをJSTに変更可能です。
タイムゾーンをJSTに変更する方法
早く変更箇所が知りたいという人のために、先に変更方法だけざっくりと記載します。
- 環境変数TZを用意し、 ‘Asia/Tokyo’ をセットする
- config/application.rbに下記の2行を足す
- config.time_zone = ‘Asia/Tokyo’
- config.active_record.default_timezone = :local
それぞれの設定項目がどう影響するか、確認方法などを次の項目から説明していきます。
Rails(Ruby)のタイムゾーンを確認
まずは変更を加える前にrails consoleでirbを起動してTimeコマンドの挙動を確認します。
Rails Console
$ rails c
irb(main):001:0> Time.now
=> 2020-01-11 20:48:31 +0000
irb(main):002:0> Time.current
=> Sat, 11 Jan 2020 20:48:35 UTC +00:00
irb(main):003:0> Time.zone.now
=> Sat, 11 Jan 2020 20:49:58 UTC +00:00
Time.nowではTZが表示されていませんが、他の2つはしっかりとUTCと記載されているのが分かります。
Rubyプロセスのタイムゾーンを変更
最初に環境変数TZをセットしてRubyプロセスのタイムゾーンを変更していきます。
Dockerを使っている場合はdocker-compose.ymlか、.envファイル経由でセットするのが楽だと思います。
docker-compose.yml
build: .
environment: #追加
TZ: "Asia/Tokyo" #追加
docker-composeの変更は一度dockerコンテナを再起動しないと反映されないので、docker-compose downとdocker-compose upの2つのコマンドを実行して再起動を行った後に動作確認をしましょう。
Rails Console
$ rails c
irb(main):001:0> Time.now
=> 2020-01-12 06:07:44 +0900
irb(main):002:0> Time.current
=> Sat, 11 Jan 2020 21:07:45 UTC +00:00
irb(main):003:0> Time.zone.now
=> Sat, 11 Jan 2020 21:07:47 UTC +00:00
Time.nowの結果の末尾に +0900と記載され、JSTの時間が表示されるようになりました。
Railsのtime_zoneを変更する
続いてRails側の設定変更です。
config/application.rb
module App
class Application < Rails::Application
# 末尾に下記の1行を追加
config.time_zone = 'Asia/Tokyo' #追加
end
end
config.time_zoneの変更を行う事でTimeやTimeWithZoneの挙動が下記のように変わります。
Rails Console
$ rails c
irb(main):001:0> Time.now
=> 2020-01-12 06:21:15 +0900
irb(main):002:0> Time.current
=> Sun, 12 Jan 2020 06:21:18 JST +09:00
irb(main):003:0> Time.zone.now
=> Sun, 12 Jan 2020 06:21:23 JST +09:00
しかしこの状態でdb:seedやActiveRecord経由でインサートを行うと…
Rails Console
$ rails c
irb(main):001:0> user = User.new
(0.5ms) SET NAMES utf8mb4, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
irb(main):002:0> user.execute_time = '2020-1-12-12:00:00'
=> "2020-1-12-12:00:00"
irb(main):003:0> user.save
(0.3ms) BEGIN
User Create (0.4ms) INSERT INTO `users` (`execute_time`, `created_at`, `updated_at`) VALUES ('2020-01-12 03:00:00', '2020-01-11 21:30:00.00000', '2020-01-11 21:30:00.00000')
12:00にセットしたexecute_timeが 03:00として扱われ、日本時間の01-12 06:30に入れたデータが 前日の01-11 21:30になってしまいました。
よって上記のtime_zoneの設定だけではActive RecordのタイムゾーンはまだUTCのままだということがわかります。
ActiveRecordのタイムゾーンをJSTに変更する
RailsではActive Recordのタイムゾーンを変更するのも1行追加するだけで可能です。
config/application.rb
module App
class Application < Rails::Application
config.time_zone = 'Asia/Tokyo'
# 末尾に下記の1行を追加
config.active_record.default_timezone = :local # 追加
end
end
ActiveReocrdのdefault_timezone = :localに指定することで、Rubyプロセスのタイムゾーンを見てくれるようになります。
Rails Console
$ rails c
irb(main):001:0> user = User.new
(0.4ms) SET NAMES utf8mb4, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
irb(main):002:0> user.execute_time = '2020-01-12 12:00:00'
=> "2020-01-12 12:00:00"
irb(main):003:0> user.save
(0.4ms) BEGIN
User Create (0.7ms) INSERT INTO `users` (`execute_time`, `created_at`, `updated_at`) VALUES ('2020-01-12 12:00:00', '2020-01-12 06:40:00.00000', '2020-01-12 06:40:00.00000')
(3.1ms) COMMIT
=> true
無事に発行されるINSERT文も想定通りのものになってくれました。
こんな感じでRailではTZの環境変数を用意して、config/application.rbに2行追加するだけでタイムゾーンをJSTにすることが出来るよ!
タイムゾーンがJSTにならない場合
今回紹介した方法でもまだタイムゾーンが日本時間のJSTにならない場合は下記の事を確認してみましょう。
- Command Lineにdate を入力したら日本時間が返ってくるか
- docker-compose変更後にコンテナを再起動したか
- コードを変更した後にRails Consoleを起動しなおしたか
まとめ
タイムゾーンを固定しないと、時間に関連する処理を行った際に予期せぬ問題が発生する場合があります。
今回紹介した方法でしっかりと、どのタイムゾーンで処理を行っているかを意識してコーディングするようにしましょう。