通常、ローカライズのプロセスでは、タイムゾーンの変換に関する問題が発生することがありますが、通常、タイムゾーンに関心を持つ前に私たちが「デフォルト」として使用するタイムゾーンは 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 は関連するファイルを読み取り、解析することができます。
ZONEINFO
環境変数が存在する場合、その変数が指すディレクトリ / 圧縮ファイルを使用して読み込みます。- Unix システムでは、システムの標準インストール場所を使用します。
- (Go をコンパイルするときに使用される)
$GOROOT/lib/time/zoneinfo.zip
から読み込みます。 - (
time/tzdata
をインポートした場合)プログラムに埋め込まれたデータから読み込みます。
私たちが関心を持っているのは、2 つ目の Unix の標準タイムゾーンファイルの保存場所です。Unix システムでは、タイムゾーンファイルは通常/usr/share/zoneinfo/
ディレクトリに保存されます(システムによっては/usr/share/lib/zoneinfo/
または/usr/lib/locale/TZ/
になる場合もあります)。たとえば、中国(Asia/Shanghai
)のタイムゾーン定義ファイルは/usr/share/zoneinfo/Asia/Shanghai
です。したがって、通常、プログラムはシステムから直接タイムゾーンの情報を取得できます。
注意点として、alpine 環境ではタイムゾーン定義ファイルが存在しないため、特別な処理が必要です。
- プログラム内で
import _ "time/tzdata"
を使用して、コンパイル時にタイムゾーンファイルをプログラムに組み込むことができます。これにより、システムのタイムゾーン定義が見つからない場合でも、標準の IANA タイムゾーン定義を検索できます。 - 特に動的なタイムゾーンが必要ない場合は、
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 の内容が
:
で始まる場合、そのコロンは無視されます
- TZ 環境変数が指定されていない場合、
/etc/localtime
を読み取ります(通常、実際のタイムゾーンファイルへのシンボリックリンクを指します)。 - 指定された TZ 環境変数が絶対パスの場合、そのファイルを読み取ります。
- それ以外の場合は、上記で分析した LoadLocation の手順に従ってタイムゾーンファイルを読み取ります。
また、上記の 3 つのステップが失敗した場合、UTC 時間を使用します。
追加情報:tzdata#
tzdata は、夏時間や閏秒など、歴史的なタイムゾーンの変更情報を詳細に定義しています。そのため、Asia/Shanghai
は単純なGMT+8
よりも一般的で、過去のデータを正しく処理できます。
興味がある場合は、zdump Asia/Shanghai -i
コマンドを使用して上海のタイムゾーンの変更を確認し、夏時間を使用する場合の時間とzdump America/Chicago -i
と比較してみてください。