Skip to content

Commit d40bddf

Browse files
committed
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
1 parent 6e50991 commit d40bddf

File tree

2 files changed

+67
-6
lines changed

2 files changed

+67
-6
lines changed

src/time/time.go

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,28 +1163,43 @@ func (t Time) UnixNano() int64 {
11631163
return (t.unixSec())*1e9 + int64(t.nsec())
11641164
}
11651165

1166-
const timeBinaryVersion byte = 1
1166+
const (
1167+
timeBinaryVersionV1 byte = iota + 1 // For general situation
1168+
timeBinaryVersionV2 // For LMT only
1169+
)
1170+
1171+
var timeBinaryVersion byte
11671172

11681173
// MarshalBinary implements the encoding.BinaryMarshaler interface.
11691174
func (t Time) MarshalBinary() ([]byte, error) {
11701175
var offsetMin int16 // minutes east of UTC. -1 is UTC.
1176+
var offsetSec int16
11711177

11721178
if t.Location() == UTC {
11731179
offsetMin = -1
11741180
} else {
11751181
_, offset := t.Zone()
11761182
if offset%60 != 0 {
1177-
return nil, errors.New("Time.MarshalBinary: zone offset has fractional minute")
1183+
timeBinaryVersion = timeBinaryVersionV2
1184+
offsetSec = int16(offset % 60)
1185+
} else {
1186+
timeBinaryVersion = timeBinaryVersionV1
1187+
if offset%60 != 0 {
1188+
return nil, errors.New("Time.MarshalBinary: zone offset has fractional minute")
1189+
}
11781190
}
1191+
11791192
offset /= 60
11801193
if offset < -32768 || offset == -1 || offset > 32767 {
11811194
return nil, errors.New("Time.MarshalBinary: unexpected zone offset")
11821195
}
1196+
11831197
offsetMin = int16(offset)
11841198
}
11851199

11861200
sec := t.sec()
11871201
nsec := t.nsec()
1202+
11881203
enc := []byte{
11891204
timeBinaryVersion, // byte 0 : version
11901205
byte(sec >> 56), // bytes 1-8: seconds
@@ -1202,6 +1217,9 @@ func (t Time) MarshalBinary() ([]byte, error) {
12021217
byte(offsetMin >> 8), // bytes 13-14: zone offset in minutes
12031218
byte(offsetMin),
12041219
}
1220+
if timeBinaryVersion == timeBinaryVersionV2 {
1221+
enc = append(enc, byte(offsetSec>>8), byte(offsetSec))
1222+
}
12051223

12061224
return enc, nil
12071225
}
@@ -1216,9 +1234,16 @@ func (t *Time) UnmarshalBinary(data []byte) error {
12161234
if buf[0] != timeBinaryVersion {
12171235
return errors.New("Time.UnmarshalBinary: unsupported version")
12181236
}
1237+
timeVersion := buf[0]
12191238

1220-
if len(buf) != /*version*/ 1+ /*sec*/ 8+ /*nsec*/ 4+ /*zone offset*/ 2 {
1221-
return errors.New("Time.UnmarshalBinary: invalid length")
1239+
if buf[0] == timeBinaryVersionV1 {
1240+
if len(buf) != /*version*/ 1+ /*sec*/ 8+ /*nsec*/ 4+ /*zone offset*/ 2 {
1241+
return errors.New("Time.UnmarshalBinary: invalid length")
1242+
}
1243+
} else if buf[0] == timeBinaryVersionV2 {
1244+
if len(buf) != /*version*/ 1+ /*sec*/ 8+ /*nsec*/ 4+ /*zone offset*/ 4 {
1245+
return errors.New("Time.UnmarshalBinary: invalid length")
1246+
}
12221247
}
12231248

12241249
buf = buf[1:]
@@ -1229,7 +1254,13 @@ func (t *Time) UnmarshalBinary(data []byte) error {
12291254
nsec := int32(buf[3]) | int32(buf[2])<<8 | int32(buf[1])<<16 | int32(buf[0])<<24
12301255

12311256
buf = buf[4:]
1232-
offset := int(int16(buf[1])|int16(buf[0])<<8) * 60
1257+
var offset int
1258+
if timeVersion == timeBinaryVersionV2 {
1259+
offset = int(int16(buf[1])|int16(buf[0])<<8)*60 + int(int16(buf[3])|int16(buf[2])<<8)
1260+
} else {
1261+
// timeVersion is equal to timeBinaryVersionV1
1262+
offset = int(int16(buf[1])|int16(buf[0])<<8) * 60
1263+
}
12331264

12341265
*t = Time{}
12351266
t.wall = uint64(nsec)

src/time/time_test.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -767,7 +767,6 @@ var notEncodableTimes = []struct {
767767
time Time
768768
want string
769769
}{
770-
{Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", 1)), "Time.MarshalBinary: zone offset has fractional minute"},
771770
{Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", -1*60)), "Time.MarshalBinary: unexpected zone offset"},
772771
{Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", -32769*60)), "Time.MarshalBinary: unexpected zone offset"},
773772
{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) {
14371436
}
14381437
}
14391438

1439+
func TestMarshalBinaryVersion2(t *testing.T) {
1440+
t0, err := Parse(RFC3339, "1880-01-01T00:00:00Z")
1441+
if err != nil {
1442+
t.Errorf("Failed to parse time, error = %v", err)
1443+
}
1444+
loc, err := LoadLocation("US/Eastern")
1445+
if err != nil {
1446+
t.Errorf("Failed to load location, error = %v", err)
1447+
}
1448+
t1 := t0.In(loc)
1449+
b, err := t1.MarshalBinary()
1450+
if err != nil {
1451+
t.Errorf("Failed to Marshal, error = %v", err)
1452+
}
1453+
1454+
t2 := Time{}
1455+
err = t2.UnmarshalBinary(b)
1456+
if err != nil {
1457+
t.Errorf("Failed to Unmarshal, error = %v", err)
1458+
}
1459+
1460+
if !(t0.Equal(t1) && t1.Equal(t2)) {
1461+
if !t0.Equal(t1) {
1462+
t.Errorf("The result t1: %+v after Marshal is not matched orignal t0: %+v", t1, t0)
1463+
}
1464+
if !t1.Equal(t2) {
1465+
t.Errorf("The result t2: %+v after Unmarshal is not matched orignal t1: %+v", t2, t1)
1466+
}
1467+
}
1468+
}
1469+
14401470
// Issue 17720: Zero value of time.Month fails to print
14411471
func TestZeroMonthString(t *testing.T) {
14421472
if got, want := Month(0).String(), "%!Month(0)"; got != want {

0 commit comments

Comments
 (0)