From 26baec742cb6476ca778270ad9acb6ac932ed042 Mon Sep 17 00:00:00 2001 From: Lazybark Date: Tue, 5 Nov 2024 18:36:28 +0200 Subject: [PATCH 1/3] feat: func to generate V7 from custom time Introduce NewV7FromTime to provide more flexible use --- uuid_test.go | 25 ++++++++++++++++++++++ version7.go | 59 +++++++++++++++++++++++++++++++--------------------- 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/uuid_test.go b/uuid_test.go index 906ecbe..1ac0887 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -541,6 +541,31 @@ func TestRandomFromReader(t *testing.T) { } } +func TestVersion7FromCustomTime(t *testing.T) { + // Test with specific timestamp. + layout := "2006-01-02 15:04:05.000 -0700" + + ts, err := time.Parse(layout, "2024-10-25 08:58:09.662 +0000") + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + uuid, err := NewV7FromTime(ts) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + + if len(uuid) != 16 { + t.Fatalf("expected UUID length of 16, got %d", len(uuid)) + } + + // Check if the timestamp part is correctly encoded. + expectedPrefix := "0192c2e5-bf7e-7000-" + if !strings.HasPrefix(uuid.String(), expectedPrefix) { + t.Fatalf("expected UUID to start with %s, got %s", expectedPrefix, uuid) + } +} + func TestRandPool(t *testing.T) { myString := "8059ddhdle77cb52" EnableRandPool() diff --git a/version7.go b/version7.go index 3fec671..783ef4c 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,37 +39,47 @@ func NewV7FromReader(r io.Reader) (UUID, error) { return uuid, err } - makeV7(uuid[:]) + makeV7(uuid[:], timeNow()) + return uuid, nil +} + +// NewV7FromTime returns a Version 7 UUID based on the provided time. +func NewV7FromTime(t time.Time) (UUID, error) { + uuid, err := NewRandom() + if err != nil { + return uuid, err + } + makeV7(uuid[:], t) 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, t 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 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | unix_ts_ms | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | unix_ts_ms | ver | rand_a (12 bit seq) | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - |var| rand_b | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | rand_b | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 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 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | unix_ts_ms | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | unix_ts_ms | ver | rand_a (12 bit seq) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |var| rand_b | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | rand_b | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ _ = uuid[15] // bounds check - t, s := getV7Time() + m, s := getV7TimeFromTime(t) - uuid[0] = byte(t >> 40) - uuid[1] = byte(t >> 32) - uuid[2] = byte(t >> 24) - uuid[3] = byte(t >> 16) - uuid[4] = byte(t >> 8) - uuid[5] = byte(t) + uuid[0] = byte(m >> 40) + uuid[1] = byte(m >> 32) + uuid[2] = byte(m >> 24) + uuid[3] = byte(m >> 16) + uuid[4] = byte(m >> 8) + uuid[5] = byte(m) uuid[6] = 0x70 | (0x0F & byte(s>>8)) uuid[7] = byte(s) @@ -82,14 +93,14 @@ var lastV7time int64 const nanoPerMilli = 1000000 -// getV7Time returns the time in milliseconds and nanoseconds / 256. +// getV7TimeFromTime returns the time in milliseconds and nanoseconds / 256 for a custom time. // 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 getV7TimeFromTime(t time.Time) (milli, seq int64) { timeMu.Lock() defer timeMu.Unlock() - nano := timeNow().UnixNano() + nano := t.UnixNano() milli = nano / nanoPerMilli // Sequence number is between 0 and 3906 (nanoPerMilli>>8) seq = (nano - milli*nanoPerMilli) >> 8 From 4decb2fe3014a11b3e4025e3de4d8186ff272708 Mon Sep 17 00:00:00 2001 From: Lazybark Date: Sun, 1 Dec 2024 11:58:14 +0200 Subject: [PATCH 2/3] rename: NewV7FromTime => NewV7WithTime --- uuid_test.go | 2 +- version7.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/uuid_test.go b/uuid_test.go index 1ac0887..175642d 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -550,7 +550,7 @@ func TestVersion7FromCustomTime(t *testing.T) { t.Fatalf("expected no error, got %v", err) } - uuid, err := NewV7FromTime(ts) + uuid, err := NewV7WithTime(ts) if err != nil { t.Fatalf("expected no error, got %v", err) } diff --git a/version7.go b/version7.go index 783ef4c..19f6484 100644 --- a/version7.go +++ b/version7.go @@ -43,8 +43,8 @@ func NewV7FromReader(r io.Reader) (UUID, error) { return uuid, nil } -// NewV7FromTime returns a Version 7 UUID based on the provided time. -func NewV7FromTime(t time.Time) (UUID, error) { +// NewV7WithTime returns a Version 7 UUID based on the provided time. +func NewV7WithTime(t time.Time) (UUID, error) { uuid, err := NewRandom() if err != nil { return uuid, err From a540b5d4fd528d3655c2aa54feba55a565e2f78c Mon Sep 17 00:00:00 2001 From: Lazybark Date: Sun, 1 Dec 2024 12:02:13 +0200 Subject: [PATCH 3/3] rename: TestVersion7FromCustomTime => TestVersion7WithTime --- uuid_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uuid_test.go b/uuid_test.go index 175642d..d5776ed 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -541,7 +541,7 @@ func TestRandomFromReader(t *testing.T) { } } -func TestVersion7FromCustomTime(t *testing.T) { +func TestVersion7WithTime(t *testing.T) { // Test with specific timestamp. layout := "2006-01-02 15:04:05.000 -0700"