Jump to content
Vincent Parrett

VSoft.UUIDv7 - a Delphi implementation of UUIDv7 (RFC 9562)

Recommended Posts

Hi All

I created a Delphi implementation of UUIDv7 - RFC 9562

UUIDv7 values are time-sortable, which means you can sort them in increasing order based on when they were generated.

 

https://github.com/VSoftTechnologies/VSoft.UUIDv7

 

Should work with XE2=12.x Win32/Win64 and all platforms on 11.3 or later.

 

Usage :
 

var
  guid : TGuid;
begin
  guid := TUUIDv7Helper.CreateV7;
  writeln(guid.ToString);
end;

 

  • Like 2
  • Thanks 6

Share this post


Link to post
Posted (edited)

Well done! Is it possible to extract datetime from a uuid?

Edited by Edwin Yip

Share this post


Link to post
30 minutes ago, Edwin Yip said:

Is it possible to extract datetime from a uuid?

for v7 yes - in theory just reverse the process that inserts the unix epoc timestamp and convert to datetime. 

 

I would have created a record helper for TGuid - except the rtl already has one (where all the useful methods are) - and only 1 helper per type can be in scope at a time.

Share this post


Link to post

Updated to add some new methods

 

type
  TUUIDV7Helper = record
    class function CreateV7 : TGuid;overload;static;
    class function CreateV7(const dt : TDateTime) : TGuid;overload;static;
    class function CreatedUTC(const guid : TGUID) : TDateTime;static;
    class function IsV7(const guid : TGuid) : boolean;static;inline;
    class function Version(const guid : TGuid) : integer;static;inline;
  end;

 

  • Like 1

Share this post


Link to post

Interesting, I also made my own implementation of GUID V7 a while back. Your implementation seems to be more optimized than mine.

 

function NewGuidV7: TGUID;
var
  LBytes: array[0..15] of Byte;
  LUnixMSec: Int64;
  I: Integer;
begin
  for I := 0 to 15 do
    LBytes[I] := Random($FF);

  LUnixMSec := DateTimeToMilliseconds(TTimeZone.Local.ToUniversalTime(Now)) - Int64(UnixDateDelta + DateDelta) * MSecsPerDay;
  LBytes[0] := (LUnixMSec shr 40) and $FF;
  LBytes[1] := (LUnixMSec shr 32) and $FF;
  LBytes[2] := (LUnixMSec shr 24) and $FF;
  LBytes[3] := (LUnixMSec shr 16) and $FF;
  LBytes[4] := (LUnixMSec shr 8) and $FF;
  LBytes[5] := LUnixMSec and $FF;
  LBytes[6] := (LBytes[6] and $0F) or $70;
  LBytes[8] := (LBytes[8] and $3F) or $80;

  Result := TGuid.Create(LBytes, 0, True);
end;

 

  • Like 1

Share this post


Link to post
1 hour ago, João Antônio Duarte said:

 


function NewGuidV7: TGUID;
var
  LBytes: array[0..15] of Byte;
  LUnixMSec: Int64;
  I: Integer;
begin
  for I := 0 to 15 do
    LBytes[I] := Random($FF);

  LUnixMSec := DateTimeToMilliseconds(TTimeZone.Local.ToUniversalTime(Now)) - Int64(UnixDateDelta + DateDelta) * MSecsPerDay;
  LBytes[0] := (LUnixMSec shr 40) and $FF;
  LBytes[1] := (LUnixMSec shr 32) and $FF;
  LBytes[2] := (LUnixMSec shr 24) and $FF;
  LBytes[3] := (LUnixMSec shr 16) and $FF;
  LBytes[4] := (LUnixMSec shr 8) and $FF;
  LBytes[5] := LUnixMSec and $FF;
  LBytes[6] := (LBytes[6] and $0F) or $70;
  LBytes[8] := (LBytes[8] and $3F) or $80;

  Result := TGuid.Create(LBytes, 0, True);
end;

 

Where is Byte[7] ?!!

 

Anyway, try this for fair comparison

function NewGuidV7: TGUID;
var
  LUnixMSec: Int64;
  LUnixMSecBytes: array[0..7] of Byte absolute LUnixMSec;
  LBytes: array[0..15] of Byte;
  I: Integer;
begin
  for I := 0 to 15 do
    LBytes[I] := Random($FF);

  LUnixMSec := DateTimeToMilliseconds(TTimeZone.Local.ToUniversalTime(Now)) - Int64(UnixDateDelta + DateDelta) * MSecsPerDay;
  LBytes[0] := LUnixMSecBytes[5];
  LBytes[1] := LUnixMSecBytes[4];
  LBytes[2] := LUnixMSecBytes[3];
  LBytes[3] := LUnixMSecBytes[2];
  LBytes[4] := LUnixMSecBytes[1];
  LBytes[5] := LUnixMSecBytes[0];

  {LBytes[0] := (LUnixMSec shr 40) and $FF;
  LBytes[1] := (LUnixMSec shr 32) and $FF;
  LBytes[2] := (LUnixMSec shr 24) and $FF;
  LBytes[3] := (LUnixMSec shr 16) and $FF;
  LBytes[4] := (LUnixMSec shr 8) and $FF;
  LBytes[5] := LUnixMSec and $FF;}
  LBytes[6] := (LBytes[6] and $0F) or $70;
  LBytes[8] := (LBytes[8] and $3F) or $80;

  Result := TGuid.Create(LBytes, 0, True);
end;

Fix LByte[7] as should, because in my sample it is missing too !

 

In case you did a benchmark then please share the result with us, (i didn't)

Share this post


Link to post

I rely on TGUID.NewGuid to generate the random parts - under the hood it uses CoCreateGuid which uses windows cryptographic apis - which are far more random than Delphi's Random function (and faster).  

  • Like 4

Share this post


Link to post
14 hours ago, Vincent Parrett said:

I rely on TGUID.NewGuid to generate the random parts - under the hood it uses CoCreateGuid which uses windows cryptographic apis - which are far more random than Delphi's Random function (and faster).  

I ran a benchmark test and found that your implementation is much more performant than mine.
Congratulations on the great work!

  • Like 1
  • Thanks 2

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×