[インデックス 17852] ファイルの概要
このコミットは、Go言語のtime
パッケージにおけるタイムゾーンデータの検索パスに関する修正と、そのフォールバックメカニズムのテスト強化を目的としています。特にUnixシステムにおいて、タイムゾーン情報がZIPファイルから読み込まれるフォールバックパスが正しくテストされるように、パスの定義とテストコードが調整されました。
コミット
commit a508381e89523c6a1682a1498b196d4262a1504a
Author: Russ Cox <rsc@golang.org>
Date: Tue Oct 29 17:11:51 2013 -0400
time: correct path to time zone zip file on Unix
Most Unix systems have their own time zone data,
so we almost never get far enough in the list to
discover that we cannot fall back to the zip file.
Adjust testing to exercise the final fallback.
Plan 9 and Windows were already correct
(and are the main users of the zip file).
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/19280043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a508381e89523c6a1682a1498b196d4262a1504a
元コミット内容
Goのtime
パッケージにおいて、UnixシステムでのタイムゾーンZIPファイルへのパスを修正し、テストを調整して最終的なフォールバックメカニズムが機能することを確認する。ほとんどのUnixシステムは独自のタイムゾーンデータを持っているため、ZIPファイルへのフォールバックパスがテストされる機会がほとんどなかった。Plan 9とWindowsは既に正しく、主にZIPファイルを使用している。
変更の背景
Go言語のtime
パッケージは、システムのタイムゾーン情報を利用して時刻の変換や表示を行います。この情報源として、Goは複数のパスを探索し、適切なタイムゾーンデータファイルを見つけようとします。一般的なUnix系システムでは、/usr/share/zoneinfo/
のような標準的なディレクトリにタイムゾーンデータが配置されています。しかし、これらのパスからデータが見つからない場合、Goは自身の配布物に含まれるタイムゾーン情報ZIPファイル(zoneinfo.zip
)にフォールバックするメカニズムを持っています。
このコミット以前は、Unixシステムにおいて、このzoneinfo.zip
へのパスが正しく設定されていなかったか、あるいはテストが不十分であったため、フォールバックメカニズムが実際に機能するかどうかの検証が困難でした。コミットメッセージによると、「ほとんどのUnixシステムは独自のタイムゾーンデータを持っているため、ZIPファイルにフォールバックできないことを発見するほどリストの奥深くまで到達することはほとんどない」という状況でした。つまり、通常の使用ではシステム提供のタイムゾーンデータが使われるため、Goが自身のZIPファイルに頼るシナリオが稀であり、結果としてそのフォールバックパスが壊れていても気づかれにくい状態だったのです。
この変更の目的は、Unix環境におけるzoneinfo.zip
へのパスを修正し、さらに、システム提供のタイムゾーンデータが見つからない状況を意図的に作り出すテストを追加することで、この重要なフォールバックメカニズムが確実に機能することを保証することです。これにより、Goアプリケーションが様々な環境でタイムゾーン情報を正しく扱えるよう、堅牢性が向上します。
前提知識の解説
- Go言語の
time
パッケージ: Go言語の標準ライブラリの一部で、時刻の表現、計算、フォーマット、タイムゾーンの処理などを担当します。time.Location
型は特定のタイムゾーンを表し、LoadLocation
関数などを使ってタイムゾーン情報をロードします。 - タイムゾーンデータ (zoneinfo): 世界中のタイムゾーンに関する情報(オフセット、夏時間規則など)を定義したデータセットです。IANA (Internet Assigned Numbers Authority) が管理するTZ database (またはzoneinfo database) が広く使われています。Unix系システムでは通常、
/usr/share/zoneinfo
などのディレクトリにバイナリ形式で格納されています。 zoneinfo.zip
: Go言語の配布物に含まれるタイムゾーンデータのZIPアーカイブです。システムにタイムゾーンデータが存在しない場合や、特定の環境(例: Windows、Plan 9)でシステム提供のデータが利用できない場合に、Goがフォールバックとして使用します。runtime.GOROOT()
: Go言語のランタイム関数で、Goのインストールルートディレクトリのパスを返します。Goの標準ライブラリやツールがどこにインストールされているかを知るために使用されます。- フォールバックメカニズム: ある主要な方法が失敗した場合に、代替の方法を試みる仕組みです。この文脈では、システム提供のタイムゾーンデータが見つからない場合に、Go自身の
zoneinfo.zip
を使用するメカニズムを指します。 - テスト駆動開発 (TDD) とテストの重要性: このコミットでは、既存のコードの修正だけでなく、その修正が意図通りに機能し、将来にわたって回帰しないことを保証するためのテストが追加されています。特に、通常では発生しにくい「フォールバック」のシナリオを強制的に発生させるテストは、システムの堅牢性を高める上で非常に重要です。
技術的詳細
このコミットの主要な変更点は、Unixシステムにおけるタイムゾーンデータの検索パスの修正と、そのフォールバックパスをテストするためのメカニズムの導入です。
Goのtime
パッケージは、zoneDirs
という文字列スライスに定義されたディレクトリパスを順番に探索してタイムゾーンデータを探します。このコミット以前のzoneinfo_unix.go
では、runtime.GOROOT() + "/lib/time/zoneinfo/"
というパスがリストに含まれていました。しかし、Goの配布物に含まれるタイムゾーンデータは通常、zoneinfo.zip
という単一のZIPファイルとして提供されており、ディレクトリではありませんでした。このため、Unixシステムでシステム提供のタイムゾーンデータが見つからなかった場合でも、Goは自身のzoneinfo.zip
を正しく見つけることができませんでした。
このコミットでは、zoneinfo_unix.go
内のzoneDirs
スライスにおいて、runtime.GOROOT() + "/lib/time/zoneinfo/"
というエントリをruntime.GOROOT() + "/lib/time/zoneinfo.zip"
に修正しました。これにより、Goは自身の配布物に含まれるZIPファイルを正しく検索対象に含めることができるようになりました。
さらに、このフォールバックメカニズムが実際に機能することをテストするために、forceZipFileForTesting
というテストヘルパー関数が導入されました。この関数は、テスト中にzoneDirs
スライスを一時的に変更し、システム提供のタイムゾーンデータパスを意図的に無効化(/XXXNOEXIST
のような存在しないパスに設定)することで、Goが強制的にzoneinfo.zip
にフォールバックする状況を作り出します。これにより、通常ではテストが難しいフォールバックシナリオを確実に検証できるようになりました。
export_test.go
では、このforceZipFileForTesting
関数がテストパッケージからアクセスできるようにエクスポートされています。そして、time_test.go
にはTestLoadLocationZipFile
という新しいテストが追加され、forceZipFileForTesting(true)
を呼び出してフォールバックを強制し、LoadLocation("Australia/Sydney")
がエラーなく成功することを確認しています。テストの終了時にはdefer ForceZipFileForTesting(false)
で元の状態に戻すことで、他のテストへの影響を防いでいます。
Plan 9とWindows向けのzoneinfo_plan9.go
とzoneinfo_windows.go
にもforceZipFileForTesting
関数が追加されていますが、これらのOSでは元々ZIPファイルのみを使用しているため、関数の中身は空(no-op)となっています。これは、クロスプラットフォームなテストヘルパーのインターフェースを統一するための措置です。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコードスニペットは以下の通りです。
-
src/pkg/time/zoneinfo_unix.go
:--- a/src/pkg/time/zoneinfo_unix.go +++ b/src/pkg/time/zoneinfo_unix.go @@ -32,7 +32,19 @@ var zoneDirs = []string{ "/usr/share/zoneinfo/", "/usr/share/lib/zoneinfo/", "/usr/lib/locale/TZ/", - runtime.GOROOT() + "/lib/time/zoneinfo/", + runtime.GOROOT() + "/lib/time/zoneinfo.zip", } +var origZoneDirs = zoneDirs + +func forceZipFileForTesting(zipOnly bool) { + zoneDirs = make([]string, len(origZoneDirs)) + copy(zoneDirs, origZoneDirs) + if zipOnly { + for i := 0; i < len(zoneDirs)-1; i++ { + zoneDirs[i] = "/XXXNOEXIST" + } + } +} func initLocal() {
-
src/pkg/time/export_test.go
:--- a/src/pkg/time/export_test.go +++ b/src/pkg/time/export_test.go @@ -18,4 +18,7 @@ func ForceUSPacificForTesting() { localOnce.Do(initTestingZone) } -var ParseTimeZone = parseTimeZone +var ( + ForceZipFileForTesting = forceZipFileForTesting + ParseTimeZone = parseTimeZone +)
-
src/pkg/time/time_test.go
:--- a/src/pkg/time/time_test.go +++ b/src/pkg/time/time_test.go @@ -578,6 +578,16 @@ func TestParseInSydney(t *testing.T) { } } +func TestLoadLocationZipFile(t *testing.T) { + ForceZipFileForTesting(true) + defer ForceZipFileForTesting(false) + + _, err := LoadLocation("Australia/Sydney") + if err != nil { + t.Fatal(err) + } +} + var rubyTests = []ParseTest{ {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010", true, true, 1, 0}, // Ignore the time zone in the test. If it parses, it'll be OK.
-
src/pkg/time/zoneinfo_plan9.go
およびsrc/pkg/time/zoneinfo_windows.go
: これらのファイルには、forceZipFileForTesting
関数が追加されましたが、Plan 9とWindowsでは元々ZIPファイルのみを使用するため、その実装は空です。// Example from zoneinfo_plan9.go func forceZipFileForTesting(zipOnly bool) { // We only use the zip file anyway. }
コアとなるコードの解説
-
src/pkg/time/zoneinfo_unix.go
の変更:zoneDirs
変数の変更:runtime.GOROOT() + "/lib/time/zoneinfo/"
がruntime.GOROOT() + "/lib/time/zoneinfo.zip"
に修正されました。これは、Goの配布物に含まれるタイムゾーンデータがディレクトリではなくZIPファイルとして提供されているため、正しいパスに修正されたことを意味します。これにより、Unixシステムでシステム提供のタイムゾーンデータが見つからない場合に、Goが自身のzoneinfo.zip
を正しく見つけられるようになります。origZoneDirs
変数の追加:zoneDirs
の元の状態を保存するために導入されました。これは、テスト中にzoneDirs
を変更した後、元の状態に戻すために使用されます。forceZipFileForTesting
関数の追加: この関数はテスト目的で導入されました。zipOnly
がtrue
の場合、zoneDirs
の最初のlen(zoneDirs)-1
個のエントリを/XXXNOEXIST
という存在しないパスに設定します。これにより、Goがシステム提供のタイムゾーンデータを見つけられなくなり、強制的にリストの最後の要素であるzoneinfo.zip
にフォールバックするシナリオをシミュレートできます。
-
src/pkg/time/export_test.go
の変更:ForceZipFileForTesting
変数の追加:forceZipFileForTesting
関数をテストパッケージからアクセスできるようにエクスポートしています。Goでは、内部の(アンエクスポートされた)関数をテスト目的で外部に公開するために、_test.go
ファイル内でエクスポートされた変数に代入する慣習があります。
-
src/pkg/time/time_test.go
の変更:TestLoadLocationZipFile
テスト関数の追加:ForceZipFileForTesting(true)
を呼び出すことで、タイムゾーンデータの検索パスを操作し、zoneinfo.zip
へのフォールバックを強制します。defer ForceZipFileForTesting(false)
は、テストが終了した後にzoneDirs
を元の状態に戻すためのものです。これにより、このテストが他のテストに影響を与えることを防ぎます。LoadLocation("Australia/Sydney")
を呼び出し、エラーが発生しないことを確認します。これは、フォールバックメカニズムが正しく機能し、zoneinfo.zip
からタイムゾーン情報がロードできることを検証しています。
-
src/pkg/time/zoneinfo_plan9.go
およびsrc/pkg/time/zoneinfo_windows.go
の変更:- これらのファイルにも
forceZipFileForTesting
関数が追加されましたが、その実装は空です。これは、Plan 9とWindowsでは元々タイムゾーンデータの主要なソースとしてzoneinfo.zip
を使用しているため、Unixのようにパスを操作してフォールバックを強制する必要がないためです。この追加は、テストヘルパーのインターフェースを統一し、クロスプラットフォームなテストコードの記述を容易にするためのものです。
- これらのファイルにも
これらの変更により、Goのtime
パッケージはUnixシステムにおいてもzoneinfo.zip
へのフォールバックが正しく機能するようになり、その動作がテストによって保証されるようになりました。
関連リンク
- Go CL 19280043: https://golang.org/cl/19280043
参考にした情報源リンク
- Go言語の公式ドキュメント (timeパッケージ): https://pkg.go.dev/time
- IANA Time Zone Database: https://www.iana.org/time-zones
- Goのテストに関するドキュメント (testingパッケージ): https://pkg.go.dev/testing
- Goの内部パッケージのエクスポートに関する慣習 (例:
_test.go
ファイル): https://go.dev/doc/effective_go#test (直接的な言及ではないが、慣習として理解するために参照) - Goの
runtime
パッケージ: https://pkg.go.dev/runtime