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

[インデックス 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
}

時刻だけでなく、タイムゾーンの名前とオフセットも比較する包括的な比較関数を追加し、テストの精度を向上させています。

関連リンク

参考にした情報源リンク