Skip to content

time: implement the encoding.(Binary|Text)Appender for Time #68942

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/next/62384.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions doc/next/6-stdlib/99-minor/time/62384.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[Time] now implements the [encoding.BinaryAppender] and [encoding.TextAppender] interfaces.
69 changes: 42 additions & 27 deletions src/time/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 b, nil
}

return enc, nil
// 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.
// UnmarshalBinary implements the [encoding.BinaryUnmarshaler] interface.
func (t *Time) UnmarshalBinary(data []byte) error {
buf := data
if len(buf) == 0 {
Expand Down Expand Up @@ -1576,19 +1584,26 @@ 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())
}
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 {
Expand Down
22 changes: 22 additions & 0 deletions src/time/time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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")
Expand Down