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:
2025-02-28 02:16:32 +00:00
committed by GitHub
parent e87d9c4726
commit fe4308607c
16 changed files with 1077 additions and 36 deletions

View File

@@ -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
//