Go で timestamppb を使いこなそう:テストで大活躍するタイムスタンプ活用ガイド

に公開

1. はじめに:timestamppbは何に使える?

timestamppb は Protocol Buffers の timestamp.proto から生成された Go の型で、Unix エポックからの秒数とナノ秒数を保持します。 google.protobuf.Timestamp を Go で扱いやすい形にしたものなので、UTC の絶対時刻として時刻情報を記録できます。たとえばテストコードで擬似的な時刻を作成したり、取得した時刻を比較するときに役立ちます。

  • Go の time.Time を protobuf のメッセージとして表現したい場合に利用します。
  • CheckValid メソッドを使えば範囲外の値を事前に検証できます。
  • JSON としてシリアライズするときに、RFC 3339 形式の文字列になるため、外部とのやり取りもしやすいです。

たとえば、時刻を固定してテストをしたい場面では timestamppb.New(...) で一度作成しておくと、protobuf のフィールドとして直接比較できたり、 AsTime で元の time.Time に戻したりと便利に使えます。

2. Goのtime.Timeとの相互変換

timestamppb を使う上で最も重要なのは、 time.Time との相互変換です。テストで扱いたい時刻を New でメッセージ化して、結果を検証するときは AsTime で戻す形が一般的です。

1. Go の時刻を timestamppb へ変換

import "google.golang.org/protobuf/types/known/timestamppb"

ts := timestamppb.New(time.Now())
// これで *timestamppb.Timestamp を取得

Now を使えば「現在時刻」のメッセージを手軽に作れます。固定時刻を生成したい場合は time.Date(...) から New を呼ぶと便利です。

2. timestamppb を Go の時刻へ変換

t := ts.AsTime()
// time.Time として普通に扱える

AsTime は内部的に Unix epoch からの経過秒数とナノ秒数を Go の time.Time に変換します。これによって標準の time パッケージにあるフォーマットや計算関数がフルに使えます。

3. 異常な範囲や値をチェック

if err := ts.CheckValid(); err != nil {
    // 範囲外の場合はエラー
}

Protocol Buffers の timestamp.proto は 0001-01-01T00:00:00Z から 9999-12-31T23:59:59Z までの範囲を想定しているため、もし範囲外ならエラーが返ります。テストで想定外の時刻を混入させないための安全策として活用できます。

これらの関数を押さえておくだけで、テスト時に protobuf の Timestamp と Go の時刻を行き来できるようになります。たとえば「固定の 2025 年の時刻を生成してイベントを検証したい」といったシナリオでも、コードがすっきり書けるようになります。

3. テストでの活用例

timestamppb はテストコードで時刻を扱う際に特に便利です。ここでは、固定した時刻を用いて検証する場合と、現在の時刻を仮想的にセットする場合の二つの例を簡単に紹介します。

1. 固定時刻の検証

func TestEventTime(t *testing.T) {
    // 2025-12-31T23:59:59Z を作成
    fixed := time.Date(2025, 12, 31, 23, 59, 59, 0, time.UTC)
    ts := timestamppb.New(fixed)

    // AsTime で戻して比較
    got := ts.AsTime()
    if !got.Equal(fixed) {
        t.Errorf("got %v, want %v", got, fixed)
    }
}
  • このように、時刻を固定してメッセージを作成すると、テスト時のブレが減ります。
  • AsTime() で取り出して Equal すると、微妙なずれやオフセットの問題に悩まされにくいです。

2. 現在時刻の仮想設定

package main

import (
	"testing"
	"time"

	"google.golang.org/protobuf/types/known/timestamppb"
)

func TestNowTimestamp(t *testing.T) {
	// 現在時刻
	tsNow := timestamppb.Now()

	// 直後に Now() を呼び出した time.Now() と大差ないかを確認
	now := time.Now()
	if now.Sub(tsNow.AsTime()) > 2*time.Second {
		t.Errorf("timestamp is too far from now")
	}
}

  • timestamppb.Now() は現時点の時刻を *Timestamp で返します。
  • 少しの誤差はあり得るので、サンプルでは 2 秒以内であれば許容する形です。

まとめ:

  • NewNow で簡単にメッセージを作成でき、 AsTime で元の time.Time に取り出せます。
  • 固定時刻を用いるテストでは、日付や時刻を確実に把握できるため、再現性が高くなります。
  • 環境依存が激しい部分が減るので、テストが安定しやすいです。

4. まとめ:まずはNewとAsTimeを押さえよう

timestamppb.Timestamp は Protocol Buffers 形式で時刻を表したいときに便利な型です。Go のテストコードで使う場合、特に NewAsTime を押さえておくだけで扱いやすくなります。次のようなポイントが重要です。

  1. New(...) でタイムスタンプを生成する

    • 固定時刻を決めてテストしたいときは timestamppb.New(myFixedTime) で手軽に作れます。
    • 現在時刻を示すメッセージを作りたい場合は timestamppb.Now() が便利です。
  2. AsTime() で Go の時刻へ戻す

    • メッセージ化された時刻を再び time.Time として取り扱うためには AsTime() を呼び出します。
    • 取得後は標準の time パッケージが提供する演算やフォーマットを利用できるため、テスト時にさまざまな検証を行いやすいです。
  3. CheckValid() で範囲外を検出する

    • 0001 年から 9999 年までの時刻のみ有効という制限があるため、想定外の値が使われた場合はエラーを返します。
    • テストで異常な入力を模擬するときにも役立ちます。

短いテストコードで時刻を固定し、 AsTime() で比較するだけでも効率的な検証が可能です。 timestamppbtime.Time との相互変換が簡単なので、まずは NewAsTime を軸に試してみることをおすすめします。

Discussion