Skip to content

Added Belgian BIS Number. #418

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 155 additions & 0 deletions stdnum/be/bis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# coding=utf-8
# bis.py - functions for handling Belgian BIS numbers
#
# Copyright (C) 2023 Jeff Horemans
#
# 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

"""BIS (Belgian BIS number).


The BIS number (BIS-nummer, Numéro BIS) is a unique identification number
for individuals who are not registered in the National Register, but who
still have a relationship with the Belgian government.
This includes frontier workers, persons who own property in Belgium,
persons with Belgian social security rights but who do not reside in Belgium, etc.

The number is issued by the Belgian Crossroads Bank for Social Security (Banque
Carrefour de la sécurité sociale, Kruispuntbank voor Sociale Zekerheid) and is
constructed much in the same way as the Belgian National Number, i.e. consisting of
11 digits, encoding the person's date of birth and gender, a checksum, etc.
Other than with the national number though, the month of birth of the BIS number
is increased by 20 or 40, depending on whether the sex of the person was known
at the time or not.


More information:

* https://sma-help.bosa.belgium.be/en/faq/where-can-i-find-my-bis-number#7326
* https://www.socialsecurity.be/site_nl/employer/applics/belgianidpro/index.htm
* https://nl.wikipedia.org/wiki/Rijksregisternummer
* https://fr.wikipedia.org/wiki/Numéro_de_registre_national

>>> compact('98.47.28-997.65')
'98472899765'
>>> validate('98 47 28 997 65')
'98472899765'
>>> validate('01 49 07 001 85')
'01490700185'
>>> validate('12345678901')
Traceback (most recent call last):
...
InvalidChecksum: ...
>>> format('98472899765')
'98.47.28-997.65'
>>> get_birth_date('98.47.28-997.65')
datetime.date(1998, 7, 28)
>>> get_birth_year('98.47.28-997.65')
1998
>>> get_birth_month('98.47.28-997.65')
7
>>> get_gender('98.47.28-997.65')
'M'
"""

import datetime

from stdnum.be import nn
from stdnum.exceptions import *
from stdnum.util import isdigits


def compact(number):
"""Convert the number to the minimal representation. This strips the number
of any valid separators and removes surrounding whitespace."""
return nn.compact(number)


def _get_birth_date_parts(number):
century = nn._checksum(number)
# The month is incremented with 20 or 40 for BIS numbers,
# so subtract to validate the embedded birth date in the same
# manner as national number.
month = int(number[2:4])
if 20 <= month <= 32:
month -= 20
elif 40 <= month <= 52:
month -= 40
else:
raise InvalidComponent('month must be in 20..32 or 40..52 range')

# Create the fictitious national number version of the BIS number,
# with recalculated checksum, based on the decreased month.
# This way, we can reuse the nn module's functionality of extracting
# the parts of the embedded birth date, which may be unknown.
number = number[:2] + str(month).zfill(2) + number[4:]
if century == 1900:
checksum = 97 - int(number[:-2]) % 97
else:
checksum = 97 - int('2' + number[:-2]) % 97
number = number[:9] + str(checksum).zfill(2)
return nn._get_birth_date_parts(number)


def validate(number):
"""Check if the number is a valid BIS Number."""
number = compact(number)
if not isdigits(number) or int(number) <= 0:
raise InvalidFormat()
if len(number) != 11:
raise InvalidLength()
_get_birth_date_parts(number)
return number


def is_valid(number):
"""Check if the number is a valid BIS Number."""
try:
return bool(validate(number))
except ValidationError:
return False


def format(number):
"""Reformat the number to the standard presentation format."""
return nn.format(number)


def get_birth_year(number):
"""Return the year of the birth date."""
year, month, day = _get_birth_date_parts(compact(number))
return year


def get_birth_month(number):
"""Return the month of the birth date."""
year, month, day = _get_birth_date_parts(compact(number))
return month


def get_birth_date(number):
"""Return the date of birth."""
year, month, day = _get_birth_date_parts(compact(number))
if None not in (year, month, day):
return datetime.date(year, month, day)


def get_gender(number):
"""Get the person's gender ('M' or 'F'), which for BIS
numbers is only known if the month was incremented with 40."""
number = compact(number)
if int(number[2:4]) >= 40:
return nn.get_gender(number)
128 changes: 128 additions & 0 deletions tests/test_be_bis.doctest
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
test_be_bis.doctest - more detailed doctests for stdnum.be.bis module

Copyright (C) 2023 Jeff Horemans

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


This file contains more detailed doctests for the stdnum.be.bis module. It
tries to test more corner cases and detailed functionality that is not
really useful as module documentation.

>>> from stdnum.be import bis


Extra tests for getting birth date, year and/or month


>>> bis.get_birth_date('75.46.08-980.95')
datetime.date(1975, 6, 8)
>>> bis.get_birth_year('75.46.08-980.95')
1975
>>> bis.get_birth_month('75.46.08-980.95')
6
>>> bis.get_birth_date('01 49 07 001 85')
datetime.date(2001, 9, 7)
>>> bis.get_birth_year('01 49 07 001 85')
2001
>>> bis.get_birth_month('01 49 07 001 85')
9
>>> bis.get_birth_date('12345678901')
Traceback (most recent call last):
...
InvalidChecksum: ...
>>> bis.get_birth_year('12345678901')
Traceback (most recent call last):
...
InvalidChecksum: ...
>>> bis.get_birth_month('12345678901')
Traceback (most recent call last):
...
InvalidChecksum: ...
>>> bis.get_birth_date('00400100155') # Exact date of birth unknown (fictitious date case 1900-00-01)
>>> bis.get_birth_year('00400100155')
>>> bis.get_birth_month('00400100155')
>>> bis.get_birth_date('00200100112') # Birth date and gender unknown
>>> bis.get_birth_year('00200100112')
>>> bis.get_birth_month('00200100112')
>>> bis.get_birth_date('00400100184') # Exact date of birth unknown (fictitious date case 2000-00-01)
>>> bis.get_birth_year('00400100184')
>>> bis.get_birth_month('00400100184')
>>> bis.get_birth_date('00200100141') # Birth date and gender unknown
>>> bis.get_birth_year('00200100141')
>>> bis.get_birth_month('00200100141')
>>> bis.get_birth_date('00400000117') # Only birth year known (2000-00-00)
>>> bis.get_birth_year('00400000117')
2000
>>> bis.get_birth_month('00400000117')
>>> bis.get_birth_date('00200000171') # Only birth year known and gender unknown
>>> bis.get_birth_year('00200000171')
2000
>>> bis.get_birth_month('00200000171')
>>> bis.get_birth_date('00410000124') # Only birth year and month known (2000-01-00)
>>> bis.get_birth_year('00410000124')
2000
>>> bis.get_birth_month('00410000124')
1
>>> bis.get_birth_date('00210000178') # Only birth year and month known (2000-01-00) and gender unknown
>>> bis.get_birth_year('00210000178')
2000
>>> bis.get_birth_month('00210000178')
1
>>> bis.get_birth_date('85473500193') # Unknown day of birth date (35)
>>> bis.get_birth_year('85473500193')
1985
>>> bis.get_birth_month('85473500193')
7
>>> bis.get_birth_date('85273500150') # Unknown day of birth date (35) and gender unknown
>>> bis.get_birth_year('85273500150')
1985
>>> bis.get_birth_month('85273500150')
7
>>> bis.get_birth_date('85533000191') # Invalid month (13)
Traceback (most recent call last):
...
InvalidComponent: ...
>>> bis.get_birth_year('85533000191')
Traceback (most recent call last):
...
InvalidComponent: ...
>>> bis.get_birth_month('85533000191')
Traceback (most recent call last):
...
InvalidComponent: ...
>>> bis.get_birth_date('85333000148')
Traceback (most recent call last):
...
InvalidComponent: ...
>>> bis.get_birth_year('85333000148')
Traceback (most recent call last):
...
InvalidComponent: ...
>>> bis.get_birth_month('85333000148')
Traceback (most recent call last):
...
InvalidComponent: ...


Extra tests for getting gender.

>>> bis.get_gender('75.46.08-980.95')
'F'
>>> bis.get_gender('75.26.08-980.52') # Gender unknown (month incremented by 20)
>>> bis.get_gender('85473500193')
'M'
>>> bis.get_gender('85273500150')