mirror of
https://github.com/jimeh/rands.git
synced 2026-02-19 03:16:39 +00:00
feat(strings/uuidv7): add UUIDv7 generation (#10)
The UUID v7 format is a time-ordered random UUID. It uses a timestamp with millisecond precision in the most significant bits, followed by random data. This provides both uniqueness and chronological ordering, making it ideal for database primary keys and situations where sorting by creation time is desired. References: - https://uuid7.com/ - https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#name-uuid-version-7
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -612,20 +613,26 @@ func TestUUID(t *testing.T) {
|
||||
`^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`,
|
||||
)
|
||||
|
||||
seen := make(map[string]struct{})
|
||||
|
||||
for i := 0; i < 10000; i++ {
|
||||
got, err := UUID()
|
||||
require.NoError(t, err)
|
||||
require.Regexp(t, m, got)
|
||||
|
||||
if _, ok := seen[got]; ok {
|
||||
require.FailNow(t, "duplicate UUID")
|
||||
}
|
||||
|
||||
seen[got] = struct{}{}
|
||||
|
||||
raw := strings.ReplaceAll(got, "-", "")
|
||||
b := make([]byte, 16)
|
||||
_, err = hex.Decode(b, []byte(raw))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 4, int(b[6]>>4), "version is not 4")
|
||||
require.Equal(t, byte(0x80), b[8]&0xc0,
|
||||
"variant is not RFC 4122",
|
||||
)
|
||||
require.Equal(t, byte(0x80), b[8]&0xc0, "variant is not RFC 4122")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -635,6 +642,68 @@ func BenchmarkUUID(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUUIDv7(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
m := regexp.MustCompile(
|
||||
`^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$`,
|
||||
)
|
||||
|
||||
// Store timestamps to verify they're increasing
|
||||
var lastTimestampBytes int64
|
||||
var lastUUID string
|
||||
|
||||
seen := make(map[string]struct{})
|
||||
for i := 0; i < 10000; i++ {
|
||||
got, err := UUIDv7()
|
||||
require.NoError(t, err)
|
||||
require.Regexp(t, m, got)
|
||||
|
||||
if _, ok := seen[got]; ok {
|
||||
require.FailNow(t, "duplicate UUID")
|
||||
}
|
||||
|
||||
seen[got] = struct{}{}
|
||||
|
||||
raw := strings.ReplaceAll(got, "-", "")
|
||||
b := make([]byte, 16)
|
||||
_, err = hex.Decode(b, []byte(raw))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check version is 7
|
||||
require.Equal(t, 7, int(b[6]>>4), "version is not 7")
|
||||
|
||||
// Check variant is RFC 4122
|
||||
require.Equal(t, byte(0x80), b[8]&0xc0, "variant is not RFC 4122")
|
||||
|
||||
// Extract timestamp bytes
|
||||
timestampBytes := int64(b[0])<<40 | int64(b[1])<<32 |
|
||||
int64(b[2])<<24 | int64(b[3])<<16 | int64(b[4])<<8 | int64(b[5])
|
||||
|
||||
// Verify timestamp is within 100 milliseconds of current time
|
||||
tsTime := time.UnixMilli(timestampBytes)
|
||||
require.WithinDuration(t, time.Now(), tsTime, 100*time.Millisecond,
|
||||
"timestamp is not within 100 milliseconds of current time",
|
||||
)
|
||||
|
||||
// After the first UUID, verify that UUIDs are monotonically increasing
|
||||
if i > 0 && timestampBytes < lastTimestampBytes {
|
||||
require.FailNow(t, "UUIDs are not monotonically increasing",
|
||||
"current: %s (ts: %d), previous: %s (ts: %d)",
|
||||
got, timestampBytes, lastUUID, lastTimestampBytes)
|
||||
}
|
||||
|
||||
lastTimestampBytes = timestampBytes
|
||||
lastUUID = got
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUUIDv7(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
_, _ = UUIDv7()
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Helpers
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user