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

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

このコミットは、Go言語の標準ライブラリ time パッケージにおけるタイムゾーンの解析ロジックを修正するものです。特に、タイムゾーンの略語(例: "MST", "EST")の認識精度を向上させることを目的としています。

コミット

commit 4c855f3830f88c0d11dbc19ff0a34cfa1beecc66
Author: Rob Pike <r@golang.org>
Date:   Fri Aug 16 14:57:49 2013 +1000

    time: fix time zones yet again.
    This time we're going for 5!
    http://goo.gl/3ETYH7
    
    Fixes #3790
    Yeah, right.
    
    R=golang-dev, adg
    CC=golang-dev
    https://golang.org/cl/13002044

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

https://github.com/golang/go/commit/4c855f3830f88c0d11dbc19ff0a34cfa1beecc66

元コミット内容

このコミットの元のメッセージは以下の通りです。

time: fix time zones yet again.
This time we're going for 5!
http://goo.gl/3ETYH7

Fixes #3790
Yeah, right.

R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/13002044

このメッセージは、タイムゾーンの修正が繰り返されていること、そして今回は「5」という数字に言及していることから、5文字のタイムゾーン略語(例: "ESAST")のサポートを強化していることを示唆しています。また、Fixes #3790 という記述がありますが、GoのIssueトラッカーでこの番号のIssueが直接タイムゾーン解析に関連するものは見つかりませんでした。これは、内部的な追跡番号であるか、あるいはコミットメッセージのユーモラスな表現である可能性があります。

変更の背景

Go言語の time パッケージは、日付と時刻の操作において非常に重要な役割を担っています。その中でも、タイムゾーンの解析は特に複雑な課題の一つです。世界には多種多様なタイムゾーンが存在し、その略語も3文字から5文字、あるいはそれ以上と様々です。また、夏時間(Daylight Saving Time, DST)の導入により、同じタイムゾーンでも時期によってオフセットが変わることもあります。

このコミットの背景には、time パッケージが文字列からタイムゾーンを正確に識別・解析する際の既存の課題があったと考えられます。特に、3文字または4文字のタイムゾーン略語の認識ルールが厳密すぎたり、特定のパターンに対応できていなかったりした可能性があります。コミットメッセージの「fix time zones yet again. This time we're going for 5!」という表現は、過去にもタイムゾーン関連の修正が行われており、今回は特に5文字のタイムゾーン略語への対応を強化していることを示唆しています。これにより、より多様なタイムゾーン表記に対応し、堅牢性を高めることが目的とされています。

前提知識の解説

タイムゾーンと略語

タイムゾーンは、地球上の特定の地域で共通して使用される標準時を定義するものです。多くの場合、UTC(協定世界時)からのオフセットで表現されます。タイムゾーンには、"EST" (Eastern Standard Time), "PST" (Pacific Standard Time), "JST" (Japan Standard Time) のような略語が存在します。これらの略語は、3文字から5文字程度のアルファベットで構成されることが多いですが、厳密なルールがあるわけではなく、同じ略語が異なるタイムゾーンを指すこともあります(例: "CST" は中国標準時、キューバ標準時、中部標準時など)。

Go言語の time パッケージ

Go言語の time パッケージは、日付と時刻を扱うための機能を提供します。time.Time 型は特定の時点を表し、time.Location 型はタイムゾーン情報をカプセル化します。time.Parse 関数は、指定されたレイアウトと文字列から time.Time オブジェクトを生成する際に、タイムゾーン情報を解析します。この解析プロセスにおいて、文字列中のタイムゾーン略語を正確に識別することが重要になります。

parseTimeZone 関数

time パッケージ内部には、文字列からタイムゾーン情報を抽出するための parseTimeZone のような関数が存在します。この関数は、入力文字列の先頭からアルファベットの連続を読み取り、それが既知のタイムゾーン略語のパターンに合致するかどうかを判断します。このコミットでは、この parseTimeZone 関数のロジックが変更されています。

技術的詳細

このコミットの主要な変更点は、src/pkg/time/format.go 内の parseTimeZone 関数のロジック修正です。以前のバージョンでは、タイムゾーン略語の認識において、主に3文字または4文字(末尾が'T'の場合)のパターンに焦点を当てていました。しかし、このアプローチでは、より多様なタイムゾーン略語(特に5文字のもの)を正確に識別できないという問題がありました。

新しい parseTimeZone 関数は、以下のルールに基づいてタイムゾーン略語を解析します。

  1. 大文字の連続の検出: 入力文字列の先頭から、連続する大文字の数を nUpper としてカウントします。最大で6文字までをチェックします。
  2. 文字数の検証:
    • nUpper が0, 1, 2, 6の場合は、タイムゾーンではないと判断し、false を返します。これは、タイムゾーン略語が通常3文字以上であり、6文字以上の連続する大文字はタイムゾーン略語としては長すぎると判断されるためです。
    • nUpper が5の場合: タイムゾーンとして認識されるためには、5文字目が 'T' であることが必須条件となります。この条件を満たせば、5文字のタイムゾーンとして認識されます。
    • nUpper が4の場合: タイムゾーンとして認識されるためには、4文字目が 'T' であることが必須条件となります。この条件を満たせば、4文字のタイムゾーンとして認識されます。
    • nUpper が3の場合: 3文字の連続する大文字は、無条件でタイムゾーンとして認識されます。

この変更により、parseTimeZone 関数は、"ESAST" のような5文字で末尾が'T'のタイムゾーン略語や、"ChST" のような4文字で末尾が'T'のタイムゾーン略語、そして従来の3文字の略語("MSK", "MSD", "HAE"など)をより柔軟かつ正確に識別できるようになります。

また、src/pkg/time/export_test.govar ParseTimeZone = parseTimeZone が追加され、内部関数である parseTimeZone がテストからアクセス可能になっています。これにより、src/pkg/time/time_test.go に追加された新しいテストケース TestParseTimeZone で、この修正されたロジックが正しく機能するかどうかを検証できるようになっています。

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

src/pkg/time/export_test.go

--- a/src/pkg/time/export_test.go
+++ b/src/pkg/time/export_test.go
@@ -17,3 +17,5 @@ func ForceUSPacificForTesting() {
 	ResetLocalOnceForTest()
 	localOnce.Do(initTestingZone)
 }
+
+var ParseTimeZone = parseTimeZone

src/pkg/time/format.go

--- a/src/pkg/time/format.go
+++ b/src/pkg/time/format.go
@@ -1027,8 +1027,11 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error)\n // are human-generated and unpredictable. We can't do precise error checking.\n // On the other hand, for a correct parse there must be a time zone at the\n // beginning of the string, so it's almost always true that there's one\n-// there. We check: 3 or 4 upper case letters (with one exception). If 4, the\n-// last letter must be a T.\n+// there. We look at the beginning of the string for a run of upper-case letters.\n+// If there are more than 5, it's an error.\n+// If there are 4 or 5 and the last is a T, it's a time zone.\n+// If there are 3, it's a time zone.\n+// Otherwise, other than special cases, it's not a time zone.\n // GMT is special because it can have an hour offset.\n func parseTimeZone(value string) (length int, ok bool) {\n \tif len(value) < 3 {\n@@ -1043,19 +1046,31 @@ func parseTimeZone(value string) (length int, ok bool) {\n \t\tlength = parseGMT(value)\n \t\treturn length, true\n \t}\n-\t// There must be three upper-case letters.\n-\tfor i := 0; i < 3; i++ {\n-\t\tc := value[i]\n-\t\tif c < 'A' || 'Z' < c {\n-\t\t\treturn 0, false\n+\t// How many upper-case letters are there? Need at least three, at most five.\n+\tvar nUpper int\n+\tfor nUpper = 0; nUpper < 6; nUpper++ {\n+\t\tif nUpper >= len(value) {\n+\t\t\tbreak\n+\t\t}\n+\t\tif c := value[nUpper]; c < 'A' || 'Z' < c {\n+\t\t\tbreak\n \t\t}\n \t}\n-\t// There may be a fourth upper case letter. If so, in a time zone it's always a 'T'.\n-\t// (The last letter is often not a 'T' in three-letter zones: MSK, MSD, HAE, etc.)\n-\tif len(value) >= 4 && value[3] == 'T' {\n-\t\treturn 4, true\n+\tswitch nUpper {\n+\tcase 0, 1, 2, 6:\n+\t\treturn 0, false\n+\tcase 5: // Must end in T to match.\n+\t\tif value[4] == 'T' {\n+\t\t\treturn 5, true\n+\t\t}\n+\tcase 4: // Must end in T to match.\n+\t\tif value[3] == 'T' {\n+\t\t\treturn 4, true\n+\t\t}\n+\tcase 3:\n+\t\treturn 3, true\n \t}\n-\treturn 3, true\n+\treturn 0, false\n }\n \n // parseGMT parses a GMT time zone. The input string is known to start "GMT".\n```

### `src/pkg/time/time_test.go`

```diff
--- a/src/pkg/time/time_test.go
+++ b/src/pkg/time/time_test.go
@@ -666,6 +666,38 @@ func TestFormatAndParse(t *testing.T) {\n \t}\n }\n \n+type ParseTimeZoneTest struct {\n+\tvalue  string\n+\tlength int\n+\tok     bool\n+}\n+\n+var parseTimeZoneTests = []ParseTimeZoneTest{\n+\t{\"gmt hi there\", 0, false},\n+\t{\"GMT hi there\", 3, true},\n+\t{\"GMT+12 hi there\", 6, true},\n+\t{\"GMT+00 hi there\", 3, true}, // 0 or 00 is not a legal offset.\n+\t{\"GMT-5 hi there\", 5, true},\n+\t{\"GMT-51 hi there\", 3, true},\n+\t{\"ChST hi there\", 4, true},\n+\t{\"MSDx\", 3, true},\n+\t{\"MSDY\", 0, false}, // four letters must end in T.\n+\t{\"ESAST hi\", 5, true},\n+\t{\"ESASTT hi\", 0, false}, // run of upper-case letters too long.\n+\t{\"ESATY hi\", 0, false},  // five letters must end in T.\n+}\n+\n+func TestParseTimeZone(t *testing.T) {\n+\tfor _, test := range parseTimeZoneTests {\n+\t\tlength, ok := ParseTimeZone(test.value)\n+\t\tif ok != test.ok {\n+\t\t\tt.Errorf("expected %t for %q got %t", test.ok, test.value, ok)\n+\t\t} else if length != test.length {\n+\t\t\tt.Errorf("expected %d for %q got %d", test.length, test.value, length)\n+\t\t}\n+\t}\n+}\n+\n type ParseErrorTest struct {\n \tformat string\n \tvalue  string\n```

## コアとなるコードの解説

### `src/pkg/time/format.go` の `parseTimeZone` 関数

この関数は、文字列の先頭からタイムゾーン略語を識別するための主要なロジックを含んでいます。

*   **変更前**:
    *   まず、3文字の大文字をチェックし、それがタイムゾーンであると仮定していました。
    *   もし4文字目があり、それが 'T' であれば、4文字のタイムゾーンとして認識していました。
    *   それ以外の場合は、3文字のタイムゾーンとして認識していました。
    *   このロジックでは、5文字のタイムゾーン略語や、4文字で末尾が'T'ではない略語(例: "MSDY")を正しく扱えませんでした。

*   **変更後**:
    *   `nUpper` という変数を導入し、文字列の先頭から連続する大文字の数を最大5文字までカウントします(ループは6まで回るが、`nUpper` は最大5)。
    *   `switch nUpper` 文を使って、カウントされた大文字の数に基づいて異なる処理を行います。
        *   `case 0, 1, 2, 6`: これらの場合はタイムゾーンではないと判断します。
        *   `case 5`: 5文字のタイムゾーンとして認識されるには、`value[4]` (5文字目) が 'T' である必要があります。
        *   `case 4`: 4文字のタイムゾーンとして認識されるには、`value[3]` (4文字目) が 'T' である必要があります。
        *   `case 3`: 3文字のタイムゾーンは無条件で認識されます。
    *   この新しいロジックにより、"ESAST" (5文字、末尾'T') や "ChST" (4文字、末尾'T') のようなパターン、そして従来の3文字パターンをより正確に識別できるようになりました。

### `src/pkg/time/export_test.go`

*   `var ParseTimeZone = parseTimeZone` の追加は、`parseTimeZone` という内部関数をテストパッケージからアクセスできるようにするためのものです。Go言語では、通常、小文字で始まる関数や変数はパッケージ外からはアクセスできませんが、`_test.go` ファイル内で `export_test.go` のように `var ExportedName = internalName` の形式でエクスポートすることで、テスト目的で内部関数にアクセスできるようになります。

### `src/pkg/time/time_test.go`

*   `ParseTimeZoneTest` 構造体と `parseTimeZoneTests` スライスが追加されました。これらは、`parseTimeZone` 関数の様々な入力に対する期待される出力(認識された長さと成功/失敗)を定義しています。
*   `TestParseTimeZone` 関数が追加され、`parseTimeZoneTests` の各テストケースをループで実行し、`ParseTimeZone` 関数(`export_test.go` 経由でアクセス)の挙動が期待通りであるかを検証しています。
    *   特に、`"MSDY"` (4文字で末尾が'T'ではない) や `"ESASTT hi"` (6文字の連続大文字) 、`"ESATY hi"` (5文字で末尾が'T'ではない) のようなエッジケースが追加され、新しいロジックがこれらの不正なパターンを正しく拒否することを確認しています。

これらの変更により、Goの `time` パッケージは、より広範なタイムゾーン略語を正確に解析できるようになり、堅牢性が向上しました。

## 関連リンク

*   Go言語 `time` パッケージのドキュメント: [https://pkg.go.dev/time](https://pkg.go.dev/time)
*   Go言語のコミット履歴: [https://github.com/golang/go/commits/master](https://github.com/golang/go/commits/master)

## 参考にした情報源リンク

*   Go言語のタイムゾーン解析に関する一般的な議論 (Stack Overflow): [https://stackoverflow.com/questions/tagged/go+timezone](https://stackoverflow.com/questions/tagged/go+timezone)
*   Go言語の `time.Parse` のレイアウト要件に関する情報 (Stack Overflow): [https://stackoverflow.com/questions/32371873/go-time-parse-layout-requirements](https://stackoverflow.com/questions/32371873/go-time-parse-layout-requirements)
*   Go言語のタイムゾーンに関するIssue (GitHub): [https://github.com/golang/go/issues?q=is%3Aissue+time+timezone](https://github.com/golang/go/issues?q=is%3Aissue+time+timezone)
*   コミットメッセージに記載されている短縮URL: [http://goo.gl/3ETYH7](http://goo.gl/3ETYH7) (現在はリダイレクトされ、元のコンテンツは確認できませんでした。)
*   Go CL (Code Review) 13002044: [https://golang.org/cl/13002044](https://golang.org/cl/13002044) (このコミットに対応するGoのコードレビューページ)