オーストラリアのサマータイムからtz databaseを読み解く

[PONOS Advent Calendar 2020](https://qiita.com/advent-calendar/2020/ponos)の13日目の記事です。

昨日は[@honeniq](https://qiita.com/honeniq)さんでした。

# サマータイムについて
サマータイム（Daylight Saving Time=DST）といえば以前少し日本でも話題になりましたが、**主に日照時間の長い夏の時期に時間を1時間進める**というイメージがあります（私は）。
逆にいえば時間に変動が生じるものであるため、プログラマーとしては本能的に危険を感じるのではないでしょうかw

例えば**日本時間**を例にしてみます。

**日本時間**は通常**UTC+09:00**であり、**JST（Japan Standard Time）**、つまり日本標準時と呼ばれるものです。
現在の日本にサマータイムはありませんが、1948年から1951年にかけて、日本でもサマータイムが実施されていたこともあるそうです。
サマータイムが適用される場合、**JDT(Japan Daylight-saving Time)**という時刻になり、**UTC+10:00**ということになるようです。

日本だけでなく、標準時は"S"、サマータイム時は"D"になるというのは世界的に見られるもので、後述の**tz databaze**からもそれがわかります。

```
UTC: 2020/12/01 00:00:00
JST: 2020/12/01 09:00:00
JDT: 2020/12/01 10:00:00
```
*※この時刻は例です。実際に日本でこの日時はサマータイムではありません。*

つまり**UTC自体は当然変更されることはありません**が、タイミングによって**JSTが適用されるかJDTが適用されるか**で、実際の現地時間が変わると言う考え方です。
プログラムにおいてはUTCを基準に処理していれば時間が逆戻りするということはないということですね。

# momentでサマータイムを処理する
プログラムでタイムゾーンを扱いたい場合、大抵ライブラリなどを使用すると思います。
例えばJavaScriptではご存知[**moment-timezone**](https://momentjs.com/timezone/)を使うととっても日付の扱いが楽になります。
サマータイムも自動的に処理してくれます。

#### 実際に確認してる

太平洋標準時（PST）を例にして**moment-timezone**で確認してみます。
PSTが適用されるタイムゾーンは米国ロサンゼルスであり、次のサマータイムは**PST 2021/03/14 02:00:00**からだそうです。
PSTは**UTC-08:00**であることから、つまり**UTC 2021/03/14 10:00:00**からサマータイムになることになります。

```JavaScript
const moment = require('moment-timezone');
console.log(moment('2021-03-14T09:00:00.000Z').tz("America/Los_Angeles").format());
> 2021-03-14T01:00:00-08:00
console.log(moment('2021-03-14T10:00:00.000Z').tz("America/Los_Angeles").format());
> 2021-03-14T03:00:00-07:00
```

**UTC 2021/03/14 09:00:00**の時は**2021/03/14 01:00:00**であることから、**UTC-08:00**になっている一方で、
**UTC 2021/03/14 10:00:00**の時は**2021/03/14 03:00:00**であることから、**UTC-07:00**になっており、1時間進んだサマータイムが適用されていることがわかります。

#### サマータイムなのかどうか

上記のコードでは特に意識することなくサマータイムが反映された時刻が取得されています。
この時間がサマータイムなのかどうなのかを知るためには、**moment-timezone**では**isDST()**関数が用意されています。

```JavaScript
console.log(moment('2021-03-14T09:00:00.000Z').tz("America/Los_Angeles").isDST());
> false
console.log(moment('2021-03-14T10:00:00.000Z').tz("America/Los_Angeles").isDST());
> true
```

この関数の中身の実装は次のようになっていました。

```JavaScript
proto.isDST = isDaylightSavingTime;
〜(省略)〜
function isDaylightSavingTime() {
    return (
        this.utcOffset() > this.clone().month(0).utcOffset() ||
        this.utcOffset() > this.clone().month(5).utcOffset()
    );
}
```

**現在のUTCオフセットと、1月のオフセットもしくは6月のオフセットを比較し、どちらかのケースで大きければサマータイム**ということのようです。
これはつまり

1. **サマータイムの場合はオフセットが大きくなる**
2. **1月と6月の両方がサマータイムであることはない**

という前提に立っているように思えます。
たしかに1月も6月もサマータイムを実施している（一年の半分以上）国はなさそうですね。。。

ではサマータイムが適用されなかった場合の時間はどうやって求めればいいのだろうか？とふと思います。
上記の内容からすれば、少ないほうのUTCオフセットを取得して、それをUTC時間に適用した時間ということにすれば解決しそうです。

#### サマータイムは1時間進むというわけではない

もっと楽をするなら、**isDST()==trueだったら1時間引くじゃだめなの？**とも思います。
そこで[サマータイムのWikipedia](https://ja.wikipedia.org/wiki/%E5%A4%8F%E6%99%82%E9%96%93)を見てみました。

~引用~
*『標準時を1時間進める制度またはその進められた時刻のこと。ただし、オーストラリアのロード・ハウ島では夏時間と通常の時間の差が30分であるなど一律ではない。』*

**でた！例外あるある！**

# オーストラリアのタイムゾーン
ということでオーストラリアのタイムゾーンを調べてみたところ、大雑把にいうと次のようになっているようです。

1. **東部時間（AEST/AEDT）、中部時時間（ACST/ACDT）、西部時間（AWST/AWDT）の3つのタイムゾーンに区分される。**
2. **サマータイムは州ごとに採用ありなしが決められており、これらのタイムゾーンの中でもサマータイム適用ありなしの地区が存在する。**
3. **中西部時間（UTC+8:45）という、ユークラという一部地域のみ使用されている、公式ではないタイムゾーンが存在する。**
4. **ロード・ハウ時間(UTC+10:30)が存在する。夏時間は+00:30である。**

なんとややこしい！うっ・・頭がっ・・

# tz databaseを読み解く

#### 中部時間の中でのサマータイムの有無
moment-timezone（や多くのライブラリ）はタイムゾーンを扱うための情報として[**tz database**](https://www.iana.org/time-zones)を使用しています。
その中身を読み解いてオーストラリアのタイムゾーンがどういう設定になっているか実際にみてみたいと思います。

今回は上記のサイトから**tzdata2020d.tar.gz**をダウンロードしました。
その中にあるたくさんのファイルから、オーストラリアが定義されている**australasia**を開いてみます。

**オーストラリア中部時間に属するダーウィン**を具体例にとって見てみます。関係する箇所は下記の部分です。

```
# Zone  NAME            STDOFF  RULES   FORMAT  [UNTIL]
# Northern Territory
Zone Australia/Darwin    8:43:20 -      LMT     1895 Feb
                         9:00   -       ACST    1899 May
                         9:30   Aus     AC%sT
```

```
# Rule  NAME    FROM    TO      -       IN      ON      AT      SAVE    LETTER/S
〜（省略）〜
Rule    Aus     1943    1944    -       Mar     lastSun 2:00    0       S
Rule    Aus     1943    only    -       Oct      3      2:00    1:00    D
```

**Zone**から始まる行は、そのタイムゾーンに対する設定になっています。
**Rule**から始まる行は、時間の変更がある場合のルールになっており、**Zone**における**RULES**で指定されているルールが適用される関係性のようです。
また、気になる**FORMAT**の部分ですが、**AC%sT**となっている部分は、**Rule**における**LETTER/S**の項目が適用されるようです。
ですので、**SであればACST（標準時間）、DであればACDT（夏時間）**ということになります。
また、**SAVE**に**1:00**となっている部分は、標準時間のUTCオフセット（**STDOFF**）に対して、さらにずれる部分のようです。夏時間は1時間ずれることを意味しています。

また、それぞれのZoneには名前がつけられています。この名前こそがよく見る**Asia/Tokyo**という部分です。

このような関係性であるため、この**Zoneにつけられた名称、Australia/Darwin**と**オーストラリア中部標準時時（ACST）**とは必ずしも1:1の関係ではないことがわかります。

具体的には、オーストラリアのアデレードも**オーストラリア中部時間**に属しています。

```
# Zone  NAME            STDOFF  RULES   FORMAT  [UNTIL]
Zone Australia/Adelaide 9:14:20 -       LMT     1895 Feb
                        9:00    -       ACST    1899 May
                        9:30    Aus     AC%sT   1971
                        9:30    AS      AC%sT
```

ここで注目なのは適用されているルールが異なる点です。

**[UNTIL]**の設定はそのルールが適用される期限を意味しています。
ダーウィンでは現在は"Aus"が適用されており、アデレードでは現在"AS"という設定が適用されています。
"Aus"の設定は前述の通りですが、**FROM、TO**を見るとわかる通り、現在適用される変則ルールはありませんのでサマータイムはなさそうです。
一方の"AS"は下記の通りです。

```
# South Australia
# Rule  NAME    FROM    TO      -       IN      ON      AT      SAVE    LETTER/S
〜（省略）〜
Rule    AS      2008    max     -       Apr     Sun>=1  2:00s   0       S
Rule    AS      2008    max     -       Oct     Sun>=1  2:00s   1:00    D
```

ここからみるとアデレードでは2008年以降、サマータイムが設定されています。
このようにして、同じオーストラリア中部時間に属する場所でも、サマータイムの適用の有無が変わるようになっているようです。

#### ユークラという公式ではないが実際に使われているタイムゾーンはどうなっているのか

探してみました。

```
# Zone  NAME            STDOFF  RULES   FORMAT  [UNTIL]
〜（省略）〜
Zone Australia/Eucla     8:35:28 -      LMT     1895 Dec
                         8:45   Aus +0845/+0945 1943 Jul
                         8:45   AW  +0845/+0945
```

FORMATの部分がACSTなどの3種類のオーストラリア時間のいずれでもなく、直接UTCオフセットが書き込まれています。
「どうやら標準時はUTC+08:45であり、夏時間はUTC+09:45っていうタイムゾーンですよ」ってことを表している名前？？？
むむむ。

ちなみにユークラに適用されているAWルールは次の通りで、

```
# Western Australia
#
# Rule  NAME    FROM    TO      -       IN      ON      AT      SAVE    LETTER/S
〜（省略）〜
Rule    AW      2007    2009    -       Mar     lastSun 2:00s   0       S
Rule    AW      2007    2008    -       Oct     lastSun 2:00s   1:00    D
```

現在サマータイムはなさそうです。

#### ロード・ハウ島はどうなっているのか

ここまでくるともう探すだけです。

```
# Zone  NAME            STDOFF  RULES   FORMAT  [UNTIL]
〜（省略）〜
Zone Australia/Lord_Howe 10:36:20 -     LMT     1895 Feb
                        10:00   -       AEST    1981 Mar
                        10:30   LH      +1030/+1130 1985 Jul
                        10:30   LH      +1030/+11
```

```
# Lord Howe Island
# Rule  NAME    FROM    TO      -       IN      ON      AT      SAVE    LETTER/S
〜（省略）〜
Rule    LH      2008    max     -       Apr     Sun>=1  2:00    0       -
Rule    LH      2008    max     -       Oct     Sun>=1  2:00    0:30    -
```

なるほど、1981年まではAESTだったようですね。
現在は独自の時間になっていて、標準時がUTC+10:30、夏時間がUTC+11:00であることがわかります。
確かにWikipediaにある通り、ロード・ハウ島のサマータイムは30分だけ早くなるようですね。

# まとめ
サマータイムだからといって1時間ずれると思ったら大間違いやで！というのと、いかに世界のタイムゾーンが複雑であるかというのを肌で感じました。
日本に住んでいるとタイムゾーンって一つですからねー。

**ここまできたらロード・ハウ島にいってサマータイムを実際に感じてみたいですね！**

明日は[@FW14B](https://qiita.com/advent-calendar/2020/ponos)さんです！