diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_proto.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_proto.go index 6dd6d8999f7..ab68181e915 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_proto.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_proto.go @@ -27,9 +27,12 @@ func (m *MicroTime) ProtoMicroTime() *Timestamp { if m == nil { return &Timestamp{} } + + // truncate precision to microseconds to match JSON marshaling/unmarshaling + truncatedNanoseconds := time.Duration(m.Time.Nanosecond()).Truncate(time.Microsecond) return &Timestamp{ Seconds: m.Time.Unix(), - Nanos: int32(m.Time.Nanosecond()), + Nanos: int32(truncatedNanoseconds), } } @@ -51,7 +54,10 @@ func (m *MicroTime) Unmarshal(data []byte) error { if err := p.Unmarshal(data); err != nil { return err } - m.Time = time.Unix(p.Seconds, int64(p.Nanos)).Local() + + // truncate precision to microseconds to match JSON marshaling/unmarshaling + truncatedNanoseconds := time.Duration(p.Nanos).Truncate(time.Microsecond) + m.Time = time.Unix(p.Seconds, int64(truncatedNanoseconds)).Local() return nil } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_test.go index 3b274578689..9c13b928e3a 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/micro_time_test.go @@ -118,7 +118,7 @@ func TestMicroTimeProto(t *testing.T) { input MicroTime }{ {MicroTime{}}, - {DateMicro(1998, time.May, 5, 1, 5, 5, 50, time.Local)}, + {DateMicro(1998, time.May, 5, 1, 5, 5, 1000, time.Local)}, {DateMicro(1998, time.May, 5, 5, 5, 5, 0, time.Local)}, } @@ -253,3 +253,68 @@ func TestMicroTimeIsZero(t *testing.T) { }) } } + +func TestMicroTimeUnmarshalJSONAndProtoEqual(t *testing.T) { + cases := []struct { + name string + input MicroTime + result bool + }{ + {"nanosecond level precision", UnixMicro(123, 123123123), true}, + {"microsecond level precision", UnixMicro(123, 123123000), true}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + jsonData, err := c.input.MarshalJSON() + if err != nil { + t.Fatalf("Failed to marshal input to JSON: '%v': %v", c.input, err) + } + + protoData, err := c.input.Marshal() + if err != nil { + t.Fatalf("Failed to marshal input to proto: '%v': %v", c.input, err) + } + + var tJSON, tProto MicroTime + if err = tJSON.UnmarshalJSON(jsonData); err != nil { + t.Fatalf("Failed to unmarshal JSON: '%v': %v", jsonData, err) + } + if err = tProto.Unmarshal(protoData); err != nil { + t.Fatalf("Failed to unmarshal proto: '%v': %v", protoData, err) + } + + result := tJSON.Equal(&tProto) + if result != c.result { + t.Errorf("Failed equality test for '%v': expected %+v, got %+v", c.input, c.result, result) + } + }) + } +} + +func TestMicroTimeProtoUnmarshalRaw(t *testing.T) { + cases := []struct { + name string + input []byte + expected MicroTime + }{ + // input is generated by Timestamp{123, 123123123}.Marshal() + {"nanosecond level precision", []byte{8, 123, 16, 179, 235, 218, 58}, UnixMicro(123, 123123000)}, + // input is generated by Timestamp{123, 123123000}.Marshal() + {"microsecond level precision", []byte{8, 123, 16, 184, 234, 218, 58}, UnixMicro(123, 123123000)}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var actual MicroTime + if err := actual.Unmarshal(c.input); err != nil { + t.Fatalf("Failed to unmarshal proto: '%v': %v", c.input, err) + } + + if !actual.Equal(&c.expected) { + t.Errorf("Failed unmarshal from nanosecond-precise raw for '%v': expected %+v, got %+v", c.input, c.expected, actual) + } + }) + } + +}