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

[インデックス 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.wMonth12であるという事実が示唆するように、月を表す値が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.December12という値を持つことが重要です。

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]という計算式を使用していました。ここでmMonth型で表される月の値です。

問題は、time.December12という値を持つことです。この場合、計算式は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])に変更することで、このインデックス範囲外エラーを修正しました。

新しい計算式では、mDecember(12)の場合、daysBefore[12] - daysBefore[11]となります。これにより、配列の有効なインデックス範囲内で計算が行われ、12月の日数が正しく計算されるようになります。この変更は、daysBefore配列が各月の開始日までの累積日数を格納しているという前提に基づいています。つまり、daysBefore[m]m月の開始日までの累積日数、daysBefore[m-1]m-1月の開始日までの累積日数であり、その差がm月の日数となるわけです。

テストの追加と変更

このコミットでは、修正の検証のために以下のテスト関連の変更も行われています。

  1. src/pkg/time/internal_test.goの変更: daysIn関数はパッケージ内部の関数であり、通常は外部から直接アクセスできません。テストのために、var DaysIn = daysInという行が追加され、daysIn関数をDaysInというエクスポートされた変数に割り当てることで、テストコードからアクセスできるようにしました。これは、内部関数のテストを行う際の一般的なパターンです。

  2. 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月の日数を計算しようとしていました。しかし、前述の通り、mtime.December(値が12)の場合、m+113となり、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日までの累積日数を格納しているという設計に合致しています。この修正により、配列のインデックスが常に有効な範囲内に収まるようになり、バグが解消されました。

関連リンク

参考にした情報源リンク

特になし。コミットメッセージとコードの差分から直接分析しました。