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

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

このコミットは、Go言語の time パッケージにおいて、システム上にタイムゾーン情報ファイル(zoneinfo)が見つからない場合に、Goディストリビューションに含まれるzoneinfoデータを使用するようにフォールバックメカニズムを導入するものです。これにより、特にWindowsやPlan 9のような非Unix系システムでのタイムゾーン処理の堅牢性が向上します。

コミット

commit ad17a9c0a9e758a584f3d5992bc75fbde9512122
Author: Russ Cox <rsc@golang.org>
Date:   Sat Feb 18 21:02:41 2012 -0500

    time: use Go distribution zoneinfo if system copy not found
    
    Fixes #2964.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/5656101
---
 src/pkg/time/zoneinfo.go         |  22 ++++-
 src/pkg/time/zoneinfo_plan9.go   |  24 ++---
 src/pkg/time/zoneinfo_read.go    | 203 +++++++++++++++++++++++++++++++++++++++
 src/pkg/time/zoneinfo_unix.go    | 199 ++------------------------------------\
 src/pkg/time/zoneinfo_windows.go |   5 +-\n 5 files changed, 243 insertions(+), 210 deletions(-)

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

https://github.com/golang/go/commit/ad17a9c0a9e758a584f3d5992bc75fbde9512122

元コミット内容

time: use Go distribution zoneinfo if system copy not found

Fixes #2964.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5656101

変更の背景

この変更は、Goの time パッケージがタイムゾーン情報を解決する際に直面していた問題に対処するために行われました。Goの time パッケージは、タイムゾーンの計算にIANA Time Zone Database(tzdata)を利用します。しかし、このデータベースのファイル(zoneinfoファイル)は、オペレーティングシステムによって保存場所が異なります。特に、WindowsやPlan 9のようなUnix系ではないシステムでは、これらのファイルが標準的なUnixのパス(例: /usr/share/zoneinfo)に存在しないことが一般的でした。

このため、Goアプリケーションがこれらのシステムで実行された際に、タイムゾーン情報を正しくロードできず、エラーが発生したり、UTC(協定世界時)にフォールバックしたりする問題がありました。Issue #2964は、この問題を具体的に指摘し、Goディストリビューション自体にzoneinfoファイルを含め、システムにファイルが見つからない場合にそれらを使用するメカニズムを導入することを提案していました。このコミットは、その提案を実装し、Goアプリケーションのタイムゾーン処理の移植性と信頼性を向上させることを目的としています。

前提知識の解説

IANA Time Zone Database (tzdata)

IANA Time Zone Database(旧称 tzdata、zoneinfo database)は、世界のタイムゾーンと夏時間(DST)のルールに関する情報を集約した公開データベースです。このデータベースは、タイムゾーンの歴史的な変更、将来の変更、および夏時間の開始・終了日など、複雑なタイムゾーン情報を網羅しています。多くのオペレーティングシステムやプログラミング言語が、正確な日時計算のためにこのデータベースを利用しています。

Zoneinfoファイル

IANA Time Zone Databaseのデータは、通常、バイナリ形式の「zoneinfoファイル」としてシステムに配布されます。これらのファイルは、特定のタイムゾーン(例: America/New_York)に関するすべてのタイムゾーンルールとオフセット情報を含んでいます。Goの time パッケージは、これらのファイルを読み込んでタイムゾーン情報を解析し、日時計算に利用します。

$GOROOT

$GOROOT は、Goのインストールディレクトリを指す環境変数です。Goのコンパイラ、標準ライブラリ、ツールなどがこのディレクトリに格納されています。このコミットでは、システムにzoneinfoファイルが見つからない場合のフォールバックとして、$GOROOT 内にGoディストリビューションが提供するzoneinfoファイルを配置し、そこから読み込むように変更されました。

syscall.Getenv

syscall.Getenv は、Go言語の syscall パッケージに含まれる関数で、指定された環境変数の値を取得するために使用されます。このコミットでは、ZONEINFO 環境変数の値を読み取るために使用されています。

技術的詳細

このコミットの主要な技術的変更点は、Goの time パッケージがタイムゾーン情報をロードする際の探索パスを拡張したことです。

  1. ZONEINFO 環境変数の優先: LoadLocation 関数は、まず ZONEINFO 環境変数が設定されているかどうかを確認します。もし設定されていれば、そのパスをタイムゾーンファイルの検索パスとして最優先で使用します。これにより、ユーザーはカスタムのzoneinfoディレクトリを指定できるようになります。

  2. システム標準パスの探索: 従来のUnix系システムにおける標準的なzoneinfoパス(例: /usr/share/zoneinfo)の探索は引き続き行われます。

  3. Goディストリビューション内のzoneinfoへのフォールバック: 最も重要な変更は、システム標準パスでzoneinfoファイルが見つからない場合に、Goディストリビューション自体に含まれるzoneinfoファイル($GOROOT/lib/time/zoneinfo/)を探索するようになった点です。これにより、特にWindowsやPlan 9のような、システムにzoneinfoファイルが標準で含まれていない環境でも、Goアプリケーションがタイムゾーン情報を正しくロードできるようになります。

  4. zoneinfo_read.go の導入: zoneinfoファイルのバイナリ形式を解析するための共通ロジックが、新しく導入された src/pkg/time/zoneinfo_read.go ファイルに集約されました。これにより、各OS固有のファイル(zoneinfo_unix.go, zoneinfo_plan9.go, zoneinfo_windows.go)から重複する解析ロジックが削除され、コードの保守性が向上しました。

  5. OS固有の変更:

    • zoneinfo_unix.go から、zoneinfoファイルの読み込みと解析に関する共通ロジックが削除され、zoneinfo_read.go に移行しました。また、zoneDirs$GOROOT/lib/time/zoneinfo/ が追加されました。
    • zoneinfo_plan9.go では、loadZoneDataloadZoneFile の関数名が loadZoneDataPlan9loadZoneFilePlan9 に変更され、zoneinfo_read.go の共通関数と区別されました。また、initLocal および loadLocation$GOROOT 内のzoneinfoを参照するようになりました。
    • zoneinfo_windows.go では、loadLocation 関数が $GOROOT 内のzoneinfoファイルを読み込むように実装されました。

これらの変更により、Goの time パッケージは、より多くの環境でタイムゾーン処理を安定して行えるようになりました。

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

このコミットでは、主に以下のファイルが変更されています。

  • src/pkg/time/zoneinfo.go:
    • syscall パッケージのインポートが追加されました。
    • ZONEINFO 環境変数を読み取るための zoneinfo グローバル変数が追加されました。
    • LoadLocation 関数が変更され、ZONEINFO 環境変数で指定されたパス、および $GOROOT/lib/time/zoneinfo を探索するロジックが追加されました。
  • src/pkg/time/zoneinfo_plan9.go:
    • loadZoneDataloadZoneFile の関数名が loadZoneDataPlan9loadZoneFilePlan9 に変更されました。
    • initTestingZoneinitLocalloadLocation 関数が $GOROOT 内のzoneinfoファイルを優先的に参照するように変更されました。
  • src/pkg/time/zoneinfo_read.go (新規ファイル):
    • zoneinfoファイルのバイナリ形式を解析するための共通ロジック(data 構造体、read, big4, byte, byteString メソッド、loadZoneData 関数)が実装されました。
    • loadZoneFile 関数もこのファイルに移動されました。
  • src/pkg/time/zoneinfo_unix.go:
    • zoneinfo_read.go に移動された共通ロジック(data 構造体とそのメソッド、loadZoneData 関数)が削除されました。
    • zoneDirs スライスに $GOROOT/lib/time/zoneinfo/ が追加され、Unix系システムでもGoディストリビューションのzoneinfoが探索されるようになりました。
  • src/pkg/time/zoneinfo_windows.go:
    • loadLocation 関数が実装され、$GOROOT 内のzoneinfoファイルを読み込むようになりました。

コアとなるコードの解説

src/pkg/time/zoneinfo.go

// ...
import (
	"sync"
	"syscall" // 追加
)

// ...

var zoneinfo, _ = syscall.Getenv("ZONEINFO") // ZONEINFO環境変数を読み込む

// LoadLocation returns the Location with the given name.
// ...
func LoadLocation(name string) (*Location, error) {
	if name == "" || name == "UTC" {
		return UTC, nil
	}
	if name == "Local" {
		return Local, nil
	}
	if zoneinfo != "" { // ZONEINFO環境変数が設定されている場合
		if z, err := loadZoneFile(zoneinfo + "/" + name); err == nil { // そのパスを試す
			z.name = name
			return z, nil
		}
	}
	return loadLocation(name) // OS固有のロードロジックを呼び出す
}

LoadLocation 関数は、タイムゾーン名を元に Location オブジェクトをロードするGoの主要なAPIです。この変更により、まず ZONEINFO 環境変数が設定されているかを確認し、設定されていればそのパスを優先的に探索します。これにより、ユーザーはシステムに依存しないカスタムのタイムゾーンデータパスを指定できるようになります。

src/pkg/time/zoneinfo_read.go (新規ファイル)

このファイルは、IANA zoneinfoファイルのバイナリ形式を解析するための汎用的なロジックを含んでいます。

// Parse "zoneinfo" time zone file.
// This is a fairly standard file format used on OS X, Linux, BSD, Sun, and others.
// See tzfile(5), http://en.wikipedia.org/wiki/Zoneinfo,
// and ftp://munnari.oz.au/pub/oldtz/

package time

import "errors"

// Simple I/O interface to binary blob of data.
type data struct {
	p     []byte
	error bool
}

// read, big4, byte, byteString などのヘルパーメソッドが定義されている

var badData = errors.New("malformed time zone information")

func loadZoneData(bytes []byte) (l *Location, err error) {
	// zoneinfoファイルのバイナリデータを解析し、Location構造体を構築するロジック
	// マジックナンバー "TZif" のチェック、ヘッダー情報の読み込み、
	// タイムゾーン遷移情報、ゾーン情報、略語などの解析を行う
}

func loadZoneFile(name string) (l *Location, err error) {
	buf, err := readFile(name) // ファイルを読み込む
	if err != nil {
		return
	}
	return loadZoneData(buf) // 読み込んだデータを解析する
}

loadZoneData 関数は、zoneinfoファイルのバイナリコンテンツをバイトスライスとして受け取り、それをGoの time.Location 構造体にパースします。この関数は、ファイルのヘッダー、タイムゾーン遷移情報、タイムゾーンのオフセットと夏時間フラグ、タイムゾーン名の略語など、zoneinfoファイルの複雑な構造を解釈します。loadZoneFile は、指定されたパスからファイルを読み込み、その内容を loadZoneData に渡して解析を行います。この共通化により、各OS固有のファイルから重複する解析ロジックが排除されました。

src/pkg/time/zoneinfo_unix.go

// ...
import (
	"errors"
	"runtime" // 追加
	"syscall"
)

// ...

var zoneDirs = []string{
	"/usr/share/zoneinfo/",
	"/usr/share/lib/zoneinfo/",
	"/usr/lib/locale/TZ/",
	runtime.GOROOT() + "/lib/time/zoneinfo/", // 追加
}

func initLocal() {
	// ...
}

func loadLocation(name string) (*Location, error) {
	// zoneDirsを順に探索し、zoneinfoファイルをロードする
	for _, zoneDir := range zoneDirs {
		if z, err := loadZoneFile(zoneDir + name); err == nil {
			z.name = name
			return z, nil
		}
	}
	return nil, errors.New("unknown time zone " + name)
}

zoneDirs スライスに $GOROOT/lib/time/zoneinfo/ が追加されたことで、Unix系システムでも、標準的なシステムパスにzoneinfoファイルが見つからない場合に、Goディストリビューションに含まれるファイルが探索されるようになりました。これにより、Goのインストールだけでタイムゾーン処理が完結するようになり、外部のシステム設定に依存する度合いが低減されます。

src/pkg/time/zoneinfo_windows.go

// ...

func loadLocation(name string) (*Location, error) {
	// Windowsでは、システムレジストリからタイムゾーン情報を取得するロジックがあるが、
	// このコミットでは、GOROOT内のzoneinfoファイルも探索するようになった。
	if z, err := loadZoneFile(runtime.GOROOT() + `\lib\time\zoneinfo\` + name); err == nil {
		z.name = name
		return z, nil
	}
	return nil, errors.New("unknown time zone " + name)
}

Windows環境では、システムレジストリからタイムゾーン情報を取得する独自のメカニズムがありますが、このコミットにより、$GOROOT 内のzoneinfoファイルも探索するようになりました。これにより、Windows上でもGoアプリケーションがより確実にタイムゾーン情報を取得できるようになります。

関連リンク

参考にした情報源リンク