diff --git a/uuid_test.go b/uuid_test.go index 906ecbe..d6f7979 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -884,6 +884,32 @@ func TestVersion7FromReader(t *testing.T) { } } +func TestVersion7FromReaderWithTime(t *testing.T) { + myString := "8059ddhdle77cb52" + // other tests affects `lastV7time`, so we need to reset it + lastV7time = 0 + timeFunc := func() time.Time { + return time.Date(1992, time.November, 5, 8, 8, 8, 8, time.UTC) + } + + id, err := NewV7FromReaderWithTime(bytes.NewReader([]byte(myString)), timeFunc) + if err != nil { + t.Fatal("failed generating UUID from a reader with time") + } + if id.String() != "00a7dc05-8640-7000-ac65-373763623532" { + t.Errorf("expected deterministic result 00a7dc05-8640-7000-ac65-373763623532 got %s", id.String()) + } + + id, err = NewV7FromReaderWithTime(bytes.NewReader([]byte(myString)), timeFunc) + if err != nil { + t.Fatal("failed generating UUID from a reader with time") + } + + if id.String() != "00a7dc05-8640-7001-ac65-373763623532" { + t.Errorf("expected deterministic result on sequential call 00a7dc05-8640-7001-ac65-373763623532 got %s", id.String()) + } +} + func TestVersion7Monotonicity(t *testing.T) { length := 10000 u1 := Must(NewV7()).String() diff --git a/version7.go b/version7.go index 3fec671..fbb1222 100644 --- a/version7.go +++ b/version7.go @@ -6,6 +6,7 @@ package uuid import ( "io" + "time" ) // UUID version 7 features a time-ordered value field derived from the widely @@ -25,7 +26,7 @@ func NewV7() (UUID, error) { if err != nil { return uuid, err } - makeV7(uuid[:]) + makeV7(uuid[:], timeNow) return uuid, nil } @@ -38,14 +39,27 @@ func NewV7FromReader(r io.Reader) (UUID, error) { return uuid, err } - makeV7(uuid[:]) + makeV7(uuid[:], timeNow) + return uuid, nil +} + +// NewV7FromReaderWithTime returns a Version 7 UUID based on the provided time function. +// it uses NewRandomFromReader fill random bits and the provided time function to get the time. +// On error, NewV7FromReader returns Nil and an error. +func NewV7FromReaderWithTime(r io.Reader, timeFunc func() time.Time) (UUID, error) { + uuid, err := NewRandomFromReader(r) + if err != nil { + return uuid, err + } + + makeV7(uuid[:], timeFunc) 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(uuid []byte, timeFunc func() time.Time) { /* 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,7 +75,7 @@ func makeV7(uuid []byte) { */ _ = uuid[15] // bounds check - t, s := getV7Time() + t, s := getV7Time(timeFunc) uuid[0] = byte(t >> 40) uuid[1] = byte(t >> 32) @@ -85,11 +99,11 @@ const nanoPerMilli = 1000000 // getV7Time returns the time in milliseconds and nanoseconds / 256. // The returned (milli << 12 + seq) is guaranteed to be greater than // (milli << 12 + seq) returned by any previous call to getV7Time. -func getV7Time() (milli, seq int64) { +func getV7Time(timeFunc func() time.Time) (milli, seq int64) { timeMu.Lock() defer timeMu.Unlock() - nano := timeNow().UnixNano() + nano := timeFunc().UnixNano() milli = nano / nanoPerMilli // Sequence number is between 0 and 3906 (nanoPerMilli>>8) seq = (nano - milli*nanoPerMilli) >> 8