On Tue, Oct 26, 2021 at 3:15 PM Ian Lance Taylor <i...@golang.org> wrote:
> On Tue, Oct 26, 2021 at 11:58 AM 'Andrei Matei' via golang-nuts > <golang-nuts@googlegroups.com> wrote: > > > > 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: > > // All UTC times are represented with loc==nil, never loc==&utcLoc. > > > > 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? > > > As you know, the timezone only affects the presentation of the > time.Time value, which is when a time.Time turns into something else, > such as a string. So I would approach this problem either by 1) > consistently running the program with the TZ environment variable set, > If you accept that there are reasons for setting TZ for a particular program, then would you also agree that there are reasons for wanting to do the same from inside the program? We distribute our software for others to run, so we don't generally have control / would rather not rely on the environment. Alternatively, if we can get (a flavor of) Time.UTC() to not strip the mono any more, I'd also be happy with that. - Andrei > or 2) consistently controlling the places where a time.Time turns into > something else, which is usually a relatively small number of places. > I don't know that that helps much, but that is my take on the issue. > > Ian > On Tue, Oct 26, 2021 at 3:15 PM Ian Lance Taylor <i...@golang.org> wrote: > On Tue, Oct 26, 2021 at 11:58 AM 'Andrei Matei' via golang-nuts > <golang-nuts@googlegroups.com> wrote: > > > > 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: > > // All UTC times are represented with loc==nil, never loc==&utcLoc. > > > > 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? > > > As you know, the timezone only affects the presentation of the > time.Time value, which is when a time.Time turns into something else, > such as a string. So I would approach this problem either by 1) > consistently running the program with the TZ environment variable set, > or 2) consistently controlling the places where a time.Time turns into > something else, which is usually a relatively small number of places. > I don't know that that helps much, but that is my take on the issue. > > Ian > -- 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/CAPqkKgnbO%3D1u%2BAd5RFXjSXDVmLu7OjUEe4s8FL4zOj-Ox1tAXg%40mail.gmail.com.