Change:
Found some error logs when sending series events to apiserver: seems the later eventTime differed from the first one in series events After investigation, I found the problem was in the marshal. MicroTime is supposed to work on microsecond level precision, but init with nanosecond level precision, in this case, generates microsecond level precision data and generates nanosecond level precision, so when unmarshalled, the new time is not considered the exactly same as the original time (they are different at nanosecond level) MicroTime marshal should work at same precision for json and proto, so unmarshal can generate the exactly same MicroTime result: This causes the series events won't be updated on api-server after created master <details</details<details</details<details</details<details</details<details</details
/sig api-machinery
/assign
Good catch... we'd probably need to truncate to microseconds when marshaling and unmarshaling proto to ensure any already persisted data is fixed up when read. Something like this:
Thanks for advice, going to send a PR soon

Discussion:
if m == nil { return &Timestamp{} } <ins> // truncate precision to microseconds to match JSON marshaling/unmarshaling truncatedNanoseconds := time.Duration(m.Time.Nanosecond()).Truncate(time.Microsecond) </ins> return &Timestamp{ Seconds: m.Time.Unix(), <del> Nanos:   int32(m.Time.Nanosecond()), </del> <ins> Nanos:   int32(truncatedNanoseconds), </ins> } }

if err := p.Unmarshal(data); err != nil { return err } <del> m.Time = time.Unix(p.Seconds, int64(p.Nanos)).Local() </del> <ins> // 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() </ins> return nil }

input MicroTime }{ {MicroTime{}}, <del> {DateMicro(1998, time.May, 5, 1, 5, 5, 50, time.Local)}, </del> <ins> {DateMicro(1998, time.May, 5, 1, 5, 5, 1000, time.Local)}, </ins> {DateMicro(1998, time.May, 5, 5, 5, 5, 0, time.Local)}, }

}) } } <ins> 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) } }) } } </ins>