31
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

protocol buffersのTimestamp, DurationとGoのtimeパッケージ型の相互変換

Last updated at Posted at 2019-07-21

deprecated

https://pkg.go.dev/github.com/golang/protobuf/ptypes
にある通り、google.golang.org/protobufに今は対応する関数があるため、この記事に書いてあるコードは移行するべきである。


(以下、元の記事)

protocol buffers IDL再入門 - Qiita

protobufにはwell-known typesと呼ばれるよく使われる型があらかじめ用意されており、その中には日時と期間がある。

google.protobuf.TimestampはTimezoneを持たない時刻。日時、時刻、ナノ秒まで記録できる。TimezoneはUTCに揃えられていると認識される。
google.protobuf.Durationは期間。○○秒、〇〇時間、などの概念を表す。これもナノ秒まで記録できる。

一方でGoにおいては、これらの概念に対応するものといえば標準パッケージ time の中にある、 time.Timetime.Duration であろう。

当然、相互に変換したくなるので、便利なパッケージが用意されている。

時刻型の相互変換

*tspb.Timestamp → time.Time

github.com/golang/protobuf/ptypes#Timestamp を使う。

func Timestamp(ts *tspb.Timestamp) (time.Time, error)

time.Time → *tspb.Timestamp

github.com/golang/protobuf/ptypes#TimestampProto を使う。

func TimestampProto(t time.Time) (*tspb.Timestamp, error)

ちなみに、便利関数として func TimestampNow() *tspb.Timestamp なども用意されている。わざわざ ts, _ := TimestampProto(time.Now()) なんてしなくてもサクッと現在時刻を作成できる。

どういうときにエラーが起きるのか?

両者とも、errorが返ってくるインターフェースになっている。これは、微妙にtime.Timeとtspb.Timestampに非互換性があることに起因するようだ。
例えばGoの time.Date のyearにメチャクチャ大きな数字や負の数を入れても、動作する。西暦1億年みたいなSFレベルの未来日付や紀元前レベルの過去の日付であっても扱えるというわけだ。

func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time

一方で、protobufのTimestamp型は 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. が表現できる幅だと明確に書いてある。
https://github.com/golang/protobuf/blob/6c65a5562fc06764971b7c5d05c76c75e84bdbf7/ptypes/timestamp/timestamp.proto

protobufはシステム間で互換性を保ちながら情報をやり取りする目的のため、様々なシステムで妥当に扱える範囲を設定した、ということなのだろう。例えばMySQLのDateTime型は上限が 9999-12-31 23:59:59 だった。

なので、Go側で超未来や超過去のtime.Timeを作って、*tspb.Timestamp に変換しようとしたらエラーになるし、Timestamp()にいきなり範囲を超えた *tspb.Timestamp を無理やり渡すとそれもエラーになる。

他にもいくつかバリデーションはしているけど、詳細はptypesのソースコードを見て欲しい。
https://github.com/golang/protobuf/blob/6c65a5562fc06764971b7c5d05c76c75e84bdbf7/ptypes/timestamp.go#L63-L77

とはいえ、通常のシステムで扱っている日時では起きないだろうから、あまり丁寧にエラーハンドリングしなくても大丈夫かもしれない。

期間型の相互変換

*durpb.Duration → time.Duration

github.com/golang/protobuf/ptypes#Duration

func Duration(p *durpb.Duration) (time.Duration, error)

time.Duration → *durpb.Duration

github.com/golang/protobuf/ptypes#DurationProto

func DurationProto(d time.Duration) *durpb.Duration

どういうときにエラーが起きるのか?

おや。timestampと違って、time.Durationから変換するときにエラーが返ってこない。

protobufの場合、扱える範囲はプラマイ1万年と書いてある。 "approximately" なのは、うるう年などを雑にしか考慮していないためだと思われる。

Range is approximately +-10,000 years.

Timestampが1万年いけるので、相応の時間をDurationでも扱えるようにしたのだと思う。

一方で、Goのtime.Durationは内部的にナノ秒をint64で保持しているだけである。なので実は扱える範囲はだいたい290年間ぐらいにしかならず、全然time.Durationよりも表現の幅がせまい。

// A Duration represents the elapsed time between two instants
// as an int64 nanosecond count. The representation limits the
// largest representable duration to approximately 290 years.
type Duration int64

time.Duration*durpb.Duration でエラーが起きようもないので、エラー判定も省略されている。

まとめ

  • 時間系は、Goの標準型とprotobufのwell-known型の相互変換メソッドがあって便利
  • 微妙にこれらは互換性のない区間があるため、変換時にエラーが起きうるインターフェースになっている
  • とはいえ、普通はエラーが起きない気がする。
31
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?