From 071e44e4e417adb9782410acfb89f0fb1cb85fb4 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Wed, 14 Aug 2013 00:34:00 -0400 Subject: [PATCH] time: make Time implement encoding interfaces See golang.org/s/go12encoding for design. R=golang-dev, bradfitz CC=golang-dev https://golang.org/cl/12706043 --- src/pkg/time/time.go | 60 ++++++++++++++++++++++++++++++--------- src/pkg/time/time_test.go | 22 +++++++++----- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/pkg/time/time.go b/src/pkg/time/time.go index 4a8ee8fd33..c504df7401 100644 --- a/src/pkg/time/time.go +++ b/src/pkg/time/time.go @@ -839,10 +839,10 @@ func (t Time) UnixNano() int64 { return (t.sec+internalToUnix)*1e9 + int64(t.nsec) } -const timeGobVersion byte = 1 +const timeBinaryVersion byte = 1 -// GobEncode implements the gob.GobEncoder interface. -func (t Time) GobEncode() ([]byte, error) { +// MarshalBinary implements the encoding.BinaryMarshaler interface. +func (t Time) MarshalBinary() ([]byte, error) { var offsetMin int16 // minutes east of UTC. -1 is UTC. if t.Location() == &utcLoc { @@ -850,17 +850,17 @@ func (t Time) GobEncode() ([]byte, error) { } else { _, offset := t.Zone() if offset%60 != 0 { - return nil, errors.New("Time.GobEncode: zone offset has fractional minute") + return nil, errors.New("Time.MarshalBinary: zone offset has fractional minute") } offset /= 60 if offset < -32768 || offset == -1 || offset > 32767 { - return nil, errors.New("Time.GobEncode: unexpected zone offset") + return nil, errors.New("Time.MarshalBinary: unexpected zone offset") } offsetMin = int16(offset) } enc := []byte{ - timeGobVersion, // byte 0 : version + timeBinaryVersion, // byte 0 : version byte(t.sec >> 56), // bytes 1-8: seconds byte(t.sec >> 48), byte(t.sec >> 40), @@ -880,18 +880,19 @@ func (t Time) GobEncode() ([]byte, error) { return enc, nil } -// GobDecode implements the gob.GobDecoder interface. -func (t *Time) GobDecode(buf []byte) error { +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. +func (t *Time) UnmarshalBinary(data []byte) error { + buf := data if len(buf) == 0 { - return errors.New("Time.GobDecode: no data") + return errors.New("Time.UnmarshalBinary: no data") } - if buf[0] != timeGobVersion { - return errors.New("Time.GobDecode: unsupported version") + if buf[0] != timeBinaryVersion { + return errors.New("Time.UnmarshalBinary: unsupported version") } if len(buf) != /*version*/ 1+ /*sec*/ 8+ /*nsec*/ 4+ /*zone offset*/ 2 { - return errors.New("Time.GobDecode: invalid length") + return errors.New("Time.UnmarshalBinary: invalid length") } buf = buf[1:] @@ -915,8 +916,22 @@ func (t *Time) GobDecode(buf []byte) error { return nil } +// TODO(rsc): Remove GobEncoder, GobDecoder, MarshalJSON, UnmarshalJSON in Go 2. +// The same semantics will be provided by the generic MarshalBinary, MarshalText, +// UnmarshalBinary, UnmarshalText. + +// GobEncode implements the gob.GobEncoder interface. +func (t Time) GobEncode() ([]byte, error) { + return t.MarshalBinary() +} + +// GobDecode implements the gob.GobDecoder interface. +func (t *Time) GobDecode(data []byte) error { + return t.UnmarshalBinary(data) +} + // MarshalJSON implements the json.Marshaler interface. -// Time is formatted as RFC3339. +// The time is a quoted string in RFC 3339 format, with sub-second precision added if present. func (t Time) MarshalJSON() ([]byte, error) { if y := t.Year(); y < 0 || y >= 10000 { return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]") @@ -925,13 +940,30 @@ func (t Time) MarshalJSON() ([]byte, error) { } // UnmarshalJSON implements the json.Unmarshaler interface. -// Time is expected in RFC3339 format. +// The time is expected to be a quoted string in RFC 3339 format. func (t *Time) UnmarshalJSON(data []byte) (err error) { // Fractional seconds are handled implicitly by Parse. *t, err = Parse(`"`+RFC3339+`"`, string(data)) return } +// MarshalText implements the encoding.TextMarshaler interface. +// The time is formatted in RFC 3339 format, with sub-second precision added if present. +func (t Time) MarshalText() ([]byte, error) { + if y := t.Year(); y < 0 || y >= 10000 { + return nil, errors.New("Time.MarshalText: year outside of range [0,9999]") + } + return []byte(t.Format(RFC3339Nano)), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// The time is expected to be in RFC 3339 format. +func (t *Time) UnmarshalText(data []byte) (err error) { + // Fractional seconds are handled implicitly by Parse. + *t, err = Parse(RFC3339, string(data)) + return +} + // Unix returns the local Time corresponding to the given Unix time, // sec seconds and nsec nanoseconds since January 1, 1970 UTC. // It is valid to pass nsec outside the range [0, 999999999]. diff --git a/src/pkg/time/time_test.go b/src/pkg/time/time_test.go index 4bea49575e..bef5fbec3e 100644 --- a/src/pkg/time/time_test.go +++ b/src/pkg/time/time_test.go @@ -1148,9 +1148,9 @@ var invalidEncodingTests = []struct { bytes []byte want string }{ - {[]byte{}, "Time.GobDecode: no data"}, - {[]byte{0, 2, 3}, "Time.GobDecode: unsupported version"}, - {[]byte{1, 2, 3}, "Time.GobDecode: invalid length"}, + {[]byte{}, "Time.UnmarshalBinary: no data"}, + {[]byte{0, 2, 3}, "Time.UnmarshalBinary: unsupported version"}, + {[]byte{1, 2, 3}, "Time.UnmarshalBinary: invalid length"}, } func TestInvalidTimeGob(t *testing.T) { @@ -1160,6 +1160,10 @@ func TestInvalidTimeGob(t *testing.T) { if err == nil || err.Error() != tt.want { t.Errorf("time.GobDecode(%#v) error = %v, want %v", tt.bytes, err, tt.want) } + err = ignored.UnmarshalBinary(tt.bytes) + if err == nil || err.Error() != tt.want { + t.Errorf("time.UnmarshalBinary(%#v) error = %v, want %v", tt.bytes, err, tt.want) + } } } @@ -1167,10 +1171,10 @@ var notEncodableTimes = []struct { time Time want string }{ - {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", 1)), "Time.GobEncode: zone offset has fractional minute"}, - {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", -1*60)), "Time.GobEncode: unexpected zone offset"}, - {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", -32769*60)), "Time.GobEncode: unexpected zone offset"}, - {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", 32768*60)), "Time.GobEncode: unexpected zone offset"}, + {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", 1)), "Time.MarshalBinary: zone offset has fractional minute"}, + {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", -1*60)), "Time.MarshalBinary: unexpected zone offset"}, + {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", -32769*60)), "Time.MarshalBinary: unexpected zone offset"}, + {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", 32768*60)), "Time.MarshalBinary: unexpected zone offset"}, } func TestNotGobEncodableTime(t *testing.T) { @@ -1179,6 +1183,10 @@ func TestNotGobEncodableTime(t *testing.T) { if err == nil || err.Error() != tt.want { t.Errorf("%v GobEncode error = %v, want %v", tt.time, err, tt.want) } + _, err = tt.time.MarshalBinary() + if err == nil || err.Error() != tt.want { + t.Errorf("%v MarshalBinary error = %v, want %v", tt.time, err, tt.want) + } } } -- 2.48.1