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

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

このコミットは、Go言語のtimeパッケージがタイムゾーン情報ファイル(TZifファイル)のバージョン3をサポートするように拡張するものです。具体的には、zoneinfo_read.go内のタイムゾーンデータ読み込みロジックが更新され、バージョン3のTZifファイルを正しく認識・解析できるようになりました。これに伴い、新しいバージョン3のサポートを検証するためのテストケースがzoneinfo_test.goに追加されています。

コミット

commit 14a75ecf4a673e99ff41c1a1166289840d4f3cbb
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Tue Jun 3 11:44:17 2014 +0900

    time: support version 3 zone records

    Fixes #8134

    LGTM=iant
    R=golang-codereviews, iant
    CC=golang-codereviews, r, rsc
    https://golang.org/cl/100930044

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

https://github.com/golang/go/commit/14a75ecf4a673e99ff41c1a1166289840d4f3cbb

元コミット内容

time: support version 3 zone records

変更の背景

このコミットは、Go言語のtimeパッケージが、より新しいバージョンのタイムゾーン情報ファイル(TZifファイル)を処理できるようにするために導入されました。コミットメッセージに「Fixes #8134」とあるように、これは特定のバグまたは機能不足を修正するものです。

タイムゾーンのルールは複雑であり、歴史的にも将来においても変化し続ける可能性があります。これに対応するため、タイムゾーンデータファイル(TZifファイル)のフォーマットも進化してきました。特に、バージョン3のTZifファイルは、従来のバージョンでは表現できなかった、より広範な時間範囲や複雑なタイムゾーン遷移ルールをサポートするために導入されました。

Goのtimeパッケージが古いTZifバージョンのみをサポートしている場合、新しいタイムゾーンデータがリリースされた際に、Goアプリケーションが正確なタイムゾーン情報を取得できなくなる可能性があります。これは、特に国際的なアプリケーションや、長期にわたって正確な時間処理が求められるシステムにおいて、重大な問題を引き起こす可能性があります。

このコミットは、Goが最新のタイムゾーンデータに対応できるようにすることで、将来的なタイムゾーンルールの変更にも柔軟に対応し、Goアプリケーションの堅牢性と正確性を向上させることを目的としています。

前提知識の解説

タイムゾーン情報ファイル (TZif)

タイムゾーン情報ファイル(TZifファイル)は、世界中のタイムゾーンに関する情報を格納するための標準的なバイナリフォーマットです。これらのファイルは通常、Unix系システムでは/usr/share/zoneinfoディレクトリに配置されており、IANA Time Zone Databaseによって管理・更新されています。

TZifファイルには、特定のタイムゾーンにおけるUTCからのオフセット、夏時間(DST)の適用ルール、および過去から未来にわたるタイムゾーンの遷移情報(いつ、どのようにタイムゾーンが変更されたか)などが含まれています。Go言語のtimeパッケージは、これらのTZifファイルを読み込むことで、特定のタイムゾーンにおける正確な時刻計算や変換を行います。

TZif フォーマットのバージョン

TZifフォーマットには複数のバージョンが存在します。これは、タイムゾーンのルールが進化し、より複雑な情報を表現する必要が生じたためです。

  • バージョン0/1 (NUL): 最も基本的なバージョンで、32ビットのタイムスタンプを使用します。これにより、2038年問題(Unix時間で2038年1月19日以降の時刻を正確に表現できない問題)の影響を受けます。
  • バージョン2: 2038年問題に対応するため、64ビットのタイムスタンプをサポートするように拡張されました。これにより、より遠い未来のタイムゾーン遷移も正確に表現できるようになりました。
  • バージョン3: バージョン2の拡張に加え、POSIX TZ文字列の拡張を導入しています。特に、遷移時刻の「時間」コンポーネントが符号付きで、より広い範囲(-167から167)を許容するようになりました。これにより、従来のバージョンでは表現が困難だった、非常に特殊なタイムゾーンルールや、歴史的なタイムゾーンの変更をより正確に記述できるようになります。このバージョンは、特定の拡張機能が必要な場合にのみ生成されるべきとされています。
  • バージョン4: バージョン3の後に続く最新のバージョンで、さらなる拡張が含まれる可能性があります。

GoのtimeパッケージがTZifファイルを読み込む際、ファイルのヘッダーに含まれるバージョンバイトをチェックし、そのバージョンに応じた解析ロジックを適用します。このコミットの目的は、Goがバージョン3のTZifファイルを正しく認識し、その拡張された情報を利用できるようにすることです。

Go言語の time パッケージ

Go言語の標準ライブラリであるtimeパッケージは、時刻、期間、タイムゾーンなどの時間に関する操作を提供します。time.LoadLocation関数は、指定されたタイムゾーン名(例: "Asia/Tokyo", "America/New_York")に対応する*time.Locationオブジェクトをロードするために使用されます。この関数は、内部的にシステム上のTZifファイルを読み込み、その情報に基づいてLocationオブジェクトを構築します。

技術的詳細

このコミットの技術的な核心は、GoのtimeパッケージがTZifファイルのバージョンを識別するロジックにバージョン3のサポートを追加することです。

TZifファイルの構造は、まず44バイトのヘッダーから始まります。このヘッダーの最初のバイトは「マジックナンバー」の一部であり、その後のバイトがファイルのバージョンを示します。Goのtimeパッケージ内のloadZoneData関数は、このヘッダーを読み込み、バージョンバイトをチェックして、ファイルのフォーマットを判断します。

変更前は、loadZoneData関数はバージョンバイトが0(バージョン0/1)または'2'(バージョン2)であるTZifファイルのみを有効なものとして認識していました。しかし、このコミットにより、バージョンバイトが'3'であるファイルも有効なTZifファイルとして扱われるようになります。

バージョン3のTZifファイルは、特にPOSIX TZ文字列の拡張をサポートしており、これにより、より複雑なタイムゾーン遷移ルールを表現できます。例えば、遷移時刻のオフセットが従来の範囲を超えていたり、符号付きであったりする場合があります。Goがバージョン3をサポートすることで、これらの新しいルールが適用されたTZifファイルを正しく解析し、Goアプリケーション内で正確な時刻計算を行うことが可能になります。

また、テストコードの追加は、この変更が正しく機能することを保証するためのものです。TestVersion3関数は、特定のタイムゾーン(例: "Asia/Jerusalem")をロードしようとすることで、バージョン3のTZifファイルがGoによって正しく処理されることを検証します。time.ForceZipFileForTesting(true)は、テスト時にGoが組み込みのタイムゾーンデータ(通常はzoneinfo.zip)を使用するように強制するもので、これによりテスト環境に依存せずに特定のTZifファイルをテストできます。

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

src/pkg/time/zoneinfo_read.go

--- a/src/pkg/time/zoneinfo_read.go
+++ b/src/pkg/time/zoneinfo_read.go
@@ -68,7 +68,7 @@ func loadZoneData(bytes []byte) (l *Location, err error) {

 	// 1-byte version, then 15 bytes of padding
 	var p []byte
-	if p = d.read(16); len(p) != 16 || p[0] != 0 && p[0] != '2' {
+	if p = d.read(16); len(p) != 16 || p[0] != 0 && p[0] != '2' && p[0] != '3' {
 		return nil, badData
 	}

@@ -123,7 +123,7 @@ func loadZoneData(bytes []byte) (l *Location, err error) {
 		return nil, badData
 	}

-	// If version == 2, the entire file repeats, this time using
+	// If version == 2 or 3, the entire file repeats, this time using
 	// 8-byte ints for txtimes and leap seconds.
 	// We won't need those until 2106.

src/pkg/time/zoneinfo_test.go

--- a/src/pkg/time/zoneinfo_test.go
+++ b/src/pkg/time/zoneinfo_test.go
@@ -9,6 +9,15 @@ import (
 	"time"
 )

+func TestVersion3(t *testing.T) {
+	time.ForceZipFileForTesting(true)
+	defer time.ForceZipFileForTesting(false)
+	_, err := time.LoadLocation("Asia/Jerusalem")
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
 // Test that we get the correct results for times before the first
 // transition time.  To do this we explicitly check early dates in a
 // couple of specific timezones.

コアとなるコードの解説

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

loadZoneData関数は、TZifファイルのバイナリデータを解析し、time.Locationオブジェクトを構築するGoの内部関数です。

変更前のコードでは、ファイルのヘッダーから読み取った1バイトのバージョン情報p[0]0(バージョン0/1)またはASCII文字の'2'(バージョン2)のいずれかである場合にのみ、そのTZifファイルを有効と判断していました。

if p = d.read(16); len(p) != 16 || p[0] != 0 && p[0] != '2' {
    return nil, badData
}

このコミットでは、この条件にASCII文字の'3'が追加されました。

if p = d.read(16); len(p) != 16 || p[0] != 0 && p[0] != '2' && p[0] != '3' {
    return nil, badData
}

これにより、loadZoneData関数はバージョン3のTZifファイルを正しく認識し、その後の解析処理に進むことができるようになります。この変更は、Goが新しいタイムゾーンデータフォーマットに対応するための基盤となります。

また、関連するコメントも更新され、バージョン2だけでなくバージョン3も8バイトの整数を使用する可能性があることが明記されました。

// If version == 2 or 3, the entire file repeats, this time using
// 8-byte ints for txtimes and leap seconds.
// We won't need those until 2106.

src/pkg/time/zoneinfo_test.go の変更

新しいテスト関数TestVersion3が追加されました。このテストの目的は、Goがバージョン3のTZifファイルを正しくロードできることを検証することです。

func TestVersion3(t *testing.T) {
	time.ForceZipFileForTesting(true)
	defer time.ForceZipFileForTesting(false)
	_, err := time.LoadLocation("Asia/Jerusalem")
	if err != nil {
		t.Fatal(err)
	}
}
  • time.ForceZipFileForTesting(true): この行は、テスト中にGoがシステム上のTZifファイルではなく、Goの配布物に含まれるzoneinfo.zipファイルからタイムゾーンデータをロードするように強制します。これにより、テストの再現性が保証され、テストが実行されるシステム環境に依存しなくなります。defer time.ForceZipFileForTesting(false)は、テスト終了後にこの設定を元に戻します。
  • _, err := time.LoadLocation("Asia/Jerusalem"): この行は、"Asia/Jerusalem"という特定のタイムゾーンをロードしようとします。このタイムゾーンは、バージョン3のTZifファイルで表現される可能性のある複雑なルールを持つタイムゾーンの例として選ばれたと考えられます。
  • if err != nil { t.Fatal(err) }: タイムゾーンのロード中にエラーが発生した場合、テストは失敗します。これにより、バージョン3のTZifファイルが正しく解析されない場合に問題が検出されます。

このテストの追加により、Goのtimeパッケージが将来的にバージョン3のTZifファイルを含むタイムゾーンデータセットを問題なく処理できることが保証されます。

関連リンク

  • Go CL 100930044: https://golang.org/cl/100930044
  • Go Issue 8134: (コミットメッセージで参照されていますが、直接のリンクは特定できませんでした。Goの古いバグトラッカーまたは内部的な問題管理システムに存在した可能性があります。)

参考にした情報源リンク