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

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

このコミットは、Go言語の標準ライブラリtimeパッケージ内のタイムゾーン情報(zoneinfo)に関するコードの可読性を向上させるための変更です。具体的には、タイムゾーン遷移時間の開始と終了を表すマジックナンバー(-1 << 631<<63 - 1)を、alphaomegaという名前付き定数に置き換えることで、コードの意図をより明確にしています。機能的な変更は一切含まれておらず、純粋なリファクタリングです。

コミット

commit fabd261fe2fe0adf5f79b9bb1069df0a93575ae9
Author: Ian Lance Taylor <iant@golang.org>
Date:   Fri Jan 31 17:22:10 2014 -0800

    time: use names for beginning and end of zone transition times
    
    No functional changes, just more readable code.
    
    LGTM=r
    R=golang-codereviews, gobot, r
    CC=golang-codereviews
    https://golang.org/cl/59240043

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

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

元コミット内容

time: use names for beginning and end of zone transition times

No functional changes, just more readable code.

LGTM=r
R=golang-codereviews, gobot, r
CC=golang-codereviews
https://golang.org/cl/59240043

変更の背景

この変更の背景には、Go言語の標準ライブラリにおけるコード品質と保守性の向上という一般的な目標があります。timeパッケージは、日付と時刻の操作、特にタイムゾーンの扱いに不可欠な部分です。タイムゾーンの遷移(夏時間への移行など)を扱う際には、時間の範囲を表現するためにint64型の最小値と最大値が使用されていました。

以前のコードでは、これらの値がリテラル定数である-1 << 63math.MinInt64に相当)と1<<63 - 1math.MaxInt64に相当)として直接記述されていました。これらの数値は、int64の最小値と最大値をビットシフト演算で表現したものであり、Go言語では一般的なイディオムですが、コードを読んだ際にその数値が具体的に何を意味するのか、即座に理解しにくいという問題がありました。

このコミットは、これらの「マジックナンバー」をalphaomegaという、より意味のある名前を持つ定数に置き換えることで、コードの意図を明確にし、将来の読者や開発者がコードを理解しやすくすることを目的としています。機能的な変更は伴わないため、既存の動作に影響を与えることなく、純粋にコードの可読性と保守性を向上させるリファクタリングとして実施されました。

前提知識の解説

このコミットを理解するためには、以下の前提知識が役立ちます。

  1. Go言語のtimeパッケージ: Go言語の標準ライブラリの一部であり、日付、時刻、期間、タイムゾーンなどを扱うための機能を提供します。特に、Location型は特定のタイムゾーンを表し、zoneTrans型はタイムゾーンの遷移(例: 夏時間への切り替え)に関する情報を保持します。
  2. int64型と最小値・最大値: int64はGo言語における64ビット符号付き整数型です。その最小値は-2^63、最大値は2^63 - 1です。
    • -1 << 63: これはint64の最小値をビットシフト演算で表現したものです。1を63ビット左にシフトすると2^63となり、それに-1を掛けることで-2^63、つまりint64の最小値が得られます。
    • 1<<63 - 1: これはint64の最大値をビットシフト演算で表現したものです。1を63ビット左にシフトすると2^63となり、そこから1を引くことで2^63 - 1、つまりint64の最大値が得られます。
  3. マジックナンバーの回避: プログラミングにおいて、コード中にその意味が明確でない数値リテラル(マジックナンバー)を直接記述することは、コードの可読性や保守性を低下させるとされています。これらの数値が何を意味するのかを理解するためには、その周辺のコードやドキュメントを詳細に調べる必要が生じるためです。名前付き定数を使用することで、この問題を解決し、コードの意図を自己記述的にすることができます。
  4. タイムゾーンの遷移: 多くのタイムゾーンでは、夏時間(Daylight Saving Time, DST)の導入などにより、特定の時点でオフセットが変更されます。timeパッケージは、これらの遷移を正確に処理するために、zoneTransのような内部構造を使用しています。時間の「始まり」と「終わり」は、これらの遷移が適用される範囲を定義するために使用されます。

技術的詳細

このコミットの技術的な核心は、int64の最小値と最大値を表すビットシフト演算を、より意味のある名前付き定数に置き換えることです。

具体的には、src/pkg/time/zoneinfo.goに以下の定数が追加されました。

// alpha and omega are the beginning and end of time for zone
// transitions.
const (
	alpha = -1 << 63  // math.MinInt64
	omega = 1<<63 - 1 // math.MaxInt64
)
  • alpha: int64の最小値を表し、タイムゾーン遷移が適用される時間の「始まり」を意味します。これはmath.MinInt64と同じ値です。
  • omega: int64の最大値を表し、タイムゾーン遷移が適用される時間の「終わり」を意味します。これはmath.MaxInt64と同じ値です。

これらの定数は、timeパッケージ内でタイムゾーンのキャッシュ範囲や、特定のタイムゾーン情報が見つからない場合のデフォルトの時間の範囲を設定する際に使用されていた、直接記述されたビットシフト演算の代わりに導入されました。

例えば、FixedZone関数で新しいLocationを作成する際や、lookup関数で特定の時刻に対応するタイムゾーン情報を検索する際に、以前は-1 << 631<<63 - 1が使われていましたが、これらがalphaomegaに置き換えられました。

この変更は、コンパイル後のバイナリサイズや実行速度に影響を与えるものではなく、純粋にソースコードレベルでの改善です。定数を使用することで、コードの意図がより明確になり、将来のメンテナンスが容易になります。また、もしint64の最小値や最大値の表現方法が変更された場合でも、この定数の定義箇所を修正するだけで済むため、変更の影響範囲を局所化できるという利点もあります(ただし、int64の範囲が変更されることは極めて稀です)。

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

このコミットで変更された主要なファイルとコードスニペットは以下の通りです。

src/pkg/time/zoneinfo.go

--- a/src/pkg/time/zoneinfo.go
+++ b/src/pkg/time/zoneinfo.go
@@ -45,6 +45,13 @@ type zoneTrans struct {
 	isstd, isutc bool  // ignored - no idea what these mean
 }
 
+// alpha and omega are the beginning and end of time for zone
+// transitions.
+const (
+	alpha = -1 << 63  // math.MinInt64
+	omega = 1<<63 - 1 // math.MaxInt64
+)
+
 // UTC represents Universal Coordinated Time (UTC).
 var UTC *Location = &utcLoc
 
@@ -83,9 +90,9 @@ func FixedZone(name string, offset int) *Location {
 	l := &Location{
 		name:       name,
 		zone:       []zone{{name, offset, false}},
-		tx:         []zoneTrans{{-1 << 63, 0, false, false}},
-		cacheStart: -1 << 63,
-		cacheEnd:   1<<63 - 1,
+		tx:         []zoneTrans{{alpha, 0, false, false}},
+		cacheStart: alpha,
+		cacheEnd:   omega,
 	}
 	l.cacheZone = &l.zone[0]
 	return l
@@ -105,8 +112,8 @@ func (l *Location) lookup(sec int64) (name string, offset int, isDST bool, start
 		name = "UTC"
 		offset = 0
 		isDST = false
-		start = -1 << 63
-		end = 1<<63 - 1
+		start = alpha
+		end = omega
 		return
 	}
 
@@ -124,11 +131,11 @@ func (l *Location) lookup(sec int64) (name string, offset int, isDST bool, start
 		name = zone.name
 		offset = zone.offset
 		isDST = zone.isDST
-		start = -1 << 63
+		start = alpha
 		if len(l.tx) > 0 {
 			end = l.tx[0].when
 		} else {
-			end = 1<<63 - 1
+			end = omega
 		}
 		return
 	}
@@ -136,7 +143,7 @@ func (l *Location) lookup(sec int64) (name string, offset int, isDST bool, start
 	// Binary search for entry with largest time <= sec.
 	// Not using sort.Search to avoid dependencies.
 	tx := l.tx
-	end = 1<<63 - 1
+	end = omega
 	lo := 0
 	hi := len(tx)
 	for hi-lo > 1 {

src/pkg/time/zoneinfo_plan9.go

--- a/src/pkg/time/zoneinfo_plan9.go
+++ b/src/pkg/time/zoneinfo_plan9.go
@@ -100,7 +100,7 @@ func loadZoneDataPlan9(s string) (l *Location, err error) {
 	for i := range tx {
 		if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) {
 			l.cacheStart = tx[i].when
-			l.cacheEnd = 1<<63 - 1
+			l.cacheEnd = omega
 			if i+1 < len(tx) {
 				l.cacheEnd = tx[i+1].when
 			}

src/pkg/time/zoneinfo_read.go

--- a/src/pkg/time/zoneinfo_read.go
+++ b/src/pkg/time/zoneinfo_read.go
@@ -173,7 +173,7 @@ func loadZoneData(bytes []byte) (l *Location, err error) {
 	if len(tx) == 0 {
 		// Build fake transition to cover all time.
 		// This happens in fixed locations like "Etc/GMT0".
-		tx = append(tx, zoneTrans{when: -1 << 63, index: 0})
+		tx = append(tx, zoneTrans{when: alpha, index: 0})
 	}
 
 	// Committed to succeed.
@@ -185,7 +185,7 @@ func loadZoneData(bytes []byte) (l *Location, err error) {
 	for i := range tx {
 		if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) {
 			l.cacheStart = tx[i].when
-			l.cacheEnd = 1<<63 - 1
+			l.cacheEnd = omega
 			if i+1 < len(tx) {
 				l.cacheEnd = tx[i+1].when
 			}

src/pkg/time/zoneinfo_windows.go

--- a/src/pkg/time/zoneinfo_windows.go
+++ b/src/pkg/time/zoneinfo_windows.go
@@ -165,8 +165,8 @@ func initLocalFromTZI(i *syscall.Timezoneinformation) {
 	if nzone == 1 {
 		// No daylight savings.
 		std.offset = -int(i.Bias) * 60
-		l.cacheStart = -1 << 63
-		l.cacheEnd = 1<<63 - 1
+		l.cacheStart = alpha
+		l.cacheEnd = omega
 		l.cacheZone = std
 		l.tx = make([]zoneTrans, 1)
 		l.tx[0].when = l.cacheStart

コアとなるコードの解説

このコミットのコアとなる変更は、src/pkg/time/zoneinfo.goで定義されたalphaomega定数の導入と、それらの定数がtimeパッケージ内の様々な場所でint64の最小値および最大値の直接的なビットシフト表現の代わりに使用されるようになった点です。

  1. alphaomega定数の定義: zoneinfo.goの冒頭付近に、以下の定数定義が追加されました。

    const (
    	alpha = -1 << 63  // math.MinInt64
    	omega = 1<<63 - 1 // math.MaxInt64
    )
    

    これにより、int64の最小値と最大値が、それぞれalphaomegaという分かりやすい名前で参照できるようになりました。コメントでmath.MinInt64math.MaxInt64に相当することが明記されており、その意図が明確です。

  2. FixedZone関数での使用: FixedZone関数は、固定オフセットのタイムゾーンを表すLocationオブジェクトを作成します。この関数内で、タイムゾーンのキャッシュ範囲(cacheStartcacheEnd)および最初のタイムゾーン遷移(txスライス)の開始時刻を設定する際に、alphaomegaが使用されるようになりました。

    		tx:         []zoneTrans{{alpha, 0, false, false}},
    		cacheStart: alpha,
    		cacheEnd:   omega,
    

    これにより、このタイムゾーンが「全時間範囲」にわたって有効であることを、より直感的に表現できるようになりました。

  3. Location.lookup関数での使用: Location.lookup関数は、特定の時刻(sec)に対応するタイムゾーン情報(名前、オフセット、夏時間かどうか、有効期間の開始と終了)を検索します。

    • UTCの場合のデフォルト値として、startendalphaomegaが設定されます。
    • タイムゾーン遷移がない場合のデフォルトのstart値としてalphaが、end値としてomegaが設定されます。
    • バイナリサーチの初期化においても、endomegaが設定されます。 これらの変更により、lookup関数が扱う時間の範囲が、より明確に「全時間」を意味することが示されます。
  4. loadZoneDataPlan9loadZoneDatainitLocalFromTZI関数での使用: これらの関数は、それぞれPlan 9、一般的なタイムゾーンデータファイル、Windowsのタイムゾーン情報からLocationオブジェクトをロードまたは初期化する際に使用されます。これらの関数内でも、タイムゾーンの遷移がない場合や、キャッシュの開始・終了時刻を設定する際に、alphaomegaが使用されるようになりました。これにより、異なるプラットフォームやデータソースからのタイムゾーン情報ロード処理においても、時間の範囲の表現が一貫し、可読性が向上しています。

全体として、このコミットは、Go言語のtimeパッケージにおけるタイムゾーン処理の内部実装において、時間の「始まり」と「終わり」を表現する際に、よりセマンティックな名前付き定数を使用することで、コードの意図を明確にし、将来のメンテナンスを容易にすることを目的とした、クリーンアップと可読性向上のための変更です。機能的な影響は一切ありません。

関連リンク

参考にした情報源リンク