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. I don't fully understand the reason for this; the UTC() call
seems to have been bucketed 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 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() 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:
Setting time.Local = time.UTC results in
time.Now() producing timestamps with local == &utcLoc. 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 been 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 as a bug report in the past. More generally, I see that the whole idea of controlling the process-wide
timezone was rejected (also
here). 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