[インデックス 16944] ファイルの概要
このコミットは、Go言語の標準ライブラリtime
パッケージにおいて、タイムゾーンオフセットに秒単位の情報が含まれる形式(例: +HHMMSS
や+HH:MM:SS
)をtime.Parse
関数が正しく解析し、time.Format
関数が正しく出力できるようにする機能追加とバグ修正です。具体的には、ISO 8601形式および数値形式のタイムゾーンオフセットに秒を追加した新しいレイアウト定数を導入し、それらを処理するためのロジックをformat.go
内のnextStdChunk
、Format
、parse
関数に実装しています。これにより、より多様なタイムゾーン表記に対応できるようになりました。
コミット
commit aa38aeaeaf5d9a87b490fa16de9de850a9f5956c
Author: Ulf Holm Nielsen <doktor@dyregod.dk>
Date: Wed Jul 31 16:11:02 2013 +1000
time: Allow Parse and Format to handle time zone offsets with seconds
Adds layout cases with seconds for stdISO8601 and stdNumTZ with and without colons. Update time.Format to append seconds for those cases.
Fixes #4934.
R=golang-dev, r, bradfitz
CC=golang-dev
https://golang.org/cl/8132044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/aa38aeaeaf5d9a87b490fa16de9de850a9f5956c
元コミット内容
time: Allow Parse and Format to handle time zone offsets with seconds
Adds layout cases with seconds for stdISO8601 and stdNumTZ with and without colons. Update time.Format to append seconds for those cases.
Fixes #4934.
変更の背景
この変更は、Go言語のtime
パッケージにおける既知のバグ、Issue #4934「time: Parse unable to handle timezone offsets with seconds (+00:34:08)」を修正するために行われました。
元のtime.Parse
関数は、タイムゾーンオフセットが時と分のみで構成されている形式(例: +HHMM
や+HH:MM
)には対応していましたが、秒単位まで含まれる形式(例: +HHMMSS
や+HH:MM:SS
)を正しく解析できませんでした。これは、特に歴史的なタイムゾーンデータや、特定のシステムで生成されるタイムスタンプにおいて、秒単位のオフセットが使用される場合に問題となっていました。
例えば、Issue #4934で報告されたケースでは、0001-01-01 00:34:08+00:34:08
のような文字列をtime.Parse
で解析しようとするとエラーが発生していました。これは、time
パッケージがタイムゾーンオフセットの秒部分を認識せず、予期しない文字として扱っていたためです。
このコミットは、このような秒単位のタイムゾーンオフセットをtime.Parse
が適切に処理できるようにし、またtime.Format
が同様の形式でタイムゾーンオフセットを出力できるようにすることで、time
パッケージの堅牢性と互換性を向上させることを目的としています。
前提知識の解説
Go言語のtime
パッケージ
Go言語のtime
パッケージは、時刻の表現、時刻の計算、時刻のフォーマットとパースなど、時間に関する様々な機能を提供します。
time.Parse
とtime.Format
time.Parse(layout, value string) (Time, error)
: 指定されたレイアウト文字列layout
に従って、時刻文字列value
をtime.Time
型に変換します。Time.Format(layout string) string
:time.Time
型の値を、指定されたレイアウト文字列layout
に従って文字列にフォーマットします。
これらの関数は、Go言語のtime
パッケージの非常に特徴的な機能である「参照時刻」に基づいたレイアウト文字列を使用します。
参照時刻 (Reference Time)
Go言語のtime
パッケージでは、時刻のフォーマットとパースのレイアウト文字列は、特定の参照時刻Mon Jan 2 15:04:05 MST 2006
(または2006-01-02 15:04:05 -0700 MST
)の各要素に対応する数字や文字を記述することで定義されます。
Mon
: 曜日(短縮形)January
: 月(完全形)Jan
: 月(短縮形)1
: 月(数字、ゼロ埋めなし)01
: 月(数字、ゼロ埋めあり)2
: 日(数字、ゼロ埋めなし)02
: 日(数字、ゼロ埋めあり)15
: 時(24時間形式)3
: 時(12時間形式、ゼロ埋めなし)03
: 時(12時間形式、ゼロ埋めあり)4
: 分(ゼロ埋めなし)04
: 分(ゼロ埋めあり)5
: 秒(ゼロ埋めなし)05
: 秒(ゼロ埋めあり)2006
: 年(完全形)06
: 年(短縮形)PM
: 午前/午後(大文字)pm
: 午前/午後(小文字)MST
: タイムゾーン名(例:PST
)-0700
: タイムゾーンオフセット(時と分、コロンなし)-07:00
: タイムゾーンオフセット(時と分、コロンあり)-07
: タイムゾーンオフセット(時のみ)Z0700
: UTCオフセット(Z
はUTCを示す)Z07:00
: UTCオフセット(コロンあり)
このコミットでは、この参照時刻の概念を拡張し、タイムゾーンオフセットに秒単位の情報を追加できるようにしています。
タイムゾーンオフセット
タイムゾーンオフセットは、協定世界時(UTC)からの時間のずれを示します。通常は+HHMM
や+HH:MM
の形式で表現されますが、一部の歴史的なタイムゾーンや特定のシステムでは、秒単位のオフセット(例: +HHMMSS
や+HH:MM:SS
)が存在します。例えば、1883年11月18日以前のニューヨークのローカル平均時(LMT)はUTCから-4:56:02
秒ずれていました。
ISO 8601
ISO 8601は、日付と時刻の表現に関する国際標準です。タイムゾーンオフセットの表現方法も定義されており、+HHMM
、+HH:MM
、+HHMMSS
、+HH:MM:SS
などの形式が含まれます。
技術的詳細
このコミットの主要な変更点は、src/pkg/time/format.go
とsrc/pkg/time/time_test.go
にあります。
src/pkg/time/format.go
の変更
-
新しいレイアウト定数の追加:
const
ブロックに、秒単位のタイムゾーンオフセットを表現するための新しい定数が追加されました。stdISO8601SecondsTZ
:Z070000
(ISO 8601形式、秒あり、コロンなし)stdISO8601ColonSecondsTZ
:Z07:00:00
(ISO 8601形式、秒あり、コロンあり)stdNumSecondsTz
:-070000
(数値形式、秒あり、コロンなし)stdNumColonSecondsTZ
:-07:00:00
(数値形式、秒あり、コロンあり)
これらの定数は、
time.Parse
が入力文字列を解析する際、およびtime.Format
が出力文字列を生成する際に、秒単位のオフセットを認識・生成するためのパターンとして使用されます。 -
nextStdChunk
関数の変更:nextStdChunk
関数は、レイアウト文字列を解析し、次の標準レイアウト要素(例: 年、月、日、時、分、タイムゾーンなど)を識別する役割を担います。この関数に、新しい秒単位のタイムゾーンオフセット形式を認識するためのロジックが追加されました。'-'
(マイナス記号)で始まるオフセット(例:-070000
,-07:00:00
)の検出ロジックが追加されました。'Z'
(Zulu time、UTCを示す)で始まるオフセット(例:Z070000
,Z07:00:00
)の検出ロジックが追加されました。
これにより、
time.Parse
がこれらの新しい形式のタイムゾーンオフセットを含む文字列を正しく分解できるようになります。 -
Time.Format
メソッドの変更:Time.Format
メソッドは、time.Time
オブジェクトを文字列に変換する際に、タイムゾーンオフセットを適切にフォーマットするロジックが更新されました。stdISO8601SecondsTZ
,stdISO8601ColonSecondsTZ
,stdNumSecondsTz
,stdNumColonSecondsTZ
の新しい定数がswitch
文のケースに追加されました。- オフセットが0の場合(UTCの場合)の
'Z'
の扱いが、新しい秒単位のオフセット形式にも適用されるようになりました。 - タイムゾーンオフセットの秒部分を計算し、出力文字列に追加するロジックが追加されました。具体的には、
absoffset%60
(秒)を計算し、必要に応じてコロンを追加して出力バッファに追記します。
-
parse
関数の変更:parse
関数は、入力文字列を解析してtime.Time
オブジェクトを構築する主要な関数です。この関数に、秒単位のタイムゾーンオフセットを解析するロジックが追加されました。stdISO8601SecondsTZ
,stdISO8601ColonSecondsTZ
,stdNumSecondsTz
,stdNumColonSecondsTZ
の新しい定数がswitch
文のケースに追加されました。- タイムゾーンオフセットの文字列から、符号、時、分に加えて秒を抽出するロジックが追加されました。
- 抽出された時、分、秒から、合計のオフセット秒数を計算するロジックが更新されました。
zoneOffset = (hr*60+mm)*60 + ss
という計算式が導入され、秒単位のオフセットが正確に考慮されるようになりました。
src/pkg/time/time_test.go
の変更
-
SecondsTimeZoneOffsetTest
構造体の追加: 新しいテストケースを定義するための構造体SecondsTimeZoneOffsetTest
が追加されました。この構造体は、フォーマット文字列、入力値文字列、期待されるオフセット秒数を含みます。 -
secondsTimeZoneOffsetTests
変数の追加:SecondsTimeZoneOffsetTest
型のスライスとして、秒単位のタイムゾーンオフセットを含む様々なテストケースが定義されました。これには、コロンあり/なし、プラス/マイナスオフセット、ISO 8601形式と数値形式の組み合わせが含まれます。 -
TestParseSecondsInTimeZone
関数の追加: このテスト関数は、secondsTimeZoneOffsetTests
で定義された各テストケースを使用して、time.Parse
が秒単位のタイムゾーンオフセットを正しく解析できることを検証します。解析結果のタイムゾーンオフセットが期待値と一致するかどうかを確認します。 -
TestFormatSecondsInTimeZone
関数の追加: このテスト関数は、time.Format
が秒単位のタイムゾーンオフセットを正しくフォーマットできることを検証します。特定のtime.Time
オブジェクトを秒単位のオフセットを含むレイアウトでフォーマットし、結果の文字列が期待値と一致するかどうかを確認します。
これらのテストの追加により、新しい機能が正しく動作すること、および既存の機能に回帰がないことが保証されます。
コアとなるコードの変更箇所
src/pkg/time/format.go
--- a/src/pkg/time/format.go
+++ b/src/pkg/time/format.go
@@ -59,35 +59,39 @@ const (
)
const (
- _ = iota
- stdLongMonth = iota + stdNeedDate // "January"
- stdMonth // "Jan"
- stdNumMonth // "1"
- stdZeroMonth // "01"
- stdLongWeekDay // "Monday"
- stdWeekDay // "Mon"
- stdDay // "2"
- stdUnderDay // "_2"
- stdZeroDay // "02"
- stdHour = iota + stdNeedClock // "15"
- stdHour12 // "3"
- stdZeroHour12 // "03"
- stdMinute // "4"
- stdZeroMinute // "04"
- stdSecond // "5"
- stdZeroSecond // "05"
- stdLongYear = iota + stdNeedDate // "2006"
- stdYear // "06"
- stdPM = iota + stdNeedClock // "PM"
- stdpm // "pm"
- stdTZ = iota // "MST"
- stdISO8601TZ // "Z0700" // prints Z for UTC
- stdISO8601ColonTZ // "Z07:00" // prints Z for UTC
- stdNumTZ // "-0700" // always numeric
- stdNumShortTZ // "-07" // always numeric
- stdNumColonTZ // "-07:00" // always numeric
- stdFracSecond0 // ".0", ".00", ... , trailing zeros included
- stdFracSecond9 // ".9", ".99", ..., trailing zeros omitted
+ _ = iota
+ stdLongMonth = iota + stdNeedDate // "January"
+ stdMonth // "Jan"
+ stdNumMonth // "1"
+ stdZeroMonth // "01"
+ stdLongWeekDay // "Monday"
+ stdWeekDay // "Mon"
+ stdDay // "2"
+ stdUnderDay // "_2"
+ stdZeroDay // "02"
+ stdHour = iota + stdNeedClock // "15"
+ stdHour12 // "3"
+ stdZeroHour12 // "03"
+ stdMinute // "4"
+ stdZeroMinute // "04"
+ stdSecond // "5"
+ stdZeroSecond // "05"
+ stdLongYear = iota + stdNeedDate // "2006"
+ stdYear // "06"
+ stdPM = iota + stdNeedClock // "PM"
+ stdpm // "pm"
+ stdTZ = iota // "MST"
+ stdISO8601TZ // "Z0700" // prints Z for UTC
+ stdISO8601SecondsTZ // "Z070000"
+ stdISO8601ColonTZ // "Z07:00" // prints Z for UTC
+ stdISO8601ColonSecondsTZ // "Z07:00:00"
+ stdNumTZ // "-0700" // always numeric
+ stdNumSecondsTz // "-070000"
+ stdNumShortTZ // "-07" // always numeric
+ stdNumColonTZ // "-07:00" // always numeric
+ stdNumColonSecondsTZ // "-07:00:00"
+ stdFracSecond0 // ".0", ".00", ... , trailing zeros included
+ stdFracSecond9 // ".9", ".99", ..., trailing zeros omitted
stdNeedDate = 1 << 8 // need month, day, year
stdNeedClock = 2 << 8 // need hour, minute, second
@@ -165,7 +169,13 @@ func nextStdChunk(layout string) (prefix string, std int, suffix string) {
\t}
-\t\tcase '-': // -0700, -07:00, -07
+\t\tcase '-': // -070000, -07:00:00, -0700, -07:00, -07
+\t\t\tif len(layout) >= i+7 && layout[i:i+7] == "-070000" {
+\t\t\t\treturn layout[0:i], stdNumSecondsTz, layout[i+7:]
+\t\t\t}
+\t\t\tif len(layout) >= i+9 && layout[i:i+9] == "-07:00:00" {
+\t\t\t\treturn layout[0:i], stdNumColonSecondsTZ, layout[i+9:]
+\t\t\t}
\t\t\tif len(layout) >= i+5 && layout[i:i+5] == "-0700" {
\t\t\t\treturn layout[0:i], stdNumTZ, layout[i+5:]
\t\t\t}
@@ -175,13 +185,21 @@ func nextStdChunk(layout string) (prefix string, std int, suffix string) {
\t\t\tif len(layout) >= i+3 && layout[i:i+3] == "-07" {
\t\t\t\treturn layout[0:i], stdNumShortTZ, layout[i+3:]
\t\t\t}
-\t\tcase 'Z': // Z0700, Z07:00
+\n+\t\tcase 'Z': // Z070000, Z07:00:00, Z0700, Z07:00,
+\t\t\tif len(layout) >= i+7 && layout[i:i+7] == "Z070000" {
+\t\t\t\treturn layout[0:i], stdISO8601SecondsTZ, layout[i+7:]
+\t\t\t}
+\t\t\tif len(layout) >= i+9 && layout[i:i+9] == "Z07:00:00" {
+\t\t\t\treturn layout[0:i], stdISO8601ColonSecondsTZ, layout[i+9:]
+\t\t\t}
\t\t\tif len(layout) >= i+5 && layout[i:i+5] == "Z0700" {
\t\t\t\treturn layout[0:i], stdISO8601TZ, layout[i+5:]
\t\t\t}
\t\t\tif len(layout) >= i+6 && layout[i:i+6] == "Z07:00" {
\t\t\t\treturn layout[0:i], stdISO8601ColonTZ, layout[i+6:]
\t\t\t}
+\n \t\tcase '.': // .000 or .999 - repeated digits for fractional seconds.
\t\t\tif i+1 < len(layout) && (layout[i+1] == '0' || layout[i+1] == '9') {
\t\t\t\tch := layout[i+1]
@@ -507,17 +525,19 @@ func (t Time) Format(layout string) string {
\t} else {
\t\tb = append(b, "am"...)
\t}
-\t\tcase stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumColonTZ:
+\t\tcase stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumColonTZ, stdNumSecondsTz, stdNumColonSecondsTZ:
\t\t\t// Ugly special case. We cheat and take the "Z" variants
\t\t\t// to mean "the time zone as formatted for ISO 8601".
-\t\t\tif offset == 0 && (std == stdISO8601TZ || std == stdISO8601ColonTZ) {
+\t\t\tif offset == 0 && (std == stdISO8601TZ || std == stdISO8601ColonTZ || std == stdISO8601SecondsTZ || std == stdISO8601ColonSecondsTZ) {
\t\t\t\tb = append(b, 'Z')
\t\t\t\tbreak
\t\t\t}
\t\t\tzone := offset / 60 // convert to minutes
+\t\t\tabsoffset := offset
\t\t\tif zone < 0 {
\t\t\t\tb = append(b, '-')
\t\t\t\tzone = -zone
+\t\t\t\tabsoffset = -absoffset
\t\t\t} else {
\t\t\t\tb = append(b, '+')
\t\t\t}
@@ -526,6 +546,15 @@ func (t Time) Format(layout string) string {
\t\t\t\tb = append(b, ':')
\t\t\t}
\t\t\tb = appendUint(b, uint(zone%60), '0')
+\n+\t\t\t// append seconds if appropriate
+\t\t\tif std == stdISO8601SecondsTZ || std == stdNumSecondsTz || std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ {
+\t\t\t\tif std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ {
+\t\t\t\t\tb = append(b, ':')
+\t\t\t\t}\n+\t\t\t\tb = appendUint(b, uint(absoffset%60), '0')
+\t\t\t}\n+\n \t\tcase stdTZ:
\t\t\tif name != "" {
\t\t\t\tb = append(b, name...)
@@ -821,13 +850,13 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error)\n \t\t\tdefault:\n \t\t\t\terr = errBad
\t\t\t}\n-\t\tcase stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ:\n+\t\tcase stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ, stdNumSecondsTz, stdNumColonSecondsTZ:\n \t\t\tif (std == stdISO8601TZ || std == stdISO8601ColonTZ) && len(value) >= 1 && value[0] == 'Z' {\n \t\t\t\tvalue = value[1:]
\t\t\t\tz = UTC
\t\t\t\tbreak
\t\t\t}\n-\t\t\tvar sign, hour, min string\n+\t\t\tvar sign, hour, min, seconds string
\t\t\tif std == stdISO8601ColonTZ || std == stdNumColonTZ {\n \t\t\t\tif len(value) < 6 {\n \t\t\t\t\terr = errBad
@@ -837,26 +866,45 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error)\n \t\t\t\t\terr = errBad
\t\t\t\t\tbreak
\t\t\t\t}\n-\t\t\t\tsign, hour, min, value = value[0:1], value[1:3], value[4:6], value[6:]
+\t\t\t\tsign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], "00", value[6:]
\t\t\t} else if std == stdNumShortTZ {\n \t\t\t\tif len(value) < 3 {\n \t\t\t\t\terr = errBad
\t\t\t\t\tbreak
\t\t\t\t}\n-\t\t\t\tsign, hour, min, value = value[0:1], value[1:3], "00", value[3:]
+\t\t\t\tsign, hour, min, seconds, value = value[0:1], value[1:3], "00", "00", value[3:]
+\t\t\t} else if std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ {\n+\t\t\t\tif len(value) < 9 {\n+\t\t\t\t\terr = errBad
+\t\t\t\t\tbreak
+\t\t\t\t}\n+\t\t\t\tif value[3] != ':' || value[6] != ':' {\n+\t\t\t\t\terr = errBad
+\t\t\t\t\tbreak
+\t\t\t\t}\n+\t\t\t\tsign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], value[7:9], value[9:]
+\t\t\t} else if std == stdISO8601SecondsTZ || std == stdNumSecondsTz {\n+\t\t\t\tif len(value) < 7 {\n+\t\t\t\t\terr = errBad
+\t\t\t\t\tbreak
+\t\t\t\t}\n+\t\t\t\tsign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], value[5:7], value[7:]
\t\t\t} else {\n \t\t\t\tif len(value) < 5 {\n \t\t\t\t\terr = errBad
\t\t\t\t\tbreak
\t\t\t\t}\n-\t\t\t\tsign, hour, min, value = value[0:1], value[1:3], value[3:5], value[5:]
+\t\t\t\tsign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], "00", value[5:]
\t\t\t}\n-\t\t\tvar hr, mm int
+\t\t\tvar hr, mm, ss int
\t\t\thr, err = atoi(hour)
\t\t\tif err == nil {\n \t\t\t\tmm, err = atoi(min)
\t\t\t}\n-\t\t\tzoneOffset = (hr*60 + mm) * 60 // offset is in seconds
+\t\t\tif err == nil {\n+\t\t\t\tss, err = atoi(seconds)\n+\t\t\t}\n+\t\t\tzoneOffset = (hr*60+mm)*60 + ss // offset is in seconds
\t\t\tswitch sign[0] {\n \t\t\tcase '+':\n \t\t\tcase '-':
src/pkg/time/time_test.go
--- a/src/pkg/time/time_test.go
+++ b/src/pkg/time/time_test.go
@@ -781,6 +781,44 @@ func TestMinutesInTimeZone(t *testing.T) {
}
}
+type SecondsTimeZoneOffsetTest struct {
+ format string
+ value string
+ expectedoffset int
+}
+
+var secondsTimeZoneOffsetTests = []SecondsTimeZoneOffsetTest{
+ {"2006-01-02T15:04:05-070000", "1871-01-01T05:33:02-003408", -(34*60 + 8)},
+ {"2006-01-02T15:04:05-07:00:00", "1871-01-01T05:33:02-00:34:08", -(34*60 + 8)},
+ {"2006-01-02T15:04:05-070000", "1871-01-01T05:33:02+003408", 34*60 + 8},
+ {"2006-01-02T15:04:05-07:00:00", "1871-01-01T05:33:02+00:34:08", 34*60 + 8},
+ {"2006-01-02T15:04:05Z070000", "1871-01-01T05:33:02-003408", -(34*60 + 8)},
+ {"2006-01-02T15:04:05Z07:00:00", "1871-01-01T05:33:02+00:34:08", 34*60 + 8},
+}
+
+func TestParseSecondsInTimeZone(t *testing.T) {
+ // should accept timezone offsets with seconds like: Zone America/New_York -4:56:02 - LMT 1883 Nov 18 12:03:58
+ for _, test := range secondsTimeZoneOffsetTests {
+ time, err := Parse(test.format, test.value)
+ if err != nil {
+ t.Fatal("error parsing date:", err)
+ }
+ _, offset := time.Zone()
+ if offset != test.expectedoffset {
+ t.Errorf("ZoneOffset = %d, want %d", offset, test.expectedoffset)
+ }
+ }
+}
+
+func TestFormatSecondsInTimeZone(t *testing.T) {
+ d := Date(1871, 9, 17, 20, 4, 26, 0, FixedZone("LMT", -(34*60+8)))
+ timestr := d.Format("2006-01-02T15:04:05Z070000")
+ expected := "1871-09-17T20:04:26-003408"
+ if timestr != expected {
+ t.Errorf("Got %s, want %s", timestr, expected)
+ }
+}
+
type ISOWeekTest struct {
year int // year
month, day int // month and day
コアとなるコードの解説
src/pkg/time/format.go
- 新しい定数:
stdISO8601SecondsTZ
,stdISO8601ColonSecondsTZ
,stdNumSecondsTz
,stdNumColonSecondsTZ
は、それぞれISO 8601形式および数値形式で秒単位のタイムゾーンオフセットを表現するための新しいレイアウト要素です。これらは、time.Parse
が入力文字列を解析する際のパターンマッチング、およびtime.Format
が出力文字列を生成する際のテンプレートとして機能します。 nextStdChunk
の拡張: この関数は、レイアウト文字列から次の「標準チャンク」(日付、時刻、タイムゾーンなどの要素)を識別します。変更点では、'-'
や'Z'
で始まる文字列が、新しい秒単位のオフセット形式(例:-070000
,Z07:00:00
)に一致するかどうかをチェックするロジックが追加されました。これにより、time.Parse
はこれらの新しい形式を正しく認識し、対応する内部定数(stdNumSecondsTz
など)にマッピングできるようになります。Time.Format
の拡張:Format
メソッドは、time.Time
オブジェクトを文字列に変換する際に、タイムゾーンオフセットを生成します。変更点では、新しい秒単位のオフセット定数に対応するcase
が追加され、offset
(秒単位のオフセット)から時、分、秒を抽出し、適切な形式(コロンの有無など)で文字列に変換して出力バッファに追加するロジックが実装されました。特に、absoffset%60
で秒数を取得し、それをフォーマットする部分が重要です。parse
関数の拡張:parse
関数は、入力文字列をtime.Time
オブジェクトに変換する際に、タイムゾーンオフセットを解析します。変更点では、新しい秒単位のオフセット定数に対応するcase
が追加され、入力文字列から符号、時、分に加えて秒を抽出するロジックが追加されました。そして、抽出した時、分、秒を組み合わせて、正確なzoneOffset
(UTCからの秒単位のずれ)を計算するようになりました。これにより、秒単位のオフセットを持つ時刻文字列も正しくtime.Time
オブジェクトに変換できるようになります。
src/pkg/time/time_test.go
SecondsTimeZoneOffsetTest
とsecondsTimeZoneOffsetTests
: これらの追加は、秒単位のタイムゾーンオフセットのパースとフォーマットが正しく機能するかを検証するための具体的なテストデータを提供します。様々な形式とオフセット値が網羅されており、堅牢なテストを可能にしています。TestParseSecondsInTimeZone
: このテスト関数は、time.Parse
が秒単位のタイムゾーンオフセットを含む文字列を正しく解析し、期待されるオフセット値(秒単位)を返すことを確認します。これにより、バグ #4934で報告されたようなパースエラーが解消されたことを検証します。TestFormatSecondsInTimeZone
: このテスト関数は、time.Format
が秒単位のタイムゾーンオフセットを正しくフォーマットし、期待される出力文字列を生成することを確認します。これにより、新しいフォーマット機能が意図通りに動作することを保証します。
これらの変更により、Go言語のtime
パッケージは、より広範なタイムゾーンオフセット形式に対応できるようになり、特に歴史的なタイムゾーンデータや、秒単位のオフセットを使用するシステムとの互換性が向上しました。
関連リンク
- Go Issue #4934: https://github.com/golang/go/issues/4934
- Gerrit Change-Id: https://golang.org/cl/8132044
参考にした情報源リンク
- Go Issue 4934 on GitHub
- ISO 8601 - Wikipedia
- Go time package documentation (現在の最新ドキュメントを参照)
- Go by Example: Time Formatting / Parsing (Goの参照時刻の概念を理解するのに役立つ)
- Time zone - Wikipedia (タイムゾーンオフセットの一般的な情報)
- List of time zone abbreviations - Wikipedia (歴史的なタイムゾーンオフセットの例)