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

[インデックス 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) > 2len(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"の部分が正しく無視されるか、あるいは適切に処理されることを検証します。このテストの追加により、修正が意図した通りに機能し、将来的な回帰を防ぐための安全網が提供されます。

関連リンク

参考にした情報源リンク