Hello Go friends,

I'm finding it difficult to satisfactorily control the timezone used by the
time.Now() function. The Go team have taken positions in the past that seem
to not make this easy; I'd like to see if I can gather sympathy for
changes, or perhaps if there's some obvious idea that I'm missing.

In CockroachDB, we like all our timestamps to have the UTC timezone. This
is important for printing them out similarly across a cluster of
geographically-distributed processes, and also because time.Now()
timestamps sometimes end up exposed as SQL values, where the time zone
matters in various ways.

If you accept that you want all the time.Now() calls in the process to
return UTC timestamps, the question is how to achieve that. We want a
solution that is confined to our program; we don't want to rely on the
environment having set the TZ envvar.

For the longest time, we used a utility function like:

func Now() time.Time {
  return time.Now().UTC()
}

and we had a linter disallowing direct use of time.Now(). The problem with
this is that the UTC() call strips out the monotonic clock part
<https://cs.opensource.google/go/go/+/refs/tags/go1.17.2:src/time/time.go;l=208;drc=refs%2Ftags%2Fgo1.17.2>.
I don't fully understand the reason for this; the UTC() call seems to have
been bucketed
<https://github.com/golang/go/issues/18991#issuecomment-306209288> in with
other calls like Truncate() which want to strip the mono part. So, we lose
the benefits of the monotonic clock, and also the time.Since() calls become
twice as expensive because an optimization
<https://cs.opensource.google/go/go/+/refs/tags/go1.17.2:src/time/time.go;l=878;drc=refs%2Ftags%2Fgo1.17.2>
is disabled when there's no mono.
Would it be feasible at all to get a flavor of UTC() that does not do the
stripping?

If not, let's see what we can do to avoid the UTC() call. One possibility
is os.Setenv("TZ", ""). The problem with that, I think, is that it needs to
be done before initLocal()
<https://cs.opensource.google/go/go/+/refs/tags/go1.17.2:src/time/zoneinfo.go;l=92>
runs, which is not easy to control.

Another way to do it is by setting the time.Local global. I thought that
the reason why this global variable exists is precisely for setting it in
func init()'s, but then I got a bit disillusioned.
First I tried

func init() {
  time.Local = time.UTC
}

A problem with doing this is that you end up violating what appears to be a
documented invariant of time.Time:
// All UTC times are represented with loc==nil, never loc==&utcLoc.
<https://cs.opensource.google/go/go/+/refs/tags/go1.17.2:src/time/time.go;l=146>

Setting time.Local = time.UTC results in time.Now() producing timestamps
with local == &utcLoc
<https://cs.opensource.google/go/go/+/refs/tags/go1.17.2:src/time/time.go;l=1080>.
I don't know if violating that invariant actually has any consequences for
the time library, but at the very least it breaks the round-tripping of UTC
values through marshall/unmarshall (e.g. json), in the sense that
reflect.DeepEqual() no longer considers the un-marhsalled value equal to
the original. The difficulties with round-tripping time.Time have
<http://19486> been <https://github.com/golang/go/issues/45960> reported
several times and were not accepted; still, time.Now().UTC() used to
round-trip fine - which was making our tests happy.

One weird trick that works is setting time.Local = nil. That does what I
want, but seems to be straying even further from intended uses of the API.

Reading around, I see that the invariant violation above was rejected
<https://github.com/golang/go/issues/19486#issuecomment-292968278> as a bug
report in the past. More generally, I see that the whole idea of
controlling the process-wide timezone was rejected
<https://github.com/golang/go/issues/10701#issuecomment-148777945> (also
here <https://github.com/golang/go/issues/19486#issuecomment-292554913>).
And yet time.Local is exported (and mutable), so I do wonder why that is.

So, would anyone have another suggestion about how to enforce process-wide
UTC timezones while preserving the monotonic clock readings?
Or, any chance for re-litigating introducing a SetLocal() function?

Thanks!

- Andrei

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/CAPqkKgmY4mfoeav9%2B3g7RjdURv8nNZ6RPZb0Vivbzf1Lq%3D0Kqw%40mail.gmail.com.

Reply via email to