From 6f59874d9ed22783310805be1d7aa4d3f0adb0e7 Mon Sep 17 00:00:00 2001 From: Serhii Lozytskyi <42421616+lozik4@users.noreply.github.com> Date: Mon, 29 Jan 2024 19:10:26 +0200 Subject: [PATCH] Add support for birthday and gender to `uk_UA` `ssn` method (#1983) * feat(ssn)Add support uk_Ua ssn method birthday and gender optional params * Fix unit test name capitalization --------- Co-authored-by: Flavio Curella <89607+fcurella@users.noreply.github.com> --- faker/providers/ssn/uk_UA/__init__.py | 77 +++++++++++++++++---------- tests/providers/test_ssn.py | 28 ++++++++++ 2 files changed, 78 insertions(+), 27 deletions(-) diff --git a/faker/providers/ssn/uk_UA/__init__.py b/faker/providers/ssn/uk_UA/__init__.py index 4848d6113a..e647be2da1 100644 --- a/faker/providers/ssn/uk_UA/__init__.py +++ b/faker/providers/ssn/uk_UA/__init__.py @@ -1,36 +1,59 @@ -from datetime import date +import random +from datetime import date, datetime +from typing import Optional + +from ....typing import SexLiteral from .. import Provider as SsnProvider +def select_gender(gender: SexLiteral) -> int: + """Choose an even number for Female and odd number for Male.""" + gender = 0 if gender.lower() == "f" else 1 + return random.choice(range(gender, 10, 2)) + + +def calculate_day_count(birthday: date) -> int: + """Calculate the day count from reference date '31 December 1899'.""" + ref_date = date(1899, 12, 31) + return (birthday - ref_date).days + + +def calculate_check_sum(val: str) -> int: + """Calculate checksum using INN calculation method.""" + weights = [-1, 5, 7, 9, 4, 6, 10, 5, 7] + checksum = sum(int(v) * w for v, w in zip(val, weights)) + + return checksum % 11 % 10 + + class Provider(SsnProvider): - def ssn(self) -> str: + def ssn(self, birthday: Optional[str] = None, gender: Optional[SexLiteral] = None) -> str: """ Ukrainian "Реєстраційний номер облікової картки платника податків" also known as "Ідентифікаційний номер фізичної особи". + @params: birthday: "DD-MM-YYYY" format, default random date + @params: gender: "M" or "F" default: random gender + + :sample: + :sample: birthday='22-06-1990', gender='F' """ - digits = [] - - # Number of days between 1899-12-31 and a birth date - for digit in str((self.generator.date_object() - date(1899, 12, 31)).days): - digits.append(int(digit)) - - # Person's sequence number - for _ in range(4): - digits.append(self.random_int(0, 9)) - - checksum = ( - digits[0] * -1 - + digits[1] * 5 - + digits[2] * 7 - + digits[3] * 9 - + digits[4] * 4 - + digits[5] * 6 - + digits[6] * 10 - + digits[7] * 5 - + digits[8] * 7 - ) - # Remainder of a checksum divided by 11 or 1 if it equals to 10 - digits.append(checksum % 11 % 10) - - return "".join(str(digit) for digit in digits) + + try: + # generate day of birthday date object + if birthday: + dob = datetime.strptime(birthday, "%d-%m-%Y").date() + else: + dob = self.generator.date_object() + except Exception: + raise ValueError("Birthday format must be DD-MM-YYYY") + + if gender and gender not in ("M", "F"): + raise ValueError('Gender must be "m" or "f" or None') + + day_count = calculate_day_count(dob) + people_num = self.random_number(3, fix_len=True) + gender_ = select_gender(gender) if gender else random.randint(0, 1) + ssn_without_checksum = f"{day_count}{people_num}{gender_}" + checksum = calculate_check_sum(ssn_without_checksum) + return f"{ssn_without_checksum}{checksum}" diff --git a/tests/providers/test_ssn.py b/tests/providers/test_ssn.py index b41f08d206..a4955c326b 100644 --- a/tests/providers/test_ssn.py +++ b/tests/providers/test_ssn.py @@ -34,6 +34,7 @@ from faker.providers.ssn.pt_BR import checksum as pt_checksum from faker.providers.ssn.ro_RO import ssn_checksum as ro_ssn_checksum from faker.providers.ssn.ro_RO import vat_checksum as ro_vat_checksum +from faker.providers.ssn.uk_UA import Provider as uk_Provider from faker.providers.ssn.zh_TW import checksum as tw_checksum from faker.utils.checksums import luhn_checksum @@ -1364,3 +1365,30 @@ def test_gender(self): def test_checksum(self): for sample in self.samples: assert tw_checksum(sample) % 10 == 0 + + +class TestUkUA(unittest.TestCase): + def setUp(self): + self.fake = Faker("uk_Ua") + Faker.seed(0) + self.provider = uk_Provider + + def test_ssn_len(self): + assert len(self.fake.ssn()) == 10 + + def test_start_ssn(self): + assert self.fake.ssn("21-06-1994")[:5] == "34505" + + def test_ssn_gender(self): + m = self.fake.ssn(gender="M") + w = self.fake.ssn(gender="F") + assert int(m[8]) % 2 != 0, "Must be odd for men" + assert int(w[8]) % 2 == 0, "Must be even for women" + + def test_incorrect_birthday(self): + with pytest.raises(ValueError): + self.fake.ssn(birthday="1994-06-01") + + def test_incorrect_gender(self): + with pytest.raises(ValueError): + self.fake.ssn(gender="f")