Skip to content

v7: specify creating uuid from specified time #182

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions version7.go
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ package uuid

import (
"io"
"time"
)

// UUID version 7 features a time-ordered value field derived from the widely
@@ -21,11 +22,20 @@ import (
// Uses the randomness pool if it was enabled with EnableRandPool.
// On error, NewV7 returns Nil and an error
func NewV7() (UUID, error) {
return NewV7WithTime(nil)
}

// NewV7WithTime returns a time ordered Version 7 UUID.
// It is similar to the NewV7 function, but allows you to specify the time.
// If time is passed as nil, then the current time is used.
//
// If getTime fails to return the current NewV7WithTime returns Nil and an error.
func NewV7WithTime(customTime *time.Time) (UUID, error) {
uuid, err := NewRandom()
if err != nil {
return uuid, err
}
makeV7(uuid[:])
makeV7(customTime, uuid[:])
return uuid, nil
}

@@ -38,14 +48,14 @@ func NewV7FromReader(r io.Reader) (UUID, error) {
return uuid, err
}

makeV7(uuid[:])
makeV7(nil, uuid[:])
return uuid, nil
}

// 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
func makeV7(uuid []byte) {
func makeV7(customTime *time.Time, uuid []byte) {
/*
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
@@ -61,8 +71,14 @@ func makeV7(uuid []byte) {
*/
_ = uuid[15] // bounds check

t, s := getV7Time()

var t, s int64
if customTime == nil {
t, s = getV7Time()
} else {
unix := customTime.UnixNano()
t = unix / nanoPerMilli
s = (unix - t*nanoPerMilli) >> 8
}
uuid[0] = byte(t >> 40)
uuid[1] = byte(t >> 32)
uuid[2] = byte(t >> 24)
65 changes: 65 additions & 0 deletions version7_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package uuid

import (
"testing"
"time"
)

func TestNewV7WithTime(t *testing.T) {
testCases := map[string]string{
"test with current date": time.Now().Format(time.RFC3339), // now
"test with past date": time.Now().Add(-1 * time.Hour * 24 * 365).Format(time.RFC3339), // 1 year ago
"test with future date": time.Now().Add(time.Hour * 24 * 365).Format(time.RFC3339), // 1 year from now
"test with different timezone": "2021-09-01T12:00:00+04:00",
"test with negative timezone": "2021-09-01T12:00:00-12:00",
"test with future date in different timezone": "2124-09-23T12:43:30+09:00",
}

for testName, inputTime := range testCases {
t.Run(testName, func(t *testing.T) {
customTime, err := time.Parse(time.RFC3339, inputTime)
if err != nil {
t.Errorf("time.Parse returned unexpected error %v", err)
}
id, err := NewV7WithTime(&customTime)
if err != nil {
t.Errorf("NewV7WithTime returned unexpected error %v", err)
}
if id.Version() != 7 {
t.Errorf("got %d, want version 7", id.Version())
}
unixTime := time.Unix(id.Time().UnixTime())
// Compare the times in UTC format, since the input time might have different timezone,
// and the result is always in system timezone
if customTime.UTC().Format(time.RFC3339) != unixTime.UTC().Format(time.RFC3339) {
t.Errorf("got %s, want %s", unixTime.Format(time.RFC3339), customTime.Format(time.RFC3339))
}
})
}
}

func TestNewV7FromTimeGeneratesUniqueUUIDs(t *testing.T) {
now := time.Now()
ids := make([]string, 0)
runs := 26000

for i := 0; i < runs; i++ {
id, err := NewV7WithTime(&now)
if err != nil {
t.Errorf("NewV7WithTime returned unexpected error %v", err)
}
if id.Version() != 7 {
t.Errorf("got %d, want version 7", id.Version())
}

// Make sure we add only unique values
if !contains(t, ids, id.String()) {
ids = append(ids, id.String())
}
}

// Check we added all the UIDs
if len(ids) != runs {
t.Errorf("got %d UUIDs, want %d", len(ids), runs)
}
}