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

[インデックス 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 関数は、valuelayout に従って正常にパースできれば 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.Parseskip 関数から返された 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 を返すように変更されています。

  1. if len(value) > 0 && value[0] != ' ': これは、prefix がスペースで始まる場合に、value の先頭がスペースでない場合にエラーを返す条件です。変更前は "" を返していましたが、変更後は value の残りの部分を返します。これにより、value のどの部分がスペースの期待に反していたのかがエラーメッセージに反映されるようになります。

  2. 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 という不正な文字がある場合のテストです。エラーメッセージは、_abcRFC3339 フォーマットの : としてパースできないことを明確に示しています。

  • {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 のエラーメッセージの改善に実際に貢献していることを検証し、将来的な回帰を防ぐための重要な役割を果たします。

関連リンク

参考にした情報源リンク