Skip to content

TimestampFromUUIDv7 Artificially Limited #195

@nathanmcgarvey-modopayments

Description

Welcome

  • Yes, I'm using the latest release.
  • Yes, I've searched similar issues on GitHub and didn't find any.

What did you expect to see?

The maximum UUIDv7 should have a timestamp that evaluates to the year 10889 per https://www.rfc-editor.org/rfc/rfc9562.html#section-6.1-2.8

Length:
The length of a given timestamp directly impacts how many timestamp ticks can be contained in a UUID before the maximum value for the timestamp field is reached. Take care to ensure that the proper length is selected for a given timestamp. UUIDv1 and UUIDv6 utilize a 60-bit timestamp valid until 5623 AD; UUIDv7 features a 48-bit timestamp valid until the year 10889 AD.

What did you see instead?

The maximum UUIDv7 had a timestamp that evaluated to the year 2121.

go test -v -timeout 30s -run ^TestTimestampFromV7$ github.com/gofrs/uuid/v5 -race -count=1 -v

=== RUN   TestTimestampFromV7
    uuid_test.go:275: 1969-12-31 18:00:00 -0600 CST <nil>
    uuid_test.go:275: 2023-09-13 14:02:18.605 -0500 CDT <nil>
    uuid_test.go:275: 2121-04-11 06:53:25.0117257 -0500 CDT <nil>
--- PASS: TestTimestampFromV7 (0.00s)
PASS
ok  	github.com/gofrs/uuid/v5	1.194s

This is likely because of inappropriate function re-use between v1 and v7 and an overflow somewhere (though I haven't fully debugged to prove that):

uuid.go:84-67

// Timestamp is the count of 100-nanosecond intervals since 00:00:00.00,
// 15 October 1582 within a V1 UUID. This type has no meaning for other
// UUID versions since they don't have an embedded timestamp.
type Timestamp uint64

const _100nsPerSecond = 10000000

// Time returns the UTC time.Time representation of a Timestamp
func (t Timestamp) Time() (time.Time, error) {
	secs := uint64(t) / _100nsPerSecond
	nsecs := 100 * (uint64(t) % _100nsPerSecond)

	return time.Unix(int64(secs)-(epochStart/_100nsPerSecond), int64(nsecs)), nil
}

Reproduction steps

Simple patch to the existing test file to see the values embedded in already-present tests:


diff --git a/uuid_test.go b/uuid_test.go
index 6dc97f9..7837c45 100644
--- a/uuid_test.go
+++ b/uuid_test.go
@@ -271,6 +271,10 @@ func TestTimestampFromV7(t *testing.T) {
        for _, tt := range tests {
                got, err := TimestampFromV7(tt.u)
 
+               if err == nil {
+                       t.Log(got.Time())
+               }
+
                switch {
                case tt.wanterr && err == nil:
                        t.Errorf("TimestampFromV7(%v) want error, got %v", tt.u, got)




go test -v -timeout 30s -run ^TestTimestampFromV7$ github.com/gofrs/uuid/v5 -race -count=1 -v

=== RUN   TestTimestampFromV7
    uuid_test.go:275: 1969-12-31 18:00:00 -0600 CST <nil>
    uuid_test.go:275: 2023-09-13 14:02:18.605 -0500 CDT <nil>
    uuid_test.go:275: 2121-04-11 06:53:25.0117257 -0500 CDT <nil>
--- PASS: TestTimestampFromV7 (0.00s)
PASS
ok  	github.com/gofrs/uuid/v5	1.194s

Version of flock

N/A

Logs

N/A

Go environment

go version go1.23.3 darwin/arm64

Validation

  • Yes, I've included all information above (version, config, etc.).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions