From 5c9bbbaeceb5352f2dd45eacaaef99814d71c339 Mon Sep 17 00:00:00 2001 From: Dejan Lokar Date: Tue, 3 Jan 2023 16:12:00 +0100 Subject: [PATCH] Add IgnoreTimeLocation option --- hashstructure.go | 41 ++++++++++++++++++++++++++--------------- hashstructure_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/hashstructure.go b/hashstructure.go index 3dc0eb7..d9c9d1e 100644 --- a/hashstructure.go +++ b/hashstructure.go @@ -37,6 +37,9 @@ type HashOptions struct { // precedence (meaning that if the type doesn't implement fmt.Stringer, we // panic) UseStringer bool + + // IgnoreTimeLocation produces the same hash for timestamps regardless of the location. + IgnoreTimeLocation bool } // Format specifies the hashing process used. Different formats typically @@ -117,25 +120,27 @@ func Hash(v interface{}, format Format, opts *HashOptions) (uint64, error) { // Create our walker and walk the structure w := &walker{ - format: format, - h: opts.Hasher, - tag: opts.TagName, - zeronil: opts.ZeroNil, - ignorezerovalue: opts.IgnoreZeroValue, - sets: opts.SlicesAsSets, - stringer: opts.UseStringer, + format: format, + h: opts.Hasher, + tag: opts.TagName, + zeronil: opts.ZeroNil, + ignorezerovalue: opts.IgnoreZeroValue, + sets: opts.SlicesAsSets, + stringer: opts.UseStringer, + ignoretimelocation: opts.IgnoreTimeLocation, } return w.visit(reflect.ValueOf(v), nil) } type walker struct { - format Format - h hash.Hash64 - tag string - zeronil bool - ignorezerovalue bool - sets bool - stringer bool + format Format + h hash.Hash64 + tag string + zeronil bool + ignorezerovalue bool + sets bool + stringer bool + ignoretimelocation bool } type visitOpts struct { @@ -207,7 +212,13 @@ func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) { switch v.Type() { case timeType: w.h.Reset() - b, err := v.Interface().(time.Time).MarshalBinary() + + timeVal := v.Interface().(time.Time) + if w.ignoretimelocation { + timeVal = timeVal.In(time.UTC) + } + + b, err := timeVal.MarshalBinary() if err != nil { return 0, err } diff --git a/hashstructure_test.go b/hashstructure_test.go index 7b0034a..df2ac33 100644 --- a/hashstructure_test.go +++ b/hashstructure_test.go @@ -676,6 +676,46 @@ func TestHash_hashable(t *testing.T) { } } +func TestHash_timestamp(t *testing.T) { + nowUTC := time.Now().In(time.UTC) + + asiaTZ, err := time.LoadLocation("Asia/Shanghai") + if err != nil { + t.Fatalf("failed to load location: %s", err) + } + nowAsia := nowUTC.In(asiaTZ) + + // compare the same timestamps with different timezones + hashUTC, err := Hash(nowUTC, FormatV2, nil) + if err != nil { + t.Fatalf("failed to hash %#v: %s", nowUTC, err) + } + hashAsia, err := Hash(nowAsia, FormatV2, nil) + if err != nil { + t.Fatalf("failed to hash %#v: %s", nowUTC, err) + } + if hashUTC == hashAsia { + t.Fatalf("bad, expected hashes to be different: %d - %d", hashUTC, hashAsia) + } + + // compare the same timestamps with different timezones and ignore timezone + hashUTC, err = Hash(nowUTC, FormatV2, &HashOptions{ + IgnoreTimeLocation: true, + }) + if err != nil { + t.Fatalf("failed to hash %#v: %s", nowUTC, err) + } + hashAsia, err = Hash(nowAsia, FormatV2, &HashOptions{ + IgnoreTimeLocation: true, + }) + if err != nil { + t.Fatalf("failed to hash %#v: %s", nowUTC, err) + } + if hashUTC != hashAsia { + t.Fatalf("bad, expected hashes to be the same: %d - %d", hashUTC, hashAsia) + } +} + type testIncludable struct { Value string Ignore string