diff --git a/bson/bson.go b/bson/bson.go index 166658aed..eb87ef620 100644 --- a/bson/bson.go +++ b/bson/bson.go @@ -42,6 +42,7 @@ import ( "errors" "fmt" "io" + "math" "os" "reflect" "runtime" @@ -426,6 +427,36 @@ func Now() time.Time { // strange reason has its own datatype defined in BSON. type MongoTimestamp int64 +// Time returns the time part of ts which is stored with second precision. +func (ts MongoTimestamp) Time() time.Time { + return time.Unix(int64(uint64(ts)>>32), 0) +} + +// Counter returns the counter part of ts. +func (ts MongoTimestamp) Counter() uint32 { + return uint32(ts) +} + +// NewMongoTimestamp creates a timestamp using the given +// date `t` (with second precision) and counter `c` (unique for `t`). +// +// Returns an error if time `t` is not between 1970-01-01T00:00:00Z +// and 2106-02-07T06:28:15Z (inclusive). +// +// Note that two MongoTimestamps should never have the same (time, counter) combination: +// the caller must ensure the counter `c` is increased if creating multiple MongoTimestamp +// values for the same time `t` (ignoring fractions of seconds). +func NewMongoTimestamp(t time.Time, c uint32) (MongoTimestamp, error) { + u := t.Unix() + if u < 0 || u > math.MaxUint32 { + return -1, errors.New("invalid value for time") + } + + i := int64(u<<32 | int64(c)) + + return MongoTimestamp(i), nil +} + type orderKey int64 // MaxKey is a special value that compares higher than all other possible BSON diff --git a/bson/bson_test.go b/bson/bson_test.go index 5a389a498..60dcde1ff 100644 --- a/bson/bson_test.go +++ b/bson/bson_test.go @@ -32,6 +32,8 @@ import ( "encoding/json" "encoding/xml" "errors" + "fmt" + "math/rand" "net/url" "reflect" "strings" @@ -286,12 +288,12 @@ func (s *S) TestPtrInline(c *C) { }, { // nil embed struct - In: &inlinePtrStruct{A: 3}, + In: &inlinePtrStruct{A: 3}, Out: bson.M{"a": 3}, }, { // nil embed struct - In: &inlinePtrPtrStruct{B: 5}, + In: &inlinePtrPtrStruct{B: 5}, Out: bson.M{"b": 5}, }, } @@ -1205,18 +1207,18 @@ type inlineBadKeyMap struct { M map[int]int `bson:",inline"` } type inlineUnexported struct { - M map[string]interface{} `bson:",inline"` - unexported `bson:",inline"` + M map[string]interface{} `bson:",inline"` + unexported `bson:",inline"` } type MStruct struct { M int `bson:"m,omitempty"` } type inlinePtrStruct struct { - A int + A int *MStruct `bson:",inline"` } type inlinePtrPtrStruct struct { - B int + B int *inlinePtrStruct `bson:",inline"` } type unexported struct { @@ -1274,11 +1276,11 @@ func (s ifaceSlice) GetBSON() (interface{}, error) { type ( MyString string - MyBytes []byte - MyBool bool - MyD []bson.DocElem - MyRawD []bson.RawDocElem - MyM map[string]interface{} + MyBytes []byte + MyBool bool + MyD []bson.DocElem + MyRawD []bson.RawDocElem + MyM map[string]interface{} ) var ( @@ -1989,3 +1991,49 @@ func (s *S) TestMarshalRespectNil(c *C) { c.Assert(testStruct2.Map, NotNil) c.Assert(testStruct2.MapPtr, NotNil) } + +func (s *S) TestMongoTimestampTime(c *C) { + t := time.Now() + ts, err := bson.NewMongoTimestamp(t, 123) + c.Assert(err, IsNil) + c.Assert(ts.Time().Unix(), Equals, t.Unix()) +} + +func (s *S) TestMongoTimestampCounter(c *C) { + rnd := rand.Uint32() + ts, err := bson.NewMongoTimestamp(time.Now(), rnd) + c.Assert(err, IsNil) + c.Assert(ts.Counter(), Equals, rnd) +} + +func (s *S) TestMongoTimestampError(c *C) { + t := time.Date(1969, time.December, 31, 23, 59, 59, 999, time.UTC) + ts, err := bson.NewMongoTimestamp(t, 321) + c.Assert(int64(ts), Equals, int64(-1)) + c.Assert(err, ErrorMatches, "invalid value for time") +} + +func ExampleNewMongoTimestamp() { + + var counter uint32 = 1 + var t time.Time + + for i := 1; i <= 3; i++ { + + if c := time.Now(); t.Unix() == c.Unix() { + counter++ + } else { + t = c + counter = 1 + } + + ts, err := bson.NewMongoTimestamp(t, counter) + if err != nil { + fmt.Printf("NewMongoTimestamp error: %v", err) + } else { + fmt.Printf("NewMongoTimestamp encoded timestamp: %d\n", ts) + } + + time.Sleep(500 * time.Millisecond) + } +}