Skip to content

Commit 2f097c7

Browse files
author
Sam Kleinman
committed
GODRIVER-406: roundtrip zero values of time.Time
Change-Id: Ifb94fb1013489a888a10a188cc877779d90b09fd
1 parent ff5ad99 commit 2f097c7

File tree

4 files changed

+59
-27
lines changed

4 files changed

+59
-27
lines changed

bson/bson_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import (
1010
"encoding/binary"
1111
"math"
1212
"testing"
13+
"time"
14+
15+
"github.com/stretchr/testify/assert"
1316
)
1417

1518
func TestValue(t *testing.T) {
@@ -102,3 +105,27 @@ func TestValue(t *testing.T) {
102105
})
103106
t.Run("document", func(t *testing.T) {})
104107
}
108+
109+
func TestTimeRoundTrip(t *testing.T) {
110+
val := struct {
111+
Value time.Time
112+
ID string
113+
}{
114+
ID: "time-rt-test",
115+
}
116+
117+
assert.True(t, val.Value.IsZero())
118+
119+
bsonOut, err := Marshal(val)
120+
assert.NoError(t, err)
121+
rtval := struct {
122+
Value time.Time
123+
ID string
124+
}{}
125+
126+
err = Unmarshal(bsonOut, &rtval)
127+
assert.NoError(t, err)
128+
assert.Equal(t, val, rtval)
129+
assert.True(t, rtval.Value.IsZero())
130+
131+
}

bson/decode.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ var tEmptySlice = reflect.TypeOf([]interface{}(nil))
5454

5555
var zeroVal reflect.Value
5656

57+
// this references the quantity of milliseconds between zero time and
58+
// the unix epoch. useful for making sure that we convert time.Time
59+
// objects correctly to match the legacy bson library's handling of
60+
// time.Time values.
61+
const zeroEpochMs = int64(62135596800000)
62+
5763
// Unmarshaler describes a type that can unmarshal itself from BSON bytes.
5864
type Unmarshaler interface {
5965
UnmarshalBSON([]byte) error
@@ -474,7 +480,11 @@ func (d *Decoder) getReflectValue(v *Value, containerType reflect.Type, outer re
474480
return val, nil
475481
}
476482

477-
val = reflect.ValueOf(v.DateTime())
483+
if int64(v.getUint64()) == -zeroEpochMs {
484+
val = reflect.ValueOf(time.Time{})
485+
} else {
486+
val = reflect.ValueOf(v.DateTime())
487+
}
478488
case 0xA:
479489
if containerType != tEmpty {
480490
return val, nil

bson/encode.go

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,9 @@ func (e *encoder) encodeSliceAsArray(rval reflect.Value, minsize bool) ([]*Value
437437
case decimal.Decimal128:
438438
vals = append(vals, VC.Decimal128(t))
439439
continue
440+
case time.Time:
441+
vals = append(vals, VC.DateTime(t.Unix()*1000+int64(t.Nanosecond()/1e6)))
442+
continue
440443
}
441444

442445
sval = e.underlyingVal(sval)
@@ -513,6 +516,9 @@ func (e *encoder) encodeStruct(val reflect.Value) ([]*Element, error) {
513516
case decimal.Decimal128:
514517
elems = append(elems, EC.Decimal128(key, t))
515518
continue
519+
case time.Time:
520+
elems = append(elems, EC.DateTime(key, t.Unix()*1000+int64(t.Nanosecond()/1e6)))
521+
continue
516522
}
517523
field = e.underlyingVal(field)
518524

@@ -659,18 +665,11 @@ func (e *encoder) elemFromValue(key string, val reflect.Value, minsize bool) (*E
659665
elem = EC.ArrayFromElements(key, arrayElems...)
660666
}
661667
case reflect.Struct:
662-
switch val.Interface().(type) {
663-
case time.Time:
664-
t := val.Interface().(time.Time)
665-
666-
elem = EC.DateTime(key, t.UnixNano()/int64(time.Millisecond))
667-
default:
668-
structElems, err := e.encodeStruct(val)
669-
if err != nil {
670-
return nil, err
671-
}
672-
elem = EC.SubDocumentFromElements(key, structElems...)
668+
structElems, err := e.encodeStruct(val)
669+
if err != nil {
670+
return nil, err
673671
}
672+
elem = EC.SubDocumentFromElements(key, structElems...)
674673
default:
675674
return nil, fmt.Errorf("Unsupported value type %s", val.Kind())
676675
}
@@ -763,18 +762,11 @@ func (e *encoder) valueFromValue(val reflect.Value, minsize bool) (*Value, error
763762
elem = VC.ArrayFromValues(arrayElems...)
764763
}
765764
case reflect.Struct:
766-
switch val.Interface().(type) {
767-
case time.Time:
768-
t := val.Interface().(time.Time)
769-
770-
elem = VC.DateTime(t.UnixNano() / int64(time.Millisecond))
771-
default:
772-
structElems, err := e.encodeStruct(val)
773-
if err != nil {
774-
return nil, err
775-
}
776-
elem = VC.DocumentFromElements(structElems...)
765+
structElems, err := e.encodeStruct(val)
766+
if err != nil {
767+
return nil, err
777768
}
769+
elem = VC.DocumentFromElements(structElems...)
778770
default:
779771
return nil, fmt.Errorf("Unsupported value type %s", val.Kind())
780772
}

bson/value.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -373,8 +373,7 @@ func (v *Value) Double() float64 {
373373
if v.data[v.start] != '\x01' {
374374
panic(ElementTypeError{"compact.Element.double", Type(v.data[v.start])})
375375
}
376-
bits := binary.LittleEndian.Uint64(v.data[v.offset : v.offset+8])
377-
return math.Float64frombits(bits)
376+
return math.Float64frombits(v.getUint64())
378377
}
379378

380379
// DoubleOK is the same as Double, but returns a boolean instead of panicking.
@@ -608,7 +607,7 @@ func (v *Value) DateTime() time.Time {
608607
if v.data[v.start] != '\x09' {
609608
panic(ElementTypeError{"compact.Element.dateTime", Type(v.data[v.start])})
610609
}
611-
i := binary.LittleEndian.Uint64(v.data[v.offset : v.offset+8])
610+
i := v.getUint64()
612611
return time.Unix(int64(i)/1000, int64(i)%1000*1000000)
613612
}
614613

@@ -840,7 +839,11 @@ func (v *Value) Int64() int64 {
840839
if v.data[v.start] != '\x12' {
841840
panic(ElementTypeError{"compact.Element.int64Type", Type(v.data[v.start])})
842841
}
843-
return int64(binary.LittleEndian.Uint64(v.data[v.offset : v.offset+8]))
842+
return int64(v.getUint64())
843+
}
844+
845+
func (v *Value) getUint64() uint64 {
846+
return binary.LittleEndian.Uint64(v.data[v.offset : v.offset+8])
844847
}
845848

846849
// Int64OK is the same as Int64, except that it returns a boolean instead of

0 commit comments

Comments
 (0)