11namespace System ;
22
33/// <summary>
4- /// Provides polyfills for GUID generation methods not available in older .NET versions.
4+ /// Provides polyfills for GUID generation methods not available in older .NET versions,
5+ /// with monotonic counter-based ordering for strict intra-millisecond sequencing.
56/// </summary>
67internal static class GuidPolyfills
78{
@@ -10,12 +11,17 @@ internal static class GuidPolyfills
1011 private static readonly object s_lock = new ( ) ;
1112
1213 /// <summary>
13- /// Creates a monotonically increasing GUID using UUID v7 format with the specified timestamp.
14+ /// Creates a UUID v7 GUID with the specified timestamp.
1415 /// Uses a counter for intra-millisecond ordering to ensure strict monotonicity.
1516 /// </summary>
16- /// <param name="timestamp">The Unix timestamp in milliseconds to embed in the GUID.</param>
17- /// <returns>A new monotonically increasing GUID.</returns>
18- public static Guid CreateMonotonicUuid ( long timestamp )
17+ /// <param name="timestamp">The timestamp to embed in the GUID.</param>
18+ /// <returns>A new UUID v7 GUID.</returns>
19+ /// <remarks>
20+ /// Unlike the built-in <c>Guid.CreateVersion7(DateTimeOffset)</c> in .NET 9+,
21+ /// this implementation uses a counter to ensure strict monotonicity within the same millisecond,
22+ /// which is required for keyset pagination to work correctly.
23+ /// </remarks>
24+ public static Guid CreateVersion7 ( DateTimeOffset timestamp )
1925 {
2026 // UUID v7 format (RFC 9562):
2127 // - 48 bits: Unix timestamp in milliseconds (big-endian)
@@ -24,19 +30,20 @@ public static Guid CreateMonotonicUuid(long timestamp)
2430 // - 2 bits: variant (10)
2531 // - 62 bits: random
2632
33+ long timestampMs = timestamp . ToUnixTimeMilliseconds ( ) ;
2734 long counter ;
2835
2936 lock ( s_lock )
3037 {
31- if ( timestamp == s_lastTimestamp )
38+ if ( timestampMs == s_lastTimestamp )
3239 {
3340 // Same millisecond - increment counter
3441 s_counter ++ ;
3542 }
3643 else
3744 {
3845 // New millisecond - reset counter
39- s_lastTimestamp = timestamp ;
46+ s_lastTimestamp = timestampMs ;
4047 s_counter = 0 ;
4148 }
4249
@@ -56,12 +63,12 @@ public static Guid CreateMonotonicUuid(long timestamp)
5663#endif
5764
5865 // Set timestamp (48 bits, big-endian) in first 6 bytes
59- bytes [ 0 ] = ( byte ) ( timestamp >> 40 ) ;
60- bytes [ 1 ] = ( byte ) ( timestamp >> 32 ) ;
61- bytes [ 2 ] = ( byte ) ( timestamp >> 24 ) ;
62- bytes [ 3 ] = ( byte ) ( timestamp >> 16 ) ;
63- bytes [ 4 ] = ( byte ) ( timestamp >> 8 ) ;
64- bytes [ 5 ] = ( byte ) timestamp ;
66+ bytes [ 0 ] = ( byte ) ( timestampMs >> 40 ) ;
67+ bytes [ 1 ] = ( byte ) ( timestampMs >> 32 ) ;
68+ bytes [ 2 ] = ( byte ) ( timestampMs >> 24 ) ;
69+ bytes [ 3 ] = ( byte ) ( timestampMs >> 16 ) ;
70+ bytes [ 4 ] = ( byte ) ( timestampMs >> 8 ) ;
71+ bytes [ 5 ] = ( byte ) timestampMs ;
6572
6673 // Set version 7 (0111) in high nibble of byte 6, and high 4 bits of counter in low nibble
6774 bytes [ 6 ] = ( byte ) ( 0x70 | ( ( counter >> 8 ) & 0x0F ) ) ;
@@ -81,3 +88,4 @@ public static Guid CreateMonotonicUuid(long timestamp)
8188 bytes [ 12 ] , bytes [ 13 ] , bytes [ 14 ] , bytes [ 15 ] ) ;
8289 }
8390}
91+
0 commit comments