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

add optional chrono support (Uuid::to_utc() -> Option<DateTime<Utc>>) #405

Closed

Conversation

jonathanstrong
Copy link
Contributor

I'm submitting a feature.

Description

Adds feature-gated support for converting v1-embedded timestamps to a chrono::DateTime<Utc> via Uuid::to_utc. Relies on underlying functionality in Uuid::to_timestamp and mimics its signature. Attempted to match existing design for feature-gated components (e.g. slog_support module) as much as possible. Tests cover post-1970 dates, pre-1970 dates, and check that v3/v4/v5 variants return None.

Rationale

chrono is ubiquitous in rust ecosystem. It's convenient to return the time in a widely-used and well-designed datetime type.

Adds feature-gated support for converting v1-embedded timestamps to a `chrono::DateTime<Utc>` via `Uuid::to_utc`. Relies on underlying functionality in `Uuid::to_timestamp` and mimics its signature. Attempted to match existing design for feature-gated components (e.g. `slog_support` module) as much as possible. Tests cover post-1970 dates, pre-1970 dates, and check that v3/v4/v5 variants return None.
@KodrAus
Copy link
Member

KodrAus commented May 10, 2019

Thanks for the PR @jonathanstrong!

This looks like a well tested piece of code, however I don’t think we should necessarily provide and support this in uuid. It could be offered in an external crate instead.

@jstrong-tios
Copy link

jstrong-tios commented May 10, 2019

You're welcome - happy to help!

If I may, I'd like to present a more detailed case for its inclusion, which perhaps should have accompanied the original pull request:

Imagine you're coming at this fresh: you have some V1 UUIDs with embedded timestamps, and you need to get them out. Pretty soon you realize the time comes in an unusual, esoteric format (as timestamps go) -- the number of 100ns intervals since 1582-10-15. Where would you go looking for an existing implementation to convert it to something that's actually usable?

If you doubt that the V1 timestamp format is the ugly duckling here, consider that Uuid::new_v1 assumes, without any explicit documentation, that the seconds and nano_seconds arguments it takes are "unix" timestamps - representing a duration since 1970-01-01. It's not even possible to represent a date prior to 1970 with the current implementation, since it takes seconds as a u64.

The "unix" timestamp passed to new_v1 is converted to the V1's format's scale and embedded in the resulting UUID. However, when the time comes back out (via to_timestamp), it's still in V1 format, which is practically useless in any other context.

In my mind, the only other candidate for housing this functionality, other than my application, is in chrono. But that makes considerably less sense because chrono didn't wander into uuid's neighborhood, uuid wandered into its neighborhood with to_timestamp -- except no one can understand it because it's speaking in a foreign language (the parts we can decipher are something about Pope Gregory XIII).

And on a related topic, this is chrono's neighborhood, and not the standard lib's time module, since there's no good type for representing this application of a time in the standard library. Duration is ambiguous, since it isn't explicitly tethered to any reference point. SystemTime is the only candidate, but it's unacceptably bare-bones -- no conversion functionality of any kind, or even the ability to construct it from an arbitrary calendar date. Instant is purposefully designed to be impossible to use for this.

Finally, you mentioned the pull request is well-tested. I appreciate the praise. But I'll admit, I'm far from a TDD-evangelizing, code coverage-measuring Ned Flanders of testing. It's because I've got scars from all the times I got dates and times wrong. Which brings me to my last point: it's not rocket science to convert the number of 100ns intervals since 1582 to the number of nanoseconds since 1970, but it's relatively easy to get wrong, and it takes a few minutes of careful concentration. That's exactly the type of code you want in a library. I'll be fine, I've written my implementation and I can use it from now on. But for the next guy coming along, asking himself "but why 1582-10-15?", consider saving him the trouble.

Thanks for your consideration and your hard work on this great library.

(edit: just realized I posted this with a work account. I am the original requestor.)

@KodrAus
Copy link
Member

KodrAus commented May 10, 2019

Thanks for the extra details @jonathanstrong It sounds like we could do better with our v1 support (I don't personally use v1 UUIDs).

The main concern I have is introducing a dependency on chrono, which even though it's widely used it's not totally ubiquitous, and as I understand there are some overhauls of its API planned. I think we should be careful about pulling it in before 1.0 and painting ourselves into a corner.

What if we took this opportunity to change Uuid::to_timestamp so it's more convenient:

pub struct Timestamp {
    timestamp: u64,
    counter: u16
}

impl Uuid {
    pub fn new_v1<T>(
        context: &T,
        ts: Timestamp,
        node_id: &[u8],
    ) -> Result<Self, crate::BytesError>
    where
        T: ClockSequence,
    {
        ..
    }

    pub fn to_timestamp(&self) -> Option<Timestamp> { .. }
}

impl Timestamp {
    pub fn from_rfc4122(ts: u64, counter: u16) -> Self { .. }

    pub fn from_unix(secs: u64, nanos: u32) -> Self { .. }

    pub fn to_rfc4122(&self) -> (u64, u16) { .. }

    pub fn to_unix(&self) -> (u64, u32) { .. }
}

That way we could be consistent in the shape of the timestamp passed in and out, and manage the trickier conversion from RFC4122 timestamps into UNIX timestamps for wrapping up in chrono.

What do you think?

@KodrAus
Copy link
Member

KodrAus commented May 11, 2019

It might need a little more thought with the Context type though, which generates a sequence number based on an input UNIX timestamp 🤔

@KodrAus
Copy link
Member

KodrAus commented May 11, 2019

Could be:

pub struct Timestamp {
    timestamp: u64,
    counter: u16
}

impl Uuid {
    pub fn new_v1(
        ts: Timestamp,
        node_id: &[u8],
    ) -> Result<Self, crate::BytesError> {
        ..
    }

    pub fn to_timestamp(&self) -> Option<Timestamp> { .. }
}

impl Timestamp {
    pub fn from_rfc4122(ts: u64, counter: u16) -> Self { .. }

    pub fn from_unix<T: ClockSequence>(secs: u64, nanos: u32, context: &T) -> Self { .. }

    pub fn to_rfc4122(&self) -> (u64, u16) { .. }

    pub fn to_unix(&self) -> (u64, u32) { .. }
}

@kinggoesgaming
Copy link
Member

chrono is ubiquitous in rust ecosystem.

chrono maybe the defacto across the rust community, but uuid cannot have the implementation this PR currently provides, as it violates the C-STABLE API guideline (see #191).

I would suggest on following on what @KodrAus has proposed above

@jonathanstrong
Copy link
Contributor Author

The alternative api proposed by @KodrAus is a huge improvement over the status quo, from my perspective. I'd be happy to file an alternative pull request matching it, but it may take a few days for me to get to it.

On a related note, do you have any further information about the changes coming to chrono? I'm planning to file an issue to inquire about path to v1.0 and what still needs to be done.

jonathanstrong added a commit to jonathanstrong/uuid that referenced this pull request May 31, 2019
Draft implementation of improved timestamp api discussed in github issue uuid-rs#405.

Establishes new `Timestamp` struct in v1 mod that stores the (timestamp, counter) values that had been used previously. The core rationale/improvement is that `Timestamp` offers several conversion routines to/from RFC4122 and unix timestamp formats. The RFC4122 timestamp format in V1 UUIDs is relatively obscure -- this provides ready access to unix timestamp formats instead. The design also prevents possible confusion/misuse by use of clearly named constructors and ample documentation about the various formats in play.

Tests covering previously existing functionality updated to reflect new api and passing. New functionality untested.

Moved `Uuid::to_timestamp` from impl in src/lib.rs to v1 mod as it requires access to the new `Timestamp` struct defined in v1. Noting as this places `to_timestamp` behind the v1 feature gate, unlike prior to the change.
@jonathanstrong
Copy link
Contributor Author

fyi - pushed draft implementation on a different branch here. Still needs tests, but thought I would solicit early feedback and check-in. It follows the proposed api discussed here pretty closely.

@kinggoesgaming
Copy link
Member

cc @Dylan-DPC @KodrAus

KodrAus pushed a commit that referenced this pull request Oct 2, 2019
Draft implementation of improved timestamp api discussed in github issue #405.

Establishes new `Timestamp` struct in v1 mod that stores the (timestamp, counter) values that had been used previously. The core rationale/improvement is that `Timestamp` offers several conversion routines to/from RFC4122 and unix timestamp formats. The RFC4122 timestamp format in V1 UUIDs is relatively obscure -- this provides ready access to unix timestamp formats instead. The design also prevents possible confusion/misuse by use of clearly named constructors and ample documentation about the various formats in play.

Tests covering previously existing functionality updated to reflect new api and passing. New functionality untested.

Moved `Uuid::to_timestamp` from impl in src/lib.rs to v1 mod as it requires access to the new `Timestamp` struct defined in v1. Noting as this places `to_timestamp` behind the v1 feature gate, unlike prior to the change.
@KodrAus
Copy link
Member

KodrAus commented Oct 2, 2019

Hi @jonathanstrong 👋

Thanks for working on this! I'm working through some breaking changes and cleanups in #427.

Your proposed draft implementation looks good to me! So I've cherry-picked it into #427 and made a few tweaks, which you can see here.

bors bot added a commit that referenced this pull request Oct 4, 2019
427: Breaking API features and refactoring for `0.8` r=kinggoesgaming a=KodrAus

**I'm submitting a(n)** (|feature|refactor|)

# Description

## Modules and Errors

This PR is a broad refactoring of our error types and module layout. The general pattern I've gone for is:

- Define specific error types in submodules. These are currently private.
- Collect these specific error types in an opaque root `Error`. All methods return this single `Error` type so it's easier for consumers to carry `uuid::Error`s in their own code.

It'll also include some implementations for open PRs as we've been discussing. I imagine we'll want to spend time working through these changes :)

I've hidden our `prelude` module for now, because I'm not sure it's something we'll want to stabilize with (it's only got a few bits in it afterall).

## 128bit support

Re-enables support for 128-bit numbers in the form of constructors on `Uuid`, following the pattern that method names without an endian suffix are BE.

## No-std

Refactors our `no-std` support so we always mark the crate as `no-std` so our std imports are always the same. This simplifies our std/core support so we need fewer modules.

## Timestamps

Includes the design proposed in #405 for timestamp support originally implemented by @jonathanstrong.

# Related Issue(s)

- #406

# Related PR(s)

- #405 
- #416

Co-authored-by: Ashley Mannix <[email protected]>
Co-authored-by: Jonathan Strong <[email protected]>
@KodrAus
Copy link
Member

KodrAus commented Oct 4, 2019

Alrighty, now that #427 is merged I think we can close this one, since it includes an implementation of this feature.

Thanks again for working on this @jonathanstrong!

@KodrAus KodrAus closed this Oct 4, 2019
@jonathanstrong
Copy link
Contributor Author

@KodrAus definitely - appreciate you working with me to refine the idea and merging the code! Huge improvement for working with v1 embedded timestamps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants