Repository: joke2k/faker
Pull request #518 (2017-05-28 16:03:29)

Add et_EE (Estonian) provider: names and ssn
Created the provider for Estonian language (et_EE).
Providers implemented for names and ssn.


Patch:
diff --git a/README.rst b/README.rst
index 32de05e4..ada79958 100644
--- a/README.rst
+++ b/README.rst
@@ -136,6 +136,7 @@ Included localized providers:
 -  `en\_US <https://faker.readthedocs.io/en/master/locales/en_US.html>`__ - English (United States)
 -  `es\_ES <https://faker.readthedocs.io/en/master/locales/es_ES.html>`__ - Spanish (Spain)
 -  `es\_MX <https://faker.readthedocs.io/en/master/locales/es_MX.html>`__ - Spanish (Mexico)
+-  `et\_EE <https://faker.readthedocs.io/en/master/locales/et_EE.html>`__ - Estonian
 -  `fa\_IR <https://faker.readthedocs.io/en/master/locales/fa_IR.html>`__ - Persian (Iran)
 -  `fi\_FI <https://faker.readthedocs.io/en/master/locales/fi_FI.html>`__ - Finnish
 -  `fr\_FR <https://faker.readthedocs.io/en/master/locales/fr_FR.html>`__ - French
diff --git a/docs/index.rst b/docs/index.rst
index b1d8c097..44cac1ae 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -141,6 +141,7 @@ Included localized providers:
 -  `en\_US <https://faker.readthedocs.io/en/master/locales/en_US.html>`__ - English (United States)
 -  `es\_ES <https://faker.readthedocs.io/en/master/locales/es_ES.html>`__ - Spanish (Spain)
 -  `es\_MX <https://faker.readthedocs.io/en/master/locales/es_MX.html>`__ - Spanish (Mexico)
+-  `et\_EE <https://faker.readthedocs.io/en/master/locales/et_EE.html>`__ - Estonian
 -  `fa\_IR <https://faker.readthedocs.io/en/master/locales/fa_IR.html>`__ - Persian (Iran)
 -  `fi\_FI <https://faker.readthedocs.io/en/master/locales/fi_FI.html>`__ - Finnish
 -  `fr\_FR <https://faker.readthedocs.io/en/master/locales/fr_FR.html>`__ - French
diff --git a/faker/providers/person/et_EE/__init__.py b/faker/providers/person/et_EE/__init__.py
new file mode 100644
index 00000000..538897c4
--- /dev/null
+++ b/faker/providers/person/et_EE/__init__.py
@@ -0,0 +1,190 @@
+# coding=utf-8
+from __future__ import unicode_literals
+from .. import Provider as PersonProvider
+
+
+class Provider(PersonProvider):
+    # https://en.wikipedia.org/wiki/Demographics_of_Estonia#Ethnic_groups
+    # Main population groups in Estonia are Estonians and ethnic Russians:
+    # About 70% of the population are Estonians and about 25% are Russians
+    est_rat = 0.7
+    rus_rat = 1.0 - est_rat
+    formats = {'{{first_name_est}} {{last_name_est}}': est_rat,
+               '{{first_name_rus}} {{last_name_rus}}': rus_rat}
+
+    formats_male = {'{{first_name_male_est}} {{last_name_est}}': est_rat,
+                    '{{first_name_male_rus}} {{last_name_rus}}': rus_rat}
+    formats_female = {'{{first_name_female_est}} {{last_name_est}}': est_rat,
+                      '{{first_name_female_rus}} {{last_name_rus}}': rus_rat}
+
+    prefixes_neutral = ('doktor', 'dr', 'prof')
+    prefixes_male = ('härra', 'hr') + prefixes_neutral
+    prefixes_female = ('proua', 'pr') + prefixes_neutral
+    prefixes = set(prefixes_male + prefixes_female)
+
+    suffixes = ('PhD', 'MSc', 'BSc')
+
+    # source: http://www.stat.ee/public/apps/nimed/TOP
+    # TOP 50 male names in 2017 according to the Statistics Estonia
+    first_names_male_est = ('Aivar', 'Aleksander', 'Alexander', 'Andres',
+                            'Andrus', 'Ants', 'Indrek', 'Jaan', 'Jaanus',
+                            'Jüri', 'Kristjan', 'Marek', 'Margus', 'Marko',
+                            'Martin', 'Mati', 'Meelis', 'Mihkel',  'Peeter',
+                            'Priit', 'Raivo', 'Rein', 'Sander', 'Siim', 'Tarmo',
+                            'Tiit', 'Toomas', 'Tõnu', 'Urmas', 'Vello')
+
+    first_names_female_est = ('Aino', 'Anna', 'Anne', 'Anneli', 'Anu', 'Diana',
+                              'Ene', 'Eve', 'Kadri', 'Katrin', 'Kristi',
+                              'Kristiina', 'Kristina', 'Laura', 'Linda', 'Maie',
+                              'Malle', 'Mare', 'Maria', 'Marika', 'Merike',
+                              'Niina', 'Piret', 'Reet', 'Riina', 'Sirje',
+                              'Tiina', 'Tiiu', 'Triin', 'Ülle')
+
+    first_names_est = first_names_male_est + first_names_female_est
+
+    first_names_male_rus = ('Aleksander', 'Aleksandr', 'Aleksei', 'Alexander',
+                            'Andrei', 'Artur', 'Dmitri', 'Igor', 'Ivan',
+                            'Jevgeni', 'Juri', 'Maksim', 'Mihhail', 'Nikolai',
+                            'Oleg', 'Pavel', 'Roman', 'Sergei', 'Sergey',
+                            'Valeri', 'Viktor', 'Vladimir')
+
+    first_names_female_rus = ('Aleksandra', 'Anna', 'Diana', 'Elena', 'Galina',
+                              'Irina', 'Jekaterina', 'Jelena', 'Julia',
+                              'Kristina', 'Ljubov', 'Ljudmila', 'Maria',
+                              'Marina', 'Nadežda', 'Natalia', 'Natalja', 'Nina',
+                              'Olga', 'Svetlana', 'Tamara', 'Tatiana',
+                              'Tatjana', 'Valentina', 'Viktoria')
+
+    first_names_rus = first_names_male_rus + first_names_female_rus
+
+    first_names_male = set(first_names_male_est + first_names_male_rus)
+    first_names_female = set(first_names_female_est + first_names_female_rus)
+    first_names = first_names_male | first_names_female
+
+    # http://ekspress.delfi.ee/kuum/\
+    # top-500-eesti-koige-levinumad-perekonnanimed?id=27677149
+    last_names_est = ('Aas', 'Aasa', 'Aasmäe', 'Aavik', 'Abel', 'Adamson',
+                      'Ader', 'Alas', 'Allas', 'Allik', 'Anderson', 'Annus',
+                      'Anton', 'Arro', 'Aru', 'Arula', 'Aun', 'Aus', 'Eller',
+                      'Erik', 'Erm', 'Ernits', 'Gross', 'Hallik', 'Hansen',
+                      'Hanson', 'Hein', 'Heinsalu', 'Heinsoo', 'Holm', 'Hunt',
+                      'Härm', 'Ilves', 'Ivask','Jaakson', 'Jaanson', 'Jaanus',
+                      'Jakobson', 'Jalakas', 'Johanson', 'Juhanson', 'Juhkam',
+                      'Jänes', 'Järv', 'Järve', 'Jõe', 'Jõesaar', 'Jõgi',
+                      'Jürgens', 'Jürgenson', 'Jürisson', 'Kaasik', 'Kadak',
+                      'Kala', 'Kalamees', 'Kalda', 'Kaljula', 'Kaljurand',
+                      'Kaljuste', 'Kaljuvee', 'Kallas', 'Kallaste', 'Kalm',
+                      'Kalmus', 'Kangro', 'Kangur', 'Kapp', 'Karro', 'Karu',
+                      'Kasak', 'Kase', 'Kasemaa', 'Kasemets', 'Kask', 'Kass',
+                      'Kattai', 'Kaur', 'Kelder', 'Kesküla', 'Kiik', 'Kiil',
+                      'Kiis', 'Kiisk', 'Kikas', 'Kikkas', 'Kilk', 'Kink',
+                      'Kirs', 'Kirsipuu', 'Kirss', 'Kivi', 'Kivilo', 'Kivimäe',
+                      'Kivistik', 'Klaas', 'Klein', 'Koger', 'Kohv', 'Koit',
+                      'Koitla', 'Kokk', 'Kolk', 'Kont', 'Kool', 'Koort',
+                      'Koppel', 'Korol', 'Kotkas', 'Kotov', 'Koval', 'Kozlov',
+                      'Kriisa', 'Kroon', 'Krõlov', 'Kudrjavtsev', 'Kulikov',
+                      'Kuningas', 'Kurg', 'Kurm', 'Kurvits', 'Kutsar', 'Kuus',
+                      'Kuuse', 'Kuusik', 'Kuusk', 'Kärner', 'Käsper', 'Käär',
+                      'Käärik', 'Kõiv', 'Kütt', 'Laan', 'Laane', 'Laanemets',
+                      'Laas', 'Laht', 'Laine', 'Laks', 'Lang', 'Lass', 'Laur',
+                      'Lauri', 'Lehiste', 'Leht', 'Lehtla', 'Lehtmets', 'Leis',
+                      'Lember', 'Lepik', 'Lepp', 'Leppik', 'Liblik', 'Liiv',
+                      'Liiva', 'Liivak', 'Liivamägi', 'Lill', 'Lillemets',
+                      'Lind', 'Link', 'Lipp', 'Lokk', 'Lomp', 'Loorits', 'Luht',
+                      'Luik', 'Lukin', 'Lukk', 'Lumi', 'Lumiste', 'Luts',
+                      'Lätt', 'Lääne', 'Lääts', 'Lõhmus', 'Maasik', 'Madisson',
+                      'Maidla', 'Mandel', 'Maripuu', 'Mark', 'Markus', 'Martin',
+                      'Martinson', 'Meier', 'Meister', 'Melnik', 'Merila',
+                      'Mets', 'Michelson', 'Mikk', 'Miller', 'Mitt', 'Moor',
+                      'Muru', 'Must', 'Mäe', 'Mäeots', 'Mäesalu', 'Mägi',
+                      'Mänd', 'Mändla', 'Männik', 'Männiste', 'Mõttus',
+                      'Mölder', 'Mürk', 'Müür', 'Müürsepp', 'Niit', 'Nurk',
+                      'Nurm', 'Nuut', 'Nõmm', 'Nõmme', 'Nõmmik', 'Oja', 'Ojala',
+                      'Ojaste', 'Oks', 'Olesk', 'Oras', 'Orav', 'Org', 'Ots',
+                      'Ott', 'Paal', 'Paap', 'Paas', 'Paju', 'Pajula', 'Palm',
+                      'Palu', 'Parts', 'Pent', 'Peterson', 'Pettai', 'Pihelgas',
+                      'Pihlak', 'Piho', 'Piir', 'Piirsalu', 'Pikk', 'Ploom',
+                      'Poom', 'Post', 'Pruul', 'Pukk', 'Pulk', 'Puusepp',
+                      'Pärn', 'Pärna', 'Pärnpuu', 'Pärtel', 'Põder', 'Põdra',
+                      'Põld', 'Põldma', 'Põldmaa', 'Põllu', 'Püvi', 'Raadik',
+                      'Raag', 'Raamat', 'Raid', 'Raidma', 'Raja', 'Rand',
+                      'Randmaa', 'Randoja', 'Raud', 'Raudsepp', 'Rebane',
+                      'Reimann', 'Reinsalu', 'Remmel', 'Rohtla', 'Roos',
+                      'Roosileht', 'Roots', 'Rosenberg', 'Rosin', 'Ruus',
+                      'Rätsep', 'Rüütel', 'Saar', 'Saare', 'Saks', 'Salu',
+                      'Salumets', 'Salumäe', 'Sander', 'Sarap', 'Sarapuu',
+                      'Sarv', 'Saul', 'Schmidt', 'Sepp', 'Sibul', 'Siim',
+                      'Sikk', 'Sild', 'Sillaots', 'Sillaste', 'Silm', 'Simson',
+                      'Sirel', 'Sisask', 'Sokk', 'Soo', 'Soon', 'Soosaar',
+                      'Soosalu', 'Soots', 'Suits', 'Sulg', 'Susi', 'Sutt',
+                      'Suur', 'Suvi', 'Säde', 'Sööt', 'Taal', 'Tali', 'Talts',
+                      'Tamberg', 'Tamm', 'Tamme', 'Tammik', 'Teder', 'Teearu',
+                      'Teesalu', 'Teras', 'Tiik', 'Tiits', 'Tilk', 'Tomingas',
+                      'Tomson', 'Toom', 'Toome', 'Tooming', 'Toomsalu', 'Toots',
+                      'Trei', 'Treial', 'Treier', 'Truu', 'Tuisk', 'Tuul',
+                      'Tuulik', 'Täht', 'Tõnisson', 'Uibo', 'Unt', 'Urb', 'Uus',
+                      'Uustalu', 'Vaher', 'Vaht', 'Vahter', 'Vahtra', 'Vain',
+                      'Vaino', 'Valge', 'Valk', 'Vares', 'Varik', 'Veski',
+                      'Viik', 'Viira', 'Viks', 'Vill', 'Villemson', 'Visnapuu',
+                      'Vähi', 'Väli', 'Võsu', 'Õispuu', 'Õun', 'Õunapuu')
+
+    last_names_rus = ('Abramov', 'Afanasjev', 'Aleksandrov', 'Alekseev',
+                      'Andreev', 'Anissimov', 'Antonov', 'Baranov', 'Beljajev',
+                      'Belov', 'Bogdanov', 'Bondarenko', 'Borissov', 'Bõstrov',
+                      'Danilov', 'Davõdov', 'Denissov', 'Dmitriev', 'Drozdov',
+                      'Egorov', 'Fedorov', 'Fedotov', 'Filatov', 'Filippov',
+                      'Fjodorov', 'Fomin', 'Frolov', 'Gavrilov', 'Gerassimov',
+                      'Golubev', 'Gontšarov', 'Gorbunov', 'Grigoriev', 'Gromov',
+                      'Gusev', 'Ignatjev', 'Iljin', 'Ivanov', 'Jakovlev',
+                      'Jefimov', 'Jegorov', 'Jermakov', 'Jeršov', 'Kalinin',
+                      'Karpov', 'Karpov', 'Kazakov', 'Kirillov', 'Kisseljov',
+                      'Klimov', 'Kolesnik', 'Komarov', 'Kondratjev',
+                      'Konovalov', 'Konstantinov', 'Korol', 'Kostin', 'Kotov',
+                      'Koval', 'Kozlov', 'Kruglov', 'Krõlov', 'Kudrjavtsev',
+                      'Kulikov', 'Kuzmin', 'Kuznetsov', 'Lebedev', 'Loginov',
+                      'Lukin', 'Makarov', 'Maksimov', 'Malõšev', 'Maslov',
+                      'Matvejev', 'Medvedev', 'Melnik', 'Mihhailov', 'Miller',
+                      'Mironov', 'Moroz', 'Naumov', 'Nazarov', 'Nikiforov',
+                      'Nikitin', 'Nikolaev', 'Novikov', 'Orlov', 'Ossipov',
+                      'Panov', 'Pavlov', 'Petrov', 'Poljakov', 'Popov',
+                      'Romanov', 'Rosenberg', 'Rumjantsev', 'Safronov',
+                      'Saveljev', 'Semenov', 'Sergejev', 'Sidorov', 'Smirnov',
+                      'Sobolev', 'Sokolov', 'Solovjov', 'Sorokin', 'Stepanov',
+                      'Suvorov', 'Tarassov', 'Tihhomirov', 'Timofejev', 'Titov',
+                      'Trofimov', 'Tsvetkov', 'Vasiliev', 'Vinogradov',
+                      'Vlassov', 'Volkov', 'Vorobjov', 'Voronin', 'Zahharov',
+                      'Zaitsev', 'Zujev', 'Ševtšenko', 'Štšerbakov',
+                      'Štšerbakov', 'Žukov', 'Žuravljov')
+    last_names = set(last_names_est + last_names_rus)
+
+    @classmethod
+    def first_name_male_est(cls):
+        return cls.random_element(cls.first_names_male_est)
+
+    @classmethod
+    def first_name_female_est(cls):
+        return cls.random_element(cls.first_names_female_est)
+
+    @classmethod
+    def first_name_male_rus(cls):
+        return cls.random_element(cls.first_names_male_rus)
+
+    @classmethod
+    def first_name_female_rus(cls):
+        return cls.random_element(cls.first_names_female_rus)
+
+    @classmethod
+    def first_name_est(cls):
+        return cls.random_element(cls.first_names_est)
+
+    @classmethod
+    def first_name_rus(cls):
+        return cls.random_element(cls.first_names_rus)
+
+    @classmethod
+    def last_name_est(cls):
+        return cls.random_element(cls.last_names_est)
+
+    @classmethod
+    def last_name_rus(cls):
+        return cls.random_element(cls.last_names_rus)
diff --git a/faker/providers/ssn/et_EE/__init__.py b/faker/providers/ssn/et_EE/__init__.py
new file mode 100644
index 00000000..f1181b54
--- /dev/null
+++ b/faker/providers/ssn/et_EE/__init__.py
@@ -0,0 +1,67 @@
+# coding=utf-8
+
+from __future__ import unicode_literals
+from .. import Provider as SsnProvider
+from faker.generator import random
+import datetime
+import operator
+
+
+def checksum(digits):
+    """Calculate checksum of Estonian personal identity code.
+
+    Checksum is calculated with "Modulo 11" method using level I or II scale:
+    Level I scale: 1 2 3 4 5 6 7 8 9 1
+    Level II scale: 3 4 5 6 7 8 9 1 2 3
+
+    The digits of the personal code are multiplied by level I scale and summed;
+    if remainder of modulo 11 of the sum is less than 10, checksum is the
+    remainder.
+    If remainder is 10, then level II scale is used; checksum is remainder if
+    remainder < 10 or 0 if remainder is 10.
+
+    See also https://et.wikipedia.org/wiki/Isikukood
+    """
+    sum_mod11 = sum(map(operator.mul, digits, Provider.scale1)) % 11
+    if sum_mod11 < 10:
+        return sum_mod11
+    sum_mod11 = sum(map(operator.mul, digits, Provider.scale2)) % 11
+    return 0 if sum_mod11 == 10 else sum_mod11
+
+
+class Provider(SsnProvider):
+    min_age = 16 * 365
+    max_age = 90 * 365
+    scale1 = (1, 2, 3, 4, 5, 6, 7, 8, 9, 1)
+    scale2 = (3, 4, 5, 6, 7, 8, 9, 1, 2, 3)
+
+    @classmethod
+    def ssn(cls):
+        """
+        Returns 11 character Estonian personal identity code (isikukood, IK).
+
+        Age of person is between 16 and 90 years, based on local computer date.
+        This function assigns random sex to person.
+        An Estonian Personal identification code consists of 11 digits,
+        generally given without any whitespace or other delimiters.
+        The form is GYYMMDDSSSC, where G shows sex and century of birth (odd
+        number male, even number female, 1-2 19th century, 3-4 20th century,
+        5-6 21st century), SSS is a serial number separating persons born on
+        the same date and C a checksum.
+
+        https://en.wikipedia.org/wiki/National_identification_number#Estonia
+        """
+        age = datetime.timedelta(days=random.randrange(Provider.min_age,
+                                                       Provider.max_age))
+        birthday = datetime.date.today() - age
+        if birthday.year < 2000:
+            ik = random.choice(('3', '4'))
+        elif birthday.year < 2100:
+            ik = random.choice(('5', '6'))
+        else:
+            ik = random.choice(('7', '8'))
+
+        ik += "%02d%02d%02d" % ((birthday.year % 100), birthday.month,
+                                birthday.day)
+        ik += str(random.randrange(0, 999)).zfill(3)
+        return ik + str(checksum([int(ch) for ch in ik]))


Test patch:
diff --git a/tests/providers/ssn.py b/tests/providers/ssn.py
index 37369aac..fe559113 100644
--- a/tests/providers/ssn.py
+++ b/tests/providers/ssn.py
@@ -6,10 +6,28 @@ import unittest
 import re
 
 from faker import Factory
+from faker.providers.ssn.et_EE import Provider as EtProvider, checksum as et_checksum
 from faker.providers.ssn.hr_HR import Provider as HrProvider, checksum as hr_checksum
 from faker.providers.ssn.pt_BR import Provider as PtProvider, checksum as pt_checksum
 
 
+class TestEtEE(unittest.TestCase):
+    """ Tests SSN in the et_EE locale """
+
+    def setUp(self):
+        self.factory = Factory.create('et_EE')
+
+    def test_ssn_checksum(self):
+        self.assertEqual(et_checksum([4, 4, 1, 1, 1, 3, 0, 4, 9, 2]), 3)
+        self.assertEqual(et_checksum([3, 6, 7, 0, 1, 1, 6, 6, 2, 7]), 8)
+        self.assertEqual(et_checksum([4, 7, 0, 0, 4, 2, 1, 5, 0, 1]), 2)
+        self.assertEqual(et_checksum([3, 9, 7, 0, 3, 0, 4, 3, 3, 6]), 0)
+
+    def test_ssn(self):
+        for i in range(100):
+            self.assertTrue(re.search(r'^\d{11}$', EtProvider.ssn()))
+
+
 class TestHrHR(unittest.TestCase):
     """ Tests SSN in the hr_HR locale """