diff --git a/main.py b/main.py new file mode 100644 index 0000000..306d454 --- /dev/null +++ b/main.py @@ -0,0 +1,102 @@ +import datetime +from random import randint +from typing import Tuple, NamedTuple, Union, Iterable + +START_LEGACY_DATE: datetime.date = datetime.date(1800, 1, 1) +FINAL_LEGACY_DATE: datetime.date = datetime.date(2017, 7, 1) + + +class PersonalCode(NamedTuple): + first_part: str + second_part: str + has_check_digit: bool = False + + @classmethod + def generate_anonymous_personal_code(cls) -> "PersonalCode": + pc = f"3{randint(2 * 10 ** 9, 10 * 10 ** 9 - 1)}" + first_part = pc[:6] + second_part = pc[6:] + instance = cls(first_part, second_part) + return instance + + @classmethod + def generate_anonymous_with_check_digit(cls) -> "PersonalCode": + tmp = cls.generate_anonymous_personal_code() + check_digit = tmp._get_checksum_digit() + if check_digit == 10: + return tmp.generate_anonymous_with_check_digit() + second_part = tmp.second_part[:-1]+str(check_digit) + instance = cls(tmp.first_part, second_part, True) + return instance + + @classmethod + def generate_legacy_with_check_digit(cls, years: int = None) -> "PersonalCode": + if years is not None: + if years < (datetime.date.today()-FINAL_LEGACY_DATE).days//365: + raise ValueError( + f"Too young for legacy Personal Code! years < {(datetime.date.today()-FINAL_LEGACY_DATE).days//365}" + ) + min_age_in_days = abs(years)*365 + max_age_in_days = abs(years+1)*365 + birthdate = datetime.date.today() - datetime.timedelta(days=randint(min_age_in_days, max_age_in_days)) + else: + max_age_in_days = (FINAL_LEGACY_DATE-START_LEGACY_DATE).days + min_age_in_days = 1 + birthdate = FINAL_LEGACY_DATE - datetime.timedelta(days=randint(min_age_in_days, max_age_in_days)) + first_part = f"{birthdate.day:02d}{birthdate.month:02d}{str(birthdate.year)[2:]}" + century_digit = 2 if birthdate.year // 2000 else 1 if birthdate.year // 1900 else 0 + second_part = f"{century_digit}{randint(0, 999):03}" + tmp = cls(first_part, second_part) + check_digit = tmp._get_checksum_digit() + if check_digit == 10: + return cls.generate_legacy_with_check_digit() + second_part = f"{second_part}{check_digit}" + return cls(first_part, second_part, True) + + @classmethod + def generate_legacy_for_birthday(cls, *args: Union[Union[datetime.date], Union[int, int, int]]) -> "PersonalCode": + if len(args) == 1: + if not isinstance(args[0], datetime.date): + raise ValueError( + f"Calling method with one parameter, it must be of type `datetime.date` not `{type(args[0])}`" + ) + birthday = args[0] + elif len(args) == 3: + if not all(isinstance(o, int) for o in args): + raise ValueError( + f"Calling method with three parameters, they all must be of type `int` not `{[type(arg) for arg in args]}`" + ) + birthday = datetime.date(*args) + else: + raise ValueError( + "Method must be called with either one argument which is `datetime.date` or three int parameters " + f"which represent year, month, day representing date between {START_LEGACY_DATE} and {FINAL_LEGACY_DATE}" + ) + if not START_LEGACY_DATE <= birthday < FINAL_LEGACY_DATE: + raise ValueError( + f"Legacy non-anonymous personal codes are generated for birthdays since {START_LEGACY_DATE} till {FINAL_LEGACY_DATE}!\n" + f"{START_LEGACY_DATE} <= {birthday} < {FINAL_LEGACY_DATE}" + ) + first_part = f"{birthday.day:02d}{birthday.month:02d}{str(birthday.year)[2:]}" + century_digit = 2 if birthday.year // 2000 else 1 if birthday.year // 1900 else 0 + second_part = f"{century_digit}{randint(0, 999):03}" + tmp = cls(first_part, second_part) + check_digit = tmp._get_checksum_digit() + if check_digit == 10: + return cls.generate_legacy_with_check_digit() + second_part = f"{second_part}{check_digit}" + return cls(first_part, second_part, True) + + def as_tuple(self) -> Tuple[str, str]: + return self.first_part, self.second_part + + def __str__(self): + return f"{self.first_part}{self.second_part}" + + def _get_checksum_digit(self) -> int: + _factors = [1, 6, 3, 7, 9, 10, 5, 8, 4, 2] + return (1101 - sum(map(lambda p, f: int(p) * f, str(self)[:10], _factors))) % 11 + + @property + def dashed(self): + return f"{self.first_part}-{self.second_part}" \ No newline at end of file