Skip to content

Commit 00b4f0f

Browse files
authored
Draft 03 (#34)
* Draft 03
1 parent 69dd680 commit 00b4f0f

File tree

3 files changed

+58
-73
lines changed

3 files changed

+58
-73
lines changed

README.md

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -34,62 +34,59 @@ assert my_uuid < uuid7()
3434
```
3535
0 1 2 3
3636
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
37-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
38-
| time_high |
39-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
40-
| time_mid | time_low_and_version |
41-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
42-
|clk_seq_hi_res | clk_seq_low | node (0-1) |
43-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
44-
| node (2-5) |
45-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
37+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
38+
| time_high |
39+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
40+
| time_mid | time_low_and_version |
41+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
42+
|clk_seq_hi_res | clk_seq_low | node (0-1) |
43+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
44+
| node (2-5) |
45+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4646
```
4747

4848
## UUIDv7 Field and Bit Layout
4949

50-
### [Draft 02][draft 02]
50+
### [Draft 03][draft 03]
5151

5252
```
5353
0 1 2 3
5454
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
55-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
56-
| unixts |
57-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
58-
|unixts | subsec_a | ver | subsec_b |
59-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
60-
|var| subsec_seq_node |
61-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
62-
| subsec_seq_node |
63-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
55+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
56+
| unix_ts_ms |
57+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
58+
| unix_ts_ms | ver | rand_a |
59+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
60+
|var| rand_b |
61+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
62+
| rand_b |
63+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
6464
```
6565

6666
### This implementation
6767

6868
```
6969
0 1 2 3
7070
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
71-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
72-
| unixts |
73-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
74-
|unixts | subsec_a | ver | subsec_b |
75-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
76-
|var| subsec_c | rand |
77-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
78-
| rand |
79-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
71+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
72+
| unix_ts_ms |
73+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
74+
| unix_ts_ms | ver | subsec_a |
75+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
76+
|var| subsec_b | rand |
77+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
78+
| rand |
79+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
8080
```
8181

82-
- `unixts`: 36 bit big-endian unsigned Unix Timestamp value
83-
- `subsec_a`: 12 bits allocated to sub-second precision values
82+
- `unix_ts_ms`: 48 bit big-endian unsigned number of Unix epoch timestamp with millisecond level of precision
8483
- `ver`: The 4 bit UUIDv7 version (0111)
85-
- `subsec_b`: 12 bits allocated to sub-second precision values
84+
- `subsec_a`: 12 bits allocated to sub-second precision values
8685
- `var`: 2 bit UUID variant (10)
87-
- `subsec_c`: 6 bits allocated to sub-second precision values
88-
- `rand`: The remaining 56 bits are filled with pseudo-random data
89-
90-
30 bits dedicated to sub-second precision provide nanosecond resolution. The `unixts` and `subsec` fields guarantee the order of UUIDs generated within the same nanosecond by monotonically incrementing the timer.
86+
- `subsec_b`: 8 bits allocated to sub-second precision values
87+
- `rand`: The remaining 54 bits are filled with pseudo-random data
9188

92-
This implementation does not include a clock sequence counter as defined in the draft RFC.
89+
20 extra bits dedicated to sub-second precision provide nanosecond resolution. The `unix_ts` and `subsec` fields guarantee the order of UUIDs generated within the same nanosecond by monotonically incrementing the timer.
9390

9491
## Performance
9592

@@ -126,6 +123,6 @@ Mean +- std dev: 7.51 us +- 1.42 us
126123
```
127124

128125
[ietf draft]: https://github.com/uuid6/uuid6-ietf-draft
129-
[draft 02]: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-02#section-4.4
126+
[draft 03]: https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#section-5.2
130127
[cloud shell]: https://cloud.google.com/shell/docs
131128
[bench]: https://github.com/oittaa/uuid6-python/blob/main/bench.sh

src/uuid6/__init__.py

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,7 @@ def __init__(
5151

5252
@property
5353
def subsec(self) -> int:
54-
return (
55-
(self.time_mid & 0x0FFF) << 18
56-
| (self.time_hi_version & 0x0FFF) << 6
57-
| self.clock_seq_hi_variant & 0x3F
58-
)
54+
return ((self.int >> 64) & 0x0FFF) << 8 | ((self.int >> 54) & 0xFF)
5955

6056
@property
6157
def time(self) -> int:
@@ -66,20 +62,16 @@ def time(self) -> int:
6662
| (self.time_hi_version & 0x0FFF)
6763
)
6864
if self.version == 7:
69-
return self.unixts * 10**9 + _subsec_decode(self.subsec)
65+
return (self.int >> 80) * 10**6 + _subsec_decode(self.subsec)
7066
return super().time
7167

72-
@property
73-
def unixts(self) -> int:
74-
return self.time_low << 4 | self.time_mid >> 12
75-
7668

7769
def _subsec_decode(value: int) -> int:
78-
return -(-value * 10**9 // 2**30)
70+
return -(-value * 10**6 // 2**20)
7971

8072

8173
def _subsec_encode(value: int) -> int:
82-
return value * 2**30 // 10**9
74+
return value * 2**20 // 10**6
8375

8476

8577
_last_v6_timestamp = None
@@ -127,15 +119,13 @@ def uuid7() -> UUID:
127119
if _last_v7_timestamp is not None and nanoseconds <= _last_v7_timestamp:
128120
nanoseconds = _last_v7_timestamp + 1
129121
_last_v7_timestamp = nanoseconds
130-
timestamp_s, timestamp_ns = divmod(nanoseconds, 10**9)
122+
timestamp_ms, timestamp_ns = divmod(nanoseconds, 10**6)
131123
subsec = _subsec_encode(timestamp_ns)
132-
subsec_a = subsec >> 18
133-
subsec_b = (subsec >> 6) & 0x0FFF
134-
subsec_c = subsec & 0x3F
135-
rand = secrets.randbits(56)
136-
uuid_int = (timestamp_s & 0x0FFFFFFFFF) << 92
137-
uuid_int |= subsec_a << 80
138-
uuid_int |= subsec_b << 64
139-
uuid_int |= subsec_c << 56
124+
subsec_a = subsec >> 8
125+
subsec_b = subsec & 0xFF
126+
rand = secrets.randbits(54)
127+
uuid_int = (timestamp_ms & 0xFFFFFFFFFFFF) << 80
128+
uuid_int |= subsec_a << 64
129+
uuid_int |= subsec_b << 54
140130
uuid_int |= rand
141131
return UUID(int=uuid_int, version=7)

test/test_uuid6.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import unittest
2-
from datetime import datetime
32
from time import time_ns
43
from unittest.mock import patch
54
from uuid import uuid1
@@ -91,17 +90,12 @@ def test_uuid6_far_in_future(self):
9190
def test_uuid7_far_in_future(self):
9291
with patch("time.time_ns", return_value=1):
9392
uuid_prev = uuid7()
94-
for i in range(1, 2170):
93+
for i in range(1, 8000):
9594
with patch("time.time_ns", return_value=i * YEAR_IN_NS):
9695
uuid_cur = uuid7()
9796
self.assertLess(uuid_prev, uuid_cur)
9897
uuid_prev = uuid_cur
9998

100-
# Overflow after 2 ** 36 seconds
101-
with patch("time.time_ns", return_value=2178 * YEAR_IN_NS):
102-
uuid_2178_from_epoch = uuid7()
103-
self.assertLess(uuid_2178_from_epoch, uuid_prev)
104-
10599
def test_time(self):
106100
uuid_1 = uuid1()
107101
uuid_6 = uuid6()
@@ -110,23 +104,27 @@ def test_time(self):
110104
uuid_7 = uuid7()
111105
self.assertAlmostEqual(uuid_7.time / 10**9, cur_time / 10**9, 3)
112106

113-
def test_time_zero(self):
107+
def test_zero_time(self):
114108
uuid_6 = UUID(hex="00000000-0000-6000-8000-000000000000")
115109
self.assertEqual(uuid_6.time, 0)
116110
uuid_7 = UUID(hex="00000000-0000-7000-8000-000000000000")
117111
self.assertEqual(uuid_7.time, 0)
118112

119-
def test_time_max(self):
113+
def test_max_time(self):
120114
uuid_6 = UUID(hex="ffffffff-ffff-6fff-bfff-ffffffffffff")
121115
self.assertEqual(uuid_6.time, 1152921504606846975)
122116
uuid_7 = UUID(hex="ffffffff-ffff-7fff-bfff-ffffffffffff")
123-
self.assertEqual(uuid_7.time, 68719476736000000000)
124-
dt = datetime.utcfromtimestamp(uuid_7.time / 10**9)
125-
self.assertEqual(dt, datetime(4147, 8, 20, 7, 32, 16))
117+
self.assertEqual(uuid_7.time, 281474976710656000000)
118+
119+
def test_uuid6_test_vector(self):
120+
uuid_6 = UUID(hex="1EC9414C-232A-6B00-B3C8-9E6BDECED846")
121+
self.assertEqual(uuid_6.time, 138648505420000000)
122+
uuid_1 = UUID(hex="C232AB00-9414-11EC-B3C8-9E6BDECED846")
123+
self.assertEqual(uuid_6.time, uuid_1.time)
126124

127-
def test_uuid7_from_hex(self):
128-
uuid_7 = UUID(hex="061d0edc-bea0-75cc-9892-f6295fd7d295")
129-
self.assertEqual(uuid_7.time, 1641082315914150976)
125+
def test_uuid7_test_vector(self):
126+
uuid_7 = UUID(hex="017F21CF-D130-7CC3-98C4-DC0C0C07398F")
127+
self.assertEqual(uuid_7.time // 10**6, 1645539742000)
130128

131129
def test_multiple_arguments(self):
132130
with self.assertRaises(TypeError):

0 commit comments

Comments
 (0)