[Go] time.Time が encode & decode で元に戻らないので、time.Timeの中を調べてみた

## time.Time を encode して decode しても元に戻らない?!

[`time.Time`](https://pkg.go.dev/time) の [gob](https://pkg.go.dev/encoding/gob) の encode / decode を試していて驚いたことがありました。以下の実行結果、みなさんわかりますか？

```golang:main.go
package main

import (
	"fmt"
	"time"
)

func main() {
	t1 := time.Now()
	b, _ := t1.GobEncode()

	t2 := time.Unix(0, 0)
	t2.GobDecode(b)

	t3 := time.Now()
	t3.GobDecode(b)

	fmt.Printf("t1: %s\nt2: %s\nt3: %s\n", t1.String(), t2.String(), t3.String())
	fmt.Printf("t1 == t2: %t\n", t1 == t2)
	fmt.Printf("t2 == t3: %t\n", t2 == t3)
}
```

```text:実行結果
t1: 2022-09-06 23:39:28.780771 +0900 JST m=+0.000100430
t2: 2022-09-06 23:39:28.780771 +0900 JST
t3: 2022-09-06 23:39:28.780771 +0900 JST
t1 == t2: false
t2 == t3: true
```

なんと、encode して decode しただけなのに、`t1 == t2` が `false` になってしまうのです。確かに、`t1.String()` と `t2.String()` も `m=+0.000100430` の有無という違いもあります。

別の方法で作った `t3` と `t2` は一致しているので、`t2` の作成方法が悪いわけではなさそうです。

これはいったいなぜでしょうか？

## monotonic clock

現在**時刻**を知るために **wall clock** というものを我々は使っています。一方 **monotonic clock** というのは、**時間**を計測するために利用される時計です。wall clock は OS の時刻変更をすることで**時間の巻戻り**が発生することがありますが、monotonic clockではそのようなケースでも適切に動くように作られています。 [^monotonic]

実は、 `m=+0.000100430` の部分がその monotonic clock の値で、 encode の際にこの値は削除されているのです。そのため、decode で元に戻したとしても monotonic clock の情報が消えており、"元に戻らない"という現象が起きるのです。 monotonic clock は時間を計測するためのものであるというユースケースを考えると encode で値が削除されるのも納得感があります。[^usecase]


では、この monotonic clock はどのように表現されているのでしょうか？ `time.Time` 構造体の中身を見てみましょう。

```golang:time.go
type Time struct {
	// wall and ext encode the wall time seconds, wall time nanoseconds,
	// and optional monotonic clock reading in nanoseconds.
	//
	// From high to low bit position, wall encodes a 1-bit flag (hasMonotonic),
	// a 33-bit seconds field, and a 30-bit wall time nanoseconds field.
	// The nanoseconds field is in the range [0, 999999999].
	// If the hasMonotonic bit is 0, then the 33-bit field must be zero
	// and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext.
	// If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit
	// unsigned wall seconds since Jan 1 year 1885, and ext holds a
	// signed 64-bit monotonic clock reading, nanoseconds since process start.
	wall uint64
	ext  int64

	// loc specifies the Location that should be used to
	// determine the minute, hour, month, day, and year
	// that correspond to this Time.
	// The nil location means UTC.
	// All UTC times are represented with loc==nil, never loc==&utcLoc.
	loc *Location
}
```

`hasMonotonic` というフラグで管理しているようです。コメントから以下のようなデータ構造になっていることがわかります。

* `hasMonotonic == 0` の場合
    * wall の値(上位bitから順に)
        * 1 bit: 0 (hasMonotonic)
        * 33 bit: すべての0
        * 30 bit: 0-999999999 までのナノ秒
    * ext の値
        * 西暦1年1月1日を基準とする符号付きの秒数
* `hasMonotonic == 1` の場合
    * wall の値(上位bitから順に)
        * 1 bit: 1 (hasMonotonic)
        * 33 bit: 西暦1885年1月1日を基準とする符号なしの秒数
        * 30 bit: 0-999999999 までのナノ秒
    * ext の値
        * プロセス開始時からの符号付きのナノ秒 (monotonic clock)

実際の値を見てみましょう。unexported なフィールドなので、少し工夫して出力しています。

### monotonic clock あり

典型的なパターンとして `time.Now` を利用しています。実際にビット演算すると計算しやすく作られていることも体感できると思います。

```golang:main.go
package main

import (
	"fmt"
	"reflect"
	"time"
)

func main() {
	t1 := time.Now()

	elem := reflect.ValueOf(&t1).Elem()
	wall := elem.FieldByName(`wall`).Uint()
	ext := elem.FieldByName(`ext`).Int()

	fmt.Printf("wall: %d\n", wall)
	fmt.Printf("ext: %d\n", ext)

	if hasMonotonic(wall) {
		fmt.Printf("secs from 1885/01/01: %d\n", ((1<<63)^wall)>>30)
		fmt.Printf("nano secs: %d\n", (wall<<34)>>34)
	} else {
		fmt.Printf("nano secs: %d\n", wall)
	}

	fmt.Printf("secs from 1885/01/01(time.Time): %d\n", t1.Unix()-time.Date(1885, 1, 1, 0, 0, 0, 0, time.UTC).Unix())
	fmt.Printf("nano secs(time.Time): %d\n", t1.Nanosecond())
}

func hasMonotonic(wall uint64) bool {
	return (wall>>63)&1 == 1
}
```

```text:実行結果
wall: 13888534293255422360
ext: 143687
secs from 1885/01/01: 4344770923
nano secs: 676463000
secs from 1885/01/01(time.Time): 4344770923
nano secs(time.Time): 676463000
```

### monotonic clock なし

あえて `time.Unix` を利用することで monotonic clock なしの値を生成しています。
こちらは `wall` にそのままナノ秒が入っていることが確かにわかります。

```golang:main.go
package main

import (
	"fmt"
	"reflect"
	"time"
)

func main() {
	now := time.Now()
	t1 := time.Unix(now.Unix(), int64(now.Nanosecond()))

	elem := reflect.ValueOf(&t1).Elem()
	wall := elem.FieldByName(`wall`).Uint()
	ext := elem.FieldByName(`ext`).Int()

	fmt.Printf("wall: %d\n", wall)
	fmt.Printf("ext: %d\n", ext)

	if hasMonotonic(wall) {
		fmt.Printf("secs from 1885/01/01: %d\n", ((1<<63)^wall)>>30)
		fmt.Printf("nano secs: %d\n", (wall<<34)>>34)
	} else {
		fmt.Printf("nano secs: %d\n", wall)
	}

	fmt.Printf("secs from 1885/01/01(time.Time): %d\n", t1.Unix()-time.Date(1885, 1, 1, 0, 0, 0, 0, time.UTC).Unix())
	fmt.Printf("nano secs(time.Time): %d\n", t1.Nanosecond())
}
```

```text:実行結果
wall: 13888534293255422360
ext: 143687
secs from 1885/01/01: 4344770923
nano secs: 676463000
secs from 1885/01/01(time.Time): 4344770923
nano secs(time.Time): 676463000
```

上記のように `wall` と `ext` に monotonic clock の情報を入れ込んでいることがわかりました。

### 2157年問題?

`hasMonotonic == 1` の場合のデータ構造を思い出すと気になることがあります。`西暦1885年1月1日を基準とする符号なしの秒数` が33bitしかありません。これでは**2157年頃までしか表現できない**ことになってしまいます。

実際の挙動はどうなっているのでしょうか。実験してみましょう。

```golang:main.go
package main

import (
	"fmt"
	"time"
)

func main() {
	t1 := time.Now()
	for i := 0; i < 100; i++ {
		t1 = t1.Add(100000 * time.Hour)
		fmt.Printf("t1: %s\n", t1.String())
	}
}
```

```text:実行結果
t1: 2034-02-02 18:44:08.473612 +0900 JST m=+360000000.000099658
t1: 2045-07-01 10:44:08.473612 +0900 JST m=+720000000.000099658
t1: 2056-11-27 02:44:08.473612 +0900 JST m=+1080000000.000099658
t1: 2068-04-24 18:44:08.473612 +0900 JST m=+1440000000.000099658
t1: 2079-09-21 10:44:08.473612 +0900 JST m=+1800000000.000099658
t1: 2091-02-17 02:44:08.473612 +0900 JST m=+2160000000.000099658
t1: 2102-07-16 18:44:08.473612 +0900 JST m=+2520000000.000099658
t1: 2113-12-12 10:44:08.473612 +0900 JST m=+2880000000.000099658
t1: 2125-05-10 02:44:08.473612 +0900 JST m=+3240000000.000099658
t1: 2136-10-05 18:44:08.473612 +0900 JST m=+3600000000.000099658
t1: 2148-03-03 10:44:08.473612 +0900 JST m=+3960000000.000099658
t1: 2159-07-31 02:44:08.473612 +0900 JST
t1: 2170-12-26 18:44:08.473612 +0900 JST
...
```

2157年頃を境に monotonic clock の情報がなくなっていることがわかります。2157年を超えてもパニックにはならず、きちんとフォールバックするようになっていることも確認できました。

## まとめ

* `time.Time` には wall clock だけでなく、 monotonic clock の情報が含まれている
* **monotonic clock の情報は encode されるタイミングで消失**してしまう
* encode & decode 後には monotonic clock がない状態になるので、構造体としては元に戻らなくなってしまうので注意
* `time.Time` では `wall` と `ext` というフィールドを用いて monotonic clock あり/なし のデータを管理している

実は、monotonic clock が Go に入ったのは [1.9](https://go.dev/doc/go1.9#monotonic-time) からで [1.8 までは普通(?)に `sec` と `nsec` で保持していました](https://github.com/golang/go/blob/go1.8.7/src/time/time.go#L40)。time.Time の中身まで見て色々動かせば、`time.Time` の encode / decode に注意しないといけないことも自然に頭に入ったのではないでしょうか。

---

## おまけ

### time.Now の中身も見てみる

`time.Now` の中を monotonic clock に注目して見てみます。最初に runtime から `mono` を取得し、基準値となる `startNano` を差し引いて設定しています。[^runtime]

```golang:time.go
// Provided by package runtime.
func now() (sec int64, nsec int32, mono int64)

// runtimeNano returns the current value of the runtime clock in nanoseconds.
//go:linkname runtimeNano runtime.nanotime
func runtimeNano() int64

// Monotonic times are reported as offsets from startNano.
// We initialize startNano to runtimeNano() - 1 so that on systems where
// monotonic time resolution is fairly low (e.g. Windows 2008
// which appears to have a default resolution of 15ms),
// we avoid ever reporting a monotonic time of 0.
// (Callers may want to use 0 as "time not set".)
var startNano int64 = runtimeNano() - 1

// Now returns the current local time.
func Now() Time {
	sec, nsec, mono := now()
	mono -= startNano
	sec += unixToInternal - minWall
	if uint64(sec)>>33 != 0 {
		return Time{uint64(nsec), sec + minWall, Local}
	}
	return Time{hasMonotonic | uint64(sec)<<nsecShift | uint64(nsec), mono, Local}
}
```

### json の encode / decode 

json の encode & decode でも gob 同様のことが起きます。

```golang:main.go
package main

import (
	"encoding/json"
	"fmt"
	"time"
)

func main() {
	t1 := time.Now()
	b, _ := json.Marshal(t1)

	var t2 time.Time
	json.Unmarshal(b, &t2)

	fmt.Printf("t1: %s\nt2: %s\n", t1.String(), t2.String())
	fmt.Printf("t1 == t2: %t\n", t1 == t2)
}
```

```text:実行結果
t1: 2022-09-06 23:51:08.263018 +0900 JST m=+0.000085313
t2: 2022-09-06 23:51:08.263018 +0900 JST
t1 == t2: false
```




[^monotonic]:monotonic という単語は"単調な"という意味なので、"戻らない"というニュアンスが込められているようです。
[^usecase]: `time.Since` などで、monotonic clock が活躍します。「encode の際に消してもよいの？」という疑問が残りますが、時間の計測を行う間に `time.Time` の構造体を encode するようなユースケースはあまりなさそうです。あったとしても、長期間の計測になりそうですので、その場合はもはや wall clock で十分 or 別の方法で時間計測を行うべきなのではないかと考えます。
[^runtime]: 開始の基準値を実は 1ns だけ前にずらしている実装になっています(1.19.0 時点)。これは、①そもそも runtime clock の時間分解能がかなり低く(Windows 2008 で 15ms 程度とのこと)、②時間を計測したときに 0 になってしまうことを避けたいためにこのようになっているとのことです。こうすることで、プロセスが始まったタイミングで `startNano` に値が入るのでその後の処理で `runtimeNano() > startNano` が保証されるということです。