Skip to content

Commit ac7c347

Browse files
howjmayianlancetaylor
authored andcommitted
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: 455d7a2 GitHub-Pull-Request: #40293 Reviewed-on: https://go-review.googlesource.com/c/go/+/243402 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Trust: Carlos Amedee <[email protected]>
1 parent 07b30a4 commit ac7c347

File tree

2 files changed

+55
-7
lines changed

2 files changed

+55
-7
lines changed

src/time/time.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,19 +1162,26 @@ func (t Time) UnixNano() int64 {
11621162
return (t.unixSec())*1e9 + int64(t.nsec())
11631163
}
11641164

1165-
const timeBinaryVersion byte = 1
1165+
const (
1166+
timeBinaryVersionV1 byte = iota + 1 // For general situation
1167+
timeBinaryVersionV2 // For LMT only
1168+
)
11661169

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

11711176
if t.Location() == UTC {
11721177
offsetMin = -1
11731178
} else {
11741179
_, offset := t.Zone()
11751180
if offset%60 != 0 {
1176-
return nil, errors.New("Time.MarshalBinary: zone offset has fractional minute")
1181+
version = timeBinaryVersionV2
1182+
offsetSec = int8(offset % 60)
11771183
}
1184+
11781185
offset /= 60
11791186
if offset < -32768 || offset == -1 || offset > 32767 {
11801187
return nil, errors.New("Time.MarshalBinary: unexpected zone offset")
@@ -1185,8 +1192,8 @@ func (t Time) MarshalBinary() ([]byte, error) {
11851192
sec := t.sec()
11861193
nsec := t.nsec()
11871194
enc := []byte{
1188-
timeBinaryVersion, // byte 0 : version
1189-
byte(sec >> 56), // bytes 1-8: seconds
1195+
version, // byte 0 : version
1196+
byte(sec >> 56), // bytes 1-8: seconds
11901197
byte(sec >> 48),
11911198
byte(sec >> 40),
11921199
byte(sec >> 32),
@@ -1201,6 +1208,9 @@ func (t Time) MarshalBinary() ([]byte, error) {
12011208
byte(offsetMin >> 8), // bytes 13-14: zone offset in minutes
12021209
byte(offsetMin),
12031210
}
1211+
if version == timeBinaryVersionV2 {
1212+
enc = append(enc, byte(offsetSec))
1213+
}
12041214

12051215
return enc, nil
12061216
}
@@ -1212,11 +1222,16 @@ func (t *Time) UnmarshalBinary(data []byte) error {
12121222
return errors.New("Time.UnmarshalBinary: no data")
12131223
}
12141224

1215-
if buf[0] != timeBinaryVersion {
1225+
version := buf[0]
1226+
if version != timeBinaryVersionV1 && version != timeBinaryVersionV2 {
12161227
return errors.New("Time.UnmarshalBinary: unsupported version")
12171228
}
12181229

1219-
if len(buf) != /*version*/ 1+ /*sec*/ 8+ /*nsec*/ 4+ /*zone offset*/ 2 {
1230+
wantLen := /*version*/ 1 + /*sec*/ 8 + /*nsec*/ 4 + /*zone offset*/ 2
1231+
if version == timeBinaryVersionV2 {
1232+
wantLen++
1233+
}
1234+
if len(buf) != wantLen {
12201235
return errors.New("Time.UnmarshalBinary: invalid length")
12211236
}
12221237

@@ -1229,6 +1244,9 @@ func (t *Time) UnmarshalBinary(data []byte) error {
12291244

12301245
buf = buf[4:]
12311246
offset := int(int16(buf[1])|int16(buf[0])<<8) * 60
1247+
if version == timeBinaryVersionV2 {
1248+
offset += int(buf[2])
1249+
}
12321250

12331251
*t = Time{}
12341252
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 original t0: %+v", t1, t0)
1463+
}
1464+
if !t1.Equal(t2) {
1465+
t.Errorf("The result t2: %+v after Unmarshal is not matched original 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)