Skip to content

time: Round(0), Truncate(0) strip monotonic clock readings but documentation still says it returns t unchanged #21485

@glycerine

Description

@glycerine

Please answer these questions before submitting your issue. Thanks!

What version of Go are you using (go version)?

go version go1.9rc2 darwin/amd64

this does not happen, for contrast, with go1.8.3 darwin/amd64

This change is documented at the top of the new go1.9rc2 time/time.go file:

// For debugging, the result of t.String does include the monotonic                                             
// clock reading if present. If t != u because of different monotonic clock readings,                           
// that difference will be visible when printing t.String() and u.String().                                     

This is actually a rather drastic proposed change. I refer not the use of the monotone clock, but the display of it in timestamps formated via String(). Many of us use String() for many purposes other than debugging. For debugging, a separate new StringWithMonotone() should be provided.

What operating system and processor architecture are you using (go env)?

go env:
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/jaten/go"
GORACE=""
GOROOT="/usr/local/go1.9rc2"
GOTOOLDIR="/usr/local/go1.9rc2/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/6s/zdc0hvvx7kqcglg5yqm3kl4r0000gn/T/go-build055854381=/tmp/go-build -gno-record-gcc-switches -fno-common"
CXX="clang++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"

What did you do?

Here code to reproduce the issue, serialize_time.go. It is extracted from the time.Time serialization in a popular msgpack serialization package. e.g. getunix() and putunix() are here https://github.com/tinylib/msgp/blob/ad0ff2e232ad2e37faf67087fb24bf8d04a8ce20/msgp/integers.go#L113

package main

import (
	"fmt"
	"time"
)

func main() {

	// time to bytes
	t := time.Now()
	fmt.Printf("original  t='%s'\n", t.String())
	t = t.UTC()
	fmt.Printf("original  t='%s' in UTC\n", t.String())
	var b [12]byte
	putUnix(b[:], t.Unix(), int32(t.Nanosecond()))

	// bytes to time:
	sec, nsec := getUnix(b[:])
	t2 := time.Unix(sec, int64(nsec)).Local()

	fmt.Printf("restored t2='%s'\n", t2.String())
}

/* output of main:

original  t='2017-08-16 19:35:41.958759249 -0400 EDT m=+0.000241395'
original  t='2017-08-16 23:35:41.958759249 +0000 UTC' in UTC
restored t2='2017-08-16 19:35:41.958759249 -0400 EDT'

*/

func getUnix(b []byte) (sec int64, nsec int32) {
	sec = (int64(b[0]) << 56) | (int64(b[1]) << 48) |
		(int64(b[2]) << 40) | (int64(b[3]) << 32) |
		(int64(b[4]) << 24) | (int64(b[5]) << 16) |
		(int64(b[6]) << 8) | (int64(b[7]))

	nsec = (int32(b[8]) << 24) | (int32(b[9]) << 16) | (int32(b[10]) << 8) | (int32(b[11]))
	return
}

func putUnix(b []byte, sec int64, nsec int32) {
	b[0] = byte(sec >> 56)
	b[1] = byte(sec >> 48)
	b[2] = byte(sec >> 40)
	b[3] = byte(sec >> 32)
	b[4] = byte(sec >> 24)
	b[5] = byte(sec >> 16)
	b[6] = byte(sec >> 8)
	b[7] = byte(sec)
	b[8] = byte(nsec >> 24)
	b[9] = byte(nsec >> 16)
	b[10] = byte(nsec >> 8)
	b[11] = byte(nsec)
}

In summary, in go1.9rc2, time.Time.String() may produce:

2017-08-16 18:41:39.184829495 -0400 EDT m=+0.057013769

whereas in go1.83, the String() applied to the same value produces always:

2017-08-16 18:41:39.184829495 -0400 EDT

While not a language consistency issue, this is a backwards-compatibility issue for programs that expected one thing from their time.Time values. For example, my tests in a fork of the tinylib/msgp lib cited above are suddenly broken under go1.9rc2. So these changes may break many programs unexpectedly when moving from go1.8.3 to go1.9.

I'm glad for the new monotonic time feature under the covers, as it solves bugs with walltime going through leap seconds, but does it have to surface itself into the string representation of time in a non-backcompatible way? For the new info, how about adding a separate stringification method if one needs both timestamps? Perhaps: StringWithMonotone() alongside the old fashioned String().

In summary, while useful, features meant for debugging the new time.Time values should not break backwards compatibility, when they can trivially be provided alongside in a separate new method.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions