Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TimestampFromV7 should return the unix timestamp in milliseconds #128

Closed
jaredLunde opened this issue Oct 30, 2023 · 9 comments
Closed

TimestampFromV7 should return the unix timestamp in milliseconds #128

jaredLunde opened this issue Oct 30, 2023 · 9 comments
Labels
enhancement New feature or request question Further information is requested

Comments

@jaredLunde
Copy link
Contributor

Apologies if I am missing something, but it appears the current implementation of TimestampFromV7() uses the Gregorian epoch timestamp as opposed to unix:

uuid/uuid.go

Line 156 in 22c52c2

tsNanos := epochStart + time.UnixMilli(t).UTC().UnixNano()/100

uuid/generator.go

Lines 37 to 39 in 22c52c2

// Difference in 100-nanosecond intervals between
// UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970).
const epochStart = 122192928000000000

The draft states:

unix_ts_ms:
48 bit big-endian unsigned number of Unix epoch timestamp as per Section 6.1.

So shouldn't this be:

func TimestampFromV7(u UUID) (Timestamp, error) {
	if u.Version() != 7 {
		return 0, fmt.Errorf("uuid: %s is version %d, not version 6", u, u.Version())
	}

	t := 0 |
		(int64(u[0]) << 40) |
		(int64(u[1]) << 32) |
		(int64(u[2]) << 24) |
		(int64(u[3]) << 16) |
		(int64(u[4]) << 8) |
		int64(u[5])
        
	return Timestamp(t), nil
}

Where tests would be as straightforward as:

	tests := []struct {
		u       UUID
		want    Timestamp
		wanterr bool
	}{
		{u: Must(FromString("00000000-0000-7000-0000-000000000000")), want: Timestamp(0x000000000000)},
		{u: Must(FromString("018a8fec-3ced-7164-995f-93c80cbdc575")), want: Timestamp(0x018a8fec3ced)},
		{u: Must(FromString("ffffffff-ffff-7fff-ffff-ffffffffffff")), want: Timestamp(0xffffffffffff)},
       }
@dylan-bourque
Copy link
Member

I didn't write the existing implementation, but it looks like it's reusing the Timestamp type and adjusting the value to match that type's documented constraints here.

@jaredLunde
Copy link
Contributor Author

Ahh, should we expect each parser to return its draft-compliant timestamp though? Happy to draft that PR if you think so!

@dylan-bourque
Copy link
Member

That would imply creating a separate TimestampVN type for each version, which doesn't feel like a good path. 🤔

@jaredLunde
Copy link
Contributor Author

jaredLunde commented Oct 30, 2023

Or separate by epoch

  • UnixTime, GregTime
  • UnixTimestamp, GregTimestamp
    etc.

@dylan-bourque
Copy link
Member

That would be a breaking change to everything currently using Timestamp, though.

I'm certainly not the final arbiter but I'd argue for leaving this as is.

@jaredLunde
Copy link
Contributor Author

I mean you could mark Timestamp as deprecated to avoid shipping a new major version. That said, I totally understand not wanting to change it. As a user, I was confused given this in the draft: https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#name-example-of-a-uuidv7-value

-------------------------------
field      bits    value
-------------------------------
unix_ts_ms   48    0x17F22E279B0
var           4    0x7
rand_a       12    0xCC3
var           2    b10
rand_b       62    0x18C4DC0C0C07398F
-------------------------------
total       128
-------------------------------
final: 017F22E2-79B0-7CC3-98C4-DC0C0C07398F

I expected that parsing a UUID v7 would yield a Unix timestamp w/ millisecond precision, as this was a deliberate design choice by the UUID authors.

@kohenkatz
Copy link
Contributor

Might I suggest having two "from v7" functions here?

  • The existing TimestampFromV7 implementation that returns a uuid.Timestamp object.
  • A new NativeTimeFromV7 (or GoTimeFromV7; TimeFromV7, etc.) that returns a Go native time.Time object.

Thoughts?

@cameracker
Copy link
Collaborator

cameracker commented Mar 28, 2024

Hi, chiming in. I think it's a better developer experience to offer a neutral, consistent timestamp type and allow the developer to convert as needed. Understood it's not compliant to spec in a technical sense. I'll close the issue but please feel free to continue discussing. Thanks for the feedback!

@kohenkatz
Copy link
Contributor

For some users, there may be a performance reason to have a native time.Time conversion. I did an experiment here - adding a TimeFromV7 method and a benchmark that compares TimeFromV7() and TimestampFromV7() + Time() - in 10 benchmark runs, converting to a native timestamp averages 33% faster than converting to this package's Timestamp and then to the native timestamp, at least on my machine:

> go test -benchmem -run=^$ -bench '^(BenchmarkGetTimeFromTimestamp|BenchmarkGetNativeTime)$' -count 10 github.com/gofrs/uuid/v5

goos: windows
goarch: amd64
pkg: github.com/gofrs/uuid/v5
cpu: 12th Gen Intel(R) Core(TM) i7-1280P
BenchmarkGetTimeFromTimestamp-20        301292166                4.005 ns/op           0 B/op          0 allocs/op
BenchmarkGetTimeFromTimestamp-20        304036849                3.908 ns/op           0 B/op          0 allocs/op
BenchmarkGetTimeFromTimestamp-20        261100980                4.021 ns/op           0 B/op          0 allocs/op
BenchmarkGetTimeFromTimestamp-20        308387047                4.134 ns/op           0 B/op          0 allocs/op
BenchmarkGetTimeFromTimestamp-20        298532562                4.177 ns/op           0 B/op          0 allocs/op
BenchmarkGetTimeFromTimestamp-20        277410657                3.922 ns/op           0 B/op          0 allocs/op
BenchmarkGetTimeFromTimestamp-20        248709250                5.145 ns/op           0 B/op          0 allocs/op
BenchmarkGetTimeFromTimestamp-20        245341626                5.171 ns/op           0 B/op          0 allocs/op
BenchmarkGetTimeFromTimestamp-20        240928731                5.481 ns/op           0 B/op          0 allocs/op
BenchmarkGetTimeFromTimestamp-20        283031826                4.415 ns/op           0 B/op          0 allocs/op
BenchmarkGetNativeTime-20               352612831                3.094 ns/op           0 B/op          0 allocs/op
BenchmarkGetNativeTime-20               395081556                3.225 ns/op           0 B/op          0 allocs/op
BenchmarkGetNativeTime-20               379098888                3.022 ns/op           0 B/op          0 allocs/op
BenchmarkGetNativeTime-20               377483012                3.257 ns/op           0 B/op          0 allocs/op
BenchmarkGetNativeTime-20               400233602                3.217 ns/op           0 B/op          0 allocs/op
BenchmarkGetNativeTime-20               392246847                3.037 ns/op           0 B/op          0 allocs/op
BenchmarkGetNativeTime-20               389608377                3.169 ns/op           0 B/op          0 allocs/op
BenchmarkGetNativeTime-20               345799002                3.361 ns/op           0 B/op          0 allocs/op
BenchmarkGetNativeTime-20               391484430                3.576 ns/op           0 B/op          0 allocs/op
BenchmarkGetNativeTime-20               303667467                4.294 ns/op           0 B/op          0 allocs/op
PASS
ok      github.com/gofrs/uuid/v5        35.777s

Admittedly, for most of us the difference between 3.022ns (fastest native run) and 5.481ns (slowest two-step run) is not going to be noticeable, but I am sure there are some people for whom it makes a difference.

@cameracker cameracker added enhancement New feature or request question Further information is requested labels May 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants