Last active
March 17, 2021 03:33
-
-
Save fabiolimace/6db9747f83b02e62db55afed8461ee5b to your computer and use it in GitHub Desktop.
UUIDv7 generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/python3 | |
| # | |
| # This is an proposed implementation for this PR: https://github.com/uuid6/uuid6-ietf-draft/pull/10 | |
| # | |
| # @date: 2021-03-06 | |
| # @author: Fabio Lima | |
| # | |
| # Changelog: | |
| # - 2021-03-16: add parameter `subsec_decimal_digits` to function `uuid7()` | |
| # - 2021-03-08: use `time.time_ns()` instead of `time.time()` to fix nanosecond precision | |
| # | |
| from uuid import UUID | |
| from time import time_ns | |
| from random import randint | |
| TOTAL_BITS=128 | |
| VERSION_BITS = 4 | |
| VARIANT_BITS = 2 | |
| # Binary digits before the binary point | |
| SEC_BITS=38 | |
| # Binary digits after the binary point | |
| SUBSEC_BITS_S=0 | |
| SUBSEC_BITS_MS=10 | |
| SUBSEC_BITS_US=20 | |
| SUBSEC_BITS_NS=30 | |
| SUBSEC_BITS_DEFAULT=SUBSEC_BITS_NS | |
| # Decimal digits after the decimal point | |
| SUBSEC_DECIMAL_DIGITS_S=0 # 0 | |
| SUBSEC_DECIMAL_DIGITS_MS=3 # 0.999 | |
| SUBSEC_DECIMAL_DIGITS_US=6 # 0.999999 | |
| SUBSEC_DECIMAL_DIGITS_NS=9 # 0.999999999 | |
| SUBSEC_DECIMAL_DIGITS_DEFAULT=SUBSEC_DECIMAL_DIGITS_NS | |
| SLICE_MASK_0 = 0xffffffffffff00000000000000000000 | |
| SLICE_MASK_1 = 0x0000000000000fff0000000000000000 | |
| SLICE_MASK_2 = 0x00000000000000003fffffffffffffff | |
| def uuid7(t=None, subsec_bits=SUBSEC_BITS_DEFAULT, subsec_decimal_digits=SUBSEC_DECIMAL_DIGITS_DEFAULT): | |
| if t == None: | |
| t = time_ns() | |
| i = get_integer_part(t) | |
| f = get_fractional_part(t, subsec_decimal_digits) | |
| sec = i | |
| subsec = round(f * (2 ** subsec_bits)) | |
| node_bits = (TOTAL_BITS - VERSION_BITS - VARIANT_BITS - SEC_BITS - subsec_bits) | |
| uuid_sec = sec << (subsec_bits + node_bits) | |
| uuid_subsec = subsec << node_bits | |
| uuid_node = randint(0, (2 ** node_bits)) | |
| uuid_int = uuid_sec | uuid_subsec | uuid_node # 122 bits | |
| uuid_int = __add_version__(uuid_int) # 128 bits | |
| return UUID(int=uuid_int) | |
| def uuid7_s(t=None): | |
| return uuid7(t, SUBSEC_BITS_S, SUBSEC_DECIMAL_DIGITS_S) | |
| def uuid7_ms(t=None): | |
| return uuid7(t, SUBSEC_BITS_MS, SUBSEC_DECIMAL_DIGITS_MS) | |
| def uuid7_us(t=None): | |
| return uuid7(t, SUBSEC_BITS_US, SUBSEC_DECIMAL_DIGITS_US) | |
| def uuid7_ns(t=None): | |
| return uuid7(t, SUBSEC_BITS_NS, SUBSEC_DECIMAL_DIGITS_NS) | |
| def __add_version__(uuid_int): | |
| slice_mask_0 = SLICE_MASK_0 >> (VERSION_BITS + VARIANT_BITS) | |
| slice_mask_1 = SLICE_MASK_1 >> (VARIANT_BITS) | |
| slice_mask_2 = SLICE_MASK_2 | |
| slice_0 = (uuid_int & slice_mask_0) << (VERSION_BITS + VARIANT_BITS) | |
| slice_1 = (uuid_int & slice_mask_1) << (VARIANT_BITS) | |
| slice_2 = (uuid_int & slice_mask_2) | |
| uuid_output = slice_0 | slice_1 | slice_2 | |
| uuid_output = uuid_output & 0xffffffffffff0fff3fffffffffffffff # clear version | |
| uuid_output = uuid_output | 0x00000000000070008000000000000000 # apply version | |
| return uuid_output | |
| def __rem_version__(uuid_int): | |
| slice_0 = (uuid_int & SLICE_MASK_0) >> (VERSION_BITS + VARIANT_BITS) | |
| slice_1 = (uuid_int & SLICE_MASK_1) >> (VARIANT_BITS) | |
| slice_2 = (uuid_int & SLICE_MASK_2) | |
| uuid_output = slice_0 | slice_1 | slice_2 | |
| return uuid_output | |
| def get_integer_part(t): | |
| SUBSEC_DECIMAL_DIGITS_PYTHON=9 | |
| subsec_decimal_divisor = (10 ** SUBSEC_DECIMAL_DIGITS_PYTHON) | |
| return int(t / subsec_decimal_divisor) | |
| def get_fractional_part(t, subsec_decimal_digits=SUBSEC_DECIMAL_DIGITS_DEFAULT): | |
| SUBSEC_DECIMAL_DIGITS_PYTHON=9 | |
| subsec_decimal_divisor = (10 ** SUBSEC_DECIMAL_DIGITS_PYTHON) | |
| return round((t % subsec_decimal_divisor) / subsec_decimal_divisor, subsec_decimal_digits) | |
| def extract_sec(uuid): | |
| uuid_int = __rem_version__(uuid.int) | |
| uuid_sec = uuid_int >> (TOTAL_BITS - VERSION_BITS - VARIANT_BITS - SEC_BITS) | |
| return uuid_sec | |
| def extract_subsec(uuid, subsec_bits=SUBSEC_BITS_DEFAULT, subsec_decimal_digits=SUBSEC_DECIMAL_DIGITS_DEFAULT): | |
| uuid_int = __rem_version__(uuid.int) | |
| node_bits = (TOTAL_BITS - VERSION_BITS - VARIANT_BITS - SEC_BITS - subsec_bits) | |
| uuid_subsec = (uuid_int >> node_bits) & ((1 << (subsec_bits)) - 1) | |
| return round(uuid_subsec / (2 ** subsec_bits), subsec_decimal_digits) | |
| def list(): | |
| print("UUIDv7 sec in sec out subsec in subsec out") | |
| for i in range(10): | |
| t = time_ns() | |
| u = uuid7(t) | |
| i = get_integer_part(t) | |
| f = get_fractional_part(t) | |
| sec = extract_sec(u) | |
| subsec = extract_subsec(u) | |
| print(u, str(i).ljust(12), str(sec).ljust(12), str(f).ljust(12), str(subsec).ljust(12)) | |
| list() | |
| # | |
| # OUTPUT: | |
| # | |
| # UUIDv7 sec in sec out subsec in subsec out | |
| # 018145e0-5fc5-7a65-ae82-507fbfe22749 1615951895 1615951895 0.943017418 0.943017418 | |
| # 018145e0-5fc5-7bad-b660-9ddd65978a4c 1615951895 1615951895 0.943095648 0.943095648 | |
| # 018145e0-5fc5-7c84-a49c-a9797ed70dc4 1615951895 1615951895 0.943146842 0.943146842 | |
| # 018145e0-5fc5-7d46-b435-4c76da9142c5 1615951895 1615951895 0.943193153 0.943193153 | |
| # 018145e0-5fc5-7de2-8716-009c3130d332 1615951895 1615951895 0.943230178 0.943230178 | |
| # 018145e0-5fc5-7e74-9231-05a35740fba0 1615951895 1615951895 0.943265028 0.943265028 | |
| # 018145e0-5fc5-7f4a-8022-e1ba32698bc3 1615951895 1615951895 0.943315983 0.943315983 | |
| # 018145e0-5fc5-7fd5-a568-b1d7bc223a91 1615951895 1615951895 0.943349262 0.943349262 | |
| # 018145e0-5fc6-7062-8b99-ba3f383d9b8e 1615951895 1615951895 0.943382783 0.943382783 | |
| # 018145e0-5fc6-70ed-98ce-fa5bf04836d8 1615951895 1615951895 0.943415972 0.943415972 | |
| # | |
| # | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment