Repository: identique/idnumbers (language: python)

Issue:
id number for Denmark
https://en.wikipedia.org/wiki/National_identification_number#Denmark


Discussion / hints:

Fix patch:
diff --git a/idnumbers/nationalid/DNK.py b/idnumbers/nationalid/DNK.py
new file mode 100644
index 00000000..aa53ab71
--- /dev/null
+++ b/idnumbers/nationalid/DNK.py
@@ -0,0 +1,71 @@
+import re
+from datetime import date
+from types import SimpleNamespace
+from typing import Literal, Optional, TypedDict, get_args
+from .util import validate_regexp
+from .constant import Gender
+
+
+CHECKSUM_TYPE = Literal['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+                        'A', 'B', 'C', 'D', 'E', 'F', 'H', 'J', 'K', 'L',
+                        'M', 'N', 'P', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+                        'Y']
+"""literal for checksum type"""
+
+
+class ParseResult(TypedDict):
+    """The parse result of Finland personal identity code, HETU"""
+    yyyymmdd: date
+    """Birthday"""
+    sn: str
+    """The serial number born at the same date"""
+
+
+class PersonalIdentityNumber:
+    """
+    Denmark personal identity number, CPR, Det Centrale Personregister
+    https://en.wikipedia.org/wiki/National_identification_number#Denmark
+    """
+    METADATA = SimpleNamespace(**{
+        'iso3166_alpha2': 'DK',
+        'min_length': 10,
+        'max_length': 10,
+        'parsable': True,
+        'checksum': False,
+        'regexp': re.compile(r'^(?P<dd>\d{2})(?P<mm>\d{2})(?P<yy>\d{2})-?'
+                             r'(?P<sn>\d{4})$')
+    })
+
+    @staticmethod
+    def validate(id_number: str) -> bool:
+        """
+        Validate the id number
+        """
+        if not validate_regexp(id_number, PersonalIdentityNumber.METADATA.regexp):
+            return False
+        return PersonalIdentityNumber.parse(id_number) is not None
+
+    @staticmethod
+    def parse(id_number: str) -> Optional[ParseResult]:
+        """ parse the CPR id"""
+        match_obj = PersonalIdentityNumber.METADATA.regexp.match(id_number)
+        if not match_obj:
+            return None
+        yy = int(match_obj.group('yy'))
+        mm = int(match_obj.group('mm'))
+        dd = int(match_obj.group('dd'))
+        sn = match_obj.group('sn')
+        yyyy_base = 1900 if yy > 50 else 2000
+        try:
+            return {
+                'yyyymmdd': date(yyyy_base + yy, mm, dd),
+                'sn': sn
+            }
+        except ValueError:
+            return None
+
+
+NationalID = PersonalIdentityNumber
+"""alias of PersonalIdentityNumber"""
+CPR = PersonalIdentityNumber
+"""alias of PersonalIdentityNumber"""


Test patch:
diff --git a/tests/nationalid/test_DNK.py b/tests/nationalid/test_DNK.py
new file mode 100644
index 00000000..35c0190f
--- /dev/null
+++ b/tests/nationalid/test_DNK.py
@@ -0,0 +1,19 @@
+from unittest import TestCase
+
+from idnumbers.nationalid import DNK
+from idnumbers.nationalid.constant import Gender
+
+
+class TestDNKValidation(TestCase):
+    def test_normal_case(self):
+        self.assertTrue(DNK.PersonalIdentityNumber.validate('061085-1178'))
+
+    def test_error_case(self):
+        self.assertFalse(DNK.PersonalIdentityNumber.validate('061085-178'))
+
+    def test_parse(self):
+        result = DNK.PersonalIdentityNumber.parse('061085-1178')
+        self.assertEqual(1985, result['yyyymmdd'].year)
+        self.assertEqual(10, result['yyyymmdd'].month)
+        self.assertEqual(6, result['yyyymmdd'].day)
+        self.assertEqual('1178', result['sn'])