アイルランドはサマータイムが冬にやってくる!?

そんなわけない。

と思われるかもしれないが、タイムゾーン情報のデファクトスタンダードである[IANA Time Zone Database](https://www.iana.org/time-zones) (以下tzdata) では、アイルランドのタイムゾーン定義として、Daylight Saving Time (一般にいうサマータイムのこと。以下DSTと略す) が冬季に設定されている。

本稿は、IANA tzdataに存在する冬のDSTに関する調査記録である。

## tzdata定義の確認

以下は、Ubuntu 24.04で、アイルランドのtzdataタイムゾーン情報 (Europe/Dublin) をダンプして抜粋したものである。

```console:アイルランド
$ zdump -v Europe/Dublin | sed -n '/Oct.*2023.*gmtoff=0/,/Oct.*2024.*gmtoff=3600/p'
Europe/Dublin  Sun Oct 29 01:00:00 2023 UT = Sun Oct 29 01:00:00 2023 GMT isdst=1 gmtoff=0
Europe/Dublin  Sun Mar 31 00:59:59 2024 UT = Sun Mar 31 00:59:59 2024 GMT isdst=1 gmtoff=0
Europe/Dublin  Sun Mar 31 01:00:00 2024 UT = Sun Mar 31 02:00:00 2024 IST isdst=0 gmtoff=3600
Europe/Dublin  Sun Oct 27 00:59:59 2024 UT = Sun Oct 27 01:59:59 2024 IST isdst=0 gmtoff=3600
```

末尾から二つ目のフィールド `isdst` で、特定の期間がDSTであるか (`isdst=1`) 、それとも標準時であるか (`isdst=0`) が示される。上記の例では、UTCベースで2023-10-29 01:00から2024-03-31 00:59までの冬季が `isdist=1`、すなわちDST期間中であり、2024-03-31 01:00から2024-10-27 00:59までの夏季が `isdst=0`、すなわち標準時であると読める。

冬にサマータイムが来るなんて、自分の頭がバグったのかと思い、同一UTCオフセット(時差)ルールを持つイギリスのタイムゾーンも見てみた。

```console:イギリス
$ zdump -v Europe/London | sed -n '/Oct.*2023.*gmtoff=0/,/Oct.*2024.*gmtoff=3600/p'
Europe/London  Sun Oct 29 01:00:00 2023 UT = Sun Oct 29 01:00:00 2023 GMT isdst=0 gmtoff=0
Europe/London  Sun Mar 31 00:59:59 2024 UT = Sun Mar 31 00:59:59 2024 GMT isdst=0 gmtoff=0
Europe/London  Sun Mar 31 01:00:00 2024 UT = Sun Mar 31 02:00:00 2024 BST isdst=1 gmtoff=3600
Europe/London  Sun Oct 27 00:59:59 2024 UT = Sun Oct 27 01:59:59 2024 BST isdst=1 gmtoff=3600
```

`isdst`フラグがアイルランドと逆である。イギリスは、こちらの期待通り夏季にDSTが設定されている。

なお、gmtoffの値は、UTCオフセットを秒単位で示している。アイルランド、イギリスともに冬季がUTC+00:00、夏季がUTC+01:00と全く同じである。ゾーンIDとその略称を除いて、ルール上の違いは`isdst`が逆になっていることだけだ。

整理すると以下のとおり。イギリスは冬の標準時から夏のDST(サマータイム)への切り替わりにプラス1時間する普通のサマータイムルールである。一方のアイルランドは、標準時が夏 (UTC+01:00)で、冬にDSTを向かえて標準時マイナス1時間となる。これはいったいどういうことだ?

| タイムゾーン | 標準時 | DST | 標準時UTCオフセット | DST時UTCオフセット |
|--------|-----|-----|-------------|--------------|
| アイルランド | 夏季  | 冬季  | UTC+01:00   | UTC+00:00    |
| イギリス   | 冬季  | 夏季  | UTC+00:00   | UTC+01:00    |

## tzdataソースファイル

tzdataの誤りを疑い、tzdataの[ソースファイル](https://github.com/eggert/tz/blob/2024a/europe#L522-L552)を確認した。(tzdataには歴史的な過去の情報も定義されており、情報量が多く複雑なので、見るべきポイントに「★」マークを付けて以下に引用する)。

ポイントをまとめると、標準時は3月の最終日曜日から10月最終日曜日までの夏季にあたり、時差はUTC+01:00。DSTは10月最終日曜日から翌年3月最終日曜日までの冬季にあたり、標準時からマイナス1時間する。上にまとめた表の通りである。

しかしながら、引用トップのコメント文と見ると、この定義が意図的なものであることが明示されている。いわく、夏が標準時であり、冬にマイナス1時間する「***Negative DST***」が採られていると。夏時間制ならぬ冬時間制と言える。

※なお、引用ソースファイル後半のコメントにある、"Vanguard section"、"Rearguard section" はtzdataを利用するシステムにとって重要な情報であり、後述する。

```text:tzdataソースファイル
# The following is like GB-Eire and EU, except with standard time in
# summer and negative daylight saving time in winter.  It is for when
# negative SAVE values are used.
# Rule	NAME	FROM	TO	-	IN	ON	AT	SAVE	LETTER/S
Rule	Eire	1971	only	-	Oct	31	 2:00u	-1:00	-
Rule	Eire	1972	1980	-	Mar	Sun>=16	 2:00u	0	-
Rule	Eire	1972	1980	-	Oct	Sun>=23	 2:00u	-1:00	-
Rule	Eire	1981	max	-	Mar	lastSun	 1:00u	0	- # ★ 標準時のルール
Rule	Eire	1981	1989	-	Oct	Sun>=23	 1:00u	-1:00	-
Rule	Eire	1990	1995	-	Oct	Sun>=22	 1:00u	-1:00	-
Rule	Eire	1996	max	-	Oct	lastSun	 1:00u	-1:00	- # ★ DSTのルール

# Zone	NAME		STDOFF	RULES	FORMAT	[UNTIL]
		#STDOFF	-0:25:21.1
Zone	Europe/Dublin	-0:25:21 -	LMT	1880 Aug  2
			-0:25:21 -	DMT	1916 May 21  2:00s
			-0:25:21 1:00	IST	1916 Oct  1  2:00s
			 0:00	GB-Eire	%s	1921 Dec  6 # independence
			 0:00	GB-Eire	GMT/IST	1940 Feb 25  2:00s
			 0:00	1:00	IST	1946 Oct  6  2:00s
			 0:00	-	GMT	1947 Mar 16  2:00s
			 0:00	1:00	IST	1947 Nov  2  2:00s
			 0:00	-	GMT	1948 Apr 18  2:00s
			 0:00	GB-Eire	GMT/IST	1968 Oct 27
# Vanguard section, for zic and other parsers that support negative DST.
			 1:00	Eire	IST/GMT # ★現在有効なゾーン定義
# Rearguard section, for parsers lacking negative DST; see ziguard.awk.
#			 1:00	-	IST	1971 Oct 31  2:00u
#			 0:00	GB-Eire	GMT/IST	1996
#			 0:00	EU	GMT/IST
# End of rearguard section.
```

Negative DSTについては、それがtzdataに初めて導入されたバージョンの[リリースノート(NEWS)](https://github.com/eggert/tz/blob/2018a/NEWS#L34-L43)にも明記されており、意図的なものであることが分かる。

```text:2018aリリースノート
  Changes to tm_isdst

    Change Europe/Dublin so that it observes Irish Standard Time (UT
    +01) in summer and GMT (as negative daylight-saving) in winter,
    instead of observing standard time (GMT) in winter and Irish
    Summer Time (UT +01) in summer.  This change does not affect UT
    offsets or abbreviations; it affects only whether timestamps are
    considered to be standard time or daylight-saving time, as
    expressed in the tm_isdst flag of C's struct tm type.
    (Discrepancy noted by Derick Rethans.)
```

## 法律上のアイルランド時間制度

tzdataは、現実社会の時間制度を正しく反映しようとするので、アイルランドの法律上の時間制度を確認した。

* 1968年、[標準時法(1968) (Standard Time Act, 1968)](https://www.irishstatutebook.ie/eli/1968/act/23/enacted/en/print) により、標準時 ("Standard time") をGMT+01:00と定める。さらに通年を標準時とした。
* 1971年、[改正標準時法(1971) (Standard Time (Amendment) Act, 1971)](https://www.irishstatutebook.ie/eli/1971/act/17/enacted/en/print) により、冬時間 ("Winter time") を導入し、標準時をGMT+01:00、冬時間をGMTとすることが規定される。冬時間の対象期間は法相 (Minister for Justice) により変更することが可能とされる。
* 2001年、法相による[冬時間令(2001) (Winter Time Order, 2001)](https://www.irishstatutebook.ie/eli/2001/si/506/made/en/print) にて、冬時間の開始を10月最終日曜日のGMT午前1時、終了を3月最終日曜日のGMT午前1時と定める。これが現在でも適用されている。なお、この冬時間規定は、EU加盟国間で夏季の時計変更タイミングを同期させる[EU夏時間規定 (Directive 2000/84/EC of the European Parliament and of the Council of 19 January 2001 on summer-time arrangements)](https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:32000L0084) に適合するために定められた。

まとめると、アイルランドの現在の時間制度は、夏季は標準時 (UTC+01:00) とし、冬季に冬時間 (UTC+00:00) を採用している。

UTCオフセット基準で考えれば夏時間制(夏季に冬季よりプラス1時間する)を採用する他のヨーロッパ諸国と同じに見えるが、その実態は夏時間制の国々とは真逆であり、冬に標準時からマイナス1時間する仕組みとなっている。

これが、tzdataで定義されるアイルランドのNegative DSTの制度的根拠である。tzdataのアイルランドのルール定義は、現実社会の時間制度の反映という点において、完全に正しい。

## tzdata への Negative DST 導入経緯

tzdataへのNegative DST導入経緯を調査したところ、紆余曲折があったことが確認できた。またNegative DST採用にあたり、その後のtzdata (tzdb) のビルドの仕組みにも影響を与える大きな変更が導入された。

以下にNegative DST 導入経緯の重要なポイントを抜粋して列挙する。

* 2017-12-08: 略語"IST"を巡る議論 (Irish Standard Time vs Irish Summer Time) に端を発して 、tzdataメンテナのPaul Eggertがアイルランドの法的な時間制度(冬時間制)を正しく反映したNegative DSTをtzdataに導入するパッチを投稿。
https://mm.icann.org/pipermail/tz/2017-December/050718.html

    ```text
    > No, I meant "Irish Standard Time". Ireland is "odd" because their
    > standard time is what the rest of the British Isles calls "Summer Time"
    > (BST). So Ireland uses "Irish Standard Time" and "GMT".
    Thanks for pointing this out; I was unaware that Ireland observes negative 
    daylight-saving time in winter, instead of positive daylight-saving time in 
    summer.
    ```

* Negative DSTは従来との互換性を損なう破壊的変更をもたらすとして、導入反対派と賛成派を中心に様々な立場から侃々諤々の議論に発展。[数][1][カ][2][月][3]に渡り続く。

    [1]: https://mm.icann.org/pipermail/tz/2017-December/date.html
    [2]: https://mm.icann.org/pipermail/tz/2018-January/date.html
    [3]: https://mm.icann.org/pipermail/tz/2018-February/date.html

* 2018-01-12: Negative DSTを導入したバージョン[2018a](https://github.com/eggert/tz/blob/2018a/NEWS#L34-L43)がリリースされる (ただしその直後に[問題(Negative DSTは無関係)が見つかり公式のリリースアナウンスはされていない](https://mm.icann.org/pipermail/tz/2018-January/050852.html)。その次の[2018bも同様](https://mm.icann.org/pipermail/tz/2018-January/050941.html))

    ```text:2018a
    Change Europe/Dublin so that it observes Irish Standard Time (UT
    +01) in summer and GMT (as negative daylight-saving) in winter,
    instead of observing standard time (GMT) in winter and Irish
    Summer Time (UT +01) in summer.
    ```

* 2018-01-18: [ICU/CLDR](https://mm.icann.org/pipermail/tz/2018-January/025820.html)、[OpenJDK](https://mm.icann.org/pipermail/tz/2018-January/050858.html) 等で Negative DSTが問題を引き起こすことがメーリングリストに投稿される。これを受け、Negative DST反対派と賛成派の議論が再燃する。

* 2018-01-22: 上記のtzdata利用プロジェクトでの問題を受け、一時的な回避策としてNegative DSTをリバートして以前のタイムゾーンルールに戻したバージョン[2018c](https://github.com/eggert/tz/blob/2018c/NEWS#L8-L24) がリリースされる

    ```text:2018c
    Revert the 2018a change to Europe/Dublin.
    (略)
    This reversion is intended to be a temporary
    workaround for problems discovered with downstream uses of
    releases 2018a and 2018b, which implemented Irish time by using
    negative DST offsets in the Eire rules of the 'europe' file.
    Although negative DST offsets have been part of tzcode for many
    years and are supported by many platforms, they were not
    documented before 2018a and ICU and OpenJDK do not currently
    support them.  A mechanism to export data to platforms lacking
    support for negative DST is planned to be developed before the
    change is reapplied.  (Problems reported by Deborah Goldsmith and
    Stephen Colebourne.)
    ```

* 2018-03-22: Negative DST有効のVanguard形式、Negative DST無効のRearguard形式を導入し、Negative DSTをサポートするプロジェクト、サポートしないプロジェクトそれぞれがどちらを採用するかビルド時に選択できるようにした、バージョン[2018d](https://github.com/eggert/tz/blob/2018d/NEWS#L72-L81) がリリースされる。(なお後述するが、実際にOSや言語処理系によって、Vanguardを採用するもの、Rearguardを採用するものが分かれている)

    ```text:2018d
    In the current version, the main and rearguard formats are
    identical and match that of 2018c, so this change does not
    affect default behavior.  
    ```

* 2018-05-01: デフォルトをVanguardとしてNegative DSTを復活させた (Reargurdも選択可能)、バージョン[2018e](https://github.com/eggert/tz/blob/2018e/NEWS#L8)がリリースされ、現在に至る。

    ```text:2018e
    The main format uses negative DST again, for Ireland etc.
    ```

* 参考までに、Negative DST導入をめぐる活発な議論を受けて、根本的な[プロジェクトの方針に対する問題提起も行われた](https://mm.icann.org/pipermail/tz/2018-February/026179.html) ことにも触れておく。これはNegative DSTだけを問題としたものではないが、それが蓄積された不満を爆発させるトリガーとなったことは確かだろう。

    ```text
    Inappropriate project direction

    In the last few years, the following changes have been made to the
    tzdb project. Each of these

    - added no value
    - had a cost that far exceeded the benefits
    - should not have happened
    (略)

    4) Negative SAVE values

    Despite being a known fact in tzdb since 2005, and despite being
    warned not to in advance, a decision to re-interpret the meaning of
    Ireland DST was pushed through.

    - downstream consumers broke
    - the interaction with CLDR is incompatible with negative DST
    - the change has no impact on actual times
    - even if someone thought it was broken, there are no benefits to making the change
    ```


## OS/言語処理系のtzdata Negative DST対応 (Vanguardか、Rearguardか)

IANA tzdataはデファクトスタンダードとして、各種OS、言語処理系等で利用されているが、上記の導入経緯でも見た通り、JavaではNegative DSTを用いるVanguard形式では動かないという問題があった。各環境において、Vanguard形式、Rearguard形式どちらが採用されているのか以下で確認する。

### Debian系

以下は、Debian 12でのzdumpの結果である。DSTが冬季に設定されており、Vanguard形式を採用していることが分かる。

```console:Debian
$ zdump -v Europe/Dublin | sed -n '/Oct.*2023.*gmtoff=0/,/Oct.*2024.*gmtoff=3600/p'
Europe/Dublin  Sun Oct 29 01:00:00 2023 UT = Sun Oct 29 01:00:00 2023 GMT isdst=1 gmtoff=0
Europe/Dublin  Sun Mar 31 00:59:59 2024 UT = Sun Mar 31 00:59:59 2024 GMT isdst=1 gmtoff=0
Europe/Dublin  Sun Mar 31 01:00:00 2024 UT = Sun Mar 31 02:00:00 2024 IST isdst=0 gmtoff=3600
Europe/Dublin  Sun Oct 27 00:59:59 2024 UT = Sun Oct 27 01:59:59 2024 IST isdst=0 gmtoff=3600

$ grep PRETTY /etc/os-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
```

以下は、Ubuntu 24.04の結果である。Debian同様Vanguardである。

```console:Ubuntu
$ zdump -v Europe/Dublin | sed -n '/Oct.*2023.*gmtoff=0/,/Oct.*2024.*gmtoff=3600/p'
Europe/Dublin  Sun Oct 29 01:00:00 2023 UT = Sun Oct 29 01:00:00 2023 GMT isdst=1 gmtoff=0
Europe/Dublin  Sun Mar 31 00:59:59 2024 UT = Sun Mar 31 00:59:59 2024 GMT isdst=1 gmtoff=0
Europe/Dublin  Sun Mar 31 01:00:00 2024 UT = Sun Mar 31 02:00:00 2024 IST isdst=0 gmtoff=3600
Europe/Dublin  Sun Oct 27 00:59:59 2024 UT = Sun Oct 27 01:59:59 2024 IST isdst=0 gmtoff=3600

$ grep PRETTY /etc/os-release
PRETTY_NAME="Ubuntu 24.04 LTS"
```

### RHEL系

RHEL系OSは事情が少し複雑である。アップストリームのFedoraはVanguardだが、RHELおよびその派生OSはRearguardとなっている。

Fedoraでは、2018年5月リリースの[バージョン29から迅速にVanguardへ対応](https://fedoraproject.org/wiki/Changes/TZDATA-VANGUARD)し、本稿執筆現在の最新版40でもVanguardとなっている。

```console:Fedora 29 (Vanguard)
$ zdump -v Europe/Dublin | sed -n '/Oct.*2023.*gmtoff=0/,/Oct.*2024.*gmtoff=3600/p'
Europe/Dublin  Sun Oct 29 01:00:00 2023 UT = Sun Oct 29 01:00:00 2023 GMT isdst=1 gmtoff=0
Europe/Dublin  Sun Mar 31 00:59:59 2024 UT = Sun Mar 31 00:59:59 2024 GMT isdst=1 gmtoff=0
Europe/Dublin  Sun Mar 31 01:00:00 2024 UT = Sun Mar 31 02:00:00 2024 IST isdst=0 gmtoff=3600
Europe/Dublin  Sun Oct 27 00:59:59 2024 UT = Sun Oct 27 01:59:59 2024 IST isdst=0 gmtoff=3600

$ grep PRETTY /etc/os-release
PRETTY_NAME="Fedora 29 (Container Image)"
```

```console:Fedora 28 (Rearguard)
$ zdump -v Europe/Dublin | sed -n '/Oct.*2023.*gmtoff=0/,/Oct.*2024.*gmtoff=3600/p'
Europe/Dublin  Sun Oct 29 01:00:00 2023 UT = Sun Oct 29 01:00:00 2023 GMT isdst=0 gmtoff=0
Europe/Dublin  Sun Mar 31 00:59:59 2024 UT = Sun Mar 31 00:59:59 2024 GMT isdst=0 gmtoff=0
Europe/Dublin  Sun Mar 31 01:00:00 2024 UT = Sun Mar 31 02:00:00 2024 IST isdst=1 gmtoff=3600
Europe/Dublin  Sun Oct 27 00:59:59 2024 UT = Sun Oct 27 01:59:59 2024 IST isdst=1 gmtoff=3600

$ grep PRETTY /etc/os-release
PRETTY_NAME="Fedora 28 (Twenty Eight)"
```

その一方で、Fedora 34ベースのRHEL9、およびその派生のAlmaLinux 9、Rocky Linux 9は、バージョン的にはVanguardとなっているかと思いきや、後方互換性維持の観点からRearguardのままとなっている。

```console:Rocky Linux 9
$ zdump -v Europe/Dublin | sed -n '/Oct.*2023.*gmtoff=0/,/Oct.*2024.*gmtoff=3600/p'
Europe/Dublin  Sun Oct 29 01:00:00 2023 UT = Sun Oct 29 01:00:00 2023 GMT isdst=0 gmtoff=0
Europe/Dublin  Sun Mar 31 00:59:59 2024 UT = Sun Mar 31 00:59:59 2024 GMT isdst=0 gmtoff=0
Europe/Dublin  Sun Mar 31 01:00:00 2024 UT = Sun Mar 31 02:00:00 2024 IST isdst=1 gmtoff=3600
Europe/Dublin  Sun Oct 27 00:59:59 2024 UT = Sun Oct 27 01:59:59 2024 IST isdst=1 gmtoff=3600

$ grep PRETTY /etc/os-release
PRETTY_NAME="Rocky Linux 9.4 (Blue Onyx)"
```

以下は、2019年の[Red Hat Developerブログ](https://developers.redhat.com/blog/2019/02/22/time-zone-data-tzdata-2018-data-format-changes-and-red-hat-enterprise-linux)からの引用だが、RHELでは、調査/検討の結果、当面の間Rearguardを採用するとしている。


> After much research and consideration, Red Hat Engineering determined that the best option for our customers and package providers was to delay introducing these new format changes while still providing accurate and timely updates. As such, the RHEL tzdata package is currently shipping with the rearguard format to allow time for our data format consumers to update their parsers to the newer format. 


この決定当時の方針としては、Rearguardの維持はあくまでVanguardへの移行までの過渡期的措置であり、将来的にはVanguardを採用しようとする方針であったようだ。これは、以下の通り[tzdataパッケージ開発ステータス](https://access.redhat.com/articles/1187353)からも読み取ることができる。


> Future releases of Red Hat Enterprise Linux will be transitioning to the default or "vanguard" tzdata data set.

Amazon Linuxはどうか。Amazon Linux 2はRearguardであり、Amazon Linux 2023からVanguardへ変わる。Amazon Linux上で、DSTフラグを用いてサマータイム判定をするなどしているアプリケーションは、バージョンアップに伴い動作がおかしくなる可能性があるので注意が必要である。この変更は、小さすぎるのだろう、AWS公式の[Amazon Linux 2とAmazon Linux 2023の比較資料](https://docs.aws.amazon.com/linux/al2023/ug/compare-with-al2.html)でも触れられてはいない。

```console:Amazon Linux 2 (Rearguard)
$ zdump -v Europe/Dublin | sed -n '/Oct.*2023.*gmtoff=0/,/Oct.*2024.*gmtoff=3600/p'
Europe/Dublin  Sun Oct 29 01:00:00 2023 UT = Sun Oct 29 01:00:00 2023 GMT isdst=0 gmtoff=0
Europe/Dublin  Sun Mar 31 00:59:59 2024 UT = Sun Mar 31 00:59:59 2024 GMT isdst=0 gmtoff=0
Europe/Dublin  Sun Mar 31 01:00:00 2024 UT = Sun Mar 31 02:00:00 2024 IST isdst=1 gmtoff=3600
Europe/Dublin  Sun Oct 27 00:59:59 2024 UT = Sun Oct 27 01:59:59 2024 IST isdst=1 gmtoff=3600

$ grep PRETTY /etc/os-release
PRETTY_NAME="Amazon Linux 2"
```

```console:Amazon Linux 2023 (Vanguard)
$ zdump -v Europe/Dublin | sed -n '/Oct.*2023.*gmtoff=0/,/Oct.*2024.*gmtoff=3600/p'
Europe/Dublin  Sun Oct 29 01:00:00 2023 UT = Sun Oct 29 01:00:00 2023 GMT isdst=1 gmtoff=0
Europe/Dublin  Sun Mar 31 00:59:59 2024 UT = Sun Mar 31 00:59:59 2024 GMT isdst=1 gmtoff=0
Europe/Dublin  Sun Mar 31 01:00:00 2024 UT = Sun Mar 31 02:00:00 2024 IST isdst=0 gmtoff=3600
Europe/Dublin  Sun Oct 27 00:59:59 2024 UT = Sun Oct 27 01:59:59 2024 IST isdst=0 gmtoff=3600

$ grep PRETTY /etc/os-release
PRETTY_NAME="Amazon Linux 2023.4.20240416"
```

### Alpine Linux

以下はAlpine Linux 3.19の結果である。AlpineもVanguardである。

なお、tzdataデータソースは [tzdata](https://pkgs.alpinelinux.org/package/edge/main/x86_64/tzdata) パッケージ、zdump等のCLIツールは [tzdata-utils](https://pkgs.alpinelinux.org/package/edge/main/x86_64/tzdata-utils)パッケージで提供される。


```console:Alpine Linux
$ zdump -v Europe/Dublin | sed -n '/Oct.*2023.*gmtoff=0/,/Oct.*2024.*gmtoff=3600/p'
Europe/Dublin  Sun Oct 29 01:00:00 2023 UT = Sun Oct 29 01:00:00 2023 GMT isdst=1 gmtoff=0
Europe/Dublin  Sun Mar 31 00:59:59 2024 UT = Sun Mar 31 00:59:59 2024 GMT isdst=1 gmtoff=0
Europe/Dublin  Sun Mar 31 01:00:00 2024 UT = Sun Mar 31 02:00:00 2024 IST isdst=0 gmtoff=3600
Europe/Dublin  Sun Oct 27 00:59:59 2024 UT = Sun Oct 27 01:59:59 2024 IST isdst=0 gmtoff=3600

$ grep PRETTY /etc/os-release
PRETTY_NAME="Alpine Linux v3.19"
```

### macOS

以下はmacOS 14.1の結果である。macOSはRearguardを採用している。

```console:macOS
$ zdump -v Europe/Dublin | sed -n '/Oct.*2023.*gmtoff=0/,/Oct.*2024.*gmtoff=3600/p'
Europe/Dublin  Sun Oct 29 01:00:00 2023 UT = Sun Oct 29 01:00:00 2023 GMT isdst=0 gmtoff=0
Europe/Dublin  Sun Mar 31 00:59:59 2024 UT = Sun Mar 31 00:59:59 2024 GMT isdst=0 gmtoff=0
Europe/Dublin  Sun Mar 31 01:00:00 2024 UT = Sun Mar 31 02:00:00 2024 IST isdst=1 gmtoff=3600
Europe/Dublin  Sun Oct 27 00:59:59 2024 UT = Sun Oct 27 01:59:59 2024 IST isdst=1 gmtoff=3600

$ sw_vers
ProductName:		macOS
ProductVersion:		14.1.1
BuildVersion:		23B81
```

### FreeBSD

以下はFreeBSD 14の結果である。FreeBSDはVanguardを採用している。

```console:FreeBSD
$ zdump -v Europe/Dublin | sed -n '/Oct.*2023.*gmtoff=0/,/Oct.*2024.*gmtoff=3600/p'
Europe/Dublin  Sun Oct 29 01:00:00 2023 UT = Sun Oct 29 01:00:00 2023 GMT isdst=1 gmtoff=0
Europe/Dublin  Sun Mar 31 00:59:59 2024 UT = Sun Mar 31 00:59:59 2024 GMT isdst=1 gmtoff=0
Europe/Dublin  Sun Mar 31 01:00:00 2024 UT = Sun Mar 31 02:00:00 2024 IST isdst=0 gmtoff=3600
Europe/Dublin  Sun Oct 27 00:59:59 2024 UT = Sun Oct 27 01:59:59 2024 IST isdst=0 gmtoff=3600

$ grep PRETTY /etc/os-release
PRETTY_NAME="FreeBSD 14.0-RELEASE"
```


### Python

Python では、ランタイムとしてtzdataデータソースを提供しておらず、[tzdata](https://pypi.org/project/tzdata/)パッケージでデータソースが提供されている。tzdataデータソースへのアクセス機能は、標準ライブラリの[zoneinfo](https://docs.python.org/3/library/zoneinfo.html) モジュールで提供される。`tzdata`パッケージはPyPIから提供される外部ライブラリだが、Pythonコアデベロッパーによりメンテナンスされている。`zoneinfo`のドキュメントからもファーストパーティ・パッケージとして参照されており、標準ライブラリに近い位置付けと言える。

`zoneinfo` は、デフォルトでOSのtzdataを優先的に参照し、それが見つからない場合に`tzdata`パッケージのデータソースを参照しようとする。ここでは、`tzdata`のデータソースがVanguardかRearguardなのかを確認したいので、OSのデータソースを無視させる必要がある。[zoneinfoマニュアル](https://docs.python.org/3/library/zoneinfo.html#envvar-PYTHONTZPATH)にもあるとおり、環境変数 `PYTHONTZPATH` に空文字列を設定しておくことで、`zoneinfo` はOSのtzdataを無視し、`tzdata`パッケージが提供するtzdataデータソースを参照するようになる。

以下は`tzdata`パッケージを用いて、アイルランドのDST切り替わり前後をDST情報を参照するサンプルコードと、その実行例である。
`tzdata` パッケージで提供されるデータソースでは、冬季にDStが設定されており、Vanguard形式を採用していることがわかる。

```python
import os
import zoneinfo
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo


def is_dst(dt: datetime) -> bool:
    return dt.astimezone(dt.tzinfo).dst() != timedelta(0)


def print_time(dt: datetime):
    print(f"{dt.tzinfo} {dt} DST={is_dst(dt)}")


if __name__ == "__main__":
    # tzdataデータソースとしてtzdataパッケージを優先する
    os.environ["PYTHONTZPATH"] = ""
    zoneinfo.reset_tzpath()

    utc = timezone.utc
    dublin = ZoneInfo("Europe/Dublin")
    print_time(datetime(2023, 10, 29, 1, 0, 0, tzinfo=utc).astimezone(dublin))
    print_time(datetime(2024, 3, 31, 0, 59, 59, tzinfo=utc).astimezone(dublin))
    print_time(datetime(2024, 3, 31, 1, 1, 1, tzinfo=utc).astimezone(dublin))
    print_time(datetime(2024, 10, 27, 0, 59, 59, tzinfo=utc).astimezone(dublin))
```

```console
$ python main.py
Europe/Dublin 2023-10-29 01:00:00+00:00 DST=True
Europe/Dublin 2024-03-31 00:59:59+00:00 DST=True
Europe/Dublin 2024-03-31 02:01:01+01:00 DST=False
Europe/Dublin 2024-10-27 01:59:59+01:00 DST=False
```

なお、tzdataパッケージには、`zic`でコンパイル済みのデータソースを同梱しており、そのまま`zdump`で参照して確認することもできる。

```console
$ curl -s -L https://github.com/python/tzdata/raw/2024.1/src/tzdata/zoneinfo/Europe/Dublin -o /tmp/Dublin
$ zdump -v /tmp/Dublin | sed -n '/Oct.*2023.*gmtoff=0/,/Oct.*2024.*gmtoff=3600/p'
/tmp/Dublin  Sun Oct 29 01:00:00 2023 UT = Sun Oct 29 01:00:00 2023 GMT isdst=1 gmtoff=0
/tmp/Dublin  Sun Mar 31 00:59:59 2024 UT = Sun Mar 31 00:59:59 2024 GMT isdst=1 gmtoff=0
/tmp/Dublin  Sun Mar 31 01:00:00 2024 UT = Sun Mar 31 02:00:00 2024 IST isdst=0 gmtoff=3600
/tmp/Dublin  Sun Oct 27 00:59:59 2024 UT = Sun Oct 27 01:59:59 2024 IST isdst=0 gmtoff=3600
```

### Ruby

Rubyも、Pythonと同様にランタイムとしてtzdataデータソースは提供していない。データソースは、[tzinfo-data](https://rubygems.org/gems/tzinfo-data/versions/1.2024.1) gemで利用できる。tzdataデータソースへのアクセス機能は、[tzinfo](https://rubygems.org/gems/tzinfo) gemで提供される。

`tzinfo` gemが参照するデータソースの優先順位は、Pythonとは逆に、`tzinfo-data` のデータソースが利用できればそれを優先的に参照し、利用できない場合はOSのtzdataデータソースを参照しようとする。

以下は `tzinfo-data` を用いて、アイルランドのDST切り替わり前後をDST情報を参照するサンプルコードと、その実行例である。
`tzinfo-data` で提供されるデータソースでは、冬季にDStが設定されており、Vanguard形式を採用していることがわかる。

```ruby
require 'tzinfo'

def print_dst(tz, t)
  printf("%s %s DST=%s\n", tz.identifier, t, t.dst?)
end

tz_dublin = TZInfo::Timezone.get('Europe/Dublin')
print_dst(tz_dublin, tz_dublin.to_local(Time.utc(2023, 10, 29, 1, 00, 00)))
print_dst(tz_dublin, tz_dublin.to_local(Time.utc(2024, 3, 31, 0, 59, 59)))
print_dst(tz_dublin, tz_dublin.to_local(Time.utc(2024, 3, 31, 1, 0, 0)))
print_dst(tz_dublin, tz_dublin.to_local(Time.utc(2024, 10, 27, 0, 59, 59)))
```

```console
$ ruby main.rb
Europe/Dublin 2023-10-29 01:00:00 +0000 DST=true
Europe/Dublin 2024-03-31 00:59:59 +0000 DST=true
Europe/Dublin 2024-03-31 02:00:00 +0100 DST=false
Europe/Dublin 2024-10-27 01:59:59 +0100 DST=false
```

### Go

Goでは、標準ライブラリの[time/tzdata](https://pkg.go.dev/time/tzdata)パッケージでtzdataデータソースが提供されている。tzdataデータソースへのアクセス機能は、標準の[time](https://pkg.go.dev/time)パッケージで提供される。

`time/tzdata`は、アプリケーションコードから直接インポートして利用することも可能ではあるが、通常はビルド時に`-tags timetzdata`を指定することでアプリケーションに組み込み、コードから直接インポートすることはしない。

`time`パッケージが参照するtzdataデータソースの優先順位は、Pythonと同様に、まずOSのtzdataデータソースを探し、それが見つからない場合に`time/tzdata`のデータソースを参照する。しかしながら、OS提供のデータソースよりも、`time/tzdata`のデータソースを優先的に参照する方法は、筆者が調査した限りで提供されていなかった (もしその方法をご存知の方がいれば、コメント等でご教示いただけますと幸いです)。

以下のサンプルコードは、強引に`time/tzdata`のデータソースを用いてそのDST定義を参照する実装例である。Goの非公開の内部実装に依存したコードであり、バージョン間の互換性は全く保証されていない点に留意のこと (go 1.22.3で確認)。

```go
package main

import (
	"fmt"
	"time"
	_ "unsafe" // for go:linkname
)

//go:linkname loadFromEmbeddedTZData time/tzdata.loadFromEmbeddedTZData
func loadFromEmbeddedTZData(name string) (string, error)

func printDst(t time.Time) {
	fmt.Printf("%s %s DST=%t\n", t.Location(), t, t.IsDST())
}

func main() {
	zoneData, _ := loadFromEmbeddedTZData("Europe/Dublin")
	dublin, _ := time.LoadLocationFromTZData("Europe/Dublin", []byte(zoneData))

	printDst(time.Date(2023, 10, 29, 1, 0, 0, 0, dublin))
	printDst(time.Date(2024, 03, 31, 0, 59, 59, 0, dublin))
	printDst(time.Date(2024, 03, 31, 1, 0, 0, 0, dublin))
	printDst(time.Date(2024, 10, 27, 0, 59, 59, 0, dublin))
}
```

```console
$ go run -tags timetzdata main.go
Europe/Dublin 2023-10-29 01:00:00 +0000 GMT DST=true
Europe/Dublin 2024-03-31 00:59:59 +0000 GMT DST=true
Europe/Dublin 2024-03-31 02:00:00 +0100 IST DST=false
Europe/Dublin 2024-10-27 00:59:59 +0100 IST DST=false

$ go version
go version go1.22.3 darwin/arm64
```

### Java

Javaは、上記で紹介した言語処理系と異なり、tzdataをランタイム内部に組み込んでいる。外部環境への依存が減る分クロスプラットフォーム運用で強味を発揮するが、逆に言えばランタイムとtzdataが結合度が高いとも言える。tzdataアップストリームでNegative DSTのような破壊的変更が導入された場合に、ランタイムとしての後方互換性維持に関わる問題が発生する。実際に、上述のNegative DST導入経緯で見た、旧来のタイムゾーンルールとの互換性維持を目的としたRearguard形式の導入は、tzdataリリースアナウンスでも触れらている通り、JavaのNegative DSTサポートが互換性観点で難しいことによる影響が大きかった。

以下は、JavaによるアイルランドDST確認のサンプルコードである。夏季にDSTが設定されており、Rearguardを採用していることが確認できる。実行例のランタイムは、OpenJDK Runtime Environment Temurin-21.0.3+9を用いている。

```java
import java.time.*;
import java.time.zone.ZoneRules;
import java.time.format.DateTimeFormatter;

public class Main {
	static void printDst(ZonedDateTime zdt) {
		var formatter = DateTimeFormatter.ofPattern("VV yyyy-MM-dd'T'HH:mm:ssZZ");
		var rule = zdt.getZone().getRules();
        System.out.println(zdt.format(formatter) + " DST=" + rule.isDaylightSavings(zdt.toInstant()));
	}

    public static void main(String[] args) {
		var dublin = ZoneId.of("Europe/Dublin");
		printDst(ZonedDateTime.ofInstant(Instant.parse("2023-10-29T01:00:00Z"), dublin));
		printDst(ZonedDateTime.ofInstant(Instant.parse("2024-03-31T00:59:59Z"), dublin));
		printDst(ZonedDateTime.ofInstant(Instant.parse("2024-03-31T01:00:00Z"), dublin));
		printDst(ZonedDateTime.ofInstant(Instant.parse("2024-10-27T00:59:59Z"), dublin));
    }
}
```

```console
$ java Main.java
Europe/Dublin 2023-10-29T01:00:00+0000 DST=false
Europe/Dublin 2024-03-31T00:59:59+0000 DST=false
Europe/Dublin 2024-03-31T02:00:00+0100 DST=true
Europe/Dublin 2024-10-27T01:59:59+0100 DST=true

$ java -version
openjdk version "21.0.3" 2024-04-16 LTS
OpenJDK Runtime Environment Temurin-21.0.3+9 (build 21.0.3+9-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.3+9 (build 21.0.3+9-LTS, mixed mode, sharing)
```

### C言語

以下は、完全におまけであるが、Cによるアイルランドのゾーン定義確認サンプルコードと、Ubuntu 24.04での実行例である。もちろんCではOS/処理系実装依存となる。

```c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

static void print_dst(const char *tz, time_t t) {
    char s_time[32] = {'\0'};
    const struct tm *tm = localtime(&t);
    strftime(s_time, sizeof(s_time), "%FT%T%z", tm);
    printf("%s %s DST=%d\n", tz, s_time, tm->tm_isdst);
}

int main() {
    const char tz_dublin[] = ":Europe/Dublin";
    setenv("TZ", tz_dublin, 1);

    print_dst(tz_dublin, 1698541200); // 2023-10-29T01:00:00Z
    print_dst(tz_dublin, 1711846799); // 2024-03-01T00:59:59Z
    print_dst(tz_dublin, 1711846800); // 2024-03-01T01:00:00Z
    print_dst(tz_dublin, 1729990799); // 2024-10-27T00:59:59Z
    return 0;
}
```

```console
$ gcc main.c && ./a.out
:Europe/Dublin 2023-10-29T01:00:00+0000 DST=1
:Europe/Dublin 2024-03-31T00:59:59+0000 DST=1
:Europe/Dublin 2024-03-31T02:00:00+0100 DST=0
:Europe/Dublin 2024-10-27T01:59:59+0100 DST=0
```

なお、本筋から脱線するが、おまけついでに上記サンプルコードにある環境変数 `TZ` に設定する `":Europe/Dublin"` の先頭のコロンについて補足しておく。コロンを見慣れないと感じる人もいるだろう。すなわち、`TZ=":Europe/Dublin"` ではなく、`TZ="Europe/Dublin"` ではないのか、と。確かにコロン無し版の方が一般によく知られており、普及しているが、実は前者のコロンで始まる形式は、[POSIX.1-2017](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03)で明示的に定義されている形式であり、後者のコロン無し版はPOSIXには(少なくともPOSIX.1-2017の時点では)規定がない。

POSIXでは、TZの先頭がコロンで始まる場合、その解釈は実装定義 (implementation-defined) とされている。glibc実装では、コロンに続けてtzdataのゾーンIDを書いて、tzdataを参照することができる。コロン無しでtzdataゾーンIDをそのまま書く形式はPOSIXでは定義されていない。glibcでは、独自拡張としてコロン無しでtzdataゾーンIDを書いても、コロン有りと同様に処理する。結局コロン有無どちらの形式でも実装に依存することに変わりはないが、コロン有りはPOSIXで明示的に定義された形式なので、未知の環境への移植性を考慮するとコロンを付ける方が好ましい。

以下は[POSIX.1-2017](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03)から、TZコロン形式の定義である:
> The value of TZ has one of the two forms (spaces inserted for clarity):
> 
> :characters
> 
> or:
> 
> std offset dst offset, rule
> 
> If TZ is of the first format (that is, if the first character is a <colon>), the characters following the <colon> are handled in an implementation-defined manner.

以下は、[glibcマニュアル](https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html)から、POSIX実装定義規定に対するglibcのTZ解釈方法の説明である:
> The third format looks like this:
> 
> :characters
> (略)
> 
> If characters begins with a slash, it is an absolute file name; otherwise the library looks for the file /usr/share/zoneinfo/characters. The zoneinfo directory contains data files describing local time zones in many different parts of the world. The names represent major cities, with subdirectories for geographical areas; for example, America/New_York, Europe/London, Asia/Hong_Kong.

### 上記の整理

全く網羅的ではないが、上記で紹介した環境の対応形式を表にまとめておく。

| OS/言語処理系           | 対応形式      |
|--------------------|-----------|
| Debian系            | Vanguard  |
| Fedora             | Vanguard  |
| RHEL 9             | Rearguard |
| Amazon Linux 2023  | Vanguard  |
| Amazon Linux 2     | Rearguard |
| Alpine Linux       | Vanguard  |
| macOS              | Rearguard |
| FreeBSD            | Vanguard  |
| Python (tzdata)    | Vanguard  |
| Ruby (tzinfo-data) | Vanguard  |
| Go (time/tzdata)   | Vanguard  |
| Java               | Rearguard |
| C                  | OS/実装依存   |

## まとめ

表題の疑問に戻ろう。アイルランドはサマータイムが冬にやってくる!? 答えは冒頭にも書いた通り、そんなわけない、だ。

* アイルランドは、夏時間(サマータイム)制ではなく、逆に冬季にマイナス1時間する冬時間制を採用している。
* これがtzdataに現れる、冬のNegative DSTである。
* tzdataプロジェクトでは、Negative DST導入を巡り、各種ダウンストリームプロジェクトを巻き込んで、ひと悶着あった。
* Negative DSTをサポートするVanguard形式、旧来との互換性を維持するRearguard形式を導入し、tzdata利用システムの都合でどちらかを選択的に採用できるようにする形で一応の和解を見た。
* OS/言語処理系は、Vanguard、Rearguard どちらを採用しているか様々である。
* tzdataに依存するシステムを使用/開発しているなら、自分が使っているtzdataデータソースがどうなっているか把握しておこう。バージョンアップで形式が変わる可能性もあり、正しく理解していないと思わぬ事故が発生するかもしれない。