[インデックス 15126] ファイルの概要
このコミットは、Go言語の標準ライブラリ time
パッケージにおける Parse
関数のエラーメッセージの改善を目的としています。具体的には、日付時刻文字列のパース中にエラーが発生した際に、不正なテキストが誤って破棄されてしまう問題を修正し、より有用なエラーメッセージが返されるように変更が加えられました。
コミット
- コミットハッシュ:
6b4cf2b36781bcc3fddd8374c68dd143d12dadc1
- Author: Russ Cox rsc@golang.org
- Date: Mon Feb 4 00:00:36 2013 -0500
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6b4cf2b36781bcc3fddd8374c68dd143d12dadc1
元コミット内容
time: fix error message from Parse
Was incorrectly discarding the offending text in some cases.
Fixes #4493.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/7277050
変更の背景
Go言語の time
パッケージの Parse
関数は、指定されたレイアウトに基づいて文字列を time.Time
型に変換します。しかし、パースに失敗した場合のエラーメッセージが、問題の原因となっている不正な部分("offending text")を正確に示さない、あるいは完全に破棄してしまうという問題が報告されていました(Issue #4493)。
この問題は、ユーザーが不正な日付時刻文字列を time.Parse
に渡した際に、エラーメッセージから何が問題だったのかを特定しにくいという点で、開発者のデバッグ体験を損なうものでした。例えば、期待されるフォーマットと異なる余分な文字が含まれている場合、その余分な文字がエラーメッセージに表示されず、単に「パースに失敗しました」といった抽象的なメッセージしか得られないことがありました。
このコミットは、time.Parse
の内部で文字列をスキップする際に使用される skip
関数が、エラー時に不正な部分を適切に返すように修正することで、より詳細でデバッグに役立つエラーメッセージを提供することを目的としています。
前提知識の解説
Go言語の time
パッケージと Parse
関数
Go言語の time
パッケージは、日付と時刻を扱うための機能を提供します。その中でも time.Parse
関数は、特定のレイアウト文字列に基づいて、日付時刻を表す文字列を time.Time
型の値に変換するために使用されます。
func Parse(layout, value string) (Time, error)
layout
: 日付時刻文字列のフォーマットを指定する文字列です。Go言語では、特定の参照時刻(Mon Jan 2 15:04:05 MST 2006
)を基準としてレイアウトを定義するというユニークな方法を採用しています。value
: パース対象の日付時刻文字列です。
Parse
関数は、value
が layout
に従って正常にパースできれば time.Time
の値と nil
エラーを返します。パースに失敗した場合は、ゼロ値の time.Time
とエラーオブジェクトを返します。
time.Parse
の内部処理と skip
関数
time.Parse
は、与えられた layout
文字列と value
文字列を比較しながら、文字を一つずつ、あるいは特定のパターンで読み進めていきます。この処理の中で、layout
に含まれるリテラル文字(例: -
, :
, T
, Z
など)や、スペースなどの区切り文字を value
から読み飛ばす(スキップする)ための内部関数が使用されます。
このコミットで修正された skip
関数は、value
文字列の先頭が prefix
文字列と一致するかどうかを確認し、一致すれば value
から prefix
の部分を削除した残りの文字列を返します。一致しない場合や、スペースの扱いに問題がある場合にエラーを返します。
エラーハンドリングとデバッグ
プログラミングにおいて、エラーメッセージは非常に重要です。特にパースエラーのような、入力データが期待されるフォーマットと異なる場合に発生するエラーでは、エラーメッセージが具体的に「どの部分が」「どのように」不正であったかを示すことで、開発者は迅速に問題を特定し、修正することができます。
従来の time.Parse
のエラーメッセージが不十分であったのは、skip
関数がエラー時に単に空文字列 ""
を返していたため、パースに失敗した value
の残りの部分(不正なテキスト)が失われ、エラーメッセージに含めることができなかったためです。
技術的詳細
このコミットの核心は、src/pkg/time/format.go
内の skip
関数の変更にあります。
元の skip
関数では、パース中に不一致や不正なスペースの扱いがあった場合、エラーを示す errBad
と共に空文字列 ""
を返していました。
// 変更前 (簡略化)
func skip(value, prefix string) (string, error) {
// ...
if len(value) == 0 || value[0] != prefix[0] {
return "", errBad // ここで空文字列を返していた
}
// ...
if len(value) > 0 && value[0] != ' ' {
return "", errBad // ここでも空文字列を返していた
}
// ...
}
この ""
を返す挙動が問題でした。time.Parse
は、skip
関数が返す文字列を基に、エラーメッセージに含める「不正なテキスト」を特定しようとします。しかし、skip
が ""
を返してしまうと、time.Parse
はどの部分が不正だったのかを判断できず、結果として「不正なテキスト」がエラーメッセージから欠落するか、誤った情報が表示されることになります。
このコミットでは、skip
関数がエラーを返す際に、空文字列 ""
の代わりに、エラーの原因となった value
の残りの部分を返すように変更されました。
// 変更後 (簡略化)
func skip(value, prefix string) (string, error) {
// ...
if len(value) == 0 || value[0] != prefix[0] {
return value, errBad // value の残りを返すように変更
}
// ...
if len(value) > 0 && value[0] != ' ' {
return value, errBad // value の残りを返すように変更
}
// ...
}
この変更により、time.Parse
は skip
関数から返された value
の残りの部分をエラーメッセージに含めることができるようになり、例えば「extra text: _abc」や「cannot parse "_abc" as ":"」のように、より具体的でデバッグに役立つエラーメッセージを生成できるようになりました。
コアとなるコードの変更箇所
src/pkg/time/format.go
--- a/src/pkg/time/format.go
+++ b/src/pkg/time/format.go
@@ -611,14 +611,14 @@ func skip(value, prefix string) (string, error) {
for len(prefix) > 0 {
if prefix[0] == ' ' {
if len(value) > 0 && value[0] != ' ' {
- return "", errBad
+ return value, errBad
}
prefix = cutspace(prefix)
value = cutspace(value)
continue
}
if len(value) == 0 || value[0] != prefix[0] {
- return "", errBad
+ return value, errBad
}
prefix = prefix[1:]
value = value[1:]
src/pkg/time/time_test.go
--- a/src/pkg/time/time_test.go
+++ b/src/pkg/time/time_test.go
@@ -676,6 +676,11 @@ var parseErrorTests = []ParseErrorTest{
// issue 4502. StampNano requires exactly 9 digits of precision.
{StampNano, "Dec 7 11:22:01.000000", `cannot parse ".000000" as ".000000000"`},
{StampNano, "Dec 7 11:22:01.0000000000", "extra text: 0"},
+ // issue 4493. Helpful errors.
+ {RFC3339, "2006-01-02T15:04:05Z07:00", `parsing time "2006-01-02T15:04:05Z07:00": extra text: 07:00`},
+ {RFC3339, "2006-01-02T15:04_abc", `parsing time "2006-01-02T15:04_abc" as "2006-01-02T15:04:05Z07:00": cannot parse "_abc" as ":"`},
+ {RFC3339, "2006-01-02T15:04:05_abc", `parsing time "2006-01-02T15:04:05_abc" as "2006-01-02T15:04:05Z07:00": cannot parse "_abc" as "Z07:00"`},
+ {RFC3339, "2006-01-02T15:04:05Z_abc", `parsing time "2006-01-02T15:04:05Z_abc": extra text: _abc`},
}
func TestParseErrors(t *testing.T) {
コアとなるコードの解説
src/pkg/time/format.go
の変更
skip
関数の2箇所で、エラー時に ""
を返していた部分が value
を返すように変更されています。
-
if len(value) > 0 && value[0] != ' '
: これは、prefix
がスペースで始まる場合に、value
の先頭がスペースでない場合にエラーを返す条件です。変更前は""
を返していましたが、変更後はvalue
の残りの部分を返します。これにより、value
のどの部分がスペースの期待に反していたのかがエラーメッセージに反映されるようになります。 -
if len(value) == 0 || value[0] != prefix[0]
: これは、value
が空であるか、value
の先頭がprefix
の先頭と一致しない場合にエラーを返す条件です。同様に、変更前は""
を返していましたが、変更後はvalue
の残りの部分を返します。これにより、value
のどの文字がprefix
と一致しなかったのかがエラーメッセージに反映されるようになります。
これらの変更により、time.Parse
がエラーを報告する際に、パースに失敗した文字列の具体的な「残りの部分」や「不正な部分」をエラーメッセージに含めることができるようになり、デバッグの際に非常に役立つ情報が提供されるようになります。
src/pkg/time/time_test.go
の変更
parseErrorTests
スライスに、Issue #4493 に関連する新しいテストケースが追加されています。これらのテストケースは、RFC3339
フォーマットを使用して、不正な入力文字列が与えられた場合に、期待されるエラーメッセージが返されることを検証しています。
-
{RFC3339, "2006-01-02T15:04:05Z07:00",
parsing time "2006-01-02T15:04:05Z07:00": extra text: 07:00}
: これは、RFC3339
フォーマットの末尾に余分なテキスト07:00
がある場合のテストです。期待されるエラーメッセージには、この「extra text」が含まれています。 -
{RFC3339, "2006-01-02T15:04_abc",
parsing time "2006-01-02T15:04_abc" as "2006-01-02T15:04:05Z07:00": cannot parse "_abc" as ":"}
: これは、:
が期待される位置に_abc
という不正な文字がある場合のテストです。エラーメッセージは、_abc
がRFC3339
フォーマットの:
としてパースできないことを明確に示しています。 -
{RFC3339, "2006-01-02T15:04:05_abc",
parsing time "2006-01-02T15:04:05_abc" as "2006-01-02T15:04:05Z07:00": cannot parse "_abc" as "Z07:00"}
: これも同様に、Z07:00
が期待される位置に_abc
がある場合のテストです。 -
{RFC3339, "2006-01-02T15:04:05Z_abc",
parsing time "2006-01-02T15:04:05Z_abc": extra text: _abc}
:Z
の後に余分なテキスト_abc
がある場合のテストです。
これらのテストケースは、skip
関数の変更が time.Parse
のエラーメッセージの改善に実際に貢献していることを検証し、将来的な回帰を防ぐための重要な役割を果たします。
関連リンク
- Go Issue #4493: https://github.com/golang/go/issues/4493
- Go Change List (CL) 7277050: https://golang.org/cl/7277050
参考にした情報源リンク
- Go言語
time
パッケージ公式ドキュメント: https://pkg.go.dev/time - Go言語
time.Parse
のエラーメッセージに関する議論 (関連する可能性のあるIssue):- Go issue #45391: "unbalanced quotation in Parse error message" - https://github.com/golang/go/issues/45391
- Go issue #56730: "time: Parse error messages may be confusing/misleading" - https://github.com/golang/go/issues/56730
- GeeksforGeeks: Golang Program to Parse a String to Time - https://www.geeksforgeeks.org/golang-program-to-parse-a-string-to-time/
- Stack Overflow: Golang time.Parse error - https://stackoverflow.com/questions/tagged/go+time.parse
- Reddit: Golang time.Parse error - https://www.reddit.com/r/golang/comments/ (具体的なスレッドは特定できませんでしたが、一般的な議論の場として参照)