diff --git a/uuid_test.go b/uuid_test.go index 83cee2c..21118cf 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -825,7 +825,7 @@ func TestVersion6(t *testing.T) { func TestVersion7(t *testing.T) { SetRand(nil) m := make(map[string]bool) - for x := 1; x < 32; x++ { + for x := 1; x < 128; x++ { uuid, err := NewV7() if err != nil { t.Fatalf("could not create UUID: %v", err) @@ -874,8 +874,7 @@ func TestVersion7_pooled(t *testing.T) { func TestVersion7FromReader(t *testing.T) { myString := "8059ddhdle77cb52" r := bytes.NewReader([]byte(myString)) - r2 := bytes.NewReader([]byte(myString)) - uuid1, err := NewV7FromReader(r) + _, err := NewV7FromReader(r) if err != nil { t.Errorf("failed generating UUID from a reader") } @@ -883,11 +882,17 @@ func TestVersion7FromReader(t *testing.T) { if err == nil { t.Errorf("expecting an error as reader has no more bytes. Got uuid. NewV7FromReader may not be using the provided reader") } - uuid3, err := NewV7FromReader(r2) - if err != nil { - t.Errorf("failed generating UUID from a reader") - } - if uuid1 != uuid3 { - t.Errorf("expected duplicates, got %q and %q", uuid1, uuid3) +} + +func TestVersion7Monotonicity(t *testing.T) { + length := 10000 + u1 := Must(NewV7()).String() + for i := 0; i < length; i++ { + u2 := Must(NewV7()).String() + if u2 <= u1 { + t.Errorf("monotonicity failed at #%d: %s(next) < %s(before)", i, u2, u1) + break + } + u1 = u2 } } diff --git a/version7.go b/version7.go index ba9dd5e..fbc902a 100644 --- a/version7.go +++ b/version7.go @@ -44,7 +44,7 @@ func NewV7FromReader(r io.Reader) (UUID, error) { // makeV7 fill 48 bits time (uuid[0] - uuid[5]), set version b0111 (uuid[6]) // uuid[8] already has the right version number (Variant is 10) -// see function NewV7 and NewV7FromReader +// see function NewV7 and NewV7FromReader func makeV7(uuid []byte) { /* 0 1 2 3 @@ -52,7 +52,7 @@ func makeV7(uuid []byte) { +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | unix_ts_ms | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | unix_ts_ms | ver | rand_a | + | unix_ts_ms | ver | rand_a (12 bit seq) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |var| rand_b | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ @@ -61,7 +61,7 @@ func makeV7(uuid []byte) { */ _ = uuid[15] // bounds check - t := timeNow().UnixMilli() + t, s := getV7Time() uuid[0] = byte(t >> 40) uuid[1] = byte(t >> 32) @@ -70,6 +70,35 @@ func makeV7(uuid []byte) { uuid[4] = byte(t >> 8) uuid[5] = byte(t) - uuid[6] = 0x70 | (uuid[6] & 0x0F) - // uuid[8] has already has right version + uuid[6] = 0x70 | (0x0F & byte(s>>8)) + uuid[7] = byte(s) +} + +// lastV7time is the last last time we returned stored as: +// +// 52 bits of time in milliseconds since epoch +// 12 bits of (fractional nanoseconds) >> 8 +var lastV7time int64 + +const nanoPerMilli = 1000000 + +// getV7Time returns the time in milliseconds and nanoseconds / 256. +// The returned (milli << 12 + seq) is guarenteed to be greater than +// (milli << 12 + seq) returned by any previous call to getV7Time. +func getV7Time() (milli, seq int64) { + timeMu.Lock() + defer timeMu.Unlock() + + nano := timeNow().UnixNano() + milli = nano / nanoPerMilli + // Sequence number is between 0 and 3906 (nanoPerMilli>>8) + seq = (nano - milli*nanoPerMilli) >> 8 + now := milli<<12 + seq + if now <= lastV7time { + now = lastV7time + 1 + milli = now >> 12 + seq = now & 0xfff + } + lastV7time = now + return milli, seq }