Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15119] ファイルの概要

このコミットは、Go言語の標準ライブラリであるtimeパッケージにおける、タイムゾーン情報ファイル(zoneinfoファイル)の取り扱いに関するバグ修正です。具体的には、タイムゾーンの移行情報(夏時間への切り替えなど)を含まないzoneinfoファイルをtimeパッケージが正しく処理できない問題に対応しています。

コミット

commit 6a003d7589e9f820bd94140ca151d5bb6b8e1a41
Author: Russ Cox <rsc@golang.org>
Date:   Sun Feb 3 22:41:00 2013 -0500

    time: handle zone file with no transitions
    
    Code fix by Alex Bramley.
    
    Fixes #4064.
    
    R=golang-dev, adg
    CC=golang-dev
    https://golang.org/cl/7289049

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/6a003d7589e9f820bd94140ca151d5bb6b8e1a41

元コミット内容

このコミットは、timeパッケージが「遷移(transitions)を持たないゾーンファイル」を適切に処理できるようにするためのものです。Alex Bramleyによってコード修正が提供されました。Goプロジェクトの内部課題トラッカーにおけるIssue #4064を修正します。

変更の背景

Go言語のtimeパッケージは、システムにインストールされているタイムゾーン情報(zoneinfoデータ)を利用して、時刻のローカライズやタイムゾーン間の変換を行います。zoneinfoデータは、特定のタイムゾーンにおけるUTCからのオフセットや夏時間(Daylight Saving Time, DST)の適用規則、およびそれらの規則が変更される「遷移(transitions)」の情報を格納しています。

しかし、一部のタイムゾーン、特にEtc/GMTのような固定オフセットのタイムゾーンは、夏時間のような時間帯の変更がなく、したがってzoneinfoファイル内に「遷移」の情報を一切持たない場合があります。Goのtimeパッケージの以前の実装では、このような遷移情報が空のzoneinfoファイルを読み込んだ際に、内部的な処理が適切に行われず、タイムゾーンの解決に失敗したり、誤ったオフセットを返したりする可能性がありました。

この問題は、GoプロジェクトのIssue #4064として報告され、Etc/GMT+1のようなタイムゾーンをLoadLocation関数で読み込んだ際に、期待されるオフセットが返されないという具体的な挙動として現れていました。このコミットは、この問題を解決し、遷移情報を持たないタイムゾーンも正しく扱えるようにすることを目的としています。

前提知識の解説

タイムゾーンとUTCオフセット

  • タイムゾーン: 地球上の特定の地域で共通して使用される標準時を定義するものです。例えば、「日本標準時 (JST)」や「協定世界時 (UTC)」などがあります。
  • UTC (Coordinated Universal Time): 世界中で時刻の基準として使われる標準時です。タイムゾーンは通常、UTCからのオフセット(ずれ)で表現されます。例えば、JSTはUTC+9時間です。
  • 夏時間 (Daylight Saving Time, DST): 特定の期間(通常は夏の間)に時計を1時間進める制度です。これにより、日中の明るい時間を有効活用し、エネルギー消費を抑えるなどの目的があります。夏時間が適用されるタイムゾーンでは、年に2回(夏時間開始時と終了時)UTCからのオフセットが変更されます。

Zoneinfoファイル

  • Zoneinfo (tzdata): タイムゾーン情報データベースの標準的な形式です。Unix系システムでは/usr/share/zoneinfoなどのディレクトリに格納されており、各ファイルが特定のタイムゾーンの規則を定義しています。
  • 遷移 (Transitions): zoneinfoファイル内で定義される、UTCオフセットやタイムゾーン名の変更が発生する時点のことです。夏時間の開始や終了、あるいは歴史的なタイムゾーン規則の変更などがこれに該当します。多くのタイムゾーンは複数の遷移情報を持っていますが、Etc/GMTのような固定オフセットのタイムゾーンは遷移を持ちません。

Go言語のtimeパッケージ

  • time.Location: Go言語でタイムゾーンを表す型です。time.LoadLocation関数を使って、システムにインストールされているzoneinfoファイルからLocationオブジェクトを読み込むことができます。
  • time.Now().In(loc): 現在時刻を特定のLocation(タイムゾーン)に変換します。
  • Zone()メソッド: time.Time型のメソッドで、その時刻におけるタイムゾーン名とUTCからのオフセット(秒単位)を返します。

技術的詳細

このコミットの技術的な核心は、timeパッケージがzoneinfoファイルを解析する際に、遷移情報が全く存在しないケースを適切に処理するように改善された点にあります。

Goのtimeパッケージは、zoneinfoファイルを読み込む際に、そのファイルに含まれる遷移情報(zoneTrans構造体のスライス)を解析します。従来のロジックでは、この遷移情報スライスが空の場合、タイムゾーンのオフセットを決定するための情報が不足し、予期せぬ動作を引き起こす可能性がありました。

具体的には、src/pkg/time/zoneinfo_read.go内のloadZoneData関数がzoneinfoファイルのバイナリデータを解析し、Locationオブジェクトを構築します。この関数内で、txという変数に遷移情報が格納されます。

修正前は、txが空の場合の特別なハンドリングがありませんでした。そのため、Etc/GMT+1のように遷移を持たないタイムゾーンを読み込もうとすると、txが空のままLocationオブジェクトが構築され、その後の時刻計算で問題が発生していました。

このコミットでは、loadZoneData関数に以下のロジックが追加されました。

if len(tx) == 0 {
	// Build fake transition to cover all time.
	// This happens in fixed locations like "Etc/GMT0".
	tx = append(tx, zoneTrans{when: -1 << 63, index: 0})
}

このコードは、tx(遷移情報スライス)が空の場合に実行されます。これは、読み込んだzoneinfoファイルがタイムゾーンの遷移情報を持たないことを意味します。このような場合、Goは「すべての時間をカバーする偽の遷移(fake transition)」を一つ追加します。

  • when: -1 << 63: これはGoのint64型の最小値であり、事実上「時間の始まり」を表します。これにより、この偽の遷移がすべての過去および未来の時刻に適用されることを保証します。
  • index: 0: これは、zoneinfoファイル内で定義されている最初のゾーン情報(オフセットや略称など)を指します。遷移がないタイムゾーンでは、常にこの最初のゾーン情報が適用されるため、indexは0で十分です。

この偽の遷移を追加することで、timeパッケージは、遷移情報を持たないタイムゾーンであっても、常に有効なzoneTransエントリを持つことになり、内部的な時刻計算ロジックが正しく機能するようになります。これにより、Etc/GMT+1のような固定オフセットのタイムゾーンも期待通りに処理できるようになりました。

また、src/pkg/time/time_test.goには、この修正を検証するための新しいテストケースTestLoadFixedが追加されました。このテストは、Etc/GMT+1を読み込み、そのタイムゾーンにおける現在時刻のオフセットが期待通り-1*60*60秒(-1時間)であることを確認します。ここで注意すべきは、Etc/GMT+1という名前は「東が負」という慣習に従っているため、Goのtimeパッケージが「東が正」という慣習に従う場合、オフセットは-1時間となる点です。

コアとなるコードの変更箇所

src/pkg/time/time_test.go

--- a/src/pkg/time/time_test.go
+++ b/src/pkg/time/time_test.go
@@ -1265,6 +1265,22 @@ func TestCountMallocs(t *testing.T) {
 	}\n }\n \n+func TestLoadFixed(t *testing.T) {\n+\t// Issue 4064: handle locations without any zone transitions.\n+\tloc, err := LoadLocation("Etc/GMT+1")\n+\tif err != nil {\n+\t\tt.Fatal(err)\n+\t}\n+\n+\t// The tzdata name Etc/GMT+1 uses "east is negative",\n+\t// but Go and most other systems use "east is positive".\n+\t// So GMT+1 corresponds to -3600 in the Go zone, not +3600.\n+\tname, offset := Now().In(loc).Zone()\n+\tif name != "GMT+1" || offset != -1*60*60 {\n+\t\tt.Errorf("Now().In(loc).Zone() = %q, %d, want %q, %d", name, offset, "GMT+1", -1*60*60)\n+\t}\n+}\n+\n func BenchmarkNow(b *testing.B) {
 \tfor i := 0; i < b.N; i++ {
 \t\tt = Now()\

src/pkg/time/zoneinfo_read.go

--- a/src/pkg/time/zoneinfo_read.go
+++ b/src/pkg/time/zoneinfo_read.go
@@ -174,6 +174,12 @@ func loadZoneData(bytes []byte) (l *Location, err error) {
 		}\n \t}\n \n+\tif len(tx) == 0 {\n+\t\t// Build fake transition to cover all time.\n+\t\t// This happens in fixed locations like "Etc/GMT0".\n+\t\ttx = append(tx, zoneTrans{when: -1 << 63, index: 0})\n+\t}\n+\n \t// Committed to succeed.\n \tl = &Location{zone: zone, tx: tx}\n \n```

## コアとなるコードの解説

### `src/pkg/time/time_test.go`の変更

*   `TestLoadFixed`関数が追加されました。
*   このテストは、Goの`time`パッケージが、タイムゾーンの遷移情報を持たない`Etc/GMT+1`のようなロケーションを正しく処理できることを検証します。
*   `LoadLocation("Etc/GMT+1")`でロケーションを読み込み、エラーがないことを確認します。
*   `Now().In(loc).Zone()`を呼び出し、現在の時刻におけるタイムゾーン名とオフセットを取得します。
*   `Etc/GMT+1`はtzdataの命名規則で「東が負」を意味するため、Goの「東が正」という慣習ではオフセットが`-1`時間(`-3600`秒)になることを確認します。

### `src/pkg/time/zoneinfo_read.go`の変更

*   `loadZoneData`関数内に、`if len(tx) == 0`という条件分岐が追加されました。
*   `tx`は、zoneinfoファイルから読み込まれたタイムゾーンの遷移情報を格納するスライスです。
*   もし`tx`が空(つまり、zoneinfoファイルに遷移情報がない)であれば、以下の処理が実行されます。
    *   `tx = append(tx, zoneTrans{when: -1 << 63, index: 0})`
    *   これは、`zoneTrans`構造体の新しい要素を`tx`スライスに追加するものです。
    *   `when: -1 << 63`は、`int64`型の最小値であり、この遷移がすべての時刻に適用されることを示します。
    *   `index: 0`は、zoneinfoファイル内で定義されている最初のタイムゾーン情報(オフセットや略称など)を使用することを示します。遷移がないタイムゾーンでは、常にこの最初の情報が適用されます。
*   この変更により、遷移情報を持たないタイムゾーンであっても、`time`パッケージが内部的に有効な遷移情報を持つことになり、時刻計算が正しく行われるようになります。

## 関連リンク

*   Go言語の公式ドキュメント: [https://golang.org/pkg/time/](https://golang.org/pkg/time/)
*   Go言語のIssueトラッカー: [https://github.com/golang/go/issues](https://github.com/golang/go/issues) (Issue #4064は、このコミットで修正されたGoプロジェクトの内部課題トラッカーの番号です。)

## 参考にした情報源リンク

*   GitHubのコミットページ: [https://github.com/golang/go/commit/6a003d7589e9f820bd94140ca151d5bb6b8e1a41](https://github.com/golang.com/go/commit/6a003d7589e9f820bd94140ca151d5bb6b8e1a41)
*   tzdata (IANA time zone database) の情報: [https://www.iana.org/time-zones](https://www.iana.org/time-zones)
*   Go言語のコードレビューシステム (Gerrit) の変更リスト: [https://golang.org/cl/7289049](https://golang.org/cl/7289049) (これはコミットメッセージに記載されている変更リストのURLです。)