[インデックス 10895] ファイルの概要
コミット
このコミットは、Go言語のtimeパッケージにJSON マーシャリング機能を追加した重要な機能追加のコミットです。Robert Henckeによって2011年12月20日に実装され、Go言語の標準ライブラリにおけるJSONサポートの基盤を確立しました。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/317ad14c6a963b9bb6f81050254026061082a3e8
元コミット内容
- コミットハッシュ: 317ad14c6a963b9bb6f81050254026061082a3e8
- 作成者: Robert Hencke robert.hencke@gmail.com
- 日付: 2011年12月20日 09:01:18 -0800
- メッセージ: "time: JSON marshaler for Time"
- レビュー: golang-dev, dsymonds, hectorchu, r, r
- 変更ファイル:
- src/pkg/time/time.go (58行追加、4行削除)
- src/pkg/time/time_test.go (66行追加、5行削除)
- 合計変更: 113行追加、11行削除
変更の背景
2011年当時、Go言語はまだ開発初期段階にあり、JSONサポートの標準化が急務でした。WebアプリケーションやAPIの開発において、時刻データのJSON形式での送受信は必須機能でしたが、timeパッケージには標準的なJSONマーシャリング機能が存在しませんでした。
この機能追加により、time.Time
型のデータを自動的にRFC3339フォーマットでJSONに変換し、逆にJSONからGoの時刻型に復元することが可能になりました。これは、Web開発における時刻データの標準的な取り扱いを確立する重要な一歩でした。
前提知識の解説
RFC3339フォーマット
RFC3339は、ISO 8601の厳密なプロファイルとして定義された時刻表現形式です。以下の特徴があります:
- フォーマット:
YYYY-MM-DDTHH:MM:SSZ
またはYYYY-MM-DDTHH:MM:SS±HH:MM
- 例:
2011-12-20T09:01:18Z
または2011-12-20T09:01:18-08:00
- ナノ秒精度の小数部分もサポート:
2011-12-20T09:01:18.123456789Z
JSON マーシャリング インターフェース
Go言語では、JSONマーシャリングのために以下のインターフェースが定義されています:
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
type Unmarshaler interface {
UnmarshalJSON([]byte) error
}
gobエンコーディング
このコミットでは、既存のgobエンコーディング機能も改良されています。gobは、Go言語固有のバイナリエンコーディング形式で、主にGoプログラム間でのデータ交換に使用されます。
技術的詳細
MarshalJSON実装の詳細
func (t Time) MarshalJSON() ([]byte, error) {
yearInt := t.Year()
if yearInt < 0 || yearInt > 9999 {
return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]")
}
// 4桁の年を強制的に生成
year := itoa(yearInt)
year = "0000"[:4-len(year)] + year
var formattedTime string
if t.nsec == 0 {
// ナノ秒がない場合:RFC3339基本形式
formattedTime = t.Format("-01-02T15:04:05Z07:00")
} else {
// ナノ秒がある場合:RFC3339拡張形式
formattedTime = t.Format("-01-02T15:04:05.000000000Z07:00")
// 末尾のゼロを削除
const nanoEnd = 24
var i int
for i = nanoEnd; formattedTime[i] == '0'; i-- {
}
formattedTime = formattedTime[:i+1] + formattedTime[nanoEnd+1:]
}
// JSON文字列として出力
buf := make([]byte, 0, 1+len(year)+len(formattedTime)+1)
buf = append(buf, '"')
buf = append(buf, year...)
buf = append(buf, formattedTime...)
buf = append(buf, '"')
return buf, nil
}
UnmarshalJSON実装の詳細
func (t *Time) UnmarshalJSON(data []byte) (err error) {
*t, err = Parse("\""+RFC3339+"\"", string(data))
return
}
この実装は非常にシンプルで、RFC3339フォーマットを使って既存のParse関数を活用しています。
コアとなるコードの変更箇所
1. importの追加
import "errors"
2. gobエラーハンドリングの標準化
// 変更前
return nil, gobError("Time.GobEncode: zone offset has fractional minute")
// 変更後
return nil, errors.New("Time.GobEncode: zone offset has fractional minute")
3. MarshalJSON/UnmarshalJSONの追加
MarshalJSON() ([]byte, error)
: 時刻をRFC3339形式のJSON文字列に変換UnmarshalJSON([]byte) error
: JSON文字列を時刻に変換
4. テストケースの追加
TestTimeJSON()
: 正常ケースのテストTestInvalidTimeJSON()
: 無効なJSONのテストTestNotJSONEncodableTime()
: エンコード不可能な時刻のテスト
コアとなるコードの解説
年の範囲制限
if yearInt < 0 || yearInt > 9999 {
return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]")
}
JSONにおける時刻表現の実用性を考慮して、年を0-9999の範囲に制限しています。これは、多くのシステムで扱いやすい範囲であり、文字列長も一定に保てます。
4桁年の強制
year := itoa(yearInt)
year = "0000"[:4-len(year)] + year
RFC3339では4桁の年が要求されるため、必要に応じて先頭に0を追加して4桁にしています。
ナノ秒の最適化
const nanoEnd = 24
var i int
for i = nanoEnd; formattedTime[i] == '0'; i-- {
}
formattedTime = formattedTime[:i+1] + formattedTime[nanoEnd+1:]
ナノ秒の末尾のゼロを削除することで、より読みやすく簡潔な時刻表現を実現しています。
テストの改良
func equalTimeAndZone(a, b Time) bool {
aname, aoffset := a.Zone()
bname, boffset := b.Zone()
return a.Equal(b) && aoffset == boffset && aname == bname
}
時刻だけでなく、タイムゾーンの名前とオフセットも比較する包括的な比較関数を追加し、テストの精度を向上させています。
関連リンク
- Go言語 time パッケージ公式ドキュメント
- RFC 3339 - Date and Time on the Internet: Timestamps
- Go言語のJSON処理に関するコードレビューガイド
- Go言語のEffective Go - JSON処理