[インデックス 12896] ファイルの概要
このコミットは、Go言語のtime
パッケージにおける、小数点以下の秒のパース処理に関するバグ修正です。具体的には、小数点以下の秒が1桁の場合に正しくパースされない問題を解決しています。
コミット
commit c81705f13a5898e40e7e51e83086903cb55da5bf
Author: Robert Hencke <robert.hencke@gmail.com>
Date: Mon Apr 16 11:56:37 2012 +1000
time: parse fractional second with single digit
Fixes #3487.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6011050
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c81705f13a5898e40e7e51e83086903cb55da5bf
元コミット内容
このコミットは、time
パッケージが小数点以下の秒をパースする際に、その桁数が1桁の場合に正しく処理できない問題を修正します。これはGo issue #3487で報告されたバグに対応するものです。
変更の背景
Go言語のtime
パッケージは、日付と時刻のフォーマットとパースを扱うための重要な機能を提供します。Parse
関数は、指定されたレイアウト文字列に基づいて入力文字列をTime
型に変換します。この機能において、小数点以下の秒(ミリ秒、マイクロ秒、ナノ秒など)の表現は非常に一般的です。
しかし、このコミット以前のtime
パッケージのParse
関数には、小数点以下の秒が1桁(例: ".0"
)である場合に、それを正しく認識できないというバグが存在しました。これは、内部的なチェックが小数点以下の秒の文字列長を2桁以上と誤って仮定していたためです。結果として、"2010-02-04 21:00:57.0"
のような形式の時刻文字列をパースしようとすると、エラーが発生するか、意図しない結果になる可能性がありました。
この問題は、GoのIssueトラッカーでIssue 3487: time: Parse fails on single digit fractional secondとして報告されました。このコミットは、この特定のバグを修正するために作成されました。
前提知識の解説
Go言語のtime
パッケージ
Go言語の標準ライブラリに含まれるtime
パッケージは、日付と時刻を扱うための包括的な機能を提供します。主要な型としてtime.Time
があり、これは特定の時点を表します。
time.Parse(layout, value string) (Time, error)
: この関数は、value
文字列をlayout
文字列で指定されたフォーマットに従ってパースし、time.Time
オブジェクトを返します。- レイアウト文字列: Goの
time
パッケージのパースおよびフォーマットでは、特定の参照時刻(Mon Jan 2 15:04:05 MST 2006
)を基準としたレイアウト文字列を使用します。小数点以下の秒は、参照時刻の秒の部分(05
)に続く小数点と数字で表現されます。例えば、"2006-01-02 15:04:05.0"
は、秒の小数点以下1桁をパースするためのレイアウトを示します。
isDigit
関数
isDigit
関数は、Goのtime
パッケージ内部で使用されるヘルパー関数で、文字列の指定されたインデックスにある文字が数字であるかどうかをチェックします。この関数は、パース処理中に数値部分を識別するために利用されます。
nextStdChunk
関数
nextStdChunk
関数もtime
パッケージ内部の関数で、レイアウト文字列を解析し、次の標準的な時刻要素(年、月、日、時、分、秒、タイムゾーンなど)のチャンクを特定します。これにより、パース処理がレイアウト文字列の各部分に適切に対応できるようになります。
バグの性質
このバグは、入力文字列の特定のパターン(小数点以下の秒が1桁)が、コード内の条件分岐(len(value) > 2
)によって誤って処理されることに起因していました。これは、プログラミングにおける「オフバイワンエラー」の一種と見なすことができます。つまり、期待される最小長と実際の最小長との間にずれがあったため、有効な入力が不正と判断されていました。
技術的詳細
このコミットの技術的詳細な変更点は、src/pkg/time/format.go
ファイル内のParse
関数における条件式の修正と、それに対応するテストケースの追加です。
src/pkg/time/format.go
の変更
元のコードでは、小数点以下の秒をパースする際の条件式が以下のようになっていました。
if len(value) > 2 && value[0] == '.' && isDigit(value, 1) {
この条件式は、value
文字列(パース対象の入力文字列の一部)の長さが2より大きい(つまり3以上)ことを要求していました。しかし、小数点以下の秒が1桁の場合(例: ".0"
)は、文字列長が2になります。このため、len(value) > 2
という条件がfalse
となり、小数点以下の秒が1桁の場合の処理がスキップされてしまっていました。
修正後のコードでは、この条件式が以下のように変更されました。
if len(value) >= 2 && value[0] == '.' && isDigit(value, 1) {
len(value) > 2
がlen(value) >= 2
に変更されたことで、value
文字列の長さが2以上であれば条件が真となるようになりました。これにより、".0"
のような小数点以下1桁の秒も正しく認識され、後続のパースロジックに進むことができるようになりました。
この変更は、非常に小さく見えますが、time
パッケージの堅牢性と正確性を向上させる上で重要です。特に、様々な形式の時刻文字列を扱うアプリケーションにとっては、このような細かなパースの挙行が正確な動作を保証するために不可欠です。
src/pkg/time/time_test.go
の変更
バグ修正を検証するために、time_test.go
に新しいテストケースが追加されました。
{"custom: \"2006-01-02 15:04:05\"","2006-01-02 15:04:05", "2010-02-04 21:00:57.0", false, false, 1, 0},
このテストケースは、レイアウト文字列"2006-01-02 15:04:05"
(秒の小数点以下を含まない)と、入力文字列"2010-02-04 21:00:57.0"
(秒の小数点以下が1桁)を組み合わせています。このテストの目的は、レイアウトに小数点以下の秒の指定がないにもかかわらず、入力文字列に小数点以下の秒が存在する場合のParse
関数の挙動を検証することです。
修正前は、このテストケースが失敗するか、意図しないパース結果を招いていた可能性があります。修正後は、Parse
関数が".0"
の部分を正しくスキップまたは処理し、期待されるtime.Time
オブジェクトを返すことを確認します。このテストの追加により、将来的に同様の回帰バグが発生するのを防ぐことができます。
コアとなるコードの変更箇所
src/pkg/time/format.go
--- a/src/pkg/time/format.go
+++ b/src/pkg/time/format.go
@@ -714,7 +714,7 @@ func Parse(layout, value string) (Time, error) {
}
// Special case: do we have a fractional second but no
// fractional second in the format?
- if len(value) > 2 && value[0] == '.' && isDigit(value, 1) {
+ if len(value) >= 2 && value[0] == '.' && isDigit(value, 1) {
_, std, _ := nextStdChunk(layout)
if len(std) > 0 && std[0] == '.' && isDigit(std, 1) {
// Fractional second in the layout; proceed normally
src/pkg/time/time_test.go
--- a/src/pkg/time/time_test.go
+++ b/src/pkg/time/time_test.go
@@ -310,6 +310,7 @@ var parseTests = []ParseTest{
{"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57.01234 PST", true, true, 1, 5},
{"RFC1123Z", RFC1123Z, "Thu, 04 Feb 2010 21:00:57.01234 -0800", true, true, 1, 5},
{"RFC3339", RFC3339, "2010-02-04T21:00:57.012345678-08:00", true, false, 1, 9},
+\t{"custom: \"2006-01-02 15:04:05\"","2006-01-02 15:04:05", "2010-02-04 21:00:57.0", false, false, 1, 0},
// Amount of white space should not matter.
{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1, 0},
{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1, 0},
コアとなるコードの解説
src/pkg/time/format.go
の変更点
この変更は、Parse
関数内で小数点以下の秒を処理する特殊なケースに関するものです。コメントにあるように、「フォーマットに小数点以下の秒がないのに、入力に小数点以下の秒がある場合」を扱っています。
元のコードのif len(value) > 2
という条件は、入力文字列の現在の部分(value
)が、小数点(.
)と少なくとも2桁の数字(例: ".01"
)で構成されていることを期待していました。しかし、".0"
のように小数点以下が1桁の場合、len(value)
は2となり、この条件を満たしませんでした。
修正後のif len(value) >= 2
という条件は、value
の長さが2以上であれば真となるため、".0"
のような1桁の小数点以下の秒も正しく処理の対象とします。これにより、value[0] == '.'
(最初の文字が小数点)とisDigit(value, 1)
(2番目の文字が数字)という後続のチェックと組み合わせて、小数点以下の秒が正しく認識されるようになります。
この修正は、Parse
関数がより柔軟に、かつ正確に様々な形式の時刻文字列を扱えるようにするために不可欠です。
src/pkg/time/time_test.go
の変更点
追加されたテストケースは、ParseTest
構造体の配列parseTests
に追加されています。
"custom: \"2006-01-02 15:04:05\""
: テストケースの名前。"2006-01-02 15:04:05"
: パースに使用するレイアウト文字列。このレイアウトは秒の小数点以下を含んでいません。"2010-02-04 21:00:57.0"
: パース対象の入力文字列。秒の小数点以下が1桁(.0
)で含まれています。false, false, 1, 0
: その他のテストパラメータ。これらは、パースが成功するかどうか、タイムゾーンの処理、期待されるナノ秒の桁数などを指定します。
このテストケースは、レイアウトに小数点以下の秒の指定がないにもかかわらず、入力文字列に小数点以下の秒が存在する場合に、Parse
関数がエラーなく、かつ期待通りに動作することを確認します。具体的には、".0"
の部分が正しく無視されるか、あるいは適切に処理されることを検証します。このテストの追加により、修正が意図した通りに機能し、将来的な回帰を防ぐための安全網が提供されます。
関連リンク
- Go issue #3487: time: Parse fails on single digit fractional second
- Go CL 6011050: time: parse fractional second with single digit
参考にした情報源リンク
- Go issue #3487
- Go CL 6011050
- Go time package documentation (一般的な
time
パッケージの理解のため) - Go language specification (Go言語の基本的な構文とセマンティクスを確認するため)
- Git diff format (diffの読み方を確認するため)
- Markdown syntax guide (Markdownのフォーマットのため)