Rails探検録 ActiveSupport 編: TimeZone

# 【概要】 
最近、ライブラリを読むのが楽しくて、今まで敬遠していた Rails のソースコードを読み始めたので、記録していく。全部のメソッドは書いてないよ。

---

- 環境
    - Ruby 2.7.0 
    - Rails 6.0.2
- 参考: 学習情報 URL
    - [Ruby 2\.7\.0 リファレンスマニュアル](https://docs.ruby-lang.org/ja/latest/doc/index.html)
    - [Ruby on Rails \- APIdock](https://apidock.com/rails?q=date_or_time)
    - [rails/rails: Ruby on Rails](https://github.com/rails/rails)

---

**※ 例文の実行環境は既存のRailsプロジェクト**

```ruby:bash
gem install timecop
rails c

# `rails c`起動後, timecop を読み込む( ディレクトリは,`gem which timecop`で検索　)
require "/usr/local/bundle/gems/timecop-0.9.1/lib/timecop.rb"

# zone を指定
Time.zone = "Asia/Tokyo"
=> "Asia/Tokyo"

# 本記事における現在時刻を`２０２０年３月１日12:00`に固定
time_for_freeze = Time.local(2020, 3, 1, 12)
Timecop.freeze time_for_freeze
```

- 参考資料
    - [RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い \- Qiita](https://qiita.com/jnchito/items/cae89ee43c30f5d6fa2c?utm_content=bufferd880a&utm_medium=social&utm_source=facebook.com&utm_campaign=buffer#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE%E5%89%8D%E6%BA%96%E5%82%99)


# 【本文】

## □ Time Class と Date Class 拡張

```ruby
time = Time.now
=> 2020-03-01 12:00:00 +0900
time.class
=> Time

after_time = time.in_time_zone
=> Sun, 01 Mar 2020 12:00:00 JST +09:00
after_time.class
=> ActiveSupport::TimeWithZone

# 引数で他都市の TimeZone を指定可能
after_time = time.in_time_zone("London")
=> Sun, 01 Mar 2020 03:00:00 GMT +00:00
```
- ↑ Time と Date インスタンスを TimeWithZone クラスに変換する。


## □ Time Class 拡張

```ruby:
Time.zone
=> #<ActiveSupport::TimeZone:0x0000563071f03910
 @name="Asia/Tokyo",
 @tzinfo=#<TZInfo::DataTimezone: Asia/Tokyo>,
 @utc_offset=nil>
```
- ↑ 設定された zone を元に TimeZone インスタンスを生成する。

```ruby
Time.zone = "Sapporo"
=> "Sapporo"

Time.zone
=> #<ActiveSupport::TimeZone:0x0000563071fd3958
 @name="Sapporo",
 @tzinfo=#<TZInfo::DataTimezone: Asia/Tokyo>,
 @utc_offset=nil>
```

- [↑ 設定可能な TimeZone](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/values/time_zone.rb) に変更する。

```ruby
Time.use_zone("London") do
  Time.zone.now
end
=> Sun, 01 Mar 2020 03:00:00 GMT +00:00
```
- ↑ ブロック内で指定した TimeZone による処理が可能となる。
- ↑ 処理後は、元の TimeZone に戻る。

```ruby
Time.find_zone!("London")
=> #<ActiveSupport::TimeZone:0x00005630705df1f0
 @name="London",
 @tzinfo=#<TZInfo::DataTimezone: Europe/London>,
 @utc_offset=nil>

Time.find_zone("Naboo")
=> nil
```

- ↑ Rails 内の定数で定義された都市の TimeZone を表示する。
- ↑ 存在しなかったら例外起こすけど、`!`を外したら nil を返す。

## □ TimeZone Class

### ■ Class Methods
```ruby
ActiveSupport::TimeZone::MAPPING
=> {"International Date Line West"=>"Etc/GMT+12",
 "Midway Island"=>"Pacific/Midway",
 "American Samoa"=>"Pacific/Pago_Pago",
 # 省略...
 "Osaka"=> "Asia/Tokyo",
 "Sapporo"=> "Asia/Tokyo",
 "Tokyo"=> "Asia/Tokyo",
 # 省略...
}
```
- ↑ Rails で定義可能な TimeZone を確認できる。
- ↑ 定数だが便宜上、ここに記載した( 他の Class Method 紹介してないや... )

### ■ Instance Methods

```ruby
Time.zone =~ "Tokyo"
=> true

Time.zone =~ "Sapporo"
=> false
```
- ↑ 設定された都市を比較できる。

```ruby
Time.zone.to_s
=> "(GMT+09:00) Tokyo"
```
- ↑ 現在の zone と utc のオフセット及び設定された都市を表示する。

```ruby
Time.zone.local(2020, 1, 1, 11, 11, 11)
=> Wed, 01 Jan 2020 11:11:11 JST +09:00

Time.zone.local(2020)
=> Wed, 01 Jan 2020 00:00:00 JST +09:00
```
- ↑ `Time.utc` を元に TimeWithZone に変換している。

```ruby
f = Time.utc(2020, 3, 1, 12, 00, 00).to_f
=> 1583064000.0

Time.zone.at(f)
=> Sun, 01 Mar 2020 21:00:00 JST +09:00
# ナノ秒まで指定可能
Time.zone.at(f, 123456.789).nsec
=> 123456789
```
- ↑ `Time.at`を元に TimeWithZone Class に変換する。


```ruby
Time.zone.iso8601("2020-03-01T12:00:00+09:00")
=> Sun, 01 Mar 2020 12:00:00 JST +09:00
```
- ↑ ISO8601 形式の文字列を TimeWithZone Class に変換する。
- ↑ `Time#iso8601`の TimeWithZone 版

```ruby
Time.zone.rfc3339("2020-03-01T12:00:00.123456789+09:00")
=> Sun, 01 Mar 2020 12:00:00 JST +09:00
```
- ↑ RFC3339 形式の文字列を TimeWithZone Class に変換する。
- ↑ `Time#rfc3339`の TimeWithZone 版

```ruby
Time.zone.parse("20200202111111")
=> Sun, 02 Feb 2020 11:11:11 JST +09:00
Time.zone.parse("20200202")
=> Sun, 02 Feb 2020 00:00:00 JST +09:00

Time.zone.parse("2020-02-02 11:11:11")
=> Sun, 02 Feb 2020 11:11:11 JST +09:00
Time.zone.parse("2020-02-02")
=> Sun, 02 Feb 2020 00:00:00 JST +09:00

Time.zone.parse("2020-02")
=> ArgumentError: argument out of range
```
- ↑ 引数の文字列を[Date._parse](https://docs.ruby-lang.org/ja/latest/method/Date/s/_parse.html)で変換するため、少なくとも月日が必要となる。


```ruby
Time.zone.strptime("2020-03-01 11:11:11", "%Y-%m-%d %H:%M:%S")
=> Sun, 01 Mar 2020 11:11:11 JST +09:00

Time.zone.strptime("11:::11:::11", "%H:::%M:::%S")
=> Sun, 01 Mar 2020 11:11:11 JST +09:00

Time.zone.strptime("Mar.1 2020", "%b.%d %Y")
=> Sun, 01 Mar 2020 00:00:00 JST +09:00
```
- ↑ 第一引数に任意の文字列、第二引数にフォーマットを指定することで、フォーマットを元に文字列を TimeWithZone に変換する。
- ↑ 引数の文字列を[DateTime._parse](https://docs.ruby-lang.org/ja/latest/method/DateTime/s/_strptime.html)で変換している。
- ？ メソッド名は String Parse Time の略 ？



```ruby
Time.zone.today
=> Sun, 01 Mar 2020

Time.zone.tomorrow
=> Mon, 02 Mar 2020

Time.zone.yesterday
=> Sat, 29 Feb 2020
```
- ↑ `Time.zone.tzinfo`を使用して、Date Class に変換する。


```ruby
Time.zone.local_to_utc(Time.parse("2020-03-01 12:00"))
=> 2020-03-01 03:00:00 UTC

Time.zone.utc_to_local(Time.parse("2020-03-01 12:00"))
=> 2020-03-01 21:00:00 UTC
```
- ↑ UTC の Time Class に変換する。



```ruby
# utc = Time.zone.local_to_utc(Time.parse("2020-03-01 12:00"))
Time.zone.period_for_utc(utc)
=> #<TZInfo::TimezonePeriod: #<TZInfo::TimezoneTransitionDefinition: #<TZInfo::TimeOrDateTime: -577962000>,#<TZInfo::TimezoneOffset: 32400,0,JST>>,nil>

Time.zone.period_for_local(Time.zone.now)
=> #<TZInfo::TimezonePeriod: #<TZInfo::TimezoneTransitionDefinition: #<TZInfo::TimeOrDateTime: -577962000>,#<TZInfo::TimezoneOffset: 32400,0,JST>>,nil>
```
- ↑ TimeWithZone Instance が`TZInfo::Timezone` Class も使用可能にする。