From 75ff36aac9f2bbbd667efcea0c100353b0fe76cf Mon Sep 17 00:00:00 2001 From: djordje Date: Mon, 5 Aug 2024 19:38:08 +0200 Subject: [PATCH] =?UTF-8?q?Add=20JMBG=20(Jedinstveni=20mati=C4=8Dni=20broj?= =?UTF-8?q?=20gra=C4=91ana?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- stdnum/rs/jmbg.py | 117 +++++++++++++++++++++++++++++++++++++ tests/test_rs_jmbg.doctest | 70 ++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 stdnum/rs/jmbg.py create mode 100644 tests/test_rs_jmbg.doctest diff --git a/stdnum/rs/jmbg.py b/stdnum/rs/jmbg.py new file mode 100644 index 00000000..47485626 --- /dev/null +++ b/stdnum/rs/jmbg.py @@ -0,0 +1,117 @@ +# jmbg.py - functions for handling Serbian JMBG (unique personal identification) numbers +# coding: utf-8 +# +# Copyright (C) 2017 Arthur de Jong +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +"""JMBG (Jedinstveni Matični Broj Građana, Serbian unique citizen identification number). + +The Serbian unique citizen identification number consists of 13 digits where the last +digit is a check digit. + +>>> validate('0101900350008') +'0101900350008' +>>> validate('0101900350007') +Traceback (most recent call last): + ... +InvalidChecksum: ... +""" + +from stdnum.exceptions import * +from stdnum import luhn +from datetime import datetime +from stdnum.util import clean, isdigits +import re + +REGEX = r"(\d{2})(\d{2})(\d{3})(\d{2})(\d{3})(\d{1})" + +def compact(number): + """Convert the number to the minimal representation. This strips the + number of any blank spaces and removes surrounding whitespace.""" + return clean(number, ' ').strip() + + +def validate(number): + """Check if the number is a valid JMBG number. This checks the length, + formatting and check digit. """ + number = compact(number) + if not isdigits(number): + raise InvalidFormat() + if len(number) != 13: + raise InvalidLength() + + day, month, year, region, sex, control = split_fields(number) + human_year = int(f"1{year}") + + if verify_date(day, month, human_year): + number_sum = get_sum(number) + if verify_control(number_sum, control) == False: + raise InvalidChecksum() + + return number + +def get_sum(number): + first = number[0:6] + second = number[6:-1] + multiplier = [7, 6, 5, 4, 3, 2] + + first_sum = sum([int(a) * b for a, b in zip(first, multiplier)]) + second_sum = sum([int(a) * b for a, b in zip(second, multiplier)]) + + return first_sum + second_sum + +def verify_date(day, month, year): + try: + if isinstance(day, str): + day = int(day) + + if isinstance(month, str): + month = int(month) + + datetime(year, month, day) + + return True + + except Exception: + raise InvalidFormat() + +def verify_control(number_sum, control): + control = int(control) + remainder = number_sum % 11 + + if remainder > 1: + return control == 11 - remainder + + elif remainder == 0 and remainder == control: + return True + + return False + +def split_fields(jmbg): + match = re.match(REGEX, jmbg) + + if not match: + raise InvalidFormat() + + return match.groups() + +def is_valid(number): + """Check if the number is a valid VAT number.""" + try: + return bool(validate(number)) + except ValidationError: + return False diff --git a/tests/test_rs_jmbg.doctest b/tests/test_rs_jmbg.doctest new file mode 100644 index 00000000..9c0b1323 --- /dev/null +++ b/tests/test_rs_jmbg.doctest @@ -0,0 +1,70 @@ +test_rs_pib.doctest - more detailed doctests for the stdnum.rs.pib module + +Copyright (C) 2017 Arthur de Jong + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + + +This file contains more detailed doctests for the stdnum.rs.pib module. + +>>> from stdnum.rs import jmbg +>>> from stdnum.exceptions import * + + +These have been generated online and should all be valid numbers. + +>>> numbers = ''' +... +... 0101900710004 +... 0101900710012 +... 0101900710020 +... 0101900710039 +... 0101900710047 +... 0101900715006 +... 0101900715014 +... 0101900715022 +... 0101900715030 +... 0101900715049 +... 0101900720018 +... 0101900720026 +... 0101900720034 +... 0101900720042 +... 0101900725001 +... 0101900725028 +... 0101900725036 +... 0101900725044 +... 3112900730008 +... 3112900730016 +... 3112900730024 +... 3112900730032 +... 3112900730040 +... 3112900730059 +... 3112900730067 +... 3112900730075 +... 3112900730083 +... 3112900730091 +... 3112900735018 +... 3112900735026 +... 3112900735034 +... 3112900735042 +... 3112900735050 +... 3112900735069 +... 3112900735077 +... 3112900735085 +... 3112900735093 +... +... ''' +>>> [x for x in numbers.splitlines() if x and not jmbg.is_valid(x)] +[]