そんなわけない。
と思われるかもしれないが、タイムゾーン情報のデファクトスタンダードであるIANA Time Zone Database (以下tzdata) では、アイルランドのタイムゾーン定義として、Daylight Saving Time (一般にいうサマータイムのこと。以下DSTと略す) が冬季に設定されている。
本稿は、IANA tzdataに存在する冬のDSTに関する調査記録である。
tzdata定義の確認
以下は、Ubuntu 24.04で、アイルランドのtzdataタイムゾーン情報 (Europe/Dublin) をダンプして抜粋したものである。
$ 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オフセット(時差)ルールを持つイギリスのタイムゾーンも見てみた。
$ 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のソースファイルを確認した。(tzdataには歴史的な過去の情報も定義されており、情報量が多く複雑なので、見るべきポイントに「★」マークを付けて以下に引用する)。
ポイントをまとめると、標準時は3月の最終日曜日から10月最終日曜日までの夏季にあたり、時差はUTC+01:00。DSTは10月最終日曜日から翌年3月最終日曜日までの冬季にあたり、標準時からマイナス1時間する。上にまとめた表の通りである。
しかしながら、引用トップのコメント文と見ると、この定義が意図的なものであることが明示されている。いわく、夏が標準時であり、冬にマイナス1時間する「Negative DST」が採られていると。夏時間制ならぬ冬時間制と言える。
※なお、引用ソースファイル後半のコメントにある、"Vanguard section"、"Rearguard section" は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)にも明記されており、意図的なものであることが分かる。
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) により、標準時 ("Standard time") をGMT+01:00と定める。さらに通年を標準時とした。
- 1971年、改正標準時法(1971) (Standard Time (Amendment) Act, 1971) により、冬時間 ("Winter time") を導入し、標準時をGMT+01:00、冬時間をGMTとすることが規定される。冬時間の対象期間は法相 (Minister for Justice) により変更することが可能とされる。
- 2001年、法相による冬時間令(2001) (Winter Time Order, 2001) にて、冬時間の開始を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) に適合するために定められた。
まとめると、アイルランドの現在の時間制度は、夏季は標準時 (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> 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は従来との互換性を損なう破壊的変更をもたらすとして、導入反対派と賛成派を中心に様々な立場から侃々諤々の議論に発展。数カ月に渡り続く。
-
2018-01-12: Negative DSTを導入したバージョン2018aがリリースされる (ただしその直後に問題(Negative DSTは無関係)が見つかり公式のリリースアナウンスはされていない。その次の2018bも同様)
2018aChange 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、OpenJDK 等で Negative DSTが問題を引き起こすことがメーリングリストに投稿される。これを受け、Negative DST反対派と賛成派の議論が再燃する。
-
2018-01-22: 上記のtzdata利用プロジェクトでの問題を受け、一時的な回避策としてNegative DSTをリバートして以前のタイムゾーンルールに戻したバージョン2018c がリリースされる
2018cRevert 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 がリリースされる。(なお後述するが、実際にOSや言語処理系によって、Vanguardを採用するもの、Rearguardを採用するものが分かれている)
2018dIn 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がリリースされ、現在に至る。
2018eThe main format uses negative DST again, for Ireland etc.
-
参考までに、Negative DST導入をめぐる活発な議論を受けて、根本的なプロジェクトの方針に対する問題提起も行われた ことにも触れておく。これはNegative DSTだけを問題としたものではないが、それが蓄積された不満を爆発させるトリガーとなったことは確かだろう。
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形式を採用していることが分かる。
$ 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である。
$ 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へ対応し、本稿執筆現在の最新版40でも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)"
$ 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のままとなっている。
$ 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ブログからの引用だが、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パッケージ開発ステータスからも読み取ることができる。
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の比較資料でも触れられてはいない。
$ 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"
$ 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 パッケージ、zdump等のCLIツールは tzdata-utilsパッケージで提供される。
$ 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を採用している。
$ 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を採用している。
$ 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パッケージでデータソースが提供されている。tzdataデータソースへのアクセス機能は、標準ライブラリのzoneinfo モジュールで提供される。tzdata
パッケージはPyPIから提供される外部ライブラリだが、Pythonコアデベロッパーによりメンテナンスされている。zoneinfo
のドキュメントからもファーストパーティ・パッケージとして参照されており、標準ライブラリに近い位置付けと言える。
zoneinfo
は、デフォルトでOSのtzdataを優先的に参照し、それが見つからない場合にtzdata
パッケージのデータソースを参照しようとする。ここでは、tzdata
のデータソースがVanguardかRearguardなのかを確認したいので、OSのデータソースを無視させる必要がある。zoneinfoマニュアルにもあるとおり、環境変数 PYTHONTZPATH
に空文字列を設定しておくことで、zoneinfo
はOSのtzdataを無視し、tzdata
パッケージが提供するtzdataデータソースを参照するようになる。
以下はtzdata
パッケージを用いて、アイルランドのDST切り替わり前後をDST情報を参照するサンプルコードと、その実行例である。
tzdata
パッケージで提供されるデータソースでは、冬季にDStが設定されており、Vanguard形式を採用していることがわかる。
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))
$ 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
で参照して確認することもできる。
$ 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 gemで利用できる。tzdataデータソースへのアクセス機能は、tzinfo gemで提供される。
tzinfo
gemが参照するデータソースの優先順位は、Pythonとは逆に、tzinfo-data
のデータソースが利用できればそれを優先的に参照し、利用できない場合はOSのtzdataデータソースを参照しようとする。
以下は tzinfo-data
を用いて、アイルランドのDST切り替わり前後をDST情報を参照するサンプルコードと、その実行例である。
tzinfo-data
で提供されるデータソースでは、冬季にDStが設定されており、Vanguard形式を採用していることがわかる。
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)))
$ 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パッケージでtzdataデータソースが提供されている。tzdataデータソースへのアクセス機能は、標準のtimeパッケージで提供される。
time/tzdata
は、アプリケーションコードから直接インポートして利用することも可能ではあるが、通常はビルド時に-tags timetzdata
を指定することでアプリケーションに組み込み、コードから直接インポートすることはしない。
time
パッケージが参照するtzdataデータソースの優先順位は、Pythonと同様に、まずOSのtzdataデータソースを探し、それが見つからない場合にtime/tzdata
のデータソースを参照する。しかしながら、OS提供のデータソースよりも、time/tzdata
のデータソースを優先的に参照する方法は、筆者が調査した限りで提供されていなかった (もしその方法をご存知の方がいれば、コメント等でご教示いただけますと幸いです)。
以下のサンプルコードは、強引にtime/tzdata
のデータソースを用いてそのDST定義を参照する実装例である。Goの非公開の内部実装に依存したコードであり、バージョン間の互換性は全く保証されていない点に留意のこと (go 1.22.3で確認)。
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))
}
$ 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を用いている。
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));
}
}
$ 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/処理系実装依存となる。
#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;
}
$ 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で明示的に定義されている形式であり、後者のコロン無し版はPOSIXには(少なくともPOSIX.1-2017の時点では)規定がない。
POSIXでは、TZの先頭がコロンで始まる場合、その解釈は実装定義 (implementation-defined) とされている。glibc実装では、コロンに続けてtzdataのゾーンIDを書いて、tzdataを参照することができる。コロン無しでtzdataゾーンIDをそのまま書く形式はPOSIXでは定義されていない。glibcでは、独自拡張としてコロン無しでtzdataゾーンIDを書いても、コロン有りと同様に処理する。結局コロン有無どちらの形式でも実装に依存することに変わりはないが、コロン有りはPOSIXで明示的に定義された形式なので、未知の環境への移植性を考慮するとコロンを付ける方が好ましい。
以下はPOSIX.1-2017から、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 ), the characters following the are handled in an implementation-defined manner.
以下は、glibcマニュアルから、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データソースがどうなっているか把握しておこう。バージョンアップで形式が変わる可能性もあり、正しく理解していないと思わぬ事故が発生するかもしれない。