Andrei, For database times enforce the use of these convenience functions:
func DBNow() time.Time { return DBTime(time.Now()) } func DBTime(t time.Time) time.Time { return t.UTC() } Peter On Tuesday, October 26, 2021 at 2:58:16 PM UTC-4 Andrei Matei wrote: > 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/6a8fb393-4652-43de-ac00-343b3af6f57cn%40googlegroups.com.