Skip to content

time.Equal is false after MarshalJSON/UnmarshalJSON outside UTC; affected/package: time #64311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
dreisBHT opened this issue Nov 21, 2023 · 4 comments

Comments

@dreisBHT
Copy link

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

$ go version
go version go1.21.4 linux/amd64

Does this issue reproduce with the latest release?

Yes, go1.21.4 is the latest as far as I can tell.

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

go env Output
$ go env
GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/XXX/.cache/go-build'
GOENV='/home/rXXX/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/XXX/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/XXX/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.21.4'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build517579625=/tmp/go-build -gno-record-gcc-switches'

What did you do?

https://go.dev/play/p/X1yF2C5EqyJ
Note the issue does not show in Go Playground because it is located in the UTC time zone.

package main

import (
	"fmt"
	"time"
)

func main() {
	t1 := time.Time{}.Local()

	b, _ := t1.MarshalJSON()

	var t2 time.Time
	t2.UnmarshalJSON(b)

	fmt.Printf("%v\n", t1.Equal(t2))
}

What did you expect to see?

true

What did you see instead?

false

Notes:

I am aware of #12141, #10089, #17875. All of them seem to be discussing time.Equal vs == but in my case the actual time instant is different:
t1.wall is 0, t1.ext is 0
t2.wall is 0, t2.ext is 44

t1.IsZero() is true
t2.IsZero() is false

t1.String() gives "0001-01-01 00:57:44 +0057 LMT"
t2.String() gives "0001-01-01 00:57:44 +0057 +0057"

The strange 57:44 offset is, as far as I can tell, because time before time zones is converted to the local solar time. So this issue shows up only if you are located outside of UTC. I assume those weird fractional time zones are expected, based on this Stack overflow issue:
https://dba.stackexchange.com/questions/127965/why-does-time-zone-have-such-a-crazy-offset-from-utc-on-year-0001-in-postgres

@seankhliao
Copy link
Member

I believe this is working as intended, and the zero time is not expected to be valid

@seankhliao seankhliao closed this as not planned Won't fix, can't repro, duplicate, stale Nov 21, 2023
@dreisBHT
Copy link
Author

dreisBHT commented Nov 21, 2023

You will have the same issue with any time before circa 1880. I used zero time here simply because it illustrates the issue even more clearly.

I just tried with:
t1 := time.Date(100, 2, 3, 11, 21, 33, 0, time.Local),
same issue.

I would also question why is midnight 1st Jan year 1 so special that it is "not valid" but that is perhaps beside the point.

For completeness sake:

	t1 := time.Date(1492, 10, 12, 8, 47, 59, 0, time.Local)

	b, _ := t1.MarshalJSON()

	var t2 time.Time
	t2.UnmarshalJSON(b)

	fmt.Printf("%v\n", t1.String())
	fmt.Printf("%v\n", t2.String())
	fmt.Printf("%v\n", t1.Equal(t2))

Prints (in my case):
1492-10-12 08:47:59 +0057 LMT
1492-10-12 08:47:59 +0057 +0057
false

@seankhliao
Copy link
Member

Duplicate of #57040

@seankhliao seankhliao marked this as a duplicate of #57040 Nov 21, 2023
@defwxyz
Copy link

defwxyz commented Feb 16, 2024

The following test is ok now and show the importance of using .UTC()

func TestTime(t *testing.T) {

	now1 := time.Now().UTC()
	n1, _ := now1.MarshalJSON()
	time.Sleep(1 * time.Second)
	now2 := time.Time{}.UTC()
	now2.UnmarshalJSON(n1)
	n2, _ := now2.MarshalJSON()
	// is ok
	if string(n1) != string(n2) {
		t.Errorf("time marshall unmarshall is not identity: %v != %v", string(n1), string(n2))
	}
	// is ok
	if !now1.Equal(now2) {
		t.Error("time.Equal return false")
	}
	// is ok
	if now1.String() != now2.String() {
		t.Errorf("time marshall unmarshall is not identity:\nnow1=%v\n!=\nnow2=%v\n", now1, now2)
	}
	// is ok
	if now1 != now2 {
		t.Errorf("time marshall unmarshall is not identity:\nnow1=%v\n!=\nnow2=%v\n", now1, now2)
	}
}

@golang golang locked and limited conversation to collaborators Feb 15, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants