Skip to content
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

Struct containing a time.Time can't be compared correctly. #984

Open
leoleoasd opened this issue Jul 27, 2020 · 9 comments
Open

Struct containing a time.Time can't be compared correctly. #984

leoleoasd opened this issue Jul 27, 2020 · 9 comments
Assignees
Labels

Comments

@leoleoasd
Copy link

#979 fixed comparison of time.Time, but struct containing time.Time still can't be compared correctly because still, we will use reflect.DeepEqual.

I'm working on a PR to fix this.

@boyan-soubachov
Copy link
Collaborator

Could you please add a unit test to cover the missed case. If you have issues, please let me know so that I can revert the change

@brackendawson
Copy link
Collaborator

I'm not sure what the success criteria are for this issue, what does "correctly" mean for comparing time.Time.

v1 uses reflect.DeepEqual and to change this would be a breaking change to anyone relying on that behaviour.

@brackendawson brackendawson closed this as not planned Won't fix, can't repro, duplicate, stale Feb 19, 2024
@mitar
Copy link

mitar commented Feb 19, 2024

v1 uses reflect.DeepEqual and to change this would be a breaking change to anyone relying on that behaviour

I do not think that anyone can be relying on that behavior. Because current behavior means that nested timestamps are randomly different.

This is why time.Time defines its own Equal function:

Note that the Go == operator compares not just the time instant but also the Location and the monotonic clock reading. Therefore, Time values should not be used as map or database keys without first guaranteeing that the identical Location has been set for all values, which can be achieved through use of the UTC or Local method, and that the monotonic clock reading has been stripped by setting t = t.Round(0). In general, prefer t.Equal(u) to t == u, since t.Equal uses the most accurate comparison available and correctly handles the case when only one of its arguments has a monotonic clock reading.

I think the same holds for "expected values" in tests. See also this comment.

@brackendawson
Copy link
Collaborator

brackendawson commented Feb 19, 2024

v1 uses reflect.DeepEqual and to change this would be a breaking change to anyone relying on that behaviour

I do not think that anyone can be relying on that behavior. Because current behavior means that nested timestamps are randomly different.

This is why time.Time defines its own Equal function:

Note that the Go == operator compares not just the time instant but also the Location and the monotonic clock reading. Therefore, Time values should not be used as map or database keys without first guaranteeing that the identical Location has been set for all values, which can be achieved through use of the UTC or Local method, and that the monotonic clock reading has been stripped by setting t = t.Round(0). In general, prefer t.Equal(u) to t == u, since t.Equal uses the most accurate comparison available and correctly handles the case when only one of its arguments has a monotonic clock reading.

I think the same holds for "expected values" in tests. See also this comment.

The location encoded in the time.Time object, which is not considered by the Equal method, is exported (public) information. Anyone writing calendar/invite software or flight booking software or any other application where the returned zone is important will be relying on the current comparison.

@brackendawson brackendawson reopened this Feb 19, 2024
@mitar
Copy link

mitar commented Feb 19, 2024

Beyond location there is also monotonic clock reading possibly in the struct. That one will never be the same unless you literally made a copy of an existing struct.

And sure, we might not want to break existing function, but there is definitely a need for having a deep-compare assertion which works on time structs.

@brackendawson
Copy link
Collaborator

I agree with you on that, we already have WithinDuration(t, expexted, actual, 0) which has the same result as time.Time.Equal. Possibly this needs documenting better. It is useless for struct fields though, what are your thoughts on this?

@mitar
Copy link

mitar commented Feb 19, 2024

Ooo, struct tags. I like it.

@fedemengo
Copy link

is there a practical solution for this?

@brackendawson
Copy link
Collaborator

With current testify, the most practical solution to this is to use a test helper function:

package kata_test

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

type MyStruct struct {
	Name     string
	Birthday time.Time
}

func TestTrue(t *testing.T) {
	now := time.Now()
	alice1 := MyStruct{
		Name:     "Alice",
		Birthday: now,
	}
	alice2 := MyStruct{
		Name:     "Alice",
		Birthday: now.In(time.UTC),
	}
	assertMyStructSortOfEqual(t, alice1, alice2)
}

func assertMyStructSortOfEqual(t *testing.T, expected, actual MyStruct) bool {
	t.Helper()
	if !assert.Equal(t, expected.Name, actual.Name,
		"expected Name to be %q but got %q", expected.Name, actual.Name) {
		return false
	}
	if !assert.WithinDuration(t, expected.Birthday, actual.Birthday, 0,
		"expected Birthday to be %dns but got %dns",
		expected.Birthday.UnixNano(), actual.Birthday.UnixNano()) {
		return false
	}
	return true
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
5 participants