-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
[API Proposal]: Add ToDateTimeOffset and TryToDateTimeOffset to System.Guid #107136
Comments
Tagging subscribers to this area: @dotnet/area-system-datetime |
Tagging subscribers to this area: @dotnet/area-system-runtime |
Isn't the date used only as part of the Why not just expose a nullable property on the struct? var timestamp = guid7.Timestamp; |
Thanks for the feedback @julealgon
I see what you mean. The new properties makes more sense because, as far as I understand, version and variant are part of the fundamental uuid standard. I may be mistaken. However the timestamp in the 6 first bytes (with big-endian ordering) is only valid for v7. But the Guid type is not version specific. And so in that way both the methods I suggested, as well as the property you suggested, is a little misleading. Something specific on something generic. I would still argue the benefit of having some way to retrieve the timestamp from the v7 Guid as part of dotnet for such things as validation purposes. I suspect it will be a highly copied extension method otherwise. And it would encapsulate the fact that the underlying byte array in the Guid is on little-endian format, which is confusing to say the least. It makes more sense to me to have it as methods instead as a property, because of the "something specific on something generic"-argument. If the Guid is a well formatted v7 uuid both methods work as expected. However if it's not, the try variant won't throw an exception and the other will. Maybe the methods should have more explicit names? Maybe it is enough to be specific in the xml docs? |
Given an arbitrary GUID, even one that is That is, per the RFC we have (roughly):
Thus, while you have 48 guaranteed bits of precision (assuming the This rather seems like the type of thing where if you are wanting to extract the data back out of a |
Wikipedia actually lists the V7 timestamp portion as:
.... which means I see all sorts of fun implementations in the future.... |
So, attempting to boil the arguments against this API proposal thus far down to a one-liner:
Is that about right? If so - does not the same hold true for the new
I would argue there is room to expose additional overloads of the proposed methods to do the same thing as you suggested for |
The consideration is that APIs for creating a On the other hand, there is no trivial way to then recover that information back out. At best we're providing APIs that are expecting the user to pass in correct values that track how the state is represented. That is, we'd have to provide something like At that point, it almost becomes easier for the domains that do need to extract the |
Thanks for the quick response! You bring some sound arguments to the table @tannergooding 😅 Although some kind of helper methods to extract the DateTImeOffset from the Guid would be helpful, the leeway in the uuidv7 standard is too broad to be able to do so in any meaningful way from the dotnet runtime. I will let the issue remain open should the community have some input. However, feel free to close it. |
Meanwhile this extension method will do the job: public unsafe static DateTimeOffset ParseDateTimeFromGuidv7(this Guid guid)
{
// Extract the first 4 bytes (_a field) as an int.
int a = *(int*)&guid; // Read the first 4 bytes
// Extract the next 2 bytes (_b field) as a short.
short b = *(short*)((byte*)&guid + sizeof(int)); // Read the next 2 bytes
// Combine the values to get the full 48-bit timestamp in milliseconds.
long unixTimestampMs = ((long)a << 16) | (ushort)b;
// Convert the Unix timestamp (milliseconds) back to DateTimeOffset.
return DateTimeOffset.FromUnixTimeMilliseconds(unixTimestampMs);
}
// alternative
public static DateTimeOffset ParseDateTimeFromGuidv7_UsingVector128(this Guid guid)
{
// Read the GUID into a Vector128<long>
Vector128<long> vector = Unsafe.ReadUnaligned<Vector128<long>>(
ref Unsafe.As<Guid, byte>(ref Unsafe.AsRef(in guid)));
// Extract the first 64 bits (two longs)
long firstLong = vector.GetElement(0);
// Directly combine the 32 bits (high) and 16 bits (low) into a 48-bit timestamp
long unixTimestampMs = ((firstLong & 0xFFFFFFFF) << 16) | ((firstLong >> 32) & 0xFFFF);
// Convert to DateTimeOffset
return DateTimeOffset.FromUnixTimeMilliseconds(unixTimestampMs);
}
// usage:
// DateTimeOffset dt = DateTimeOffset.UtcNow;
// Guid guid = Guid.CreateVersion7(dt);
// Console.WriteLine(dt.ToUnixTimeMilliseconds() == guid.ParseDateTimeFromGuidv7().ToUnixTimeMilliseconds());
// Console.WriteLine(dt.ToUnixTimeMilliseconds() == guid.ParseDateTimeFromGuidv7_UsingVector128().ToUnixTimeMilliseconds()); (I was trying to optimize Note that the calculation of GUDI v7 skips the first 16 bits of since-epoch timestamp milliseconds. Which is not a problem because the first time 16 bit is set is cc @gatapia per #103658 (comment) |
This isn't safe and can cause undefined behavior on platforms due to a mismatch between the natural alignments of the types.
Not requiring APIs involving Use of such APIs can also cause issues like GC holes or other unintuitive/dangerous problems and there is a general proposal that some kind of |
Wonderful explanation! Thanks 😄 |
Closing this as per the comments above. This isn't something that can be trivially exposed and is fairly domain/scenario specific due to the way the format is actually defined. |
Background and motivation
Dotnet 9 preview adds support for
Guid.CreateVersion7()
andGuid.CreateVersion7(DateTimeOffset timestamp)
. Alongside the new static methods comes the propertiesVersion
andVariant
to further support the concept of distinct types of uuids represented by theGuid
.While there exists a way to create a
Guid
on version 7 and passing down aDateTimeOffset
timestamp, there is no native way to retrieve the timestamp from theGuid
.Why would you need it?
One example is as follows. Say you have a web API that allows for user defined identifiers, but only on uuid v7 format within a timeframe. Say this id is used as a primary key in a relational database (postgres). I would argue that not validating the timestamp defeats the purpose as users could create uuids that would massively impact the clustering of the primary key index. The
Version
andVariant
is enough to validate the first part. However, to validate the period one would need to extract the timestamp.API Proposal
API Usage
Alternative Designs
No response
Risks
No response
The text was updated successfully, but these errors were encountered: