Bryan

Bryan

twitter
medium

時差について話しましょう

通常、ローカライズのプロセスでは、タイムゾーンの変換に関する問題が発生することがありますが、通常、タイムゾーンに関心を持つ前に私たちが「デフォルト」として使用するタイムゾーンは UTC または「ローカル」です。

この記事では、Go を例に挙げて、Go でのタイムゾーンの使用方法を分析します。

タイムゾーンの読み込み#

Go では、タイムゾーンの読み込みにはLoadLocation関数が使用されます。

// LoadLocationは、指定された名前のLocationを返します。
//
// nameが""または"UTC"の場合、LoadLocationはUTCを返します。
// nameが"Local"の場合、LoadLocationはLocalを返します。
//
// それ以外の場合、nameはファイルに対応する場所の場所名と見なされます
// IANAタイムゾーンデータベース内の、たとえば"America/New_York"のようなものです。
//
// LoadLocationは、次の順序でIANAタイムゾーンデータベースを検索します。
//
// - ZONEINFO環境変数で指定されたディレクトリまたは展開されていないzipファイル
// - Unixシステムでは、システムの標準インストール場所
// - $GOROOT/lib/time/zoneinfo.zip
// - インポートされた場合、time/tzdataパッケージ
func LoadLocation(name string) (*Location, error)

コメントを読むと、name が空または UTC の場合は UTC を使用し、name が Local の場合はローカルを使用します(後で説明します)。それ以外の場合は、特定の場所から読み込みます。

読み込みとは、tzfileタイムゾーンファイルを読み込むことを意味し、詳細についてはこのドキュメントを参照してください。簡単に言えば、タイムゾーンファイルは、TZifで始まるバイナリファイルであり、タイムゾーンのオフセット、閏秒、夏時間などの情報が含まれています。Go は関連するファイルを読み取り、解析することができます。

  1. ZONEINFO環境変数が存在する場合、その変数が指すディレクトリ / 圧縮ファイルを使用して読み込みます。
  2. Unix システムでは、システムの標準インストール場所を使用します。
  3. (Go をコンパイルするときに使用される)$GOROOT/lib/time/zoneinfo.zipから読み込みます。
  4. time/tzdataをインポートした場合)プログラムに埋め込まれたデータから読み込みます。

私たちが関心を持っているのは、2 つ目の Unix の標準タイムゾーンファイルの保存場所です。Unix システムでは、タイムゾーンファイルは通常/usr/share/zoneinfo/ディレクトリに保存されます(システムによっては/usr/share/lib/zoneinfo/または/usr/lib/locale/TZ/になる場合もあります)。たとえば、中国(Asia/Shanghai)のタイムゾーン定義ファイルは/usr/share/zoneinfo/Asia/Shanghaiです。したがって、通常、プログラムはシステムから直接タイムゾーンの情報を取得できます。

注意点として、alpine 環境ではタイムゾーン定義ファイルが存在しないため、特別な処理が必要です。

  1. プログラム内でimport _ "time/tzdata"を使用して、コンパイル時にタイムゾーンファイルをプログラムに組み込むことができます。これにより、システムのタイムゾーン定義が見つからない場合でも、標準の IANA タイムゾーン定義を検索できます。
  2. 特に動的なタイムゾーンが必要ない場合は、LoadLocationの代わりにFixedZoneを使用して、自分でタイムゾーン名とオフセットを指定することができます。たとえば、中国の場合はtime.FixedZone("Asia/Shanghai", 8*60*60)を使用できます。

ローカルタイムゾーン#

通常、タイムゾーンの問題に本当に関心を持つ前に、私たちが「デフォルト」として使用するタイムゾーンは「ローカルタイムゾーン」と呼ばれます。

time.Nowを例に挙げます。

type Time struct {
	wall uint64
	ext  int64

	loc *Location
}

// Now returns the current local time.
func Now() Time {
	sec, nsec, mono := now()
	mono -= startNano
	sec += unixToInternal - minWall
	if uint64(sec)>>33 != 0 {
		// Seconds field overflowed the 33 bits available when
		// storing a monotonic time. This will be true after
		// March 16, 2157.
		return Time{uint64(nsec), sec + minWall, Local}
	}
	return Time{hasMonotonic | uint64(sec)<<nsecShift | uint64(nsec), mono, Local}
}

わかるように、Time構造体の最後のフィールドloc *Locationがタイムゾーンであり、time.Nowで使用されるタイムゾーンはLocalです。

この記事では主にタイムゾーンに焦点を当てていますが、このコードの他の要素に興味がある場合は、あなたは本当に time.Now () を理解していますか?を読んでください。

ここでのLocalはローカルタイムゾーンを表しており、このプログラムが実行されているマシンのタイムゾーンです。

// Localはシステムのローカルタイムゾーンを表します。
// Unixシステムでは、LocalはTZ環境変数を参照して使用するタイムゾーンを見つけます。
// TZがない場合は、システムのデフォルト/ etc / localtimeを使用します。
// TZ = ""の場合はUTCを使用します。
// TZ = "foo"の場合は、システムのタイムゾーンディレクトリのファイルfooを使用します。
var Local *Location = &localLoc

Go のLocalに関する説明を読むと、Go は TZ 環境変数で指定されたタイムゾーンを優先し、特に指定がない場合は/etc/localtimeファイルから現在のタイムゾーンを読み取ります。

それでは、Localはどのように初期化されるのでしょうか?

// localLoc is separate so that initLocal can initialize
// it even if a client has changed Local.
var localLoc Location
var localOnce sync.Once

func (l *Location) get() *Location {
	if l == nil {
		return &utcLoc
	}
	if l == &localLoc {
		localOnce.Do(initLocal)
	}
	return l
}

このコードのロジックからは、Localがプログラム起動時に実際にこれらの情報を読み取るのではなく、最初に使用されるときにのみinitLocal関数を実行して初期化されることがわかります。また、このコードは Location の使用に対して要件を示しており、必ず get メソッドを呼び出して「本当の Location」を取得する必要があるということも暗に示しています。

initLocal関数はzoneinfo_*.goで定義されており、異なるマシンで異なる実装がされていますが、本質的には次のようなものです。

TZ の内容が:で始まる場合、そのコロンは無視されます

  1. TZ 環境変数が指定されていない場合、/etc/localtimeを読み取ります(通常、実際のタイムゾーンファイルへのシンボリックリンクを指します)。
  2. 指定された TZ 環境変数が絶対パスの場合、そのファイルを読み取ります。
  3. それ以外の場合は、上記で分析した LoadLocation の手順に従ってタイムゾーンファイルを読み取ります。

また、上記の 3 つのステップが失敗した場合、UTC 時間を使用します。

追加情報:tzdata#

tzdata は、夏時間や閏秒など、歴史的なタイムゾーンの変更情報を詳細に定義しています。そのため、Asia/Shanghaiは単純なGMT+8よりも一般的で、過去のデータを正しく処理できます。

興味がある場合は、zdump Asia/Shanghai -iコマンドを使用して上海のタイムゾーンの変更を確認し、夏時間を使用する場合の時間とzdump America/Chicago -iと比較してみてください。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。