[インデックス 10645] ファイルの概要
このコミットは、Go言語の標準ライブラリであるtime
パッケージ内のdaysIn
関数におけるバグ修正です。具体的には、12月の日数を計算する際に発生していた配列のインデックス範囲外エラー(index out of range
)を修正し、関連するテストを追加しています。
コミット
commit 69191553e7a67f63c21d79caf1881ee7487cb6bc
Author: Peter Mundy <go.peter.90@gmail.com>
Date: Wed Dec 7 14:47:25 2011 -0500
time: fix daysIn for December
daysBefore[12+1]: index out of range
time.December and Windows SYSTEMTIME.wMonth
are 12 for December.
R=rsc, dsymonds
CC=golang-dev
https://golang.org/cl/5448130
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/69191553e7a67f63c21d79caf1881ee7487cb6bc
元コミット内容
time: fix daysIn for December
daysBefore[12+1]: index out of range
time.December and Windows SYSTEMTIME.wMonth
are 12 for December.
R=rsc, dsymonds
CC=golang-dev
https://golang.org/cl/5448130
変更の背景
time
パッケージのdaysIn
関数は、特定の月が何日あるかを計算する役割を担っています。しかし、この関数には、12月(time.December
)の日数を計算する際に、内部で使用しているdaysBefore
という配列のインデックスが範囲外になるというバグが存在しました。
具体的には、time.December
の値が12
であり、WindowsのSYSTEMTIME.wMonth
も12
であるという事実が示唆するように、月を表す値が1から始まるインデックス(1-indexed)として扱われているにも関わらず、daysBefore
配列の計算式が0から始まるインデックス(0-indexed)の配列に対して不適切に適用されていたため、daysBefore[12+1]
、つまりdaysBefore[13]
へのアクセスが発生し、これが配列の境界を超えてしまう原因となっていました。
このバグは、特に12月の日数を正確に取得する必要があるアプリケーションにおいて、ランタイムエラーや不正な動作を引き起こす可能性がありました。そのため、この問題を解決し、daysIn
関数がすべての月に対して正しく機能するように修正する必要がありました。
前提知識の解説
Go言語のtime
パッケージ
Go言語のtime
パッケージは、日付と時刻を扱うための標準ライブラリです。時間の測定、表示、フォーマット、および時間帯の変換など、幅広い機能を提供します。time.Time
型は特定の時点を表し、time.Duration
型は時間の長さを表します。
Month
型
time
パッケージにはMonth
という型が定義されており、これは1月から12月までの月を表す列挙型です。
例えば、time.January
は1、time.February
は2、...、time.December
は12という整数値に対応しています。このコミットの文脈では、time.December
が12
という値を持つことが重要です。
daysBefore
配列
time
パッケージの内部では、各月の前日までの累積日数を格納するdaysBefore
という配列が使用されています。この配列は、特定の月の日数を効率的に計算するために利用されます。
例えば、daysBefore[n]
は、1月1日からn月の1日までの日数を表すように設計されていることが多いです。したがって、n月の正確な日数を計算するには、daysBefore[n]
からdaysBefore[n-1]
を引くことで求められます。
配列のインデックス
プログラミングにおいて、多くの言語(Go言語を含む)では配列のインデックスは0
から始まります。つまり、要素がN個ある配列の場合、インデックスは0
からN-1
までとなります。このコミットのバグは、月の値が1から始まるにも関わらず、配列のインデックス計算でこのオフセットが考慮されていなかったことに起因します。
閏年(うるうどし)の扱い
daysIn
関数は、2月の日数を計算する際に閏年を考慮する必要があります。閏年には2月が29日となり、それ以外の年は28日となります。このコミットの修正は閏年のロジック自体には影響を与えませんが、daysIn
関数全体の正確性に関わる重要な要素です。
技術的詳細
daysIn
関数の役割と元のバグ
daysIn
関数は、与えられた月と年に対して、その月が何日あるかを返す関数です。元の実装では、この関数はdaysBefore[m+1] - daysBefore[m]
という計算式を使用していました。ここでm
はMonth
型で表される月の値です。
問題は、time.December
が12
という値を持つことです。この場合、計算式はdaysBefore[12+1] - daysBefore[12]
、つまりdaysBefore[13] - daysBefore[12]
となります。もしdaysBefore
配列がインデックス0
から12
まで(合計13要素)で定義されていた場合、インデックス13
は配列の範囲外となり、ランタイムパニック(index out of range
)を引き起こします。
この挙動は、月の値が1-indexed(1月が1、12月が12)であるにも関わらず、daysBefore
配列のインデックス計算が0-indexedの配列の末尾を超えてアクセスしようとしたために発生しました。
修正内容
このコミットでは、daysIn
関数の計算式をint(daysBefore[m] - daysBefore[m-1])
に変更することで、このインデックス範囲外エラーを修正しました。
新しい計算式では、m
がDecember
(12)の場合、daysBefore[12] - daysBefore[11]
となります。これにより、配列の有効なインデックス範囲内で計算が行われ、12月の日数が正しく計算されるようになります。この変更は、daysBefore
配列が各月の開始日までの累積日数を格納しているという前提に基づいています。つまり、daysBefore[m]
はm
月の開始日までの累積日数、daysBefore[m-1]
はm-1
月の開始日までの累積日数であり、その差がm
月の日数となるわけです。
テストの追加と変更
このコミットでは、修正の検証のために以下のテスト関連の変更も行われています。
-
src/pkg/time/internal_test.go
の変更:daysIn
関数はパッケージ内部の関数であり、通常は外部から直接アクセスできません。テストのために、var DaysIn = daysIn
という行が追加され、daysIn
関数をDaysIn
というエクスポートされた変数に割り当てることで、テストコードからアクセスできるようにしました。これは、内部関数のテストを行う際の一般的なパターンです。 -
src/pkg/time/time_test.go
の変更:daysInTests
という新しいテストケースのスライスが追加されました。これには、1月、2月(平年と閏年)、6月、12月といった様々な月のテストデータが含まれています。TestDaysIn
という新しいテスト関数が追加され、daysInTests
スライス内の各テストケースに対してDaysIn
関数(internal_test.go
でエクスポートされたもの)を呼び出し、期待される日数と実際の日数を比較して検証しています。これにより、daysIn
関数の修正が正しく機能していることが確認されます。
これらの変更により、daysIn
関数の正確性が向上し、将来的な回帰バグを防ぐためのテストカバレッジも強化されました。
コアとなるコードの変更箇所
src/pkg/time/time.go
ファイルのdaysIn
関数の変更がコアとなります。
--- a/src/pkg/time/time.go
+++ b/src/pkg/time/time.go
@@ -673,7 +673,7 @@ func daysIn(m Month, year int) int {
if m == February && isLeap(year) {
return 29
}
- return int(daysBefore[m+1] - daysBefore[m])
+ return int(daysBefore[m] - daysBefore[m-1])
}
// Provided by package runtime.
コアとなるコードの解説
上記の差分は、daysIn
関数の日数を計算するロジックの変更を示しています。
-
変更前:
return int(daysBefore[m+1] - daysBefore[m])
この行は、m
月の次の月の累積日数からm
月の累積日数を引くことでm
月の日数を計算しようとしていました。しかし、前述の通り、m
がtime.December
(値が12)の場合、m+1
は13
となり、daysBefore
配列の範囲外アクセスを引き起こしていました。 -
変更後:
return int(daysBefore[m] - daysBefore[m-1])
この行は、m
月の累積日数からm-1
月の累積日数を引くことでm
月の日数を計算します。例えば、12月(m=12
)の場合、daysBefore[12] - daysBefore[11]
となります。これは、daysBefore
配列が1-indexedの月の値に対応しており、daysBefore[n]
が1月1日からn
月の1日までの累積日数を格納しているという設計に合致しています。この修正により、配列のインデックスが常に有効な範囲内に収まるようになり、バグが解消されました。
関連リンク
- Go CL (Code Review) リンク: https://golang.org/cl/5448130
参考にした情報源リンク
特になし。コミットメッセージとコードの差分から直接分析しました。