Skip to content

Commit fc29404

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 fc29404

File tree

2 files changed

+64
-10
lines changed

2 files changed

+64
-10
lines changed

src/time/time.go

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,31 +1163,39 @@ 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)
11781184
}
1185+
11791186
offset /= 60
11801187
if offset < -32768 || offset == -1 || offset > 32767 {
11811188
return nil, errors.New("Time.MarshalBinary: unexpected zone offset")
11821189
}
1190+
11831191
offsetMin = int16(offset)
11841192
}
11851193

11861194
sec := t.sec()
11871195
nsec := t.nsec()
11881196
enc := []byte{
1189-
timeBinaryVersion, // byte 0 : version
1190-
byte(sec >> 56), // bytes 1-8: seconds
1197+
version, // byte 0 : version
1198+
byte(sec >> 56), // bytes 1-8: seconds
11911199
byte(sec >> 48),
11921200
byte(sec >> 40),
11931201
byte(sec >> 32),
@@ -1202,6 +1210,9 @@ func (t Time) MarshalBinary() ([]byte, error) {
12021210
byte(offsetMin >> 8), // bytes 13-14: zone offset in minutes
12031211
byte(offsetMin),
12041212
}
1213+
if version == timeBinaryVersionV2 {
1214+
enc = append(enc, byte(offsetSec>>8), byte(offsetSec))
1215+
}
12051216

12061217
return enc, nil
12071218
}
@@ -1213,12 +1224,19 @@ func (t *Time) UnmarshalBinary(data []byte) error {
12131224
return errors.New("Time.UnmarshalBinary: no data")
12141225
}
12151226

1216-
if buf[0] != timeBinaryVersion {
1227+
timeVersion := buf[0]
1228+
if timeVersion != timeBinaryVersionV1 && timeVersion != timeBinaryVersionV2 {
12171229
return errors.New("Time.UnmarshalBinary: unsupported version")
12181230
}
12191231

1220-
if len(buf) != /*version*/ 1+ /*sec*/ 8+ /*nsec*/ 4+ /*zone offset*/ 2 {
1221-
return errors.New("Time.UnmarshalBinary: invalid length")
1232+
if timeVersion == timeBinaryVersionV1 {
1233+
if len(buf) != /*version*/ 1+ /*sec*/ 8+ /*nsec*/ 4+ /*zone offset*/ 2 {
1234+
return errors.New("Time.UnmarshalBinary: invalid length")
1235+
}
1236+
} else if timeVersion == timeBinaryVersionV2 {
1237+
if len(buf) != /*version*/ 1+ /*sec*/ 8+ /*nsec*/ 4+ /*zone offset*/ 4 {
1238+
return errors.New("Time.UnmarshalBinary: invalid length")
1239+
}
12221240
}
12231241

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

12311249
buf = buf[4:]
1232-
offset := int(int16(buf[1])|int16(buf[0])<<8) * 60
1250+
var offset int
1251+
if timeVersion == timeBinaryVersionV2 {
1252+
offset = int(int16(buf[1])|int16(buf[0])<<8)*60 + int(int16(buf[3])|int16(buf[2])<<8)
1253+
} else {
1254+
// timeVersion is equal to timeBinaryVersionV1
1255+
offset = int(int16(buf[1])|int16(buf[0])<<8) * 60
1256+
}
12331257

12341258
*t = Time{}
12351259
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)