Photo of Dali`s melted clock sculpture, displayed in Matera, Italy by Joaquin Corbalan P via Shutterstock

No Time Like Now

A Go Brain Teaser

Miki Tebeka
3 min readDec 5, 2022

--

https://pragprog.com/newsletter/
https://pragprog.com/newsletter/

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.

--

--