Sep-16-2023, 08:14 AM
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')