Repository: joke2k/faker

Issue:
Norwegian SSN: Incorrect format
The format for Norwegian SSN is 11 digits: 
- The first six digits represent the date of birth in the order date, month, year
- The next three digits are an individual number, the third digit of which indicates gender – even numbers for women and odd numbers for men
- The last two digits are control digits

More details: http://www.skatteetaten.no/en/person/National-Registry/Birth-and-name-selection/Children-born-in-Norway/National-ID-number/

### Steps to reproduce

```
from faker import Faker
fake = Faker('no_NO')
print(fake.ssn())
```

The SSN format for 'no_NO' is currently '465-18-3530': https://faker.readthedocs.io/en/master/locales/no_NO.html#faker-providers-ssn 

### Expected behavior

SSN should be on the format specified here:
http://www.skatteetaten.no/en/person/National-Registry/Birth-and-name-selection/Children-born-in-Norway/National-ID-number/

This would be nice: Get a SSN ("fødselsnummer": the birth date and the 5 digits after) for a female or male person born on a specific date:

```
from faker import Faker
fake = Faker('no_NO')

# Get Norwegian SSN for a female born on December 31 2000
gender = 'F'
year = 2000
month = 12
day = 31
fodselsnummer = fake.ssn(gender, year, month, day) # 11 digits
```
Maybe an option to get only the last 5 digits?

fodselsnummer.py here may be useful: https://github.com/magnuswatn/fodselsnummer

### Actual behavior

SSN returned is on the format '465-18-3530'.



Discussion:
Thanks for reporting this @cloveras 

Could you submit a PR to add the `no_NO` ssn provider?

As for the specific implementation, I"m ok with the `gender`, `year`, `month` and `day` arguments, as long as they're all optional.

I'm less inclined to have an option to get only the last 5 digits. Python already provides a convenient way with string slicing, so you could just call `Faker('no_NO').ssn()[:-5]`
Hi!

I'm not sure how to add a pull request in that way, sorry. I have reported an issue (more of a tip, really) here: https://github.com/magnuswatn/fodselsnummer/issues/2, which looks positive: "Hi. I wasn't aware of faker, I'll see if I can create a provider real quick. Thanks for the tip!" 

Optional parameters are fine, of course, but great to have them, since Norwegian SSNs depend on all of them, and it would be nice to have verifiable SSNs.

No problem to skip the 5 digit option, of course, and I see that it may be a bit too specialized and complicating.




I'll wait for a PR from @magnuswatn
I'll see what I can do. But I see that the Hungarian provider takes a single `dob` date string in the format "YYMMDD" instead of separate `year`, `month` and `day` arguments. Should the Norwegian one do the same, or is it acceptable that the localized providers differ?
@magnuswatn I'd rather be consistent and use the `YYMMDD` string for `no_NO` as well.

Resolving patch:
diff --git a/faker/providers/ssn/no_NO/__init__.py b/faker/providers/ssn/no_NO/__init__.py
new file mode 100644
--- /dev/null
+++ b/faker/providers/ssn/no_NO/__init__.py
@@ -0,0 +1,83 @@
+# coding=utf-8
+
+from __future__ import unicode_literals
+from .. import Provider as SsnProvider
+import datetime
+import operator
+
+
+def checksum(digits, scale):
+    """
+    Calculate checksum of Norwegian personal identity code.
+
+    Checksum is calculated with "Module 11" method using a scale.
+    The digits of the personal code are multiplied by the corresponding
+    number in the scale and summed;
+    if remainder of module 11 of the sum is less than 10, checksum is the
+    remainder.
+    If remainder is 0, the checksum is 0.
+
+    https://no.wikipedia.org/wiki/F%C3%B8dselsnummer
+    """
+    chk_nbr = 11 - (sum(map(operator.mul, digits, scale)) % 11)
+    if chk_nbr == 11:
+        return 0
+    return chk_nbr
+
+
+class Provider(SsnProvider):
+    scale1 = (3, 7, 6, 1, 8, 9, 4, 5, 2)
+    scale2 = (5, 4, 3, 2, 7, 6, 5, 4, 3, 2)
+
+    def ssn(self, dob=None, gender=None):
+        """
+        Returns 11 character Norwegian personal identity code (Fødselsnummer).
+
+        A Norwegian personal identity code consists of 11 digits, without any
+        whitespace or other delimiters. The form is DDMMYYIIICC, where III is
+        a serial number separating persons born oh the same date with different
+        intervals depending on the year they are born. CC is two checksums.
+        https://en.wikipedia.org/wiki/National_identification_number#Norway
+
+        :param dob: date of birth as a "YYYYMMDD" string
+        :type dob: str
+        :param gender: gender of the person - "F" for female, M for male.
+        :type gender: str
+        :return: Fødselsnummer in str format (11 digs)
+        :rtype: str
+        """
+
+        if dob:
+            birthday = datetime.datetime.strptime(dob, '%Y%m%d')
+        else:
+            age = datetime.timedelta(
+                days=self.generator.random.randrange(18 * 365, 90 * 365))
+            birthday = datetime.datetime.now() - age
+        if not gender:
+            gender = self.generator.random.choice(('F', 'M'))
+        elif gender not in ('F', 'M'):
+            raise ValueError('Gender must be one of F or M.')
+
+        while True:
+            if birthday.year >= 1900 and birthday.year <= 1999:
+                suffix = str(self.generator.random.randrange(0, 49))
+            elif birthday.year >= 1854 and birthday.year <= 1899:
+                suffix = str(self.generator.random.randrange(50, 74))
+            elif birthday.year >= 2000 and birthday.year <= 2039:
+                suffix = str(self.generator.random.randrange(50, 99))
+            elif birthday.year >= 1940 and birthday.year <= 1999:
+                suffix = str(self.generator.random.randrange(90, 99))
+            if gender == 'F':
+                gender_num = self.generator.random.choice((0, 2, 4, 6, 8))
+            elif gender == 'M':
+                gender_num = self.generator.random.choice((1, 3, 5, 7, 9))
+            pnr = birthday.strftime('%y%m%d') + suffix.zfill(2) + str(gender_num)
+            pnr_nums = [int(ch) for ch in pnr]
+            k1 = checksum(Provider.scale1, pnr_nums)
+            k2 = checksum(Provider.scale2, pnr_nums + [k1])
+            # Checksums with a value of 10 is rejected.
+            # https://no.wikipedia.org/wiki/F%C3%B8dselsnummer
+            if k1 == 10 or k2 == 10:
+                continue
+            pnr += '{}{}'.format(k1, k2)
+            return pnr


Test patch:
diff --git a/tests/providers/test_ssn.py b/tests/providers/test_ssn.py
--- a/tests/providers/test_ssn.py
+++ b/tests/providers/test_ssn.py
@@ -11,6 +11,7 @@
 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
+from faker.providers.ssn.no_NO import checksum as no_checksum, Provider
 
 
 class TestEtEE(unittest.TestCase):
@@ -127,3 +128,43 @@ def test_calculate_month(self):
     def test_ssn(self):
         for _ in range(100):
             self.assertTrue(re.search(r'^\d{11}$', self.factory.ssn()))
+
+
+class TestNoNO(unittest.TestCase):
+    def setUp(self):
+        self.factory = Faker('no_NO')
+
+    def test_no_NO_ssn_checksum(self):
+        self.assertEqual(no_checksum([0, 1, 0, 2, 0, 3, 9, 8, 7], Provider.scale1), 6)
+        self.assertEqual(no_checksum([0, 1, 0, 2, 0, 3, 9, 8, 7, 6], Provider.scale2), 7)
+
+    def test_no_NO_ssn(self):
+        for _ in range(100):
+            ssn = self.factory.ssn()
+            self.assertTrue(ssn.isdigit())
+            self.assertEqual(len(ssn), 11)
+
+    def test_no_NO_ssn_dob_passed(self):
+        date_of_birth = '20010203'
+        ssn = self.factory.ssn(dob=date_of_birth)
+        self.assertEqual(ssn[:6], date_of_birth[2:])
+
+    def test_no_NO_ssn_invalid_dob_passed(self):
+        with self.assertRaises(ValueError):
+            self.factory.ssn(dob='010401')
+        with self.assertRaises(ValueError):
+            self.factory.ssn(dob='hello_world')
+        with self.assertRaises(ValueError):
+            self.factory.ssn(dob='001301')
+
+    def test_no_NO_ssn_gender_passed(self):
+        # Females have even number at index 8
+        ssn = self.factory.ssn(gender='F')
+        self.assertTrue(int(ssn[8]) % 2 == 0)
+        # Males have odd number at index 8
+        ssn = self.factory.ssn(gender='M')
+        self.assertTrue(int(ssn[8]) % 2 == 1)
+
+    def test_no_NO_ssn_invalid_gender_passed(self):
+        with self.assertRaises(ValueError):
+            self.factory.ssn(gender='A')