[インデックス 14233] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/asn1
パッケージ内の marshal.go
および marshal_test.go
ファイルに対する変更です。具体的には、ASN.1 の UTCTime
型のエンコーディングロジックが修正され、それに伴うテストケースが更新されています。
コミット
- コミットハッシュ:
4d17fe3cd62788b9b15af471806063f8cc071c97
- Author: Adam Langley agl@golang.org
- Date: Mon Oct 29 11:16:05 2012 -0400
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4d17fe3cd62788b9b15af471806063f8cc071c97
元コミット内容
encoding/asn1: don't convert UTCTime to UTC.
Previously we converted a time to UTC *and* serialized the timezone of
the original time. With this change, we serialize a UTCTime in the
original timezone.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/6817048
変更の背景
このコミットの背景には、Go言語の encoding/asn1
パッケージにおける UTCTime
型のエンコーディングに関する不正確な挙動がありました。以前の実装では、time.Time
オブジェクトを UTCTime
としてASN.1形式にシリアライズする際に、まず time.Time
オブジェクトをUTC(協定世界時)に変換していました。しかし、その変換後のUTC時刻に対して、元の time.Time
オブジェクトが持っていたタイムゾーン情報(オフセット)を付加してシリアライズしていました。
これは論理的な矛盾を生じさせます。例えば、日本時間(JST, UTC+9)の時刻を UTCTime
としてエンコードしようとした場合、まず内部的にUTCに変換され、そのUTC時刻に対して「+0900」のようなJSTのオフセットが付加されてしまうことになります。結果として、エンコードされた時刻は、意図した時刻とは異なる時刻(UTC時刻にJSTオフセットが付いた、存在しない時刻)として解釈される可能性がありました。
このコミットは、この二重の変換(UTCへの変換と元のタイムゾーンのシリアライズ)を修正し、UTCTime
をエンコードする際には、元の time.Time
オブジェクトが保持しているタイムゾーン情報をそのまま利用してシリアライズするように変更することで、正確なASN.1 UTCTime
の表現を実現することを目的としています。
前提知識の解説
ASN.1 (Abstract Syntax Notation One)
ASN.1は、データ構造を記述するための国際標準です。異なるシステム間でデータを交換する際に、データの形式を明確に定義するために使用されます。通信プロトコル(例: X.509証明書、SNMP、LDAP)や暗号化技術(例: PKCS#7、PKCS#10)などで広く利用されています。ASN.1は、データの抽象的な構文を定義し、そのデータをバイト列に変換するためのエンコーディングルール(例: DER, BER, PER)も提供します。
UTCTime
UTCTime
は、ASN.1で定義されている時間型の一つです。主に1950年から2049年までの日付と時刻を表すために使用されます。UTCTime
のフォーマットは通常 YYMMDDhhmmssZ
または YYMMDDhhmmss+hhmm
/ YYMMDDhhmmss-hhmm
の形式を取ります。
YY
: 年の下2桁 (例: 2012年なら12
)MM
: 月 (01-12)DD
: 日 (01-31)hh
: 時 (00-23)mm
: 分 (00-59)ss
: 秒 (00-59)Z
: UTC(協定世界時)であることを示す。+hhmm
/-hhmm
: UTCからのオフセットを示す。
例えば、121029111605Z
は2012年10月29日11時16分05秒UTCを表し、121029111605-0400
は2012年10月29日11時16分05秒、UTCから4時間遅れたタイムゾーン(例: 東部夏時間EDT)を表します。
Go言語の time.Time
とタイムゾーン
Go言語の time
パッケージは、日付と時刻を扱うための強力な機能を提供します。time.Time
型は、特定の時点を表す構造体です。time.Time
オブジェクトは、内部的にはUTCからの経過時間を保持していますが、表示や計算の際には、そのオブジェクトが関連付けられているタイムゾーン(Location)の情報を利用します。
t.UTC()
:time.Time
オブジェクトt
をUTCに変換した新しいtime.Time
オブジェクトを返します。この操作は、時刻の絶対的な時点は変えずに、表示されるタイムゾーンをUTCに変更します。t.Date()
,t.Clock()
:time.Time
オブジェクトが現在設定されているタイムゾーンに基づいて、年、月、日、時、分、秒などの各要素を返します。t.In(loc *time.Location)
:time.Time
オブジェクトを、指定されたtime.Location
に関連付けられた新しいtime.Time
オブジェクトを返します。これも時刻の絶対的な時点は変えずに、表示されるタイムゾーンを変更します。
このコミットの文脈では、time.Time
オブジェクトが持つ「どのタイムゾーンでこの時刻が表現されているか」という情報が重要になります。
技術的詳細
このコミットの技術的な核心は、ASN.1 UTCTime
のエンコーディングにおいて、Goの time.Time
オブジェクトのタイムゾーン情報を正しく扱うことにあります。
以前の実装では、encoding/asn1
パッケージの marshalUTCTime
関数内で、引数として渡された time.Time
オブジェクト t
をまず t.UTC()
を呼び出してUTCに変換していました。そして、このUTCに変換された時刻から年、月、日、時、分、秒の各要素を取り出していました。しかし、その後にタイムゾーンオフセットをシリアライズする際には、元の t
が持っていたタイムゾーン情報(例えば、t.Location()
から得られるオフセット)を使用していました。
この挙動は、以下のような問題を引き起こします。
-
時刻の不一致:
t.UTC()
で得られた時刻はUTCですが、それに元のタイムゾーンのオフセットを付加すると、結果としてエンコードされる時刻は、元のタイムゾーンでの時刻がUTCに変換されたものとは異なる、誤った時刻表現になってしまいます。例えば、日本時間2012年10月29日11時16分05秒(UTC+9)をエンコードしようとした場合、まずUTCに変換されて2012年10月29日02時16分05秒UTCとなります。しかし、これに「+0900」というオフセットが付加されると、ASN.1のデコーダは「2012年10月29日02時16分05秒、UTCから9時間進んだタイムゾーンの時刻」と解釈してしまい、これは実際には2012年10月29日11時16分05秒UTC+9とは異なる時刻(2012年10月29日02時16分05秒+0900 = 2012年10月29日11時16分05秒UTC)を指すことになります。つまり、元の時刻がUTC+9であったにもかかわらず、エンコードされた結果はUTC時刻として解釈されてしまうという矛盾が生じていました。 -
標準への不適合: ASN.1の
UTCTime
は、通常、指定されたタイムゾーンの時刻をそのまま表現するか、またはUTC時刻を明示的にZ
で示すかのいずれかです。元のタイムゾーンの時刻をUTCに変換してから、元のタイムゾーンのオフセットを付加するという方法は、ASN.1の標準的なUTCTime
の解釈とは異なります。
このコミットでは、この問題を解決するために、marshalUTCTime
関数から t.UTC()
の呼び出しを削除しました。これにより、t.Date()
や t.Clock()
から取得される年、月、日、時、分、秒の各要素は、time.Time
オブジェクト t
が元々持っていたタイムゾーン(Location)に基づいて計算されるようになります。そして、そのタイムゾーンのオフセットがそのままシリアライズされるため、ASN.1 UTCTime
は元の time.Time
オブジェクトが表現していた時刻とタイムゾーンを正確に反映するようになります。
テストケース marshal_test.go
の変更もこの修正を反映しています。特に、time.Unix(1258325776, 0).In(PST)
のテストケースでは、以前は 17113039311152256162d30383030
というエンコード結果を期待していましたが、修正後は 17113039311151456162d30383030
となっています。これは、PST(太平洋標準時、UTC-8)の時刻が正しくエンコードされるようになったことを示しています。具体的には、1258325776
はUnixエポックからの秒数で、これは2009年11月15日22時56分16秒UTCに相当します。PSTでは、これは2009年11月15日14時56分16秒PST(UTC-8)となります。修正前のエンコード結果は、UTC時刻(22時56分16秒)にPSTのオフセット(-0800)が付加されたような形になっていましたが、修正後はPSTの時刻(14時56分16秒)にPSTのオフセット(-0800)が正しく付加されるようになっています。
コアとなるコードの変更箇所
src/pkg/encoding/asn1/marshal.go
の marshalUTCTime
関数が変更されています。
--- a/src/pkg/encoding/asn1/marshal.go
+++ b/src/pkg/encoding/asn1/marshal.go
@@ -296,8 +296,7 @@ func marshalTwoDigits(out *forkableWriter, v int) (err error) {
}
func marshalUTCTime(out *forkableWriter, t time.Time) (err error) {
- utc := t.UTC()
- year, month, day := utc.Date()
+ year, month, day := t.Date()
switch {
case 1950 <= year && year < 2000:
@@ -321,7 +320,7 @@ func marshalUTCTime(out *forkableWriter, t time.Time) (err error) {\n return
}\n
- hour, min, sec := utc.Clock()
+ hour, min, sec := t.Clock()
err = marshalTwoDigits(out, hour)
if err != nil {
また、src/pkg/encoding/asn1/marshal_test.go
のテストケースも変更されています。
--- a/src/pkg/encoding/asn1/marshal_test.go
+++ b/src/pkg/encoding/asn1/marshal_test.go
@@ -82,7 +82,7 @@ var marshalTests = []marshalTest{\
{explicitTagTest{64}, "3005a503020140"},\
{time.Unix(0, 0).UTC(), "170d3730303130313030303030305a"},\
{time.Unix(1258325776, 0).UTC(), "170d3039313131353232353631365a"},\
- {time.Unix(1258325776, 0).In(PST), "17113039313131353232353631362d30383030"},\
+ {time.Unix(1258325776, 0).In(PST), "17113039311151456162d30383030"},\
{BitString{[]byte{0x80}, 1}, "03020780"},\
{BitString{[]byte{0x81, 0xf0}, 12}, "03030481f0"},\
{ObjectIdentifier([]int{1, 2, 3, 4}), "06032a0304"},\
コアとなるコードの解説
marshal.go
の変更は非常にシンプルですが、その影響は大きいです。
変更前:
func marshalUTCTime(out *forkableWriter, t time.Time) (err error) {
utc := t.UTC() // ここで時刻をUTCに変換
year, month, day := utc.Date() // UTC時刻から日付要素を取得
// ...
hour, min, sec := utc.Clock() // UTC時刻から時間要素を取得
// ...
}
変更前は、t.UTC()
を呼び出して time.Time
オブジェクトを明示的にUTCに変換し、その utc
変数から年、月、日、時、分、秒の各要素を取得していました。しかし、ASN.1 UTCTime
のエンコーディングでは、時刻の各要素とタイムゾーンオフセットが整合している必要があります。この場合、時刻要素はUTCで、タイムゾーンオフセットは元のタイムゾーンのもの、という不整合が生じていました。
変更後:
func marshalUTCTime(out *forkableWriter, t time.Time) (err error) {
year, month, day := t.Date() // 元のtime.Timeオブジェクトから日付要素を取得
// ...
hour, min, sec := t.Clock() // 元のtime.Timeオブジェクトから時間要素を取得
// ...
}
変更後では、t.UTC()
の呼び出しが削除され、直接 t.Date()
と t.Clock()
を呼び出して年、月、日、時、分、秒の各要素を取得しています。Goの time.Time
オブジェクトの Date()
や Clock()
メソッドは、その time.Time
オブジェクトが現在関連付けられている time.Location
に基づいて時刻の各要素を返します。これにより、marshalUTCTime
関数は、引数として渡された time.Time
オブジェクト t
が持つタイムゾーン情報(Location)をそのまま利用して時刻の各要素を抽出し、その後にそのタイムゾーンのオフセットをシリアライズするようになります。
この修正により、UTCTime
のエンコーディングは、元の time.Time
オブジェクトが表現していた時刻とタイムゾーンを正確に反映するようになり、ASN.1の標準的な解釈に合致するようになりました。
marshal_test.go
の変更は、この新しい正しいエンコーディング結果を反映するためのテストデータの更新です。特に time.Unix(1258325776, 0).In(PST)
のテストケースは、PSTタイムゾーンでの時刻が正しくエンコードされることを検証しています。
関連リンク
- Go Gerrit Change-ID: https://golang.org/cl/6817048
参考にした情報源リンク
- ASN.1 UTCTime - Wikipedia
- Go time package documentation
- X.680 (ASN.1) - ITU-T Recommendation (ASN.1の公式仕様書)
- X.690 (ASN.1 encoding rules) - ITU-T Recommendation (ASN.1のエンコーディングルールに関する公式仕様書)
- Go言語のtimeパッケージのLocationとUTCについて - Qiita (Goのtimeパッケージに関する日本語解説、概念理解に役立つ)
- Go言語のtime.Timeの内部表現とタイムゾーンの扱い - Zenn (Goのtime.Timeの内部表現に関する日本語解説)