From e04f8df9c277481f5678208d3f1f59643bf5e429 Mon Sep 17 00:00:00 2001 From: apocelipes Date: Mon, 19 Aug 2024 19:30:06 +0800 Subject: [PATCH] time: implement the encoding.(Binary|Text)Appender for Time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Time.Marshal(Binary|Text)" could also gain some performance improvements. Here is the benchmark highlight: │ old │ new │ │ sec/op │ sec/op vs base │ MarshalText-8 104.00n ± 3% 67.27n ± 2% -35.32% (p=0.000 n=10) MarshalBinary-8 31.77n ± 2% 12.13n ± 1% -61.82% (p=0.000 n=10) geomean 57.48n 28.57n -50.30% │ old │ new │ │ B/op │ B/op vs base │ MarshalText-8 48.00 ± 0% 0.00 ± 0% -100.00% (p=0.000 n=10) MarshalBinary-8 16.00 ± 0% 0.00 ± 0% -100.00% (p=0.000 n=10) │ old │ new │ │ allocs/op │ allocs/op vs base │ MarshalText-8 1.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10) MarshalBinary-8 1.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10) For #62384 --- api/next/62384.txt | 2 + doc/next/6-stdlib/99-minor/time/62384.md | 1 + src/time/time.go | 71 ++++++++++++++---------- src/time/time_test.go | 22 ++++++++ 4 files changed, 68 insertions(+), 28 deletions(-) create mode 100644 doc/next/6-stdlib/99-minor/time/62384.md diff --git a/api/next/62384.txt b/api/next/62384.txt index af7fc5363c..0c6053d2a0 100644 --- a/api/next/62384.txt +++ b/api/next/62384.txt @@ -9,3 +9,5 @@ pkg math/big, method (*Float) AppendText([]uint8) ([]uint8, error) #62384 pkg math/big, method (*Int) AppendText([]uint8) ([]uint8, error) #62384 pkg math/big, method (*Rat) AppendText([]uint8) ([]uint8, error) #62384 pkg regexp, method (*Regexp) AppendText([]uint8) ([]uint8, error) #62384 +pkg time, method (Time) AppendBinary([]uint8) ([]uint8, error) #62384 +pkg time, method (Time) AppendText([]uint8) ([]uint8, error) #62384 diff --git a/doc/next/6-stdlib/99-minor/time/62384.md b/doc/next/6-stdlib/99-minor/time/62384.md new file mode 100644 index 0000000000..99c7a39a59 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/time/62384.md @@ -0,0 +1 @@ +[Time] now implements the [encoding.BinaryAppender] and [encoding.TextAppender] interfaces. diff --git a/src/time/time.go b/src/time/time.go index 572ea64ccf..4b7b8e62e4 100644 --- a/src/time/time.go +++ b/src/time/time.go @@ -119,9 +119,9 @@ import ( // these methods does not change the actual instant it represents, only the time // zone in which to interpret it. // -// Representations of a Time value saved by the [Time.GobEncode], [Time.MarshalBinary], -// [Time.MarshalJSON], and [Time.MarshalText] methods store the [Time.Location]'s offset, but not -// the location name. They therefore lose information about Daylight Saving Time. +// Representations of a Time value saved by the [Time.GobEncode], [Time.MarshalBinary], [Time.AppendBinary], +// [Time.MarshalJSON], [Time.MarshalText] and [Time.AppendText] methods store the [Time.Location]'s offset, +// but not the location name. They therefore lose information about Daylight Saving Time. // // In addition to the required “wall clock” reading, a Time may contain an optional // reading of the current process's monotonic clock, to provide additional precision @@ -1435,8 +1435,8 @@ const ( timeBinaryVersionV2 // For LMT only ) -// MarshalBinary implements the encoding.BinaryMarshaler interface. -func (t Time) MarshalBinary() ([]byte, error) { +// AppendBinary implements the [encoding.BinaryAppender] interface. +func (t Time) AppendBinary(b []byte) ([]byte, error) { var offsetMin int16 // minutes east of UTC. -1 is UTC. var offsetSec int8 version := timeBinaryVersionV1 @@ -1452,38 +1452,46 @@ func (t Time) MarshalBinary() ([]byte, error) { offset /= 60 if offset < -32768 || offset == -1 || offset > 32767 { - return nil, errors.New("Time.MarshalBinary: unexpected zone offset") + return b, errors.New("Time.MarshalBinary: unexpected zone offset") } offsetMin = int16(offset) } sec := t.sec() nsec := t.nsec() - enc := []byte{ - version, // byte 0 : version - byte(sec >> 56), // bytes 1-8: seconds - byte(sec >> 48), - byte(sec >> 40), - byte(sec >> 32), - byte(sec >> 24), - byte(sec >> 16), - byte(sec >> 8), + b = append(b, + version, // byte 0 : version + byte(sec>>56), // bytes 1-8: seconds + byte(sec>>48), + byte(sec>>40), + byte(sec>>32), + byte(sec>>24), + byte(sec>>16), + byte(sec>>8), byte(sec), - byte(nsec >> 24), // bytes 9-12: nanoseconds - byte(nsec >> 16), - byte(nsec >> 8), + byte(nsec>>24), // bytes 9-12: nanoseconds + byte(nsec>>16), + byte(nsec>>8), byte(nsec), - byte(offsetMin >> 8), // bytes 13-14: zone offset in minutes + byte(offsetMin>>8), // bytes 13-14: zone offset in minutes byte(offsetMin), - } + ) if version == timeBinaryVersionV2 { - enc = append(enc, byte(offsetSec)) + b = append(b, byte(offsetSec)) } - - return enc, nil + return b, nil } -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. +// MarshalBinary implements the [encoding.BinaryMarshaler] interface. +func (t Time) MarshalBinary() ([]byte, error) { + b, err := t.AppendBinary(make([]byte, 0, 16)) + if err != nil { + return nil, err + } + return b, nil +} + +// UnmarshalBinary implements the [encoding.BinaryUnmarshaler] interface. func (t *Time) UnmarshalBinary(data []byte) error { buf := data if len(buf) == 0 { @@ -1576,12 +1584,11 @@ func (t *Time) UnmarshalJSON(data []byte) error { return err } -// MarshalText implements the [encoding.TextMarshaler] interface. +// AppendText implements the [encoding.TextAppender] interface. // The time is formatted in RFC 3339 format with sub-second precision. // If the timestamp cannot be represented as valid RFC 3339 -// (e.g., the year is out of range), then an error is reported. -func (t Time) MarshalText() ([]byte, error) { - b := make([]byte, 0, len(RFC3339Nano)) +// (e.g., the year is out of range), then an error is returned. +func (t Time) AppendText(b []byte) ([]byte, error) { b, err := t.appendStrictRFC3339(b) if err != nil { return nil, errors.New("Time.MarshalText: " + err.Error()) @@ -1589,6 +1596,14 @@ func (t Time) MarshalText() ([]byte, error) { return b, nil } +// MarshalText implements the [encoding.TextMarshaler] interface. The output +// matches that of calling the [Time.AppendText] method. +// +// See [Time.AppendText] for more information. +func (t Time) MarshalText() ([]byte, error) { + return t.AppendText(make([]byte, 0, len(RFC3339Nano))) +} + // UnmarshalText implements the [encoding.TextUnmarshaler] interface. // The time must be in the RFC 3339 format. func (t *Time) UnmarshalText(data []byte) error { diff --git a/src/time/time_test.go b/src/time/time_test.go index e375d3f973..18fd21c27c 100644 --- a/src/time/time_test.go +++ b/src/time/time_test.go @@ -1403,6 +1403,13 @@ var defaultLocTests = []struct { {"UnixMilli", func(t1, t2 Time) bool { return t1.UnixMilli() == t2.UnixMilli() }}, {"UnixMicro", func(t1, t2 Time) bool { return t1.UnixMicro() == t2.UnixMicro() }}, + {"AppendBinary", func(t1, t2 Time) bool { + buf1 := make([]byte, 4, 32) + buf2 := make([]byte, 4, 32) + a1, b1 := t1.AppendBinary(buf1) + a2, b2 := t2.AppendBinary(buf2) + return bytes.Equal(a1[4:], a2[4:]) && b1 == b2 + }}, {"MarshalBinary", func(t1, t2 Time) bool { a1, b1 := t1.MarshalBinary() a2, b2 := t2.MarshalBinary() @@ -1418,6 +1425,14 @@ var defaultLocTests = []struct { a2, b2 := t2.MarshalJSON() return bytes.Equal(a1, a2) && b1 == b2 }}, + {"AppendText", func(t1, t2 Time) bool { + maxCap := len(RFC3339Nano) + 4 + buf1 := make([]byte, 4, maxCap) + buf2 := make([]byte, 4, maxCap) + a1, b1 := t1.AppendText(buf1) + a2, b2 := t2.AppendText(buf2) + return bytes.Equal(a1[4:], a2[4:]) && b1 == b2 + }}, {"MarshalText", func(t1, t2 Time) bool { a1, b1 := t1.MarshalText() a2, b2 := t2.MarshalText() @@ -1510,6 +1525,13 @@ func BenchmarkMarshalText(b *testing.B) { } } +func BenchmarkMarshalBinary(b *testing.B) { + t := Now() + for i := 0; i < b.N; i++ { + t.MarshalBinary() + } +} + func BenchmarkParse(b *testing.B) { for i := 0; i < b.N; i++ { Parse(ANSIC, "Mon Jan 2 15:04:05 2006")