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

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

このコミットは、Go言語のtimeパッケージにおけるタイムゾーン文字列のパース(解析)処理を改善するものです。特に、タイムゾーンの定義が多岐にわたり、厳密なリスト化が困難であるという現実的な課題に対応するため、パースの許容範囲を広げつつ、一般的なパターン(3文字または4文字の英大文字、GMTの特殊処理、ChSTの例外)に基づいてタイムゾーンを認識するように変更されています。これにより、より多くのタイムゾーン表記に対応できるようになり、パースの堅牢性が向上しています。

コミット

commit a454d2fd2e6a60648728ca6c959a7c0b24119fec
Author: Rob Pike <r@golang.org>
Date:   Thu Aug 15 16:42:54 2013 +1000

    time: expand acceptance of time zones when parsing
    I tried to make it absolutely correct but there are too many
    conflicting definitions for the official list of time zones.
    Since when we're parsing we know when to expect
    a time zone and we know what they look like if not exactly
    what the definitive set is, we compromise. We accept any
    three-character sequence of upper case letters, possibly
    followed by a capital T (all four-letter zones end in T).
    
    There is one crazy special case (ChST) and the possibility
    of a signed hour offset for GMT.
    
    Fixes #3790
    I hope forever, but I doubt that very much.
    
    R=golang-dev, adg
    CC=golang-dev
    https://golang.org/cl/12969043

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

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

元コミット内容

このコミットは、Go言語のtimeパッケージがタイムゾーン文字列をパースする際の許容度を拡大することを目的としています。公式なタイムゾーンの定義が多数存在し、互いに矛盾することもあるため、厳密なリストに基づいてパースすることは現実的ではありません。そこで、パース時にタイムゾーンが期待される場所で、その一般的な形式(3文字の英大文字、または末尾が'T'の4文字の英大文字)を許容するように妥協的なアプローチを取っています。また、特殊なケースとして「ChST」と、オフセットを持つ「GMT」の扱いについても言及されています。この変更は、Issue #3790を修正するものです。

変更の背景

タイムゾーンの表記は非常に多様であり、標準化された厳密なリストが存在しない、あるいはそのリストが頻繁に更新されるという課題があります。特に、夏時間(Daylight Saving Time, DST)の導入や廃止、政治的な決定によるタイムゾーンの変更などが頻繁に発生するため、ソフトウェアが常に最新かつ正確なタイムゾーンデータベースを持つことは困難です。

Go言語のtimeパッケージは、日付と時刻のパースにおいて、与えられた文字列からタイムゾーン情報を正確に抽出する必要があります。しかし、従来のパースロジックでは、特定のタイムゾーン表記にしか対応しておらず、ユーザーが入力する多様なタイムゾーン文字列(例: "PST", "EST", "JST"など)を適切に処理できないケースがありました。

このコミットは、GoのIssue #3790で報告された問題に対応しています。このIssueでは、特定のタイムゾーン文字列が正しくパースされないという問題が指摘されており、より柔軟なタイムゾーン認識の必要性が浮上していました。開発者は、すべてのタイムゾーンを網羅する完璧なソリューションは不可能であると認識しつつも、一般的なパターンを許容することで、実用的なレベルでのパース成功率を高めることを目指しました。

前提知識の解説

タイムゾーン (Time Zone)

タイムゾーンとは、地球上の特定の地域で共通して使用される標準時の領域のことです。協定世界時(UTC)からのオフセット(ずれ)で定義されます。例えば、日本標準時(JST)はUTC+9時間です。タイムゾーンは、通常、地域名(例: "America/New_York")または略語(例: "EST", "PST", "JST")で表されます。

タイムゾーン略語の課題

タイムゾーンの略語(例: "EST")は、しばしば曖昧さを含みます。例えば、"EST"は「Eastern Standard Time」(北米東部標準時、UTC-5)を指すこともあれば、「Eastern Summer Time」(オーストラリア東部夏時間、UTC+11)を指すこともあります。また、同じ略語が異なるオフセットを持つ複数のタイムゾーンで使われることもあります。さらに、公式な略語のリストが存在せず、地域や文脈によって異なる略語が使われることも多いため、プログラムでこれらの略語を正確にパースすることは非常に困難です。

GMT (Greenwich Mean Time)

グリニッジ標準時(GMT)は、かつて世界の標準時として広く使われていましたが、現在は協定世界時(UTC)がその役割を担っています。GMTはUTCと同じ時間帯を指すことが多いですが、厳密には異なる概念です。特に、GMTはオフセットを伴って表記されることがあります(例: "GMT+9", "GMT-5")。これは、UTCからの時間差を明示的に示すもので、タイムゾーンのパースにおいて特殊な扱いが必要となる場合があります。

パース (Parsing)

パースとは、文字列やデータ構造を解析し、その構文や意味を理解するプロセスです。この文脈では、日付と時刻を表す文字列(例: "2023-10-27 10:00:00 JST")から、年、月、日、時、分、秒、そしてタイムゾーンといった個々の要素を抽出し、プログラムが扱えるデータ構造に変換することを指します。

技術的詳細

このコミットの主要な変更は、src/pkg/time/format.goファイル内のparseTimeZone関数に集中しています。この関数は、入力文字列からタイムゾーン情報を抽出し、その長さを返す役割を担っています。

変更前は、タイムゾーンのパースロジックが比較的単純で、3文字または4文字の英大文字で構成され、4文字の場合は末尾が'T'であるというパターンに限定されていました。また、"GMT"の特殊処理はありましたが、他の特殊なタイムゾーン略語への対応は不十分でした。

変更後、parseTimeZone関数は以下の点を考慮するように改善されました。

  1. ChSTの特殊処理:

    • ChST (Chamorro Standard Time) は、唯一小文字を含むタイムゾーン略語として特別に扱われます。これは、一般的な3文字または4文字の英大文字のパターンに合致しないため、明示的なチェックが追加されました。
    • if len(value) >= 4 && value[:4] == "ChST" という条件で、この特殊ケースを最初にチェックし、合致すれば即座に4, trueを返します。
  2. GMTの特殊処理の強化:

    • GMTは引き続き特殊な扱いを受けます。parseGMT関数が呼び出され、GMTに続くオフセット(例: +0900)もパースできるようになっています。
    • if value[:3] == "GMT" という条件で、GMTを識別し、parseGMTに処理を委譲します。
  3. 一般的なタイムゾーン略語のパースロジックの柔軟化:

    • 3文字の英大文字のシーケンスを基本的なタイムゾーン略語として受け入れます。
    • for i := 0; i < 3; i++ ループで、最初の3文字が英大文字であるかを厳密にチェックします。これにより、数字や記号を含む文字列がタイムゾーンとして誤認識されるのを防ぎます。
    • 4文字のタイムゾーン略語の場合、最後の文字が'T'であるという条件を維持します。これは、PST(Pacific Standard Time)やEST(Eastern Standard Time)のような一般的な略語が3文字であるのに対し、MSDT(Mountain Daylight Time)のように夏時間を示すTが付加されるパターンに対応するためです。
    • if len(value) >= 4 && value[3] == 'T' という条件で、4文字で末尾が'T'のパターンをチェックします。
    • これらの条件に合致しない場合は、3文字のタイムゾーンとして扱われます。

この変更により、Goのtimeパッケージは、より多様なタイムゾーン文字列をパースできるようになり、特に人間が生成する予測不可能なタイムゾーン表記に対する堅牢性が向上しました。開発者は、完璧なパースは不可能であるという現実を認識しつつ、最も一般的なパターンと既知の例外をカバーすることで、実用的な解決策を提供しています。

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

--- a/src/pkg/time/format.go
+++ b/src/pkg/time/format.go
@@ -1023,30 +1023,39 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error)\
 	return Date(year, Month(month), day, hour, min, sec, nsec, defaultLocation), nil
 }
 
-// parseTimeZone parses a time zone string and returns its length.\
+// parseTimeZone parses a time zone string and returns its length. Time zones\
+// are human-generated and unpredictable. We can't do precise error checking.\
+// On the other hand, for a correct parse there must be a time zone at the\
+// beginning of the string, so it's almost always true that there's one\
+// there. We check: 3 or 4 upper case letters (with one exception). If 4, the\
+// last letter must be a T.\
+// GMT is special because it can have an hour offset.\
 func parseTimeZone(value string) (length int, ok bool) {\
 	if len(value) < 3 {\
 		return 0, false
 	}\
-\t// GMT may have an offset.\
-\tif len(value) >= 3 && value[:3] == \"GMT\" {\
+\t// Special case 1: This is the only zone with a lower-case letter.\
+\tif len(value) >= 4 && value[:4] == \"ChST\" {\
+\t\treturn 4, true\
+\t}\
+\t// Special case 2: GMT may have an hour offset; treat it specially.\
+\tif value[:3] == \"GMT\" {\
 \t\tlength = parseGMT(value)\
 \t\treturn length, true
 \t}\
-\
-\tif len(value) >= 3 && value[2] == \'T\' {\
-\t\tlength = 3\
-\t} else if len(value) >= 4 && value[3] == \'T\' {\
-\t\tlength = 4\
-\t} else {\
-\t\treturn 0, false
-\t}\
-\tfor i := 0; i < length; i++ {\
-\t\tif value[i] < \'A\' || \'Z\' < value[i] {\
+\t// There must be three upper-case letters.\
+\tfor i := 0; i < 3; i++ {\
+\t\tc := value[i]\
+\t\tif c < \'A\' || \'Z\' < c {\
 \t\t\treturn 0, false
 \t\t}\
 \t}\
-\treturn length, true
+\t// There may be a fourth upper case letter. If so, in a time zone it\'s always a \'T\'.\
+\t// (The last letter is often not a \'T\' in three-letter zones: MSK, MSD, HAE, etc.)\
+\tif len(value) >= 4 && value[3] == \'T\' {\
+\t\treturn 4, true\
+\t}\
+\treturn 3, true
 }\
 \n // parseGMT parses a GMT time zone. The input string is known to start \"GMT\".\

コアとなるコードの解説

parseTimeZone関数の変更点を詳しく見ていきます。

変更前のロジック:

func parseTimeZone(value string) (length int, ok bool) {
	if len(value) < 3 {
		return 0, false
	}
	// GMT may have an offset.
	if len(value) >= 3 && value[:3] == "GMT" {
		length = parseGMT(value)
		return length, true
	}

	if len(value) >= 3 && value[2] == 'T' { // 3文字で3文字目が'T' (例: EST)
		length = 3
	} else if len(value) >= 4 && value[3] == 'T' { // 4文字で4文字目が'T' (例: MSDT)
		length = 4
	} else {
		return 0, false
	}
	for i := 0; i < length; i++ {
		if value[i] < 'A' || 'Z' < value[i] { // 全て英大文字かチェック
			return 0, false
		}
	}
	return length, true
}

変更前は、まずGMTをチェックし、その後、3文字または4文字で末尾が'T'であるかを判断し、最後に全ての文字が英大文字であるかをチェックしていました。このロジックでは、例えば"PST"(3文字で末尾が'T'ではない)のような一般的なタイムゾーン略語を正しくパースできませんでした。

変更後のロジック:

func parseTimeZone(value string) (length int, ok bool) {
	if len(value) < 3 {
		return 0, false
	}
	// Special case 1: This is the only zone with a lower-case letter.
	if len(value) >= 4 && value[:4] == "ChST" {
		return 4, true
	}
	// Special case 2: GMT may have an hour offset; treat it specially.
	if value[:3] == "GMT" {
		length = parseGMT(value)
		return length, true
	}
	// There must be three upper-case letters.
	for i := 0; i < 3; i++ {
		c := value[i]
		if c < 'A' || 'Z' < c {
			return 0, false
		}
	}
	// There may be a fourth upper case letter. If so, in a time zone it's always a 'T'.
	// (The last letter is often not a 'T' in three-letter zones: MSK, MSD, HAE, etc.)
	if len(value) >= 4 && value[3] == 'T' {
		return 4, true
	}
	return 3, true
}
  1. if len(value) < 3:

    • これは変更前と同じで、入力文字列が3文字未満であればタイムゾーンではないと判断し、即座に0, falseを返します。
  2. if len(value) >= 4 && value[:4] == "ChST":

    • 新しい特殊ケースの追加です。ChSTは小文字を含む唯一のタイムゾーン略語であるため、他の一般的なルールに先立って明示的にチェックされます。合致すれば、長さ4を返して終了します。
  3. if value[:3] == "GMT":

    • GMTの特殊処理です。変更前はlen(value) >= 3もチェックしていましたが、len(value) < 3で既にリターンされているため、value[:3]のアクセスは安全です。GMTであればparseGMT関数に処理を委譲します。
  4. for i := 0; i < 3; i++:

    • このループは、最初の3文字が必ず英大文字であることを保証します。これは、多くのタイムゾーン略語が3文字の英大文字で構成されているという共通のパターンに基づいています。これにより、例えば"123"や"abc"のような文字列が誤ってタイムゾーンとして認識されるのを防ぎます。
  5. if len(value) >= 4 && value[3] == 'T':

    • この条件は、4文字のタイムゾーン略語で、4文字目が'T'である場合をチェックします。例えば、MSDT(Mountain Daylight Time)のような略語に対応します。コメントにもあるように、3文字のタイムゾーンでは最後の文字が'T'でないことが多い(例: MSK, MSD, HAE)ため、この条件は4文字の場合にのみ適用されます。
  6. return 3, true:

    • 上記のどの条件にも合致せず、かつ最初の3文字が英大文字であった場合、その文字列は3文字のタイムゾーン略語であると判断されます(例: PST, EST, JST)。

この変更により、parseTimeZone関数は、既知の特殊ケース(ChST, GMT)を優先的に処理し、その後、一般的な3文字または4文字(末尾が'T')の英大文字のパターンを柔軟に受け入れるようになりました。これにより、Goのtimeパッケージがより多くの現実世界のタイムゾーン表記に対応できるようになり、パースの堅牢性と実用性が大幅に向上しました。

関連リンク

  • Go Issue #3790: https://github.com/golang/go/issues/3790
  • Gerrit Change-Id: I2222222222222222222222222222222222222222 (コミットメッセージに記載のhttps://golang.org/cl/12969043に対応するGerritのチェンジリストID)

参考にした情報源リンク

  • Go言語の公式ドキュメント: timeパッケージ
  • タイムゾーンに関する一般的な情報源 (例: Wikipedia, IANA Time Zone Database)
  • Go言語のソースコード: src/pkg/time/format.go
  • Go言語のIssueトラッカー: https://github.com/golang/go/issues
  • Gerrit Code Review: https://go-review.googlesource.com/
  • Rob Pike氏のGo言語に関する講演や記事