Skip to content

Commit 799caf9

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 799caf9

File tree

2 files changed

+68
-10
lines changed

2 files changed

+68
-10
lines changed

src/time/time.go

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,31 +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 // For general situation
1168+
timeBinaryVersionV2 // For LMT only
1169+
)
11671170

11681171
// MarshalBinary implements the encoding.BinaryMarshaler interface.
11691172
func (t Time) MarshalBinary() ([]byte, error) {
11701173
var offsetMin int16 // minutes east of UTC. -1 is UTC.
1174+
var offsetSec int16
1175+
var version byte = timeBinaryVersionV1
11711176

11721177
if t.Location() == UTC {
11731178
offsetMin = -1
11741179
} else {
11751180
_, offset := t.Zone()
11761181
if offset%60 != 0 {
1177-
return nil, errors.New("Time.MarshalBinary: zone offset has fractional minute")
1182+
version = timeBinaryVersionV2
1183+
offsetSec = int16(offset % 60)
1184+
} else {
1185+
if offset%60 != 0 {
1186+
return nil, errors.New("Time.MarshalBinary: zone offset has fractional minute")
1187+
}
11781188
}
1189+
11791190
offset /= 60
11801191
if offset < -32768 || offset == -1 || offset > 32767 {
11811192
return nil, errors.New("Time.MarshalBinary: unexpected zone offset")
11821193
}
1194+
11831195
offsetMin = int16(offset)
11841196
}
11851197

11861198
sec := t.sec()
11871199
nsec := t.nsec()
11881200
enc := []byte{
1189-
timeBinaryVersion, // byte 0 : version
1190-
byte(sec >> 56), // bytes 1-8: seconds
1201+
version, // byte 0 : version
1202+
byte(sec >> 56), // bytes 1-8: seconds
11911203
byte(sec >> 48),
11921204
byte(sec >> 40),
11931205
byte(sec >> 32),
@@ -1202,6 +1214,9 @@ func (t Time) MarshalBinary() ([]byte, error) {
12021214
byte(offsetMin >> 8), // bytes 13-14: zone offset in minutes
12031215
byte(offsetMin),
12041216
}
1217+
if version == timeBinaryVersionV2 {
1218+
enc = append(enc, byte(offsetSec>>8), byte(offsetSec))
1219+
}
12051220

12061221
return enc, nil
12071222
}
@@ -1213,12 +1228,19 @@ func (t *Time) UnmarshalBinary(data []byte) error {
12131228
return errors.New("Time.UnmarshalBinary: no data")
12141229
}
12151230

1216-
if buf[0] != timeBinaryVersion {
1231+
if buf[0] != timeBinaryVersionV1 && buf[0] != timeBinaryVersionV2 {
12171232
return errors.New("Time.UnmarshalBinary: unsupported version")
12181233
}
12191234

1220-
if len(buf) != /*version*/ 1+ /*sec*/ 8+ /*nsec*/ 4+ /*zone offset*/ 2 {
1221-
return errors.New("Time.UnmarshalBinary: invalid length")
1235+
timeVersion := buf[0]
1236+
if timeVersion == timeBinaryVersionV1 {
1237+
if len(buf) != /*version*/ 1+ /*sec*/ 8+ /*nsec*/ 4+ /*zone offset*/ 2 {
1238+
return errors.New("Time.UnmarshalBinary: invalid length")
1239+
}
1240+
} else if timeVersion == timeBinaryVersionV2 {
1241+
if len(buf) != /*version*/ 1+ /*sec*/ 8+ /*nsec*/ 4+ /*zone offset*/ 4 {
1242+
return errors.New("Time.UnmarshalBinary: invalid length")
1243+
}
12221244
}
12231245

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

12311253
buf = buf[4:]
1232-
offset := int(int16(buf[1])|int16(buf[0])<<8) * 60
1254+
var offset int
1255+
if timeVersion == timeBinaryVersionV2 {
1256+
offset = int(int16(buf[1])|int16(buf[0])<<8)*60 + int(int16(buf[3])|int16(buf[2])<<8)
1257+
} else {
1258+
// timeVersion is equal to timeBinaryVersionV1
1259+
offset = int(int16(buf[1])|int16(buf[0])<<8) * 60
1260+
}
12331261

12341262
*t = Time{}
12351263
t.wall = uint64(nsec)

src/time/time_test.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ var invalidEncodingTests = []struct {
745745
want string
746746
}{
747747
{[]byte{}, "Time.UnmarshalBinary: no data"},
748-
{[]byte{0, 2, 3}, "Time.UnmarshalBinary: unsupported version"},
748+
{[]byte{3, 2, 3, 3, 2, 3, 3, 2, 3, 3, 2, 3, 3, 2, 3}, "Time.UnmarshalBinary: unsupported version"},
749749
{[]byte{1, 2, 3}, "Time.UnmarshalBinary: invalid length"},
750750
}
751751

@@ -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)