diff --git a/bson/bson.go b/bson/bson.go index f1f9ab747..f283b3a03 100644 --- a/bson/bson.go +++ b/bson/bson.go @@ -41,6 +41,7 @@ import ( "errors" "fmt" "io" + "math" "os" "reflect" "runtime" @@ -201,7 +202,6 @@ func readRandomUint32() uint32 { return uint32((uint32(b[0]) << 0) | (uint32(b[1]) << 8) | (uint32(b[2]) << 16) | (uint32(b[3]) << 24)) } - // machineId stores machine id generated once and used in subsequent calls // to NewObjectId function. var machineId = readMachineId() @@ -352,6 +352,46 @@ 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. +// +// Returns -1 and en error if time t is earlier than 1970-01-01T00:00:00Z +// or later than 2106-02-07T06:28:15Z. +// +// Note that two timestamps are not allowed to have the same combination of time and counter, +// so you have to make sure to increase the counter when creating multiple MongoTimestamps +// within one second. +func NewMongoTimestamp(t time.Time, c uint32) (MongoTimestamp, error) { + var tv uint32 + + u := t.Unix() + + if u < 0 || u > math.MaxUint32 { + return -1, errors.New("invalid value for time") + } + + tv = uint32(u) + + buf := bytes.Buffer{} + + binary.Write(&buf, binary.BigEndian, tv) + binary.Write(&buf, binary.BigEndian, c) + + i := int64(binary.BigEndian.Uint64(buf.Bytes())) + + 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 beabbb65e..44d91865d 100644 --- a/bson/bson_test.go +++ b/bson/bson_test.go @@ -32,6 +32,8 @@ import ( "encoding/hex" "encoding/json" "errors" + "fmt" + "math/rand" "net/url" "reflect" "strings" @@ -1691,3 +1693,50 @@ func (s *S) BenchmarkUnmarshalRaw(c *C) { panic(err) } } + +func (s *S) TestMongoTimestampTime(c *C) { + t := time.Now() + ts, err := bson.NewMongoTimestamp(t, 1) + 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, 1) + 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(time.Now(), counter) + + if err != nil { + fmt.Printf("Error occured: %v", err) + } else { + fmt.Printf("Encoded timestamp: %d\n", ts) + } + + time.Sleep(500 * time.Millisecond) + } +}