[インデックス 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/ (具体的なスレッドは特定できませんでしたが、一般的な議論の場として参照)