1
0
mirror of https://github.com/golang/go synced 2024-11-17 07:45:09 -07:00

time: support fractional timezone minutes in MarshalBinary

If the time is in 'LMT' and has fractional minute, then
`MarshalBinary()` and `UnmarshalBinary()` will encode/decode the time
in `timeBinaryVersionV2` in which the fractional minute is at
bit 15 and 16, and presented in seconds.

Fixes #39616

Change-Id: Ib762fb5fa26f54b1a8377a5dde0b994dd5a1236a
GitHub-Last-Rev: 455d7a2496
GitHub-Pull-Request: golang/go#40293
Reviewed-on: https://go-review.googlesource.com/c/go/+/243402
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Trust: Carlos Amedee <carlos@golang.org>
This commit is contained in:
HowJMay 2021-09-16 18:22:06 +00:00 committed by Ian Lance Taylor
parent 07b30a4f77
commit ac7c34767d
2 changed files with 55 additions and 7 deletions

View File

@ -1162,19 +1162,26 @@ func (t Time) UnixNano() int64 {
return (t.unixSec())*1e9 + int64(t.nsec()) return (t.unixSec())*1e9 + int64(t.nsec())
} }
const timeBinaryVersion byte = 1 const (
timeBinaryVersionV1 byte = iota + 1 // For general situation
timeBinaryVersionV2 // For LMT only
)
// MarshalBinary implements the encoding.BinaryMarshaler interface. // MarshalBinary implements the encoding.BinaryMarshaler interface.
func (t Time) MarshalBinary() ([]byte, error) { func (t Time) MarshalBinary() ([]byte, error) {
var offsetMin int16 // minutes east of UTC. -1 is UTC. var offsetMin int16 // minutes east of UTC. -1 is UTC.
var offsetSec int8
version := timeBinaryVersionV1
if t.Location() == UTC { if t.Location() == UTC {
offsetMin = -1 offsetMin = -1
} else { } else {
_, offset := t.Zone() _, offset := t.Zone()
if offset%60 != 0 { if offset%60 != 0 {
return nil, errors.New("Time.MarshalBinary: zone offset has fractional minute") version = timeBinaryVersionV2
offsetSec = int8(offset % 60)
} }
offset /= 60 offset /= 60
if offset < -32768 || offset == -1 || offset > 32767 { if offset < -32768 || offset == -1 || offset > 32767 {
return nil, errors.New("Time.MarshalBinary: unexpected zone offset") return nil, errors.New("Time.MarshalBinary: unexpected zone offset")
@ -1185,8 +1192,8 @@ func (t Time) MarshalBinary() ([]byte, error) {
sec := t.sec() sec := t.sec()
nsec := t.nsec() nsec := t.nsec()
enc := []byte{ enc := []byte{
timeBinaryVersion, // byte 0 : version version, // byte 0 : version
byte(sec >> 56), // bytes 1-8: seconds byte(sec >> 56), // bytes 1-8: seconds
byte(sec >> 48), byte(sec >> 48),
byte(sec >> 40), byte(sec >> 40),
byte(sec >> 32), byte(sec >> 32),
@ -1201,6 +1208,9 @@ func (t Time) MarshalBinary() ([]byte, error) {
byte(offsetMin >> 8), // bytes 13-14: zone offset in minutes byte(offsetMin >> 8), // bytes 13-14: zone offset in minutes
byte(offsetMin), byte(offsetMin),
} }
if version == timeBinaryVersionV2 {
enc = append(enc, byte(offsetSec))
}
return enc, nil return enc, nil
} }
@ -1212,11 +1222,16 @@ func (t *Time) UnmarshalBinary(data []byte) error {
return errors.New("Time.UnmarshalBinary: no data") return errors.New("Time.UnmarshalBinary: no data")
} }
if buf[0] != timeBinaryVersion { version := buf[0]
if version != timeBinaryVersionV1 && version != timeBinaryVersionV2 {
return errors.New("Time.UnmarshalBinary: unsupported version") return errors.New("Time.UnmarshalBinary: unsupported version")
} }
if len(buf) != /*version*/ 1+ /*sec*/ 8+ /*nsec*/ 4+ /*zone offset*/ 2 { wantLen := /*version*/ 1 + /*sec*/ 8 + /*nsec*/ 4 + /*zone offset*/ 2
if version == timeBinaryVersionV2 {
wantLen++
}
if len(buf) != wantLen {
return errors.New("Time.UnmarshalBinary: invalid length") return errors.New("Time.UnmarshalBinary: invalid length")
} }
@ -1229,6 +1244,9 @@ func (t *Time) UnmarshalBinary(data []byte) error {
buf = buf[4:] buf = buf[4:]
offset := int(int16(buf[1])|int16(buf[0])<<8) * 60 offset := int(int16(buf[1])|int16(buf[0])<<8) * 60
if version == timeBinaryVersionV2 {
offset += int(buf[2])
}
*t = Time{} *t = Time{}
t.wall = uint64(nsec) t.wall = uint64(nsec)

View File

@ -767,7 +767,6 @@ var notEncodableTimes = []struct {
time Time time Time
want string want string
}{ }{
{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("", -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("", -32769*60)), "Time.MarshalBinary: unexpected zone offset"},
{Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", 32768*60)), "Time.MarshalBinary: unexpected zone offset"}, {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", 32768*60)), "Time.MarshalBinary: unexpected zone offset"},
@ -1437,6 +1436,37 @@ func TestMarshalBinaryZeroTime(t *testing.T) {
} }
} }
func TestMarshalBinaryVersion2(t *testing.T) {
t0, err := Parse(RFC3339, "1880-01-01T00:00:00Z")
if err != nil {
t.Errorf("Failed to parse time, error = %v", err)
}
loc, err := LoadLocation("US/Eastern")
if err != nil {
t.Errorf("Failed to load location, error = %v", err)
}
t1 := t0.In(loc)
b, err := t1.MarshalBinary()
if err != nil {
t.Errorf("Failed to Marshal, error = %v", err)
}
t2 := Time{}
err = t2.UnmarshalBinary(b)
if err != nil {
t.Errorf("Failed to Unmarshal, error = %v", err)
}
if !(t0.Equal(t1) && t1.Equal(t2)) {
if !t0.Equal(t1) {
t.Errorf("The result t1: %+v after Marshal is not matched original t0: %+v", t1, t0)
}
if !t1.Equal(t2) {
t.Errorf("The result t2: %+v after Unmarshal is not matched original t1: %+v", t2, t1)
}
}
}
// Issue 17720: Zero value of time.Month fails to print // Issue 17720: Zero value of time.Month fails to print
func TestZeroMonthString(t *testing.T) { func TestZeroMonthString(t *testing.T) {
if got, want := Month(0).String(), "%!Month(0)"; got != want { if got, want := Month(0).String(), "%!Month(0)"; got != want {