Python Forum

Full Version: Yet another unique identifier generator
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
This small module generates unique ever 11 letters identifiers by converting the number of nanoseconds since 1970 to a string. I know there are many ways to generate unique strings, one of them being using the uuid module, but I like to have a simpler solution
#!/usr/bin/env python
# SPDX-FileCopyrightText: 2023 Eric Ringeisen
# SPDX-License-Identifier: MIT
"""Module idunique.py - generate unique identifiers
"""
from time import perf_counter_ns
import datetime as dt
from string import ascii_letters

__version__ = "2023.09.17"

TZINFO_UTC = dt.timezone.utc
UNIX_TIME_ZERO = dt.datetime(year=1970, month=1, day=1, tzinfo=TZINFO_UTC)
ASCII_LETTERS = str("").join(sorted(ascii_letters))
NLETTERS = len(ASCII_LETTERS)
NANO_PER_MICRO = 1000
NANO_PER_SEC = 1_000_000_000
NANO_PER_DAY = 24 * 3600 * NANO_PER_SEC


@(lambda x: x())
class static:
    """Helper class for Identifier implementation"""
    def __init__(self):
        tdelta = dt.datetime.now(tz=TZINFO_UTC) - UNIX_TIME_ZERO
        self.base = (
            tdelta.days * NANO_PER_DAY
            + tdelta.seconds * NANO_PER_SEC
            + tdelta.microseconds * NANO_PER_MICRO
        )
        self.zero = perf_counter_ns()
        self._xA = -ord("A")
        self._xa = -ord("a") + ASCII_LETTERS.index("a")

    @staticmethod
    def euclid(n: int):
        while n:
            n, r = divmod(n, NLETTERS)
            yield ASCII_LETTERS[r]

    @staticmethod
    def euclid_inv(s):
        n = 0
        for c in s:
            n = ord(c) + (static._xA if c < "a" else static._xa) + n * NLETTERS
        return n


class Identifier(str):
    @classmethod
    def new(cls):
        """Create a new unique Identifier based on time

        The Identifier is created by converting the approximate number of
        nanoseconds since the Unix time zero to a string of lowercase
        and uppercase letters. If created between 1975 and 2208, this string
        has exactly 11 letters.
        """
        d = 0
        while d == 0:
            p = perf_counter_ns()
            d = p - static.zero
        static.zero = p
        static.base += d
        return cls(("".join(static.euclid(static.base)))[::-1])

    def __int__(self):
        return static.euclid_inv(self)

    def timedelta(self):
        return dt.timedelta(microseconds=int(self) / 1000)

    def datetime(self):
        return UNIX_TIME_ZERO + self.timedelta()

    def sort_key(self):
        """Key to sort Identifiers according to their creation time.

        This is only useful if some are not between 1975 and 2208
        because all Identifiers between these dates have 11 characters"""
        return (len(self), str(self))


if __name__ == "__main__":
    for i in range(5):
        j = Identifier.new()
        print(j, int(j), j.datetime(), j.sort_key())
Output:
LljZOKloeke 1694859756292125518 2023-09-16 10:22:36.292126 (11, 'LljZOKloeke') LljZOKlpAfl 1694859756292184753 2023-09-16 10:22:36.292185 (11, 'LljZOKlpAfl') LljZOKlpJZM 1694859756292208752 2023-09-16 10:22:36.292209 (11, 'LljZOKlpJZM') LljZOKlpQcU 1694859756292227844 2023-09-16 10:22:36.292228 (11, 'LljZOKlpQcU') LljZOKlpWna 1694859756292244646 2023-09-16 10:22:36.292245 (11, 'LljZOKlpWna')
sounds like how linux time is generated https://unixtime.org/
(Sep-16-2023, 11:15 AM)Larz60+ Wrote: [ -> ]sounds like how linux time is generated https://unixtime.org/
I updated the generator by replacing datetime.now() with datetime.now(tz=datetime.timezone.utc) in order to use the UTC time instead of my local time. For some reason it appears to be 2 seconds ahead of the linux time displayed by the web page.
That would be a fun one to figure out (if you had nothing else in the world to do).