From 95877580d6a69738d42e770dffd774257cb6118f Mon Sep 17 00:00:00 2001 From: Onur Gumus Date: Wed, 1 Apr 2026 12:20:26 +0200 Subject: [PATCH 1/8] [All] Add support for Guid.CreateVersion7() and Guid.CreateVersion7(DateTimeOffset) Implement RFC 9562 UUID v7 generation across all targets (JS/TS, Python, Rust, Dart, Beam) with both parameterless and DateTimeOffset overloads. --- src/Fable.Cli/CHANGELOG.md | 4 +++ src/Fable.Compiler/CHANGELOG.md | 4 +++ src/Fable.Transforms/Beam/Replacements.fs | 4 +++ src/Fable.Transforms/Dart/Replacements.fs | 6 ++++ src/Fable.Transforms/Python/Replacements.fs | 6 ++++ src/Fable.Transforms/Replacements.fs | 6 ++++ src/Fable.Transforms/Rust/Replacements.fs | 4 +++ src/fable-library-beam/fable_guid.erl | 36 ++++++++++++++++++++- src/fable-library-py/fable_library/guid.py | 32 ++++++++++++++++++ src/fable-library-rust/Cargo.toml | 4 +-- src/fable-library-rust/src/Guid.rs | 12 +++++++ src/fable-library-ts/Guid.ts | 22 +++++++++++++ tests/Beam/GuidTests.fs | 21 ++++++++++++ tests/Js/Main/TypeTests.fs | 21 ++++++++++++ tests/Python/TestGuid.fs | 21 ++++++++++++ tests/Rust/tests/src/GuidTests.fs | 11 +++++++ 16 files changed, 211 insertions(+), 3 deletions(-) diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index cdc2dd1683..0748f399aa 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +* [All] Add support for `Guid.CreateVersion7()` and `Guid.CreateVersion7(DateTimeOffset)` + ## 5.0.0-rc.6 - 2026-03-31 ### Fixed diff --git a/src/Fable.Compiler/CHANGELOG.md b/src/Fable.Compiler/CHANGELOG.md index 045c355c7f..e916128cd5 100644 --- a/src/Fable.Compiler/CHANGELOG.md +++ b/src/Fable.Compiler/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +* [All] Add support for `Guid.CreateVersion7()` and `Guid.CreateVersion7(DateTimeOffset)` + ## 5.0.0-rc.12 - 2026-03-31 ### Fixed diff --git a/src/Fable.Transforms/Beam/Replacements.fs b/src/Fable.Transforms/Beam/Replacements.fs index 318675cf8a..7e08dc1891 100644 --- a/src/Fable.Transforms/Beam/Replacements.fs +++ b/src/Fable.Transforms/Beam/Replacements.fs @@ -3855,6 +3855,10 @@ let private guids Helper.LibCall(com, "fable_guid", "from_bytes", t, [ arg ], ?loc = r) |> Some | _ -> None | "NewGuid" -> Helper.LibCall(com, "fable_guid", "new_guid", t, [], ?loc = r) |> Some + | "CreateVersion7" -> + match args with + | [] -> Helper.LibCall(com, "fable_guid", "create_version7", t, [], ?loc = r) |> Some + | _ -> Helper.LibCall(com, "fable_guid", "create_version7", t, args, ?loc = r) |> Some | "Parse" -> match args with | [ StringConst literalGuid ] -> diff --git a/src/Fable.Transforms/Dart/Replacements.fs b/src/Fable.Transforms/Dart/Replacements.fs index ae56c31014..9a8448239d 100644 --- a/src/Fable.Transforms/Dart/Replacements.fs +++ b/src/Fable.Transforms/Dart/Replacements.fs @@ -3450,6 +3450,12 @@ let guids match i.CompiledName with | "NewGuid" -> Helper.LibCall(com, "Guid", "newGuid", t, []) |> Some + | "CreateVersion7" -> + match args with + | [] -> Helper.LibCall(com, "Guid", "createVersion7", t, []) |> Some + | _ -> + Helper.LibCall(com, "Guid", "createVersion7", t, args, i.SignatureArgTypes) + |> Some | "Parse" -> match args with | [ StringConst literalGuid ] -> parseGuid literalGuid diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index ce8f64fb6d..a1364a51b2 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -3478,6 +3478,12 @@ let guids = match i.CompiledName with | "NewGuid" -> Helper.LibCall(com, "guid", "new_guid", t, []) |> Some + | "CreateVersion7" -> + match args with + | [] -> Helper.LibCall(com, "guid", "create_version7", t, []) |> Some + | _ -> + Helper.LibCall(com, "guid", "create_version7", t, args, i.SignatureArgTypes) + |> Some | "Parse" -> Helper.LibCall(com, "guid", "parse", t, args, i.SignatureArgTypes) |> Some | "TryParse" -> Helper.LibCall(com, "guid", "try_parse", t, args, i.SignatureArgTypes) |> Some | "ToByteArray" -> diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index d6a69464ed..55fb9aa4f8 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -3822,6 +3822,12 @@ let guids match i.CompiledName with | "NewGuid" -> Helper.LibCall(com, "Guid", "newGuid", t, []) |> Some + | "CreateVersion7" -> + match args with + | [] -> Helper.LibCall(com, "Guid", "createVersion7", t, []) |> Some + | _ -> + Helper.LibCall(com, "Guid", "createVersion7", t, args, i.SignatureArgTypes) + |> Some | "Parse" -> match args with | [ StringConst literalGuid ] -> parseGuid literalGuid diff --git a/src/Fable.Transforms/Rust/Replacements.fs b/src/Fable.Transforms/Rust/Replacements.fs index 861aeb0763..377d1e9c34 100644 --- a/src/Fable.Transforms/Rust/Replacements.fs +++ b/src/Fable.Transforms/Rust/Replacements.fs @@ -3121,6 +3121,10 @@ let guids | _ -> None // | "Empty", None, [] -> // it's a static field, see tryField | "NewGuid", None, [] -> Helper.LibCall(com, "Guid", "new_guid", t, args, ?loc = r) |> Some + | "CreateVersion7", None, [] -> Helper.LibCall(com, "Guid", "create_version7", t, [], ?loc = r) |> Some + | "CreateVersion7", None, _ -> + Helper.LibCall(com, "Guid", "create_version7_with_timestamp", t, args, ?loc = r) + |> Some | "Parse", None, [ ExprType String ] -> Helper.LibCall(com, "Guid", "parse", t, args, ?loc = r) |> Some | "TryParse", None, [ ExprType String; _ ] -> Helper.LibCall(com, "Guid", "tryParse", t, args, ?loc = r) |> Some | "ToByteArray", Some x, [] -> Helper.LibCall(com, "Guid", "toByteArray", t, [ x ], ?loc = r) |> Some diff --git a/src/fable-library-beam/fable_guid.erl b/src/fable-library-beam/fable_guid.erl index a9ba857b70..074435f75d 100644 --- a/src/fable-library-beam/fable_guid.erl +++ b/src/fable-library-beam/fable_guid.erl @@ -1,9 +1,12 @@ -module(fable_guid). -export([ - new_guid/0, parse/1, try_parse/1, try_parse/2, from_bytes/1, to_byte_array/1, to_string_format/2 + new_guid/0, create_version7/0, create_version7/1, + parse/1, try_parse/1, try_parse/2, from_bytes/1, to_byte_array/1, to_string_format/2 ]). -spec new_guid() -> binary(). +-spec create_version7() -> binary(). +-spec create_version7(integer()) -> binary(). -spec parse(binary()) -> binary(). -spec try_parse(binary()) -> {boolean(), binary()}. -spec try_parse(binary(), reference()) -> boolean(). @@ -26,6 +29,37 @@ new_guid() -> ) ). +%% Generate a new UUID v7 (RFC 9562) with current timestamp. +create_version7() -> + Ms = erlang:system_time(millisecond), + create_version7(Ms). + +%% Generate a new UUID v7 (RFC 9562) with given DateTimeOffset or ms since Unix epoch. +create_version7({Ticks, _Kind, _Offset}) -> + %% DateTimeOffset tuple: convert .NET ticks to Unix epoch milliseconds + %% .NET ticks are 100ns intervals since 0001-01-01 + %% Unix epoch in .NET ticks = 621355968000000000 + Ms = (Ticks - 621355968000000000) div 10000, + create_version7(Ms); +create_version7(Timestamp) when is_integer(Timestamp) -> + <> = crypto:strong_rand_bytes(10), + RandA = RandA0 band 16#0FFF, + RandB = RandB0 band 16#3FFFFFFFFFFFFFFF, + %% 48-bit timestamp split into A (high 32 bits) and B (low 16 bits) + A = (Timestamp bsr 16) band 16#FFFFFFFF, + B = Timestamp band 16#FFFF, + %% Version 7: top 4 bits = 0111 + C = 16#7000 bor RandA, + %% Variant: top 2 bits = 10 + D = 16#8000 bor ((RandB bsr 48) band 16#3FFF), + E = RandB band 16#FFFFFFFFFFFF, + list_to_binary( + io_lib:format( + "~8.16.0b-~4.16.0b-~4.16.0b-~4.16.0b-~12.16.0b", + [A, B, C, D, E] + ) + ). + %% Construct a GUID from a 16-byte array (binary or list of integers). %% .NET uses mixed-endian: first 3 groups are little-endian, last 2 are big-endian. from_bytes({byte_array, _, _} = BA) -> diff --git a/src/fable-library-py/fable_library/guid.py b/src/fable-library-py/fable_library/guid.py index b1a291fe83..ffe1c95605 100644 --- a/src/fable-library-py/fable_library/guid.py +++ b/src/fable-library-py/fable_library/guid.py @@ -1,3 +1,5 @@ +import os +import time import uuid from .array_ import Array @@ -32,8 +34,38 @@ def array_to_guid(guid: Array[byte]) -> uuid.UUID: return uuid.UUID(bytes_le=bytes(guid)) +def create_version7(timestamp: object | None = None) -> uuid.UUID: + """Create a UUID v7 per RFC 9562.""" + if timestamp is None: + ms = int(time.time() * 1000) + else: + # DateTimeOffset has getTime() returning ms since epoch + ms = int(timestamp.getTime()) # type: ignore + + # Build 16 bytes per RFC 9562 + rand_bytes = os.urandom(10) + + # Bytes 0-5: 48-bit big-endian ms timestamp + ts_bytes = ms.to_bytes(6, byteorder="big") + + # Bytes 6-7: version (0111) + 12 bits rand_a + rand_a = int.from_bytes(rand_bytes[0:2], "big") + ver_rand_a = (0x7000 | (rand_a & 0x0FFF)).to_bytes(2, "big") + + # Bytes 8-9: variant (10) + 14 bits rand_b + rand_b_hi = int.from_bytes(rand_bytes[2:4], "big") + var_rand_b = (0x8000 | (rand_b_hi & 0x3FFF)).to_bytes(2, "big") + + # Bytes 10-15: 48 bits rand_b continued + rand_rest = rand_bytes[4:10] + + all_bytes = ts_bytes + ver_rand_a + var_rand_b + rand_rest + return uuid.UUID(bytes=bytes(all_bytes)) + + __all__ = [ "array_to_guid", + "create_version7", "guid_to_array", "new_guid", "parse", diff --git a/src/fable-library-rust/Cargo.toml b/src/fable-library-rust/Cargo.toml index 093e229017..1f738b2508 100644 --- a/src/fable-library-rust/Cargo.toml +++ b/src/fable-library-rust/Cargo.toml @@ -31,7 +31,7 @@ rust_decimal = { version = "1.40", features = ["maths"], default-features = fals startup = { version = "0.1", path = "vendored/startup", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -uuid = { version = "1.22", features = ["v4"], default-features = false, optional = true } +uuid = { version = "1.22", features = ["v4", "v7"], default-features = false, optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] -uuid = { version = "1.22", features = ["v4", "js"], default-features = false, optional = true } +uuid = { version = "1.22", features = ["v4", "v7", "js"], default-features = false, optional = true } diff --git a/src/fable-library-rust/src/Guid.rs b/src/fable-library-rust/src/Guid.rs index b7da890424..eca1af0547 100644 --- a/src/fable-library-rust/src/Guid.rs +++ b/src/fable-library-rust/src/Guid.rs @@ -52,4 +52,16 @@ pub mod Guid_ { pub fn toByteArray(x: Guid) -> Array { new_array(&x.0.to_bytes_le()) } + + pub fn create_version7() -> Guid { + Guid(Uuid::now_v7()) + } + + pub fn create_version7_with_timestamp(dto: crate::DateTimeOffset_::DateTimeOffset) -> Guid { + let millis = dto.toUnixTimeMilliseconds(); + let secs = (millis / 1000) as u64; + let nanos = ((millis % 1000) * 1_000_000) as u32; + let ts = uuid::Timestamp::from_unix(uuid::NoContext, secs, nanos); + Guid(Uuid::new_v7(ts)) + } } diff --git a/src/fable-library-ts/Guid.ts b/src/fable-library-ts/Guid.ts index b9a9ff2cd7..5bf7f31e78 100644 --- a/src/fable-library-ts/Guid.ts +++ b/src/fable-library-ts/Guid.ts @@ -74,6 +74,28 @@ export function newGuid() { return b; } +// RFC 9562 UUID v7 +export function createVersion7(timestamp?: Date): string { + const ms = timestamp != null ? timestamp.getTime() : Date.now(); + + // 48-bit timestamp as hex + const msHex = Math.floor(ms).toString(16).padStart(12, "0"); + + // random bits + const randA = Math.floor(Math.random() * 0x1000); // 12 bits + const randB1 = Math.floor(Math.random() * 0x4000); // 14 bits + const randB2 = Math.floor(Math.random() * 0x100000000); // 32 bits + const randB3 = Math.floor(Math.random() * 0x10000); // 16 bits + + const timeLow = msHex.slice(0, 8); + const timeMid = msHex.slice(8, 12); + const ver = (0x7000 | randA).toString(16).padStart(4, "0"); + const variantAndRandB = (0x8000 | randB1).toString(16).padStart(4, "0"); + const node = randB2.toString(16).padStart(8, "0") + randB3.toString(16).padStart(4, "0"); + + return `${timeLow}-${timeMid}-${ver}-${variantAndRandB}-${node}`; +} + // Maps for number <-> hex string conversion let _convertMapsInitialized = false; let _byteToHex: string[]; diff --git a/tests/Beam/GuidTests.fs b/tests/Beam/GuidTests.fs index c65a67317f..f0eccab334 100644 --- a/tests/Beam/GuidTests.fs +++ b/tests/Beam/GuidTests.fs @@ -21,6 +21,27 @@ let ``test Guid.NewGuid creates different guids`` () = let g2 = Guid.NewGuid() g = g2 |> equal false +[] +let ``test Guid.CreateVersion7 works`` () = + let g1 = Guid.CreateVersion7() + let g2 = Guid.CreateVersion7() + g1 = g2 |> equal false + let s1 = string g1 + equal 36 s1.Length + equal '7' s1.[14] + let variantChar = s1.[19] + (variantChar = '8' || variantChar = '9' || variantChar = 'a' || variantChar = 'b') |> equal true + +[] +let ``test Guid.CreateVersion7 with DateTimeOffset works`` () = + let dto = DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero) + let g1 = Guid.CreateVersion7(dto) + let s1 = string g1 + equal 36 s1.Length + equal '7' s1.[14] + let variantChar = s1.[19] + (variantChar = '8' || variantChar = '9' || variantChar = 'a' || variantChar = 'b') |> equal true + [] let ``test Guid equality works`` () = let s = "f46d24f0-183e-4fca-9f47-e382afdfde8b" diff --git a/tests/Js/Main/TypeTests.fs b/tests/Js/Main/TypeTests.fs index c97a5e73f4..2435ef4534 100644 --- a/tests/Js/Main/TypeTests.fs +++ b/tests/Js/Main/TypeTests.fs @@ -1119,6 +1119,27 @@ let tests = let g3 = Guid.Parse s1 g1 = g3 |> equal true + testCase "Guid.CreateVersion7 works" <| fun () -> + let g1 = Guid.CreateVersion7() + let g2 = Guid.CreateVersion7() + g1 = g2 |> equal false + let s1 = string g1 + equal 36 s1.Length + // Check version nibble is '7' + equal '7' s1.[14] + // Check variant nibble is 8, 9, a, or b + let variantChar = s1.[19] + (variantChar = '8' || variantChar = '9' || variantChar = 'a' || variantChar = 'b') |> equal true + + testCase "Guid.CreateVersion7 with DateTimeOffset works" <| fun () -> + let dto = DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero) + let g1 = Guid.CreateVersion7(dto) + let s1 = string g1 + equal 36 s1.Length + equal '7' s1.[14] + let variantChar = s1.[19] + (variantChar = '8' || variantChar = '9' || variantChar = 'a' || variantChar = 'b') |> equal true + testCase "Guid.Empty works" <| fun () -> let g1 = Guid.Empty string g1 |> equal "00000000-0000-0000-0000-000000000000" diff --git a/tests/Python/TestGuid.fs b/tests/Python/TestGuid.fs index 7505d68d71..bb5cc843a3 100644 --- a/tests/Python/TestGuid.fs +++ b/tests/Python/TestGuid.fs @@ -21,6 +21,27 @@ let ``test Guid.NewGuid creates different guids`` () = let g2 = Guid.NewGuid() g = g2 |> equal false +[] +let ``test Guid.CreateVersion7 works`` () = + let g1 = Guid.CreateVersion7() + let g2 = Guid.CreateVersion7() + g1 = g2 |> equal false + let s1 = string g1 + equal 36 s1.Length + equal '7' s1.[14] + let variantChar = s1.[19] + (variantChar = '8' || variantChar = '9' || variantChar = 'a' || variantChar = 'b') |> equal true + +[] +let ``test Guid.CreateVersion7 with DateTimeOffset works`` () = + let dto = DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero) + let g1 = Guid.CreateVersion7(dto) + let s1 = string g1 + equal 36 s1.Length + equal '7' s1.[14] + let variantChar = s1.[19] + (variantChar = '8' || variantChar = '9' || variantChar = 'a' || variantChar = 'b') |> equal true + [] let ``test Guid equality works`` () = let s = "f46d24f0-183e-4fca-9f47-e382afdfde8b" diff --git a/tests/Rust/tests/src/GuidTests.fs b/tests/Rust/tests/src/GuidTests.fs index 1cb2c84d00..0b76ab697f 100644 --- a/tests/Rust/tests/src/GuidTests.fs +++ b/tests/Rust/tests/src/GuidTests.fs @@ -49,6 +49,17 @@ let ``Guid.NewGuid works`` () = let g3 = Guid.Parse s1 g1 = g3 |> equal true +[] +let ``Guid.CreateVersion7 works`` () = + let g1 = Guid.CreateVersion7() + let g2 = Guid.CreateVersion7() + g1 = g2 |> equal false + let s1 = string g1 + equal 36 s1.Length + equal '7' s1.[14] + let variantChar = s1.[19] + (variantChar = '8' || variantChar = '9' || variantChar = 'a' || variantChar = 'b') |> equal true + // id is prefixed for guid creation as we check at compile time (if able) to create a string const [] let ``Guid.Parse works`` () = From 1d44d90a299b3dbbaaee9d4fb75939c63dbc6b75 Mon Sep 17 00:00:00 2001 From: Onur Gumus Date: Wed, 1 Apr 2026 16:30:35 +0200 Subject: [PATCH 2/8] Fix Rust Guid.CreateVersion7: use Timestamp::now instead of now_v7 --- src/fable-library-rust/src/Guid.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fable-library-rust/src/Guid.rs b/src/fable-library-rust/src/Guid.rs index eca1af0547..08321a2788 100644 --- a/src/fable-library-rust/src/Guid.rs +++ b/src/fable-library-rust/src/Guid.rs @@ -54,7 +54,8 @@ pub mod Guid_ { } pub fn create_version7() -> Guid { - Guid(Uuid::now_v7()) + let ts = uuid::Timestamp::now(uuid::NoContext); + Guid(Uuid::new_v7(ts)) } pub fn create_version7_with_timestamp(dto: crate::DateTimeOffset_::DateTimeOffset) -> Guid { From bc3c88a64d33bcabea6576e133ca4f7485b63e78 Mon Sep 17 00:00:00 2001 From: Onur Gumus Date: Wed, 1 Apr 2026 19:02:19 +0200 Subject: [PATCH 3/8] Simplify CreateVersion7 replacements: forward args directly --- src/Fable.Transforms/Beam/Replacements.fs | 5 +---- src/Fable.Transforms/Dart/Replacements.fs | 7 ++----- src/Fable.Transforms/Python/Replacements.fs | 7 ++----- src/Fable.Transforms/Replacements.fs | 7 ++----- 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/Fable.Transforms/Beam/Replacements.fs b/src/Fable.Transforms/Beam/Replacements.fs index 7e08dc1891..d260ea022c 100644 --- a/src/Fable.Transforms/Beam/Replacements.fs +++ b/src/Fable.Transforms/Beam/Replacements.fs @@ -3855,10 +3855,7 @@ let private guids Helper.LibCall(com, "fable_guid", "from_bytes", t, [ arg ], ?loc = r) |> Some | _ -> None | "NewGuid" -> Helper.LibCall(com, "fable_guid", "new_guid", t, [], ?loc = r) |> Some - | "CreateVersion7" -> - match args with - | [] -> Helper.LibCall(com, "fable_guid", "create_version7", t, [], ?loc = r) |> Some - | _ -> Helper.LibCall(com, "fable_guid", "create_version7", t, args, ?loc = r) |> Some + | "CreateVersion7" -> Helper.LibCall(com, "fable_guid", "create_version7", t, args, ?loc = r) |> Some | "Parse" -> match args with | [ StringConst literalGuid ] -> diff --git a/src/Fable.Transforms/Dart/Replacements.fs b/src/Fable.Transforms/Dart/Replacements.fs index 9a8448239d..5f8dd6f1f0 100644 --- a/src/Fable.Transforms/Dart/Replacements.fs +++ b/src/Fable.Transforms/Dart/Replacements.fs @@ -3451,11 +3451,8 @@ let guids match i.CompiledName with | "NewGuid" -> Helper.LibCall(com, "Guid", "newGuid", t, []) |> Some | "CreateVersion7" -> - match args with - | [] -> Helper.LibCall(com, "Guid", "createVersion7", t, []) |> Some - | _ -> - Helper.LibCall(com, "Guid", "createVersion7", t, args, i.SignatureArgTypes) - |> Some + Helper.LibCall(com, "Guid", "createVersion7", t, args, i.SignatureArgTypes) + |> Some | "Parse" -> match args with | [ StringConst literalGuid ] -> parseGuid literalGuid diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index a1364a51b2..7f1f9215c4 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -3479,11 +3479,8 @@ let guids match i.CompiledName with | "NewGuid" -> Helper.LibCall(com, "guid", "new_guid", t, []) |> Some | "CreateVersion7" -> - match args with - | [] -> Helper.LibCall(com, "guid", "create_version7", t, []) |> Some - | _ -> - Helper.LibCall(com, "guid", "create_version7", t, args, i.SignatureArgTypes) - |> Some + Helper.LibCall(com, "guid", "create_version7", t, args, i.SignatureArgTypes) + |> Some | "Parse" -> Helper.LibCall(com, "guid", "parse", t, args, i.SignatureArgTypes) |> Some | "TryParse" -> Helper.LibCall(com, "guid", "try_parse", t, args, i.SignatureArgTypes) |> Some | "ToByteArray" -> diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 55fb9aa4f8..0d82ed9084 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -3823,11 +3823,8 @@ let guids match i.CompiledName with | "NewGuid" -> Helper.LibCall(com, "Guid", "newGuid", t, []) |> Some | "CreateVersion7" -> - match args with - | [] -> Helper.LibCall(com, "Guid", "createVersion7", t, []) |> Some - | _ -> - Helper.LibCall(com, "Guid", "createVersion7", t, args, i.SignatureArgTypes) - |> Some + Helper.LibCall(com, "Guid", "createVersion7", t, args, i.SignatureArgTypes) + |> Some | "Parse" -> match args with | [ StringConst literalGuid ] -> parseGuid literalGuid From 285d0c448b3b323cd3ff5ca45ed7975b21f1a33b Mon Sep 17 00:00:00 2001 From: ncave <777696+ncave@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:28:25 -0700 Subject: [PATCH 4/8] [Rust] Fix guid v7 impl --- src/fable-library-rust/Cargo.toml | 4 ++-- src/fable-library-rust/src/DateTimeOffset.rs | 4 ++++ src/fable-library-rust/src/Guid.rs | 16 ++++++++-------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/fable-library-rust/Cargo.toml b/src/fable-library-rust/Cargo.toml index 1f738b2508..0244c55b69 100644 --- a/src/fable-library-rust/Cargo.toml +++ b/src/fable-library-rust/Cargo.toml @@ -31,7 +31,7 @@ rust_decimal = { version = "1.40", features = ["maths"], default-features = fals startup = { version = "0.1", path = "vendored/startup", optional = true } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -uuid = { version = "1.22", features = ["v4", "v7"], default-features = false, optional = true } +uuid = { version = "1.23", features = ["v4", "v7"], default-features = false, optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] -uuid = { version = "1.22", features = ["v4", "v7", "js"], default-features = false, optional = true } +uuid = { version = "1.23", features = ["v4", "v7", "js"], default-features = false, optional = true } diff --git a/src/fable-library-rust/src/DateTimeOffset.rs b/src/fable-library-rust/src/DateTimeOffset.rs index 419058d622..9f3ce85de5 100644 --- a/src/fable-library-rust/src/DateTimeOffset.rs +++ b/src/fable-library-rust/src/DateTimeOffset.rs @@ -277,6 +277,10 @@ pub mod DateTimeOffset_ { self.0.timestamp() } + pub fn toUnixTimeSubsecNanos(&self) -> u32 { + self.0.timestamp_subsec_nanos() + } + pub fn year(&self) -> i32 { self.0.year() } diff --git a/src/fable-library-rust/src/Guid.rs b/src/fable-library-rust/src/Guid.rs index 08321a2788..4037b7d3c1 100644 --- a/src/fable-library-rust/src/Guid.rs +++ b/src/fable-library-rust/src/Guid.rs @@ -1,9 +1,10 @@ #[cfg(feature = "guid")] pub mod Guid_ { + use crate::DateTimeOffset_::DateTimeOffset; use crate::NativeArray_::{new_array, Array}; use crate::Native_::{compare, MutCell}; use crate::String_::{string, toString}; - use uuid::Uuid; + use uuid::{NoContext, Timestamp, Uuid}; #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] pub struct Guid(Uuid); @@ -54,15 +55,14 @@ pub mod Guid_ { } pub fn create_version7() -> Guid { - let ts = uuid::Timestamp::now(uuid::NoContext); - Guid(Uuid::new_v7(ts)) + let dto = DateTimeOffset::utcNow(); + create_version7_with_timestamp(dto) } - pub fn create_version7_with_timestamp(dto: crate::DateTimeOffset_::DateTimeOffset) -> Guid { - let millis = dto.toUnixTimeMilliseconds(); - let secs = (millis / 1000) as u64; - let nanos = ((millis % 1000) * 1_000_000) as u32; - let ts = uuid::Timestamp::from_unix(uuid::NoContext, secs, nanos); + pub fn create_version7_with_timestamp(dto: DateTimeOffset) -> Guid { + let seconds = dto.toUnixTimeSeconds() as u64; + let subsec_nanos = dto.toUnixTimeSubsecNanos(); + let ts = Timestamp::from_unix(NoContext, seconds, subsec_nanos); Guid(Uuid::new_v7(ts)) } } From b27bd28701c9bb3db58bad7f53f16e20ac6ac172 Mon Sep 17 00:00:00 2001 From: ncave <777696+ncave@users.noreply.github.com> Date: Wed, 1 Apr 2026 17:41:14 -0700 Subject: [PATCH 5/8] Test guids v7 are monotonic --- src/Fable.Cli/CHANGELOG.md | 1 + src/Fable.Compiler/CHANGELOG.md | 1 + .../fable_library/date_offset.py | 12 +++++++++- tests/Beam/DateTimeOffsetTests.fs | 2 ++ tests/Beam/GuidTests.fs | 18 ++++++++++---- tests/Js/Main/TypeTests.fs | 17 +++++++++---- tests/Python/TestDateTimeOffset.fs | 2 ++ tests/Python/TestGuid.fs | 18 ++++++++++---- tests/Rust/tests/src/GuidTests.fs | 24 +++++++++++++++++-- 9 files changed, 80 insertions(+), 15 deletions(-) diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index 0748f399aa..7f577100c9 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* [Python] Fix `DateTimeOffset` millisecond constructor and property (by @ncave) * [All] Add support for `Guid.CreateVersion7()` and `Guid.CreateVersion7(DateTimeOffset)` ## 5.0.0-rc.6 - 2026-03-31 diff --git a/src/Fable.Compiler/CHANGELOG.md b/src/Fable.Compiler/CHANGELOG.md index e916128cd5..8ac54fe7f6 100644 --- a/src/Fable.Compiler/CHANGELOG.md +++ b/src/Fable.Compiler/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* [Python] Fix `DateTimeOffset` millisecond constructor and property (by @ncave) * [All] Add support for `Guid.CreateVersion7()` and `Guid.CreateVersion7(DateTimeOffset)` ## 5.0.0-rc.12 - 2026-03-31 diff --git a/src/fable-library-py/fable_library/date_offset.py b/src/fable-library-py/fable_library/date_offset.py index 22dbd0285c..a06c4219f2 100644 --- a/src/fable-library-py/fable_library/date_offset.py +++ b/src/fable-library-py/fable_library/date_offset.py @@ -149,7 +149,7 @@ def create( ms = 0 if python_offset is None: - dt = datetime(year, month, day, h, m, s, ms) + dt = datetime(year, month, day, h, m, s, ms * 1000) if offset is not None: offset_ms = int(time_span.total_microseconds(offset) / 1000) python_offset = timedelta(microseconds=float(time_span.total_microseconds(offset))) @@ -207,6 +207,14 @@ def second(d: DateTimeOffset) -> int: return d.second +def millisecond(d: DateTimeOffset) -> int: + return d.microsecond // 1000 + + +def microsecond(d: DateTimeOffset) -> int: + return d.microsecond % 1000 + + def op_subtraction(x: DateTimeOffset, y: DateTimeOffset | TimeSpan) -> DateTimeOffset | TimeSpan: if isinstance(y, TimeSpan): # Subtract TimeSpan from DateTimeOffset @@ -231,6 +239,8 @@ def op_subtraction(x: DateTimeOffset, y: DateTimeOffset | TimeSpan) -> DateTimeO "op_subtraction", "parse", "second", + "millisecond", + "microsecond", "try_parse", "utc_now", "year", diff --git a/tests/Beam/DateTimeOffsetTests.fs b/tests/Beam/DateTimeOffsetTests.fs index a0fe1da132..ae5d2cdd5f 100644 --- a/tests/Beam/DateTimeOffsetTests.fs +++ b/tests/Beam/DateTimeOffsetTests.fs @@ -35,6 +35,8 @@ let ``test DateTimeOffset constructors work`` () = d.Hour |> equal 13 d.Minute |> equal 23 d.Second |> equal 30 + let d2 = DateTimeOffset(2014, 10, 9, 13, 23, 30, 500, TimeSpan.Zero) + d2.Second + d2.Millisecond |> equal 530 [] let ``test DateTimeOffset.Offset works`` () = diff --git a/tests/Beam/GuidTests.fs b/tests/Beam/GuidTests.fs index f0eccab334..bdcb9c09e2 100644 --- a/tests/Beam/GuidTests.fs +++ b/tests/Beam/GuidTests.fs @@ -28,20 +28,30 @@ let ``test Guid.CreateVersion7 works`` () = g1 = g2 |> equal false let s1 = string g1 equal 36 s1.Length - equal '7' s1.[14] - let variantChar = s1.[19] + equal '7' s1[14] + let variantChar = s1[19] (variantChar = '8' || variantChar = '9' || variantChar = 'a' || variantChar = 'b') |> equal true [] let ``test Guid.CreateVersion7 with DateTimeOffset works`` () = let dto = DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero) let g1 = Guid.CreateVersion7(dto) + let g2 = Guid.CreateVersion7(dto) + g1 = g2 |> equal false let s1 = string g1 equal 36 s1.Length - equal '7' s1.[14] - let variantChar = s1.[19] + equal '7' s1[14] + let variantChar = s1[19] (variantChar = '8' || variantChar = '9' || variantChar = 'a' || variantChar = 'b') |> equal true +[] +let ``test Guid.CreateVersion7 is monotonic`` () = + [1 .. 100] + |> List.map (fun ms -> DateTimeOffset(2024, 1, 1, 0, 0, 0, ms, TimeSpan.Zero)) + |> List.map (fun dto -> Guid.CreateVersion7(dto)) + |> List.pairwise + |> List.iter (fun (g1, g2) -> g1 < g2 |> equal true) + [] let ``test Guid equality works`` () = let s = "f46d24f0-183e-4fca-9f47-e382afdfde8b" diff --git a/tests/Js/Main/TypeTests.fs b/tests/Js/Main/TypeTests.fs index 2435ef4534..986d434c15 100644 --- a/tests/Js/Main/TypeTests.fs +++ b/tests/Js/Main/TypeTests.fs @@ -1126,20 +1126,29 @@ let tests = let s1 = string g1 equal 36 s1.Length // Check version nibble is '7' - equal '7' s1.[14] + equal '7' s1[14] // Check variant nibble is 8, 9, a, or b - let variantChar = s1.[19] + let variantChar = s1[19] (variantChar = '8' || variantChar = '9' || variantChar = 'a' || variantChar = 'b') |> equal true testCase "Guid.CreateVersion7 with DateTimeOffset works" <| fun () -> let dto = DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero) let g1 = Guid.CreateVersion7(dto) + let g2 = Guid.CreateVersion7(dto) + g1 = g2 |> equal false let s1 = string g1 equal 36 s1.Length - equal '7' s1.[14] - let variantChar = s1.[19] + equal '7' s1[14] + let variantChar = s1[19] (variantChar = '8' || variantChar = '9' || variantChar = 'a' || variantChar = 'b') |> equal true + testCase "Guid.CreateVersion7 is monotonic" <| fun () -> + [1 .. 100] + |> List.map (fun ms -> DateTimeOffset(2024, 1, 1, 0, 0, 0, ms, TimeSpan.Zero)) + |> List.map (fun dto -> Guid.CreateVersion7(dto)) + |> List.pairwise + |> List.iter (fun (g1, g2) -> g1 < g2 |> equal true) + testCase "Guid.Empty works" <| fun () -> let g1 = Guid.Empty string g1 |> equal "00000000-0000-0000-0000-000000000000" diff --git a/tests/Python/TestDateTimeOffset.fs b/tests/Python/TestDateTimeOffset.fs index 8e39fe878c..59a59b3394 100644 --- a/tests/Python/TestDateTimeOffset.fs +++ b/tests/Python/TestDateTimeOffset.fs @@ -37,6 +37,8 @@ let ``test DateTimeOffset constructors work`` () = d.Hour |> equal 13 d.Minute |> equal 23 d.Second |> equal 30 + let d2 = DateTimeOffset(2014, 10, 9, 13, 23, 30, 500, TimeSpan.Zero) + d2.Second + d2.Millisecond |> equal 530 [] let ``test DateTimeOffset.Offset works`` () = diff --git a/tests/Python/TestGuid.fs b/tests/Python/TestGuid.fs index bb5cc843a3..04cf7aad89 100644 --- a/tests/Python/TestGuid.fs +++ b/tests/Python/TestGuid.fs @@ -28,20 +28,30 @@ let ``test Guid.CreateVersion7 works`` () = g1 = g2 |> equal false let s1 = string g1 equal 36 s1.Length - equal '7' s1.[14] - let variantChar = s1.[19] + equal '7' s1[14] + let variantChar = s1[19] (variantChar = '8' || variantChar = '9' || variantChar = 'a' || variantChar = 'b') |> equal true [] let ``test Guid.CreateVersion7 with DateTimeOffset works`` () = let dto = DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero) let g1 = Guid.CreateVersion7(dto) + let g2 = Guid.CreateVersion7(dto) + g1 = g2 |> equal false let s1 = string g1 equal 36 s1.Length - equal '7' s1.[14] - let variantChar = s1.[19] + equal '7' s1[14] + let variantChar = s1[19] (variantChar = '8' || variantChar = '9' || variantChar = 'a' || variantChar = 'b') |> equal true +[] +let ``test Guid.CreateVersion7 is monotonic`` () = + [1 .. 100] + |> List.map (fun ms -> DateTimeOffset(2024, 1, 1, 0, 0, 0, ms, TimeSpan.Zero)) + |> List.map (fun dto -> Guid.CreateVersion7(dto)) + |> List.pairwise + |> List.iter (fun (g1, g2) -> g1 < g2 |> equal true) + [] let ``test Guid equality works`` () = let s = "f46d24f0-183e-4fca-9f47-e382afdfde8b" diff --git a/tests/Rust/tests/src/GuidTests.fs b/tests/Rust/tests/src/GuidTests.fs index 0b76ab697f..976b9267bf 100644 --- a/tests/Rust/tests/src/GuidTests.fs +++ b/tests/Rust/tests/src/GuidTests.fs @@ -56,10 +56,30 @@ let ``Guid.CreateVersion7 works`` () = g1 = g2 |> equal false let s1 = string g1 equal 36 s1.Length - equal '7' s1.[14] - let variantChar = s1.[19] + equal '7' s1[14] + let variantChar = s1[19] (variantChar = '8' || variantChar = '9' || variantChar = 'a' || variantChar = 'b') |> equal true +[] +let ``Guid.CreateVersion7 with DateTimeOffset works`` () = + let dto = DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero) + let g1 = Guid.CreateVersion7(dto) + let g2 = Guid.CreateVersion7(dto) + g1 = g2 |> equal false + let s1 = string g1 + equal 36 s1.Length + equal '7' s1[14] + let variantChar = s1[19] + (variantChar = '8' || variantChar = '9' || variantChar = 'a' || variantChar = 'b') |> equal true + +[] +let ``Guid.CreateVersion7 with DateTimeOffset is monotonic`` () = + [1 .. 100] + |> List.map (fun ms -> DateTimeOffset(2024, 1, 1, 0, 0, 0, ms, TimeSpan.Zero)) + |> List.map (fun dto -> Guid.CreateVersion7(dto)) + |> List.pairwise + |> List.iter (fun (g1, g2) -> g1 < g2 |> equal true) + // id is prefixed for guid creation as we check at compile time (if able) to create a string const [] let ``Guid.Parse works`` () = From 5ea70116b9bedc3301a37429aa038e8c7d02b2d0 Mon Sep 17 00:00:00 2001 From: ncave <777696+ncave@users.noreply.github.com> Date: Thu, 2 Apr 2026 00:43:53 -0700 Subject: [PATCH 6/8] Code review fixes --- src/fable-library-py/fable_library/guid.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/fable-library-py/fable_library/guid.py b/src/fable-library-py/fable_library/guid.py index ffe1c95605..da5b07391b 100644 --- a/src/fable-library-py/fable_library/guid.py +++ b/src/fable-library-py/fable_library/guid.py @@ -1,9 +1,10 @@ -import os +import secrets import time import uuid from .array_ import Array from .core import FSharpRef, byte +from .date_offset import DateTimeOffset def parse(string: str) -> uuid.UUID: @@ -34,7 +35,7 @@ def array_to_guid(guid: Array[byte]) -> uuid.UUID: return uuid.UUID(bytes_le=bytes(guid)) -def create_version7(timestamp: object | None = None) -> uuid.UUID: +def create_version7(timestamp: DateTimeOffset | None = None) -> uuid.UUID: """Create a UUID v7 per RFC 9562.""" if timestamp is None: ms = int(time.time() * 1000) @@ -43,7 +44,7 @@ def create_version7(timestamp: object | None = None) -> uuid.UUID: ms = int(timestamp.getTime()) # type: ignore # Build 16 bytes per RFC 9562 - rand_bytes = os.urandom(10) + rand_bytes = secrets.token_bytes(10) # Bytes 0-5: 48-bit big-endian ms timestamp ts_bytes = ms.to_bytes(6, byteorder="big") From b5fdcdcd224c99d4274eeb9649fbd60d8cd866ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BB=D0=B0=D0=B2=D0=B0=20=D0=A3=D0=BA=D1=80=D0=B0?= =?UTF-8?q?=D1=97=D0=BD=D1=96!=20=D0=93=D0=B5=D1=80=D0=BE=D1=8F=D0=BC=20?= =?UTF-8?q?=D1=81=D0=BB=D0=B0=D0=B2=D0=B0!?= <777696+ncave@users.noreply.github.com> Date: Thu, 2 Apr 2026 02:35:43 -0700 Subject: [PATCH 7/8] Update src/fable-library-py/fable_library/guid.py Co-authored-by: Dag Brattli --- src/fable-library-py/fable_library/guid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fable-library-py/fable_library/guid.py b/src/fable-library-py/fable_library/guid.py index da5b07391b..114d12c21d 100644 --- a/src/fable-library-py/fable_library/guid.py +++ b/src/fable-library-py/fable_library/guid.py @@ -41,7 +41,7 @@ def create_version7(timestamp: DateTimeOffset | None = None) -> uuid.UUID: ms = int(time.time() * 1000) else: # DateTimeOffset has getTime() returning ms since epoch - ms = int(timestamp.getTime()) # type: ignore + ms = int(timestamp.getTime()) # Build 16 bytes per RFC 9562 rand_bytes = secrets.token_bytes(10) From 6bce8f952bdde8dd59e95fd992aa2bf36a6cc02b Mon Sep 17 00:00:00 2001 From: ncave <777696+ncave@users.noreply.github.com> Date: Thu, 2 Apr 2026 03:23:07 -0700 Subject: [PATCH 8/8] [JS/TS] Fix `Guid` to use cryptographically strong random values --- src/Fable.Cli/CHANGELOG.md | 4 +++- src/Fable.Compiler/CHANGELOG.md | 4 +++- src/fable-library-ts/Guid.ts | 25 +++++++++++++------------ 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index d1482782dd..ce1258499b 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -9,10 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -* [Python] Fix `DateTimeOffset` millisecond constructor and property (by @ncave) * [All] Add support for `Guid.CreateVersion7()` and `Guid.CreateVersion7(DateTimeOffset)` + ### Fixed +* [JS/TS] Fix `Guid` to use cryptographically strong random values (by @ncave) +* [Python] Fix `DateTimeOffset` millisecond constructor and property (by @ncave) * [Python] Fix `String.IndexOf`/`LastIndexOf` with `StringComparison` argument emitting it as a start-index instead of a compile error (by @repo-assist) * [Beam] Fix `String.IndexOf`/`LastIndexOf` with `StringComparison` argument incorrectly treating the enum value as a start index diff --git a/src/Fable.Compiler/CHANGELOG.md b/src/Fable.Compiler/CHANGELOG.md index 81d85a92d8..1fe7753e52 100644 --- a/src/Fable.Compiler/CHANGELOG.md +++ b/src/Fable.Compiler/CHANGELOG.md @@ -9,10 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -* [Python] Fix `DateTimeOffset` millisecond constructor and property (by @ncave) * [All] Add support for `Guid.CreateVersion7()` and `Guid.CreateVersion7(DateTimeOffset)` + ### Fixed +* [JS/TS] Fix `Guid` to use cryptographically strong random values (by @ncave) +* [Python] Fix `DateTimeOffset` millisecond constructor and property (by @ncave) * [Python] Fix `String.IndexOf`/`LastIndexOf` with `StringComparison` argument emitting it as a start-index instead of a compile error (by @repo-assist) * [Beam] Fix `String.IndexOf`/`LastIndexOf` with `StringComparison` argument incorrectly treating the enum value as a start index diff --git a/src/fable-library-ts/Guid.ts b/src/fable-library-ts/Guid.ts index 5bf7f31e78..e74d2011a0 100644 --- a/src/fable-library-ts/Guid.ts +++ b/src/fable-library-ts/Guid.ts @@ -63,15 +63,13 @@ export function tryParse(str: string, defValue: FSharpRef): boolean { } } -// From https://gist.github.com/LeverOne/1308368 export function newGuid() { - let b = ""; - for (let a = 0; a++ < 36;) { - b += a * 51 & 52 - ? (a ^ 15 ? 8 ^ Math.random() * (a ^ 20 ? 16 : 4) : 4).toString(16) - : "-"; - } - return b; + const bytes = new Uint8Array(16); + crypto.getRandomValues(bytes); + bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4 + bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 1 + const hex = Array.from(bytes, b => b.toString(16).padStart(2, "0")).join(""); + return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; } // RFC 9562 UUID v7 @@ -82,10 +80,13 @@ export function createVersion7(timestamp?: Date): string { const msHex = Math.floor(ms).toString(16).padStart(12, "0"); // random bits - const randA = Math.floor(Math.random() * 0x1000); // 12 bits - const randB1 = Math.floor(Math.random() * 0x4000); // 14 bits - const randB2 = Math.floor(Math.random() * 0x100000000); // 32 bits - const randB3 = Math.floor(Math.random() * 0x10000); // 16 bits + const bytes = new Uint8Array(10); + crypto.getRandomValues(bytes); + const view = new DataView(bytes.buffer); + const randA = view.getUint16(0) & 0x0fff; // 12 bits + const randB1 = view.getUint16(2) & 0x3fff; // 14 bits + const randB2 = view.getUint32(4); // 32 bits + const randB3 = view.getUint16(8); // 16 bits const timeLow = msHex.slice(0, 8); const timeMid = msHex.slice(8, 12);