Repository: joke2k/faker
Pull request #732 (2018-03-28 10:49:48)

don't exclude edgecases from Finnish SSN by default
### What does this changes

Do not exclude the "edgecases" for the Finnish SSN provider by default. Where "edgecases" means people born after 2000 (who's SSN have a slightly different format in Finland)

### What was wrong

Presumably this library is used mostly for testing. Currently, by default the Finnish SSN provider was missing the was not generating precisely the class of SSNs that are most likely to cause problems for SSN parsers.

### How this fixes it

Includes SSN of people born after 2000

Fixes #...


Patch:
diff --git a/faker/providers/automotive/de_DE/__init__.py b/faker/providers/automotive/de_DE/__init__.py
deleted file mode 100644
index 14bc54d3..00000000
--- a/faker/providers/automotive/de_DE/__init__.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# coding=utf-8
-
-
-from __future__ import unicode_literals
-from .. import Provider as AutomotiveProvider
-import string
-
-
-class Provider(AutomotiveProvider):
-
-    # http://berlin.de/daten/liste-der-kfz-kennzeichen/kfz-kennz-d.csv
-    license_plate_prefix = (
-        'A', 'AA', 'AB', 'ABI', 'ABG', 'AC', 'AE', 'AIC', 'AK', 'AM', 'AN', 'AÖ', 'AP', 'AS', 'AUR', 'AW', 'AZ', 'B',
-        'BA', 'BAD', 'BAR', 'BB', 'BC', 'BD', 'BGL', 'BI', 'BIR', 'BIT', 'BK', 'BL', 'BLK', 'BM', 'BN', 'BO', 'BOR',
-        'BOT', 'BP', 'BRA', 'BRB', 'BS', 'BT', 'BTF', 'BÜS', 'BW', 'BWL', 'BYL', 'BZ', 'C', 'CB', 'CE', 'CHA', 'CO',
-        'COC', 'COE', 'CUX', 'CW', 'D', 'DA', 'DAH', 'DAN', 'DAU', 'DBR', 'DD', 'DE', 'DEG', 'DEL', 'DGF', 'DH', 'DL',
-        'DLG', 'DN', 'Do', 'DON', 'DU', 'DÜW', 'E', 'EA', 'EB', 'EBE', 'ED', 'EE', 'EF', 'EI', 'EIC', 'EL', 'EM', 'EMD',
-        'EMS', 'EN', 'ER', 'ERB', 'ERH', 'ERZ', 'ES', 'ESW', 'EU', 'F', 'FB', 'FD', 'FDS', 'FF', 'FFB', 'FG', 'FL',
-        'FN', 'FO', 'FR', 'FRG', 'FRI', 'FS', 'FT', 'FÜ', 'G', 'GAP', 'GE', 'GER', 'GF', 'GG', 'GI', 'GL', 'GM', 'GÖ',
-        'GP', 'GR', 'GRZ', 'GS', 'GT', 'GTH', 'GÜ', 'GZ', 'H', 'HA', 'HAL', 'HAM', 'HAS', 'HB', 'HBN', 'HD', 'HDH',
-        'HE', 'HEF', 'HEI', 'HEL', 'HER', 'HF', 'HG', 'HGW', 'HH', 'HI', 'HL', 'HM', 'HN', 'HO', 'HOL', 'HOM', 'HP',
-        'HR', 'HRO', 'HS', 'HSK', 'HST', 'HU', 'HVL', 'HWI', 'HX', 'HZ', 'IGB', 'IK', 'IN', 'IZ', 'J', 'JL', 'K', 'KA',
-        'KB', 'KC', 'KE', 'KEH', 'KF', 'KG', 'KH', 'KI', 'KIB', 'KL', 'KLE', 'KN', 'KO', 'KR', 'KS', 'KT', 'KU', 'KÜN',
-        'KUS', 'KYF', 'L', 'LA', 'LAU', 'LB', 'LD', 'LDK', 'LDS', 'LER', 'LEV', 'LG', 'LI', 'LIF', 'LIP', 'LL', 'LM',
-        'LÖ', 'LOS', 'LRO', 'LSA', 'LSN', 'LU', 'LWL', 'M', 'MA', 'MB', 'MD', 'ME', 'MEI', 'MG', 'MI', 'MIL', 'MK',
-        'MKK', 'MM', 'MN', 'MOL', 'MOS', 'MR', 'MS', 'MSH', 'MSP', 'MST', 'MTK', 'MÜ', 'MÜR', 'MVL', 'MYK', 'MZ', 'MZG',
-        'N', 'NB', 'ND', 'NDH', 'NE', 'NEA', 'NES', 'NEW', 'NF', 'NI', 'NK', 'NL', 'NM', 'NMS', 'NOH', 'NOM', 'NR',
-        'NU', 'NVP', 'NW', 'NWM', 'OA', 'OAL', 'OB', 'OD', 'OE', 'OF', 'OG', 'OH', 'OHA', 'OHV', 'OHZ', 'OL', 'OPR',
-        'OS', 'OSL', 'OVP', 'P', 'PA', 'PAF', 'PAN', 'PB', 'PCH', 'PE', 'PF', 'PI', 'PIR', 'PLÖ', 'PM', 'PR', 'PS', 'R',
-        'RA', 'RD', 'RE', 'REG', 'RO', 'ROS', 'ROW', 'RP', 'RPL', 'RS', 'RT', 'RÜD', 'RÜG', 'RV', 'RW', 'RZ', 'S',
-        'SAD', 'SAL', 'SAW', 'SB', 'SC', 'SDL', 'SE', 'SG', 'SH', 'SHA', 'SHG', 'SHK', 'SHL', 'SI', 'SIG', 'SIM', 'SK',
-        'SL', 'SLF', 'SLK', 'SLS', 'SM', 'SN', 'SO', 'SOK', 'SÖM', 'SON', 'SP', 'SPN', 'SR', 'ST', 'STA', 'STD', 'SU',
-        'SÜW', 'SW', 'SZ', 'TDO', 'TBB', 'TF', 'TG', 'THL', 'THW', 'TIR', 'TÖL', 'TR', 'TS', 'TÜ', 'TUT', 'UE', 'UL',
-        'UM', 'UN', 'V', 'VB', 'VEC', 'VER', 'VIE', 'VK', 'VR', 'VS', 'W', 'WAF', 'WAK', 'WB', 'WE', 'WEN', 'WES', 'WF',
-        'WHV', 'WI', 'WIL', 'WL', 'WM', 'WN', 'WND', 'WO', 'WOB', 'WST', 'WT', 'WTM', 'WÜ', 'WUG', 'WUN', 'WW', 'WZ',
-        'Y', 'Z', 'ZW',
-    )
-
-    license_plate_suffix = (
-        '-??-%@@@',
-        '-?-%@@@',
-    )
-
-    def license_plate(self):
-        return self.random_element(self.license_plate_prefix) + \
-               self.lexify(self.numerify(self.random_element(self.license_plate_suffix)), string.ascii_uppercase)
diff --git a/faker/providers/ssn/fi_FI/__init__.py b/faker/providers/ssn/fi_FI/__init__.py
index cc4d9227..faa3e8e8 100644
--- a/faker/providers/ssn/fi_FI/__init__.py
+++ b/faker/providers/ssn/fi_FI/__init__.py
@@ -7,11 +7,10 @@ import datetime
 
 class Provider(SsnProvider):
 
-    def ssn(self, min_age=18, max_age=90):
+    def ssn(self, min_age=0, max_age=105):
         """
         Returns 11 character Finnish personal identity code (Henkilötunnus,
-        HETU, Swedish: Personbeteckning). Age of person is between 18 and 90
-        years, based on local computer date. This function assigns random
+        HETU, Swedish: Personbeteckning). This function assigns random
         sex to person.
 
         HETU consists of eleven characters of the form DDMMYYCZZZQ, where
@@ -34,11 +33,24 @@ class Provider(SsnProvider):
         birthday = datetime.date.today() - age
         hetu_date = "%02d%02d%s" % (
             birthday.day, birthday.month, str(birthday.year)[-2:])
-        if birthday.year < 2000:
-            separator = '-'
-        else:
-            separator = 'A'
         suffix = str(self.generator.random.randrange(2, 899)).zfill(3)
         checksum = _checksum(hetu_date + suffix)
+        separator = self._get_century_code(birthday.year)
         hetu = "".join([hetu_date, separator, suffix, checksum])
         return hetu
+
+    @staticmethod
+    def _get_century_code(year):
+        """Returns the century code for a given year"""
+        if 2000 <= year < 3000:
+            separator = 'A'
+        elif 1900 <= year < 2000:
+            separator = '-'
+        elif 1800 <= year < 1900:
+            separator = '+'
+        else:
+            raise ValueError(
+                'Finnish SSN do not support people born '
+                'before the year 1800 or after the year 2999'
+            )
+        return separator


Test patch:
diff --git a/tests/providers/test_automotive.py b/tests/providers/test_automotive.py
index 82c529a2..10604e2e 100644
--- a/tests/providers/test_automotive.py
+++ b/tests/providers/test_automotive.py
@@ -28,13 +28,3 @@ class TestHuHU(unittest.TestCase):
         plate = self.factory.license_plate()
 
         assert re.match("[A-Z]{3}-\d{3}", plate)
-
-class TestDeDe(unittest.TestCase):
-
-    def setUp(self):
-        self.factory = Faker('de_DE')
-
-    def test_de_DE_plate_format(self):
-        plate = self.factory.license_plate()
-
-        assert re.match("[A-Z]{1,3}-[A-Z]{1,2}-\d{1,4}", plate)
diff --git a/tests/providers/test_ssn.py b/tests/providers/test_ssn.py
index ab5db852..42490102 100644
--- a/tests/providers/test_ssn.py
+++ b/tests/providers/test_ssn.py
@@ -8,6 +8,7 @@ from datetime import datetime
 
 from faker import Faker
 from faker.providers.ssn.et_EE import checksum as et_checksum
+from faker.providers.ssn.fi_FI import Provider as fi_Provider
 from faker.providers.ssn.hr_HR import checksum as hr_checksum
 from faker.providers.ssn.pt_BR import checksum as pt_checksum
 from faker.providers.ssn.pl_PL import checksum as pl_checksum, calculate_month as pl_calculate_mouth
@@ -31,6 +32,30 @@ class TestEtEE(unittest.TestCase):
             self.assertTrue(re.search(r'^\d{11}$', self.factory.ssn()))
 
 
+class TestFiFI(unittest.TestCase):
+    """ Tests SSN in the fi_FI locale """
+
+    def setUp(self):
+        self.factory = Faker('fi_FI')
+        self.provider = fi_Provider
+
+    def test_century_code(self):
+        self.assertEqual(self.provider._get_century_code(1900), '-')
+        self.assertEqual(self.provider._get_century_code(1999), '-')
+        self.assertEqual(self.provider._get_century_code(2000), 'A')
+        self.assertEqual(self.provider._get_century_code(2999), 'A')
+        self.assertEqual(self.provider._get_century_code(1800), '+')
+        self.assertEqual(self.provider._get_century_code(1899), '+')
+        with self.assertRaises(ValueError):
+            self.provider._get_century_code(1799)
+        with self.assertRaises(ValueError):
+            self.provider._get_century_code(3000)
+
+    def test_ssn_sanity(self):
+        for age in range(100):
+            self.factory.ssn(min_age=age, max_age=age+1)
+
+
 class TestHrHR(unittest.TestCase):
     """ Tests SSN in the hr_HR locale """