Railsタイムゾーンまとめ

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

1. rubyプロセスのタイムゾーン
2. config.time_zone
3. config.active_record.default_timezone
4. 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` もこのクラスなので影響を受ける。

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

# 3. config.active_record.default_timezone

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

postgresの場合、Railsのmigrationで`datetime`型を指定すると`timestamp without time zone`型のカラムになる。この型は名前の通りタイムゾーン情報を持たない [^timestamp_with_time_zone]。したがってこのカラムが`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日付操作まとめ](https://qiita.com/aosho235/items/ec19d200f3bb42738030)
- `SELECT now()`などに影響する。


# おすすめ設定

(A) 1〜4すべてをJSTで統一
(B) 1, 2（Ruby側）はJSTにし、3, 4（DB側）はUTCにする
(C) 1〜４すべてを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) のタイムゾーン

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

DBサーバ（postgres）

```sql
-- 現在のセッションのみタイムゾーンを変更するには
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/application.rb
    config.time_zone = 'Tokyo'
    config.active_record.default_timezone = :local
```

# コーディング規約

Rails上では`Time`クラスの代りに`TimeWithZone`クラスを使うのがよいと言われている（参考：[Rails 時刻処理では、Time.current, Time.zone.local を使う](
https://doruby.jp/users/takeshita/entries/Rails-%E6%99%82%E5%88%BB%E5%87%A6%E7%90%86%E3%81%A7%E3%81%AF%E3%80%81Time-current--Time-zone-local-%3CTimeWithZone%E3%82%AF%E3%83%A9%E3%82%B9%3E-%E3%82%92%E4%BD%BF%E3%81%86)）。

|非推奨|推奨|
|---|---|
| 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は複数のタイムゾーン間を相互変換できる。

```pry
[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
```

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