Skip to content

Commit

Permalink
Add JMBG (Jedinstveni matični broj građana
Browse files Browse the repository at this point in the history
  • Loading branch information
djordje-m committed Aug 5, 2024
1 parent 6cbb9bc commit 75ff36a
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 0 deletions.
117 changes: 117 additions & 0 deletions stdnum/rs/jmbg.py
Original file line number Diff line number Diff line change
@@ -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
70 changes: 70 additions & 0 deletions tests/test_rs_jmbg.doctest
Original file line number Diff line number Diff line change
@@ -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)]
[]

0 comments on commit 75ff36a

Please sign in to comment.