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

Add Time() and Nanos() methods #31

Merged
merged 2 commits into from Aug 22, 2018

Conversation

Projects
None yet
7 participants
@rkuris
Contributor

rkuris commented Jul 18, 2018

These methods can be used to extract time values
from V1 UUIDs

Originally added as satori/go.uuid#50

@coveralls

This comment has been minimized.

Show comment
Hide comment
@coveralls

coveralls Jul 18, 2018

Pull Request Test Coverage Report for Build 92

  • 15 of 15 (100.0%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.04%) to 99.104%

Totals Coverage Status
Change from base Build 90: 0.04%
Covered Lines: 332
Relevant Lines: 335

💛 - Coveralls

coveralls commented Jul 18, 2018

Pull Request Test Coverage Report for Build 92

  • 15 of 15 (100.0%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.04%) to 99.104%

Totals Coverage Status
Change from base Build 90: 0.04%
Covered Lines: 332
Relevant Lines: 335

💛 - Coveralls
@coveralls

This comment has been minimized.

Show comment
Hide comment
@coveralls

coveralls Jul 18, 2018

Pull Request Test Coverage Report for Build 134

  • 14 of 14 (100.0%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.04%) to 99.143%

Totals Coverage Status
Change from base Build 128: 0.04%
Covered Lines: 347
Relevant Lines: 350

💛 - Coveralls

coveralls commented Jul 18, 2018

Pull Request Test Coverage Report for Build 134

  • 14 of 14 (100.0%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.04%) to 99.143%

Totals Coverage Status
Change from base Build 128: 0.04%
Covered Lines: 347
Relevant Lines: 350

💛 - Coveralls
@rkuris

This comment has been minimized.

Show comment
Hide comment
@rkuris

rkuris Jul 18, 2018

Contributor

I'd gladly stop using my fork if this can be maintained by gofrs; suggest bumping to revision 2.1.0 when adding this functionality.

Contributor

rkuris commented Jul 18, 2018

I'd gladly stop using my fork if this can be maintained by gofrs; suggest bumping to revision 2.1.0 when adding this functionality.

@rkuris

This comment has been minimized.

Show comment
Hide comment
@rkuris

rkuris Jul 18, 2018

Contributor

@jbsturgeon @michaelrios your upvote here would be appreciated.

I strongly support moving away from satori/go.uuid, and this looks like a good set of developers to steward such an important library forward.

Contributor

rkuris commented Jul 18, 2018

@jbsturgeon @michaelrios your upvote here would be appreciated.

I strongly support moving away from satori/go.uuid, and this looks like a good set of developers to steward such an important library forward.

@theckman

@rkuris thank you for being the first outside contributor to this project. 👍

I like the direction we took with changing the method names from the original PR. I do have some small things we can tweak to make this PR even more polished, in my opinion.

Show outdated Hide outdated uuid.go
Show outdated Hide outdated uuid.go
Show outdated Hide outdated uuid.go
Show outdated Hide outdated uuid.go
Show outdated Hide outdated uuid.go
Show outdated Hide outdated generator_test.go
@acln0

Thanks! A few minor nitpicks from me too.

Show outdated Hide outdated generator_test.go
Show outdated Hide outdated generator_test.go
Show outdated Hide outdated generator_test.go
@zerkms

This comment has been minimized.

Show comment
Hide comment
@zerkms

zerkms Jul 19, 2018

Member

After giving it some more thoughts and reading the spec I came with the following new types to introduce

// 100s of nanos since 00:00:00.00, 15 October 1582
type V1Timestamp int64

func TimestampFromV1(UUID) (V1Timestamp, error)

func TimeFromV1Timestamp(V1Timestamp) (time.Time, error)

Rationale behind it:

  • Those are functions not methods of UUID since they make sense only for V1 and not the other types of UUID

  • The V1Timestamp type specifically chosen according the RFC terminology (https://tools.ietf.org/html/rfc4122#section-4.1.4)

  • V1Timestamp values are comparable by themselves, if one needs exact time - there is a converting function for convenience

Member

zerkms commented Jul 19, 2018

After giving it some more thoughts and reading the spec I came with the following new types to introduce

// 100s of nanos since 00:00:00.00, 15 October 1582
type V1Timestamp int64

func TimestampFromV1(UUID) (V1Timestamp, error)

func TimeFromV1Timestamp(V1Timestamp) (time.Time, error)

Rationale behind it:

  • Those are functions not methods of UUID since they make sense only for V1 and not the other types of UUID

  • The V1Timestamp type specifically chosen according the RFC terminology (https://tools.ietf.org/html/rfc4122#section-4.1.4)

  • V1Timestamp values are comparable by themselves, if one needs exact time - there is a converting function for convenience

@acln0

This comment has been minimized.

Show comment
Hide comment
@acln0

acln0 Jul 19, 2018

Member

@zerkms I like this API very much. It's a little verbose, but it's very explicit, and it encodes the notion that timestamps are 100s of nanoseconds since the Gregorian calendar in a type. Big +1 from me.

Member

acln0 commented Jul 19, 2018

@zerkms I like this API very much. It's a little verbose, but it's very explicit, and it encodes the notion that timestamps are 100s of nanoseconds since the Gregorian calendar in a type. Big +1 from me.

@jadr2ddude

This comment has been minimized.

Show comment
Hide comment
@jadr2ddude

jadr2ddude Jul 19, 2018

Member

Would it be a better idea to do V1Timestamp.Time() or TimeFromV1Timestamp(V1Timestamp)?

Member

jadr2ddude commented Jul 19, 2018

Would it be a better idea to do V1Timestamp.Time() or TimeFromV1Timestamp(V1Timestamp)?

@acln0

This comment has been minimized.

Show comment
Hide comment
@acln0

acln0 Jul 19, 2018

Member

I have no opinion on the method vs. top-level function matter, but I do like the proposed data type, and I think we should use it.

Member

acln0 commented Jul 19, 2018

I have no opinion on the method vs. top-level function matter, but I do like the proposed data type, and I think we should use it.

@rkuris

This comment has been minimized.

Show comment
Hide comment
@rkuris

rkuris Jul 19, 2018

Contributor
Contributor

rkuris commented Jul 19, 2018

@jadr2ddude

This comment has been minimized.

Show comment
Hide comment
@jadr2ddude

jadr2ddude Jul 19, 2018

Member

@rkuris it doesn't really make sense to do both. I would be fine with either.

Member

jadr2ddude commented Jul 19, 2018

@rkuris it doesn't really make sense to do both. I would be fine with either.

@theckman

This comment has been minimized.

Show comment
Hide comment
@theckman

theckman Jul 21, 2018

Member

I would prefer we only offer a function, and not both.

Member

theckman commented Jul 21, 2018

I would prefer we only offer a function, and not both.

@acln0

This comment has been minimized.

Show comment
Hide comment
@acln0

acln0 Jul 21, 2018

Member

I agree with not offering both. Let's have one, obvious way to do things.

That being said, an argument can be made for the method variant, based on prior cases. Note, for example, that the time package offers https://golang.org/pkg/time/#Time.Minute and https://golang.org/pkg/time/#Time.Month rather than MinuteFromTime and MonthFromTime.

I wouldn't be opposed to an API like

func (u UUID) V1Timestamp() (V1Timestamp, error)

and

func (t V1Timestamp) Time() (time.Time, error)

Both this and the function-based API read well to me. I suppose it's a question of taste and style. We need a resolution nevertheless, so we can move forward with the proposal.

Member

acln0 commented Jul 21, 2018

I agree with not offering both. Let's have one, obvious way to do things.

That being said, an argument can be made for the method variant, based on prior cases. Note, for example, that the time package offers https://golang.org/pkg/time/#Time.Minute and https://golang.org/pkg/time/#Time.Month rather than MinuteFromTime and MonthFromTime.

I wouldn't be opposed to an API like

func (u UUID) V1Timestamp() (V1Timestamp, error)

and

func (t V1Timestamp) Time() (time.Time, error)

Both this and the function-based API read well to me. I suppose it's a question of taste and style. We need a resolution nevertheless, so we can move forward with the proposal.

@theckman

This comment has been minimized.

Show comment
Hide comment
@theckman

theckman Jul 21, 2018

Member

I think I'm hesitant to have it on the type because not every UUID is a V1 UUID. I'm looking at it from the perspective of what the implicit meaning of the implementation is. I think having it on the type implies the wrong thing.

Member

theckman commented Jul 21, 2018

I think I'm hesitant to have it on the type because not every UUID is a V1 UUID. I'm looking at it from the perspective of what the implicit meaning of the implementation is. I think having it on the type implies the wrong thing.

@acln0

This comment has been minimized.

Show comment
Hide comment
@acln0

acln0 Jul 21, 2018

Member

@theckman Thanks. That's a very good point, and I am now leaning strongly towards the function variant as well.

Member

acln0 commented Jul 21, 2018

@theckman Thanks. That's a very good point, and I am now leaning strongly towards the function variant as well.

@theckman theckman added this to the 2.2.0 milestone Jul 23, 2018

@theckman

This comment has been minimized.

Show comment
Hide comment
@theckman

theckman Jul 28, 2018

Member

We've had quite a few discussions in this PR, so I think it'd be good to summarize what we've talked about.

I think the agreement is that we want to create a new unsigned integer timestamp type [1], to represent the number of 100-nanosecond intervals since 00:00:00.00, 15 October 1582 [2]. This is to be closer to the RFC and how it thinks about time. A user will obtain one of these timestamps by calling a function on the package, not a method on a type within the package, while giving it a V1 UUID which it can parse the time out of.

To then get a time.Time value, I'm of the opinion that it should be a method on this timestamp type (taking inspiration from the time package).

I like the idea of the proposal so far, but I've been thinking and I want to propose a slight tweak and see what people think. Based on reading the RFC again, the "timestamp" of V2 ~ V5 UUIDs isn't actually a timestamp at all, confirming that only applies to V1. So with this I think specifying V1 is a bit redundant. We can just mention it in the docs for those who are familiar with the RFC.

Here's what I was thinking of, to expand on the example above:

// 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
// V2 ~ V5 UUIDs as they don't have an embedded timestamp .
type Timestamp uint64

// Time returns the UTC time.Time representation of the Timestamp.
func (t Timestamp) Time() (time.Time, error) {}

// TimestampFromV1 returns the Timestamp embedded within a V1 UUID.
// Returns an error if the UUID is any version other than 1.
func TimestampFromV1(u UUID) (Timestamp, error) {}

What does everyone think? Would this be acceptable for everyone as the API we want to support?

[1] https://tools.ietf.org/html/rfc4122#section-4.2.2
[2] https://tools.ietf.org/html/rfc4122#section-4.1.4

Member

theckman commented Jul 28, 2018

We've had quite a few discussions in this PR, so I think it'd be good to summarize what we've talked about.

I think the agreement is that we want to create a new unsigned integer timestamp type [1], to represent the number of 100-nanosecond intervals since 00:00:00.00, 15 October 1582 [2]. This is to be closer to the RFC and how it thinks about time. A user will obtain one of these timestamps by calling a function on the package, not a method on a type within the package, while giving it a V1 UUID which it can parse the time out of.

To then get a time.Time value, I'm of the opinion that it should be a method on this timestamp type (taking inspiration from the time package).

I like the idea of the proposal so far, but I've been thinking and I want to propose a slight tweak and see what people think. Based on reading the RFC again, the "timestamp" of V2 ~ V5 UUIDs isn't actually a timestamp at all, confirming that only applies to V1. So with this I think specifying V1 is a bit redundant. We can just mention it in the docs for those who are familiar with the RFC.

Here's what I was thinking of, to expand on the example above:

// 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
// V2 ~ V5 UUIDs as they don't have an embedded timestamp .
type Timestamp uint64

// Time returns the UTC time.Time representation of the Timestamp.
func (t Timestamp) Time() (time.Time, error) {}

// TimestampFromV1 returns the Timestamp embedded within a V1 UUID.
// Returns an error if the UUID is any version other than 1.
func TimestampFromV1(u UUID) (Timestamp, error) {}

What does everyone think? Would this be acceptable for everyone as the API we want to support?

[1] https://tools.ietf.org/html/rfc4122#section-4.2.2
[2] https://tools.ietf.org/html/rfc4122#section-4.1.4

@theckman theckman modified the milestones: 2.2.0, 2.3.0 Jul 28, 2018

@theckman

This comment has been minimized.

Show comment
Hide comment
@theckman

theckman Jul 29, 2018

Member

@rkuris let us know if there's anything we can do to help with this PR.

Member

theckman commented Jul 29, 2018

@rkuris let us know if there's anything we can do to help with this PR.

@rkuris

This comment has been minimized.

Show comment
Hide comment
@rkuris

rkuris Jul 30, 2018

Contributor
Contributor

rkuris commented Jul 30, 2018

@theckman

This comment has been minimized.

Show comment
Hide comment
@theckman

theckman Aug 11, 2018

Member

@rkuris I wanted to follow back up and see if there's anything we can do to help you with this PR. I think we'd be happy to adapt your work and raise a separate PR, if you're okay with that. If we do go that route, I'd like to do it in a way that tries to make sure we are able to still attribute the contribution to you in some capacity.

Member

theckman commented Aug 11, 2018

@rkuris I wanted to follow back up and see if there's anything we can do to help you with this PR. I think we'd be happy to adapt your work and raise a separate PR, if you're okay with that. If we do go that route, I'd like to do it in a way that tries to make sure we are able to still attribute the contribution to you in some capacity.

@rkuris

This comment has been minimized.

Show comment
Hide comment
@rkuris

rkuris Aug 13, 2018

Contributor
Contributor

rkuris commented Aug 13, 2018

@rkuris

This comment has been minimized.

Show comment
Hide comment
@rkuris

rkuris Aug 18, 2018

Contributor

Testing the edge cases turned up a bug in how times were constructed in the previous PR. I'll make a note in the satori.uuid repo to that effect.

Contributor

rkuris commented Aug 18, 2018

Testing the edge cases turned up a bug in how times were constructed in the previous PR. I'll make a note in the satori.uuid repo to that effect.

@rkuris

This comment has been minimized.

Show comment
Hide comment
@rkuris

rkuris Aug 21, 2018

Contributor

Greetings,

I think all the comments have been addressed by this rewrite. Is this ready to merge now or does this need more work?

Contributor

rkuris commented Aug 21, 2018

Greetings,

I think all the comments have been addressed by this rewrite. Is this ready to merge now or does this need more work?

const _100nsPerSecond = 10000000
// Time returns the UTC time.Time representation of a Timestamp
func (t Timestamp) Time() (time.Time, error) {

This comment has been minimized.

@zerkms

zerkms Aug 21, 2018

Member

Is error as a second return value here for potential unknown cases in future to avoid BCs, or is there any other reason?

I'm not against, or pro- having it, just curious.

@zerkms

zerkms Aug 21, 2018

Member

Is error as a second return value here for potential unknown cases in future to avoid BCs, or is there any other reason?

I'm not against, or pro- having it, just curious.

This comment has been minimized.

@rkuris

rkuris Aug 21, 2018

Contributor

I think that's the idea. You can use Must(thing.Time()) if you don't care about errors.

@rkuris

rkuris Aug 21, 2018

Contributor

I think that's the idea. You can use Must(thing.Time()) if you don't care about errors.

Show outdated Hide outdated uuid.go
@@ -133,3 +134,51 @@ func TestMust(t *testing.T) {
}
Must(fn())
}
func TestTimeFromTimestamp(t *testing.T) {
tests := []struct {

This comment has been minimized.

@acln0

acln0 Aug 21, 2018

Member

gofmt please.

@acln0

acln0 Aug 21, 2018

Member

gofmt please.

rkuris added some commits Aug 18, 2018

Reimplementation of timestamps from V1 UUIDs
Following the code review suggestions, added a new Timestamp type,
and broke the code into two functions, one to get the timestamp from
a V1 UUID and one to convert the timestamp to a time.Time value.
gofmt changes
Also changed from Timestamp shifts to uint64 shifts
@acln0

acln0 approved these changes Aug 21, 2018

@acln0

This comment has been minimized.

Show comment
Hide comment
@acln0

acln0 Aug 21, 2018

Member

LGTM now. Ping @theckman.

Member

acln0 commented Aug 21, 2018

LGTM now. Ping @theckman.

@zerkms

zerkms approved these changes Aug 21, 2018

@theckman

This comment has been minimized.

Show comment
Hide comment
@theckman

theckman Aug 22, 2018

Member

I'll review / merge tomorrow morning (in ~8 hours).

Member

theckman commented Aug 22, 2018

I'll review / merge tomorrow morning (in ~8 hours).

@theckman

Thank you so much for this contribution!

@theckman theckman merged commit bfaa575 into gofrs:master Aug 22, 2018

2 checks passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
coverage/coveralls Coverage increased (+0.04%) to 99.143%
Details

theckman added a commit that referenced this pull request Aug 22, 2018

Merge pull request #31 from rkuris/master
Add Time() and Nanos() methods

Signed-off-by: Tim Heckman <t@heckman.io>
@theckman

This comment has been minimized.

Show comment
Hide comment
@theckman

theckman Aug 22, 2018

Member

New release cut that includes this change: https://github.com/gofrs/uuid/releases/tag/v3.1.0

Member

theckman commented Aug 22, 2018

New release cut that includes this change: https://github.com/gofrs/uuid/releases/tag/v3.1.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment