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

[インデックス 17022] ファイルの概要

このコミットは、Go言語のtimeパッケージにおける日付と時刻のフォーマット処理に関するバグ修正と改善を目的としています。具体的には、月名や曜日名の短縮形(例: "Jan", "Mon")が、その直後に小文字が続く文字列と誤ってマッチしてしまう問題を解決します。これにより、「Janet」のような文字列が「Januaryet」と誤認識されるといった、意図しないマッチングを防ぎます。

コミット

commit af8426eebe6c1681f4a3e7a6619f9e3abe9704e8
Author: Rob Pike <r@golang.org>
Date:   Mon Aug 5 10:53:46 2013 +1000

    time: match month and day names only when not followed immediately by a lower-case letter
    Avoids seeing "Janet" as "Januaryet".
    
    Fixes #6020.
    
    R=golang-dev, dsymonds
    CC=golang-dev
    https://golang.org/cl/12448044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/af8426eebe6c1681f4a3e7a6619f9e3abe9704e8

元コミット内容

time: match month and day names only when not followed immediately by a lower-case letter
Avoids seeing "Janet" as "Januaryet".

Fixes #6020.

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/12448044

変更の背景

この変更は、Go言語のtimeパッケージが日付や時刻の文字列を解析する際に発生していた、特定の誤認識バグ(Issue #6020)を修正するために導入されました。

従来のtimeパッケージのフォーマットパーサーは、月名や曜日名の短縮形(例: "Jan" for January, "Mon" for Monday)を識別する際に、その文字列の直後に続く文字の種別を十分に考慮していませんでした。このため、例えば「Janet」という文字列があった場合、「Jan」が「January」の短縮形として誤って認識され、残りの「et」が続くことで「Januaryet」のような不正確な解析結果を生み出す可能性がありました。

このような誤認識は、特にユーザーが入力した自由形式のテキストや、特定の命名規則を持つデータソースから日付情報を抽出する際に問題となります。正確な日付・時刻の解析は、アプリケーションのロババスト性と信頼性にとって不可欠であるため、この問題は優先的に対処されるべき課題でした。

このコミットは、このような誤認識を防ぐために、月名や曜日名の短縮形をマッチングする際に、その直後の文字が小文字でないことを確認するロジックを追加することで、パーサーの精度を向上させています。

前提知識の解説

このコミットを理解するためには、以下のGo言語のtimeパッケージに関する知識が役立ちます。

  • timeパッケージ: Go言語の標準ライブラリの一部で、日付と時刻の操作、フォーマット、解析を提供します。
  • レイアウト文字列 (Layout String): timeパッケージでは、日付と時刻のフォーマットや解析のパターンを定義するために、特定の参照時刻(Mon Jan 2 15:04:05 MST 2006)に基づいたレイアウト文字列を使用します。例えば、"Jan"は月名の短縮形(例: "Jan", "Feb")を表し、"Mon"は曜日名の短縮形(例: "Mon", "Tue")を表します。
  • time.Parse関数: 指定されたレイアウトと文字列からtime.Timeオブジェクトを解析する関数です。
  • time.Format関数: time.Timeオブジェクトを指定されたレイアウトに従って文字列にフォーマットする関数です。
  • stdMonth, stdWeekDay: timeパッケージ内部で定義されている定数で、それぞれ月名の短縮形と曜日名の短縮形を表すレイアウト要素に対応します。
  • パーサーの貪欲性 (Greedy Parsing): 一般的な正規表現や文字列パーサーでは、可能な限り長い文字列にマッチしようとする「貪欲性」という特性があります。この特性が、今回の問題のように意図しない部分文字列のマッチングを引き起こすことがあります。

このコミットの核心は、timeパッケージの内部的なパーシングロジック、特にnextStdChunk関数がどのようにレイアウト文字列を解析し、標準的な日付/時刻要素を識別しているかという点にあります。

技術的詳細

このコミットの技術的な核心は、timeパッケージの内部パーサーが、月名や曜日名の短縮形を識別する際に、その直後の文字が小文字であるかどうかをチェックする新しいロジックを導入した点にあります。

具体的には、以下の変更が行われました。

  1. startsWithLowerCase関数の追加: この新しいヘルパー関数は、与えられた文字列の最初の文字が小文字('a'から'z')であるかどうかを判定します。これは、月名や曜日名の短縮形(例: "Jan", "Mon")が、その直後に続く小文字によって誤って拡張されるのを防ぐために使用されます。

    func startsWithLowerCase(str string) bool {
    	if len(str) == 0 {
    		return false
    	}
    	c := str[0]
    	return 'a' <= c && c <= 'z'
    }
    
  2. nextStdChunk関数におけるstartsWithLowerCaseの利用: nextStdChunk関数は、レイアウト文字列から標準的な日付/時刻要素(例: 月、日、曜日など)を識別する内部関数です。この関数内で、月名の短縮形(stdMonth)と曜日名の短縮形(stdWeekDay)をマッチングするロジックに、startsWithLowerCase関数によるチェックが追加されました。

    • 月名(stdMonth)の処理: 以前は、レイアウト文字列が"Jan"で始まる場合、無条件にstdMonthとしてマッチングしていました。変更後は、layout[i+3:]("Jan"の直後から始まる文字列)が小文字で始まらない場合にのみstdMonthとしてマッチングするようになりました。これにより、「Janet」のようなケースで「Jan」が月名として誤認識されることを防ぎます。

    • 曜日名(stdWeekDay)の処理: 同様に、以前は"Mon"で始まる場合、無条件にstdWeekDayとしてマッチングしていました。変更後は、layout[i+3:]("Mon"の直後から始まる文字列)が小文字で始まらない場合にのみstdWeekDayとしてマッチングするようになりました。

これらの変更により、パーサーはより厳密なマッチングを行うようになり、"Janet"が"Januaryet"と誤認識されるような、意図しない部分文字列のマッチングが回避されます。これは、パーサーのロバスト性を高め、より正確な日付・時刻の解析を保証します。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更は、src/pkg/time/format.goファイルとsrc/pkg/time/time_test.goファイルに集中しています。

src/pkg/time/format.go

  1. startsWithLowerCase関数の追加:
    @@ -102,6 +102,16 @@ const (
     // std0x records the std values for "01", "02", ..., "06".
     var std0x = [...]int{stdZeroMonth, stdZeroDay, stdZeroHour12, stdZeroMinute, stdZeroSecond, stdYear}
     
    +// startsWithLowerCase reports whether the the string has a lower-case letter at the beginning.
    +// Its purpose is to prevent matching strings like "Month" when looking for "Mon".
    +func startsWithLowerCase(str string) bool {
    +	if len(str) == 0 {
    +		return false
    +	}
    +	c := str[0]
    +	return 'a' <= c && c <= 'z'
    +}
    +
     // nextStdChunk finds the first occurrence of a std string in
     // layout and returns the text before, the std string, and the text after.
     func nextStdChunk(layout string) (prefix string, std int, suffix string) {
    @@ -112,7 +122,9 @@ func nextStdChunk(layout string) (prefix string, std int, suffix string) {
     			if len(layout) >= i+7 && layout[i:i+7] == "January" {
     				return layout[0:i], stdLongMonth, layout[i+7:]
     			}
    -			return layout[0:i], stdMonth, layout[i+3:]
    +			if !startsWithLowerCase(layout[i+3:]) {
    +				return layout[0:i], stdMonth, layout[i+3:]
    +			}
     		}
     
     	case 'M': // Monday, Mon, MST
    @@ -121,7 +133,9 @@ func nextStdChunk(layout string) (prefix string, std int, suffix string) {
     				if len(layout) >= i+6 && layout[i:i+6] == "Monday" {
     					return layout[0:i], stdLongWeekDay, layout[i+6:]
     				}
    -				return layout[0:i], stdWeekDay, layout[i+3:]
    +				if !startsWithLowerCase(layout[i+3:]) {
    +					return layout[0:i], stdWeekDay, layout[i+3:]
    +				}
     			}
     			if layout[i:i+3] == "MST" {
     				return layout[0:i], stdTZ, layout[i+3:]
    

src/pkg/time/time_test.go

  1. 新しいテストケースの追加: formatTestsparseTestsに、月名や曜日名の短縮形が小文字に続く場合に誤認識されないことを確認するテストケースが追加されました。

    @@ -413,6 +413,8 @@ var formatTests = []FormatTest{
     	{"am/pm", "3pm", "9pm"},
     	{"AM/PM", "3PM", "9PM"},
     	{"two-digit year", "06 01 02", "09 02 04"},
    +	// Three-letter months and days must not be followed by lower-case letter.
    +	{"Janet", "Hi Janet, the Month is January", "Hi Janet, the Month is February"},
     	// Time stamps, Fractional seconds.
     	{"Stamp", Stamp, "Feb  4 21:00:57"},
     	{"StampMilli", StampMilli, "Feb  4 21:00:57.012"},
    @@ -505,6 +507,8 @@ var parseTests = []ParseTest{
     	// Leading zeros in other places should not be taken as fractional seconds.
     	{"zero1", "2006.01.02.15.04.05.0", "2010.02.04.21.00.57.0", false, false, 1, 1},
     	{"zero2", "2006.01.02.15.04.05.00", "2010.02.04.21.00.57.01", false, false, 1, 2},
    +	// Month and day names only match when not followed by a lower-case letter.
    +	{"Janet", "Hi Janet, the Month is January: Jan _2 15:04:05 2006", "Hi Janet, the Month is February: Feb  4 21:00:57 2010", false, true, 1, 0},
     
     	// Accept any number of fractional second digits (including none) for .999...
     	// In Go 1, .999... was completely ignored in the format, meaning the first two
    

コアとなるコードの解説

このコミットの主要な変更は、timeパッケージのformat.goファイルに新しいヘルパー関数startsWithLowerCaseが追加され、既存のnextStdChunk関数内でその関数が利用されるようになった点です。

startsWithLowerCase関数

func startsWithLowerCase(str string) bool {
	if len(str) == 0 {
		return false
	}
	c := str[0]
	return 'a' <= c && c <= 'z'
}

この関数は非常にシンプルで、与えられた文字列strが空でない場合、その最初の文字cが小文字('a'から'z'の範囲)であるかどうかを判定します。この関数の目的は、月名や曜日名の短縮形(例: "Jan", "Mon")が、その直後に続く小文字によって誤って「Month」のような単語の一部として認識されるのを防ぐことです。

nextStdChunk関数における変更

nextStdChunk関数は、timeパッケージの内部で、日付/時刻のレイアウト文字列を解析し、"Jan"や"Mon"のような標準的な要素を識別するために使用されます。この関数内のcase 'J'(January/Janの処理)とcase 'M'(Monday/Mon/MSTの処理)のブロックに、startsWithLowerCase関数を用いた条件が追加されました。

変更前:

// 月名の短縮形 "Jan" のマッチング
return layout[0:i], stdMonth, layout[i+3:]

// 曜日名の短縮形 "Mon" のマッチング
return layout[0:i], stdWeekDay, layout[i+3:]

変更前は、"Jan"や"Mon"という文字列がレイアウト中に見つかると、その直後の文字が何であっても、無条件にそれぞれstdMonthstdWeekDayとしてマッチングしていました。これが、「Janet」のような文字列で「Jan」が月名として誤認識される原因となっていました。

変更後:

// 月名の短縮形 "Jan" のマッチング
if !startsWithLowerCase(layout[i+3:]) {
	return layout[0:i], stdMonth, layout[i+3:]
}

// 曜日名の短縮形 "Mon" のマッチング
if !startsWithLowerCase(layout[i+3:]) {
	return layout[0:i], stdWeekDay, layout[i+3:]
}

変更後では、layout[i+3:](つまり、マッチングしようとしている"Jan"や"Mon"の直後から始まる文字列)の最初の文字が小文字でない場合にのみ、stdMonthstdWeekDayとしてマッチングするようになりました。

例えば、入力文字列が「Janet」で、パーサーが「Jan」を検出した場合、layout[i+3:]は「et」となります。startsWithLowerCase("et")trueを返すため、この条件!startsWithLowerCase(...)falseとなり、「Jan」はstdMonthとしてマッチングされなくなります。これにより、「Janet」が「Januaryet」と誤認識されることが防がれます。

この修正は、パーサーの「貪欲性」を適切に制御し、より正確な文字列解析を可能にすることで、timeパッケージの堅牢性を向上させています。

関連リンク

参考にした情報源リンク