Golang nanosecond timestamp precision not playing well in containers

Hey! I’ve got a test suite that’s failing inside containers but passing on the host machine.

It’s written in Go and uses time.Now() to get nanosecond precision timestamps. The tests are basically doing a custom json.Marshal and json.Unmarshal in order to convert the timestamp to an integer and back again - it all works nicely in our system (if anyone’s interested why, it’s to save bytes on large BSON payloads with tons of time-series data, timestamps are just 8 bytes whereas a string representation can be up to 55 bytes!)

So the weird thing here is that time.Now() is actually returning nanosecond precision because it uses it in the test case to set the expected value (as you’ll see in the diff) but the conversion code doesn’t appear to be functioning the same inside the container.

DataStructure{Time:time.Time{wall:0xbe680503db9047f2, ext:3924085, loc:(*time.Location)(0x9fe260)}, <other fields...>} (expected)

DataStructure{Time:time.Time{wall:0x1b9047f2, ext:63641326479, loc:(*time.Location)(0x9fe260)}, <other fields...>} (actual)

You can see the expected value contains the full 64 bit timestamp bytes, no zeroes,
And here’s a larger breakdown from the awesome Testify library:

Diff:
--- Expected
+++ Actual
@@ -1,3 +1,3 @@
    (shared.TaskPosition) {
- Time: (time.Time) 2017-09-18 10:14:39.462440434 +0000 UTC m=+0.003924085,
+ Time: (time.Time) 2017-09-18 10:14:39.462440434 +0000 UTC,
    TaskID: (shared.TaskID) (len=2) "t1",

So in the test, the expected value is being loaded correctly but somewhere in the conversion code, it’s not kicking out the same value again - and this only happens inside docker. I actually have no idea what m=+0.003924085 is, I assumed the .462440434 contains the granularity for nanoseconds. Why is this m value missing from the actual result?

The conversion code looks like this:

func (tp *DataStructure) UnmarshalJSON(b []byte) error {

	// create a dummy data structure of the expected format, all other fields are composed in and
	// the `Time` field is overwritten with an int64 instead of a time.Time as in the alias
	type alias DataStructure
	tmp := struct {
		Time int64 `json:"t"`
		*alias
	}{}

	// Unmarshal the fake structure, processing Time as an int64
	err := json.Unmarshal(b, &tmp)
	if err != nil {
		return err
	}

	// convert the Time in the temporary structure to a time.Time and assign it to the real one
	tp.Time := time.Unix(0, tmp.Time)

	// assign all other fields to `tp`
	tp.OtherFields = tmp.OtherFields
	// etc...

	return nil
}

I understand this may be a Go question but the fact that it only happens in a container is odd to me. Hopefully someone can help find a solution!

Thanks!

Quick heads up to those finding this via search: it was resolved on golangbridge: https://forum.golangbridge.org/t/nanosecond-timestamp-precision-not-playing-well-in-containers/6663/5