Try Golang! time.Timeの等値判定で注意すること

Why isn’t the same time equal in Golang time.Time?

Takuo
VELTRA Engineering
7 min readJul 16, 2019

--

ふらっと遭遇したエラーに関連して、Goの time.Time について調べてみたので、簡単にまとめてみます。まずは、下記のコードをご覧ください。

time.Parse メソッドを用いて同じ文字列から time.Time のインスタンスをふたつ生成し、 == 演算子で比較しています。わざわざブログで書くくらいなので、さすがに true にはなりません。同じ時刻のはずなのに、なぜ true にならないのでしょうか。今日はそこを順に見ていきます。

Goの “==” 演算子

Goの言語仕様にもある通り、 == 演算子で構造体を比較する場合、全てのフィールドが等しければ、等しいと判定されます。 time.Time は下記のように定義されているため、 wallextloc の全てが等しければ == 演算子で比較した時に true となります。

ここで注意したいのが、ポインタ型になっている loc です。ポインタ型のフィールドについては、参照先の構造体ではなく、参照アドレスが等しいかどうかの判定になります。

Locationの参照アドレスを確認

loc の値を確認すると、参照アドレスが異なっていることが分かります。実は time.Parse 関数で time.Time を生成する際、特定のロケーションを除いて、毎回新規でロケーションが作成されるため、参照アドレスが異なるのです。

ちなみに、タイムゾーンがUTCの場合は nil が、タイムゾーンが実行環境のタイムゾーンと一致する場合はそのローカルロケーションとして用意された同一のインスタンスが毎回設定されるため、参照アドレスも一致します。

time.Timeの同値判定方法

Godocにも記載されている通り、 time パッケージには Equal メソッドが用意されているので、これを用いれば、タイムゾーンを考慮した時刻の比較ができます。しかし、マップのキーとして用いる場合などは、 Equal メソッドは使用できません。このように、どうしても == 演算子で比較したい場合はどうすれば良いでしょうか。

ひとつは、UTC(協定世界時)に変換しておくという方法です。上でも少し触れたように、UTCの場合は、必ず loc の値が nil になるようになっています。

なお、上記の判定方法はいずれも「タイムゾーンを考慮して同じ時刻を同じとみなす」ものです。ロケーションの一致まで判定する必要があるのであれば、 reflect.DeepEqual を使用したりと、要件に応じた判定方法を採用する必要があります。

Monotonic Clocksについて

time.Time のフィールド loc については上記で説明は終わりですが、残りの wallext は何ものなのでしょうか。実は、下記のように、時刻も loc も等しいにも関わらず、 == 演算子で比較すると false となることがあります。これは wallext の値が異なるためですが、そのためにはMonotonic Clocksについて知っておく必要があります。

Godocによると、プログラムの世界における「時間」には、時刻同期の影響を受ける時間(ここでは実時間とします)と、影響を受けない時間(Monotonic Clocks。ここでは単調時間とします。プロセス継続時間等)の2つがあり、 time.Time パッケージでは、時間を「示す」際は実時間を、「測定する」場合は単調時間を用います。

time.Now 関数などでインスタンスを生成すると、実時間と単調時間の両方が time.Time 内に保持されており、時間を測定する場合は、たとえプログラムの実行中にOSの時刻が変更されても、正しく経過時間を測定できるように単調時間が使用されます。例えば、下記のようなコードでは、変数 elapsed は、たとえOSの時刻が変更されようと、必ず約20秒の値となります。

一方で、 time.Parse 関数などで生成した場合は単調時間は保持しません。また、単調時間を保持しているインスタンスについて、 Round(0) とすることで、単調時間を取り除くことができます。実際の wallext の設定要領はやや複雑なのですが、同じ時刻でも単調時間の有無によって値が異なるため、上述の例では結果が false となったのです。

ただ、実は UTC メソッドなどでロケーションを変更すると、 Round(0) と同様、単調時間が取り除かれるため、単純な == 演算子での比較するためには、実際は UTC だけで十分ということになります。

ざっくりですが、 time.Time の同値判定について調べた内容をまとめてみました。Goのコードを読んでいると、なるほど、こういったことまで考慮されているのね、という気づきがあるので楽しいですね。

--

--

Takuo
VELTRA Engineering

Engineer who likes travel, simple code, and something new