Repository: identique/idnumbers (language: python)

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


Discussion / hints:
no checksum, no parse
https://learn.microsoft.com/en-us/microsoft-365/compliance/sit-defn-philippines-unified-multi-purpose-identification-number?view=o365-worldwide

Fix patch:
diff --git a/README.md b/README.md
index 00638fc9..b40dee34 100644
--- a/README.md
+++ b/README.md
@@ -60,6 +60,7 @@ print(id_data['citizenship'])
 * JPN - Japan
 * KOR - South Korea
 * LKA - Sri Lanka
+* MYS - Malaysia
 * NGA - Nigeria
 * NZL - New Zealand
 * PHL - Philippine
diff --git a/idnumbers/nationalid/MYS.py b/idnumbers/nationalid/MYS.py
new file mode 100644
index 00000000..185d75ab
--- /dev/null
+++ b/idnumbers/nationalid/MYS.py
@@ -0,0 +1,72 @@
+import re
+from datetime import date
+from types import SimpleNamespace
+from typing import Optional, TypedDict
+from .constant import Citizenship
+from .util import validate_regexp
+
+
+def normalize(id_number):
+    return re.sub(r'-', '', id_number)
+
+
+class ParseResult(TypedDict):
+    yyyymmdd: date
+    location: str
+    citizenship: Citizenship
+    sn: str
+
+
+class NationalID:
+    """
+    Malaysia National ID number format, NRIC
+    https://en.wikipedia.org/wiki/Malaysian_identity_card#Structure_of_the_National_Registration_Identity_Card_Number_(NRIC)
+    """
+    METADATA = SimpleNamespace(**{
+        'iso3166_alpha2': 'MY',
+        'min_length': 12,
+        'max_length': 12,
+        'parsable': True,
+        'checksum': False,
+        'regexp': re.compile(r'^(?P<yy>\d{2})(?P<mm>\d{2})(?P<dd>\d{2})-?'
+                             r'(?P<pb>\d{2})-?'
+                             r'(?P<sn>\d{4})$')
+    })
+
+    WRONG_PB_CODE = ['00', '17', '18', '19', '20',
+                     '69', '70', '73', '80', '81', '94', '95', '96', '97']
+
+    @staticmethod
+    def validate(id_number: str) -> bool:
+        """
+        Validate the SGP id number
+        """
+        if not validate_regexp(id_number, NationalID.METADATA.regexp):
+            return False
+        return NationalID.parse(id_number) is not None
+
+    @staticmethod
+    def parse(id_number: str) -> Optional[ParseResult]:
+        match_obj = NationalID.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'))
+        location = match_obj.group('pb')
+        if not NationalID.check_location_code(location):
+            return None
+        sn = match_obj.group('sn')
+        yyyy_base = 1900 if int(sn[0]) > 4 else 2000
+
+        return {
+            'yyyymmdd': date(yyyy_base + yy, mm, dd),
+            'location': location,
+            'citizenship': Citizenship.CITIZEN if int(location) < 60 else Citizenship.RESIDENT,
+            'sn': sn
+        }
+
+    @staticmethod
+    def check_location_code(location_code: str) -> bool:
+        return location_code not in NationalID.WRONG_PB_CODE


Test patch:
diff --git a/idnumbers/nationalid/test_MYS.py b/idnumbers/nationalid/test_MYS.py
new file mode 100644
index 00000000..fef4b1d7
--- /dev/null
+++ b/idnumbers/nationalid/test_MYS.py
@@ -0,0 +1,28 @@
+from unittest import TestCase, main
+
+from idnumbers.nationalid import MYS
+from idnumbers.nationalid.constant import Gender, Citizenship
+
+
+class TestMYSValidation(TestCase):
+    def test_normal_case(self):
+        self.assertTrue(MYS.NationalID.validate('691206-10-5330'))
+        self.assertTrue(MYS.NationalID.validate('510317-13-5131'))
+        self.assertTrue(MYS.NationalID.validate('690602-13-6118'))
+        self.assertTrue(MYS.NationalID.validate('801101-06-6085'))
+
+    def test_error_case(self):
+        self.assertFalse(MYS.NationalID.validate('6912010533'))
+
+    def test_parse(self):
+        result = MYS.NationalID.parse('690602-13-6118')
+        self.assertEqual(1969, result['yyyymmdd'].year)
+        self.assertEqual(6, result['yyyymmdd'].month)
+        self.assertEqual(2, result['yyyymmdd'].day)
+        self.assertEqual('13', result['location'])
+        self.assertEqual(Citizenship.CITIZEN, result['citizenship'])
+        self.assertEqual('6118', result['sn'])
+
+
+if __name__ == '__main__':
+    main()