Help us understand the problem. What is going on with this article?

Railsタイムゾーンまとめ

以下の4個の設定を考慮する必要がある。

  1. rubyプロセスのタイムゾーン
  2. config.time_zone
  3. config.active_record.default_timezone
  4. DBサーバ(postgres等)のタイムゾーン

1. rubyプロセスのタイムゾーン

  • OSのタイムゾーン(/etc/localtime)または環境変数TZにより決まる。
  • Time.nowDate.todayに影響する。

2. config.time_zone

  • ActiveSupport::TimeWithZoneに影響する。
    • ActiveRecordの日時のフィールド(created_atなど)はこのクラスなので、影響を受ける。
    • Time.current Time.zone.now Time.zone.local もこのクラスなので影響を受ける。

Railsのデフォルトでは"UTC"である。

3. config.active_record.default_timezone

  • DBに読み書きする時刻に影響する。
    • DBに書かれている時刻をどのタイムゾーンとして解釈するか。
    • 時刻をDBに書き込むとき、どのタイムゾーンに変換して書き込むか。

postgresの場合、Railsのmigrationでdatetime型を指定するとtimestamp without time zone型のカラムになる。この型は名前の通りタイムゾーン情報を持たない 1。したがってこのカラムに2018-06-11 01:23:45という値が入っていても、これがどこのタイムゾーンにおける時刻なのかはどこにも記録されていない。そこでRailsはconfig.active_record.default_timezoneの指定に従ってこの時刻を解釈する。:localの場合はrubyプロセスのタイムゾーンであると解釈し、:utcの場合はUTCつまり2018-06-11 01:23:45+00:00と解釈する。

Railsのデフォルトでは:utcである。

4. DBサーバ(postgres等)のタイムゾーン

  • postgresの場合はshow timezoneで確認できる。
  • SELECT now()などに影響する。

おすすめ設定

(A) 1〜4すべてをJSTで統一
(B) 1, 2(Ruby側)はJSTにし、3, 4(DB側)はUTCにする

日本国内でだけ開発・運用するなら(A)がおすすめ。
初期設定の手間こそかかるものの、一度設定してしまえば時刻データを見たとき頭の中で日本時間に変換する必要がなくなるので非常に楽である。アプリケーションにおいて時刻の重要度が高ければ高いほどそのメリットも大きくなる。

世間的には(B)もかなり一般的なようである。
Railsのデフォルトはconfig.time_zone = "UTC"だし、DBサーバもAWS RDSやDockerなどではUTCがデフォルトになっている場合が多い。なので設定の手間を大幅に省ける。
(B)にする場合、RailsからのみDBを読み書きするなら特に何も意識しなくても問題は出ないが、Rails以外のフレームワークやSQLを使って読み書きする場合は、時刻をUTCに変換する必要があるか?を常に意識する必要がある。

(A)すべてをJSTで統一する場合

ruby (OS) のタイムゾーン

ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

DBサーバ(postgres)

-- 現在のセッションのみタイムゾーンを変更するには
set time zone 'Asia/Tokyo';

-- 永続化させるにはpostgresql.confに
-- timezone = 'Japan'
-- と書いて再起動する。

Rails

config/application.rb
    config.time_zone = 'Tokyo'
    config.active_record.default_timezone = :local

コーディング規約

Rails上ではTimeクラスの代りにTimeWithZoneクラスを使うのがよいと言われている(参考:Rails 時刻処理では、Time.current, Time.zone.local を使う)。

非推奨 推奨
Time TimeWithZone
Time.now Time.zone.now (Time.currentでも同じ)
Time.parse('2007-02-10 15:30:45') Time.zone.parse('2007-02-10 15:30:45')
Time.at(1170361845) Time.zone.at(1170361845)

TimeとTimeWithZoneの違い

TimeはUTCとOSのタイムゾーンの2種類しか扱えない。TimeWithZoneは複数のタイムゾーン間を相互変換できる。

[2] pry(main)> a = Time.current               # 現在の日本時間
=> Tue, 12 Jun 2018 06:41:57 JST +09:00
[3] pry(main)> a.in_time_zone('Moscow')       # モスクワ時間に変換(日本との時差は6時間)
=> Tue, 12 Jun 2018 00:41:57 MSK +03:00
[4] pry(main)> a.in_time_zone('Moscow').in_time_zone('Hawaii')  # それをさらにハワイ時間に変換
=> Mon, 11 Jun 2018 11:41:57 HST -10:00

  1. postgresにはtimestamp with time zoneというデータ型もあるが、これは名前から推測されるのと違って、内部的にはUTCで保存される。2019-05-24T12:00:21+09:00のような値を入れたとき、timestamp without time zone型だと+09:00部分が無視されるのに対し、timestamp with time zone型だと+09:00部分も解釈されてUTCに変換されてから保存される。取り出すときのタイムゾーンはpostgresセッションのタイムゾーンになる。入れたときどのタイムゾーン表記だったかの情報は保存されない。 

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away