POSIXタイムゾーン形式解説

タイムゾーン情報を示す環境変数 `TZ` がPOSIXで定められている。`TZ`設定方法として、もっとよく使用されるものが、`TZ=Asia/Tokyo` のように [IANA Time Zone Database](https://www.iana.org/time-zones) (以下、tzdata) のゾーンIDを指定する形である。ゾーンIDを指定するこの方法は、デファクトスタンダードとして広く使用されているが、POSIX上は最新のPOSIX-1.2024になるまで規定のなかった、処理系の独自拡張方式であった。

ゾーンID指定方式が標準化される前の、POSIX-1.2017以前からPOSIXは独自のタイムゾーン指定形式として、具体的なシンタックスおよびセマンティクスを定めている。本稿では、その形式を「POSIXタイムゾーン形式」(または単純に「POSIX形式」)と呼び、解説する。

:::note info
「POSIXタイムゾーン形式」あるいは「POSIXスタイルタイムゾーン形式」等それに類似した呼称はよく使われるが、定められたものではない。glibcでは、POSIX-1.2024対応のために、バージョン2.40でタイムゾーン関連のマニュアルを大きく更新しており、そこでは「[Proleptic Format](https://sourceware.org/glibc/manual/2.40/html_node/Proleptic-TZ.html)」と呼んでいる。glibcにおいて「Proleptic Format」という呼称は2024年7月リリースの2.40で初めて登場した (2024年1月時点の[2.39以前ではこの形式を指す用語すら存在しなかった](https://sourceware.org/glibc/manual/2.39/html_node/TZ-Variable.html))。なお、2.40のマニュアルを更新したのは、IANA tzdataコーディネータのPaul Eggertである。
:::

:::note info
`TZ=:Asia/Tokyo` のように先頭をコロンで始める場合は、処理系定義 (implementation-defined) の記法としてPOSIXでも以前から定められていたが、コロン無しでゾーンIDを指定する形はPOSIX.1-2017以前では規定がなかった
:::


# POSIXタイムゾーン形式

まずはPOSIXタイムゾーン形式の具体例を示す。以下などを使用あるいは見たことのある人も多いだろう:

* `TZ=JST-9`: 日本標準時に相当する
* `TZ=EST+5EDT,M3.2.0/2,M11.1.0/2`: 米ニューヨーク等で採用されている米東部時間に相当する (夏時間有り)

日本時間など、DST (いわゆる夏時間) を採用しないタイムゾーンのPOSIX形式TZ値はシンプルだが、DSTを採用するタイムゾーンは複雑な形を取る。上記例の構成は以下のように分解できる。

```text:日本時間
 /-------------------------- 標準時名称 (略称)   = JST
 |  /----------------------- 標準時UTCオフセット = UTC+09:00
JST-9
```

```text:米東部時間
 /-------------------------- 標準時名称 (略称)   = EST
 |  /----------------------- 標準時UTCオフセット = UTC-05:00
 |  | /--------------------- DST名称 (略称)      = EDT
 |  | |   /----------------- 標準時→DST切替      = 3月
 |  | |   | /--------------- 標準時→DST切替      = 第2
 |  | |   | | /------------- 標準時→DST切替      = 日曜日
 |  | |   | | | /----------- 標準時→DST切替      = 午前2時
 |  | |   | | | |   /------- DST→標準時切替      = 11月
 |  | |   | | | |   | /----- DST→標準時切替      = 第1
 |  | |   | | | |   | | /--- DST→標準時切替      = 日曜日
 |  | |   | | | |   | | | /- DST→標準時切替      = 午前2時
EST+5EDT,M3.2.0/2,M11.1.0/2
```

米東部時間の標準時↔DST切り替わりを少し整理すると以下の通りとなる:
| 標準時→DST切替発生日時 | DST→標準時切替発生日時 |
|----------------------|-----------------------|
| 3月 第2日曜日 午前2時 | 11月 第1日曜日 午前2時 |

## 形式の詳細

POSIXタイムゾーン形式を一般化すると以下の通りとなる。角括弧は省略可能であることを示す。視認性のために`std`と`std_offset`の間にスペースを入れたが、実際の値にスペースは含まない。

```
std std_offset[dst[dst_offset][,rule]]
```

`rule` 部は、標準時↔DSTの切り替わりルール (いつ切り替わりが発生するか) を示し、より詳細に書き下すと以下の形式となる。

```
std std_offset[dst[dst_offset][,start[/start_time],end[/end_time]]]
```

各パートの詳細は以下の通りである。

### std

標準時の名称/略称を示す。必須。先述の例では"JST"や"EST"がこれに該当する。

3文字以上かつ`TZNAME_MAX`以下で任意のアルファベットが使用可能 (`TZNAME_MAX`の値は `getconf TZNAME_MAX`で取得可能) である。また、数字や'+', '-'の符号自体を名称に含めたい場合、クオート形式という特殊なフォーマットも使用できる。山括弧 (<>) で挟む形で、アルファベット、数字、'+', '-'を指定する。山括弧は文字数制限に含まれない。この形式に従わない場合の解釈は未規定 (unspecified) となる。(繰り返になるが、ここでのクオートとは、シングルクオートでもダブルクオートでもなく、山括弧(<>) である。)

```console:クオート形式
$ TZ='<+09>-9' date '+%Z [%z]' 　# "+09" というstd名称を指定
+09 [+0900]

$ TZ='+09-9' date '+%Z [%z]'  # クオートせずに数字や符号を使うと不正となる
 [+0000]
```

POSIX上は具体的な値に意味は無いので、日本時間なら"JST"にしなければならない等の決まりはない。"JST"という略称を使いたくなければ、"JapanStandardTime"と書いてもよい。アプリケーションやユーザーに意味のある名称を指定すればよい。

`std`の値は、`time.h`の`tzname[0]`や`strftime()`の`%Z`、あるいはシェルコマンドの`date '+%Z'`で取得できる。

```console:std名称の取得例
$ TZ=JST-9 date '+%Z [%z]'
JST [+0900]
$ TZ=JapanStandardTime-9 date '+%Z [%z]'
JapanStandardTime [+0900]
```

### std_offset

標準時のUTCオフセットを示す。必須。フォーマットは以下となる。

```
[±]hh[:mm[:ss]]
```

+(正の符号)、分、秒は省略可能である。時、分、秒は1桁でも良い。hhは0以上24以下、mmおよびssは0以上59以下を指定できる。この範囲外を指定した場合の結果は未規定 (unspecified) である。

注意点は、ISO 8601やRFC 3339で広く使われているUTCオフセット書式と正負が逆になることである。RFC 3339では、日本標準時のUTCオフセットは"+09:00"だが、POSIX形式では"-09:00"となる。POSIX形式では、現地時間に加算することでUTCとなる値を書く仕様である。日本時間から9時間マイナスすればUTCとなるので、"JST-9"とする (もちろん"JST-09:00"と書いてもよい)。

```console
$ TZ=JST-9 date -d '1970-01-01T00:00:00+00:00' '+%Y-%m-%dT%H:%M:%S%z[%Z]'
1970-01-01T09:00:00+0900[JST]
```

### dst

`dst` は、Daylight Saving Time (いわゆる夏時間)の名称/略称を示す。形式は `std` と同一である。

日本時間のようにDST不採用のタイムゾーンでは、`TZ=JST-9`のように`dst`以降を省略する。

### dst_offset

DSTのUTCオフセットを示す。形式は `std_offset` と同一である。

`dst_offset` は省略されるケースが多い。省略した場合、標準時オフセット+1時間の値とみなされる。先述の米東部時間の`TZ=EST+5EDT`の場合は、標準時がUTCより5時遅く、夏時間が4時間遅いことを示す。ただし、省略せずに書いた方がPOSIX形式に馴染みの無い人にとっては可読性が高くなるだろう。以下のふたつは同じ意味だが、後者の方が初見でも何を意味しているか理解しやすい。

```console
TZ=EST+5EDT

TZ=EST+05:00EDT+04:00
```

オーストラリアのロードハウ島のように30分の夏時間シフトを採用するタイムゾーンでは`dst_offset`は省略できず、`LHST-10:30LHDT-11:00` (ルール部は割愛) のように書く。


### start[/start_time],end[/end_time] (ルール部)

標準時とDSTの切り替わりがいつ発生するか、切り替わりルールを示す(以下まとめて「ルール部」と呼ぶ)。

`start`, `start_time` は、それぞれDST開始 (標準時からDSTへの遷移) の日付と時刻を指定し、`end`, `end_time` は、それぞれDST終了 (DSTから標準時への遷移) の日付と時刻を指定する。すべて現地時間を基準とする。

日付を指定する`start`および`end`は、以下の3種類の形式のいずれかを取る。

* M<i>m.n.d</i>

    Mはリテラルであり、m, n, dは変数である。m月の第n番目のd曜日という指定の仕方をする。

    * m: 1 <= m <= 12。1月 - 12月
    * n: 1 <= n <= 5。1から4はそれぞれ第1週目から第4週目の意味。5は特殊で、その月の最終d曜日を示す。 月によっては、第4週目かもしれないし、第　5週目かもしれない。
    * d: 0 <= d <= 6。 0, 1 .. 6がそれぞれ日曜日、月曜日 .. 土曜日を示す。

* J<i>n</i>

    Jはリテラルであり、nは変数。1 <= n <= 365。1月1日を1として、12月31日を365とする。2月29日のうるう日を指定できない点に注意。うるう年かどうかに関係なく、59 = 2月28日, 60 = 3月1日となる。

* <i>n</i>

    0 <= n <= 365。J<i>n</i>と異なり、1月1日を0として指定する。うるう日を指定できる。うるう年と非うるう年とで、59以降の値の指す日付が異なる点に注意。

"Mm.n.d" 形式は、この3形式の中で最もよく使用される。むしろ、本稿執筆現在の現実のタイムゾーンを表現するために、"Jn" または "n" 形式が必要になるケースは極めて稀であろう。過去の例ではあるが、2007年以前のイラク (Asia/Baghdad) では夏時間が採用されており、毎年4/1(J91)に標準時→DSTへ遷移し、10/1(J247)にDST→標準時へ遷移していた。これは `TZ=AST-3ADT,J91/3,J274/4` と書くことができる (現在、現実社会で使用されているタイムゾーンのうち、"Jn" または "n" 形式で表現できるタイムゾーンをご存知の方がいればコメント等でご教示いただけるとありがたい)。

`start` と `end` はそれぞれ別の形式を取ってもよい (`start` を"Mm.n.d"、`end` を "Jn" とすることが可能である)。

`start_time`、`end_time` は、指定可能な値の範囲を除いて、`std_offset`, `dst_offset` と同じ `hh[:mm[:ss]]` の形式を取る。`hh`に指定可能な値として、POSIX-1.2017以前では0以上24以下、POSIX-1.2024では-167以上+167以下と規定されている。POSIXバージョン間の差異については、記事「[POSIX-1.2024でのタイムゾーン環境変数TZ定義の変更点と処理系の比較](https://qiita.com/jmatsuzawa/items/f05e33026f66db0a3261#%E5%A4%89%E6%9B%B4%E7%82%B93-posix%E3%82%BF%E3%82%A4%E3%83%A0%E3%82%BE%E3%83%BC%E3%83%B3%E5%BD%A2%E5%BC%8F%E3%81%AEdst-rule%E3%81%AB%E6%8C%87%E5%AE%9A%E5%8F%AF%E8%83%BD%E3%81%AA%E6%99%82%E5%88%BB%E3%81%AE%E7%AF%84%E5%9B%B2%E3%81%8C%E5%A4%89%E3%82%8F%E3%81%A3%E3%81%9F)」でより詳細に説明している。`start_time`、`end_time` はそれぞれ省略可能であり、省略すると02:00:00を指定したものとみなされる。


なお、ルール部全体はPOSIX形式のシンタックス定義では省略可能とされているが、省略すべきではない。ルール部省略時の挙動については処理系間の移植性は無い。最も普及しているであろうglibcは[不具合](https://sourceware.org/bugzilla/show_bug.cgi?id=32118)があり、バージョン間で挙動も違う。glibcはFreeBSD libcやmacOS libcとも挙動が異なる。muslはルール部の省略をサポートしていない。詳細は[別記事](https://qiita.com/jmatsuzawa/items/f05e33026f66db0a3261#posix%E5%BD%A2%E5%BC%8F%E3%83%AB%E3%83%BC%E3%83%AB%E7%9C%81%E7%95%A5%E6%99%82%E3%81%AE%E5%87%A6%E7%90%86%E7%B3%BB%E3%81%AE%E6%8C%99%E5%8B%95)で述べているので、そちらを参照されたい。

POSIX-1.2017以前から、ルール部の省略をシンタックス上許可しているが、省略した場合の挙動についてはPOSIX-1.2017以前は一切規定がなかった。POSIX-1.2024でようやく処理系定義 (implementation-defined) と明示された。

:::note alert
移植性を考慮したアプリケーションではルール部を省略してはならない。
:::

## POSIX形式の具体例

以下は、よく知られたタイムゾーンや、少し個性的なタイムゾーンを、POSIX形式を用いて記述した例である。

```console:UTC
TZ=UTC0
```

```console:日本時間 (Asia/Tokyo)
TZ=JST-9
```

```console:米東部時間 (America/New_York)
TZ=EST+5EDT,M3.2.0/2,M11.1.0/2
```

```console:米太平洋(西海岸)時間 (America/Los_Angeles)
TZ=PST+8PDT,M3.2.0/2,M11.1.0/2
```

```console:イギリス時間 (Europe/London)
TZ=GMT0BST,M3.5.0/1,M10.5.0/2
```

```console:アイルランド時間 (Europe/Dublin)
# Negative DSTを適用
TZ=IST-1GMT0,M10.5.0/2,M3.5.0/1
```

:::note info
Negative DSTとは、一般的なDST (いわゆる夏時間) とは逆に、DST期間中に時計を遅らせるDSTのことである。アイルランドはNegative DSTを採用しており、標準時オフセットがUTC+01:00、DSTオフセットがUTC+00:00である。Negative DSTの詳細については、記事「[アイルランドはサマータイムが冬にやってくる!?](https://qiita.com/jmatsuzawa/items/0b1baf1e9cd35eea88de)」を参照されたい。
:::

```console:ロード・ハウ島時間 (Australia/Lord_Howe)
# 夏時間シフトが+30分
TZ=LHST-10:30LHDT-11:00,M10.1.0/2,M4.1.0/2
```

```console:イスラエル時間 (Asia/Jerusalem)
# POSIX-1.2024で標準化された、0-24範囲外のstart_timeが必要
TZ=IST-2IDT,M3.5.0/-46,M10.5.0/2
```

```console:ネパール時間 (Asia/Kathmandu)
# UTCオフセットが45分
TZ=NPT-05:45
```

```console:東京2020五輪に向けて提案された幻の2時間繰り上げサマータイム (標準時↔DST切替日時は仮)
TZ=JST-9JDT-11,M4.1.0/2,M10.1.0/2
```

## 参考資料

* [POSIX-1.2017, Base Definitions 8.3 Other Environment Variables](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03)
* [POSIX-1.2024, Base Definitions 8.3 Other Environment Variables](https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap08.html#tag_08_03)
* [The GNU C Library (glibc) manual version 2.39, 22.5.6 Specifying the Time Zone with TZ](https://sourceware.org/glibc/manual/2.39/html_node/TZ-Variable.html)
* [The GNU C Library (glibc) manual version 2.40, 22.5.6.2 Proleptic Format for TZ](https://sourceware.org/glibc/manual/2.40/html_node/Proleptic-TZ.html)