以下の4個の設定を考慮する必要がある。
- rubyプロセスのタイムゾーン
- config.time_zone
- config.active_record.default_timezone
- DBサーバ(postgres等)のタイムゾーン
1. rubyプロセスのタイムゾーン
- OSのタイムゾーン(
/etc/localtime
)または環境変数TZ
により決まる。 -
Time.now
やDate.today
に影響する。
2. config.time_zone
-
ActiveSupport::TimeWithZone
に影響する。- ActiveRecordの日時のフィールド(
created_at
など)はこのクラスなので、影響を受ける。 -
Time.current
Time.zone.now
Time.zone.local
もこのクラスなので影響を受ける。
- ActiveRecordの日時のフィールド(
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
で確認できる。参考: PostgreSQL日付操作まとめ -
SELECT now()
などに影響する。
おすすめ設定
(A) 1〜4すべてをJSTで統一
(B) 1, 2(Ruby側)はJSTにし、3, 4(DB側)はUTCにする
(C) 1〜4すべてをUTCで統一
日本国内でだけ開発・運用するなら(A)がおすすめ。
初期設定の手間こそかかるものの、一度設定してしまえば時刻データを見たとき頭の中で日本時間に変換する必要がなくなるので非常に楽である。アプリケーションにおいて時刻の重要度が高ければ高いほどそのメリットも大きくなる。
世間的には(B)もかなり一般的なようである。
Railsのデフォルトはconfig.time_zone = "UTC"
だし、DBサーバもAWS RDSやDockerなどではUTCがデフォルトになっている場合が多い。なので設定の手間を省ける。
(B)にする場合、RailsからのみDBを読み書きするなら特に何も意識しなくても問題は出ないが、Rails以外のフレームワークやSQLを使って読み書きする場合は、時刻をUTCに変換する必要があるか?を常に意識する必要がある。
グローバルなアプリケーションでは(C)が一般的なようである。
参考:https://blog.studysapuri.jp/entry/2016/12/05/090000
(A)すべてをJSTで統一する場合
ruby (OS) のタイムゾーン
ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
DBサーバ(postgres)
-- 現在のセッションのみタイムゾーンを変更するには
set time zone 'Asia/Tokyo';
-- 永続的にデータベースのタイムゾーンを変更するには
ALTER DATABASE d1 SET TIMEZONE TO 'Asia/Tokyo';
-- 永続化するにはpostgresql.confに
-- timezone = 'Japan'
-- と書いて再起動する。
-- 参考: https://zenn.dev/team_zenn/articles/postgresql-timestamp
Rails
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 # 現在の日本時間。Time.currentはTimeWithZoneを
=> 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
-
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セッションのタイムゾーンになる。入れたときどのタイムゾーン表記だったかの情報は保存されない。 ↩