No Time Like Now
A Go Brain Teaser
What do you think the following program will print?
t1 := time.Now()
data, err := json.Marshal(t1)
if err != nil {
log.Fatal(err)
}
var t2 time.Time
if err := json.Unmarshal(data, &t2); err != nil {
log.Fatal(err)
}
fmt.Println(t1 == t2)
This program will print: false
.
Why? Is there a bug in the encoding/json
package?
Let’s take a look at the definition of time.Time
(taken from here):
type Time struct {
// ... (redacted)
// 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
// ... (redacted)
loc *Location
}
It mentions a monotonic clock — what’s that?
Computers and clocks have a long and complicated history. We have daylight savings time, leap years, and even leap seconds. The “wall clock” on your machine can jump to account for these time shifts — it can also adjust the time from an accurate clock server (see NTP).
These time shifts can cause a problem when when you try to measure how much time an operation takes. Most computers have a monotonic clock that is always increasing. A monotonic clock is a time source that won’t jump forward or backward. A single reading of a monotonic does not mean much, but the difference between two is accurate.
Which brings us to our problem. Since a monotonic clock has meaning only on the same machine, there’s no reason to include it in serialized data.
If you print out the times, you’ll see the issue:
fmt.Println("t1:", t1)
fmt.Println("t2:", t2)
Which will output:
t1: 2022-11-29 14:21:10.981666007 +0200 IST m=+0.000013388
t2: 2022-11-29 14:21:10.981666007 +0200 IST
How can you compare times then? By using time.Equal
. The following:
fmt.Println(t1.Equal(t2))
will print true
.
The time.Time
documentation even says:
Note that the Go == operator compares not just the time instant but also the Location and the monotonic clock reading. Therefore, Time values should not be used as map or database keys without first guaranteeing that the identical Location has been set for all values, which can be achieved through use of the UTC or Local method, and that the monotonic clock reading has been stripped by setting t = t.Round(0). In general, prefer t.Equal(u) to t == u, since t.Equal uses the most accurate comparison available and correctly handles the case when only one of its arguments has a monotonic clock reading.
If you think this tibit about time and programming in Go is messed up, I encourage you to read “Falsehoods programmers believe about time” and “Falsehoods programmers believe about time zones” for even more horrors.
About the Author
If you like to solve programming problems, check out Miki Tebeka’s Brain Teaser books from The Pragmatic Bookshelf. You can save 35 percent on the ebook versions with promo code brain_teasers_35 through August 30, 2022: