Skip to content

Commit be33a80

Browse files
jeffh92arthurdejong
authored andcommitted
Add Belgian BIS Number
Closes #418
1 parent 8ce4a47 commit be33a80

File tree

4 files changed

+275
-3
lines changed

4 files changed

+275
-3
lines changed

stdnum/be/bis.py

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# coding=utf-8
2+
# bis.py - functions for handling Belgian BIS numbers
3+
#
4+
# Copyright (C) 2023 Jeff Horemans
5+
#
6+
# This library is free software; you can redistribute it and/or
7+
# modify it under the terms of the GNU Lesser General Public
8+
# License as published by the Free Software Foundation; either
9+
# version 2.1 of the License, or (at your option) any later version.
10+
#
11+
# This library is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
# Lesser General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU Lesser General Public
17+
# License along with this library; if not, write to the Free Software
18+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19+
# 02110-1301 USA
20+
21+
"""BIS (Belgian BIS number).
22+
23+
24+
The BIS number (BIS-nummer, Numéro BIS) is a unique identification number
25+
for individuals who are not registered in the National Register, but who
26+
still have a relationship with the Belgian government.
27+
This includes frontier workers, persons who own property in Belgium,
28+
persons with Belgian social security rights but who do not reside in Belgium, etc.
29+
30+
The number is issued by the Belgian Crossroads Bank for Social Security (Banque
31+
Carrefour de la sécurité sociale, Kruispuntbank voor Sociale Zekerheid) and is
32+
constructed much in the same way as the Belgian National Number, i.e. consisting of
33+
11 digits, encoding the person's date of birth and gender, a checksum, etc.
34+
Other than with the national number though, the month of birth of the BIS number
35+
is increased by 20 or 40, depending on whether the sex of the person was known
36+
at the time or not.
37+
38+
39+
More information:
40+
41+
* https://sma-help.bosa.belgium.be/en/faq/where-can-i-find-my-bis-number#7326
42+
* https://www.socialsecurity.be/site_nl/employer/applics/belgianidpro/index.htm
43+
* https://nl.wikipedia.org/wiki/Rijksregisternummer
44+
* https://fr.wikipedia.org/wiki/Numéro_de_registre_national
45+
46+
>>> compact('98.47.28-997.65')
47+
'98472899765'
48+
>>> validate('98 47 28 997 65')
49+
'98472899765'
50+
>>> validate('01 49 07 001 85')
51+
'01490700185'
52+
>>> validate('12345678901')
53+
Traceback (most recent call last):
54+
...
55+
InvalidChecksum: ...
56+
>>> format('98472899765')
57+
'98.47.28-997.65'
58+
>>> get_birth_date('98.47.28-997.65')
59+
datetime.date(1998, 7, 28)
60+
>>> get_birth_year('98.47.28-997.65')
61+
1998
62+
>>> get_birth_month('98.47.28-997.65')
63+
7
64+
>>> get_gender('98.47.28-997.65')
65+
'M'
66+
"""
67+
68+
from stdnum.be import nn
69+
from stdnum.exceptions import *
70+
from stdnum.util import isdigits
71+
72+
73+
def compact(number):
74+
"""Convert the number to the minimal representation. This strips the number
75+
of any valid separators and removes surrounding whitespace."""
76+
return nn.compact(number)
77+
78+
79+
def validate(number):
80+
"""Check if the number is a valid BIS Number."""
81+
number = compact(number)
82+
if not isdigits(number) or int(number) <= 0:
83+
raise InvalidFormat()
84+
if len(number) != 11:
85+
raise InvalidLength()
86+
nn._get_birth_date_parts(number)
87+
if not 20 <= int(number[2:4]) <= 32 and not 40 <= int(number[2:4]) <= 52:
88+
raise InvalidComponent('Month must be in 20..32 or 40..52 range')
89+
return number
90+
91+
92+
def is_valid(number):
93+
"""Check if the number is a valid BIS Number."""
94+
try:
95+
return bool(validate(number))
96+
except ValidationError:
97+
return False
98+
99+
100+
def format(number):
101+
"""Reformat the number to the standard presentation format."""
102+
return nn.format(number)
103+
104+
105+
def get_birth_year(number):
106+
"""Return the year of the birth date."""
107+
return nn.get_birth_year(number)
108+
109+
110+
def get_birth_month(number):
111+
"""Return the month of the birth date."""
112+
return nn.get_birth_month(number)
113+
114+
115+
def get_birth_date(number):
116+
"""Return the date of birth."""
117+
return nn.get_birth_date(number)
118+
119+
120+
def get_gender(number):
121+
"""Get the person's gender ('M' or 'F'), which for BIS
122+
numbers is only known if the month was incremented with 40."""
123+
number = compact(number)
124+
if int(number[2:4]) >= 40:
125+
return nn.get_gender(number)

stdnum/be/nn.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,12 @@ def _get_birth_date_parts(number):
115115
# If the fictitious dates 1900/00/01 or 2000/00/01 are detected,
116116
# the birth date (including the year) was not known when the number
117117
# was issued.
118-
if number[:6] == '000001':
118+
if number[:6] in ('000001', '002001', '004001'):
119119
return (None, None, None)
120120

121121
year = int(number[:2]) + century
122-
month, day = int(number[2:4]), int(number[4:6])
122+
month = int(number[2:4]) % 20
123+
day = int(number[4:6])
123124
# When the month is zero, it was either unknown when the number was issued,
124125
# or the day counter ran out. In both cases, the month and day are not known
125126
# reliably.
@@ -128,7 +129,7 @@ def _get_birth_date_parts(number):
128129

129130
# Verify range of month
130131
if month > 12:
131-
raise InvalidComponent('month must be in 1..12')
132+
raise InvalidComponent('Month must be in 1..12')
132133

133134
# Case when only the day of the birth date is unknown
134135
if day == 0 or day > calendar.monthrange(year, month)[1]:
@@ -145,6 +146,8 @@ def validate(number):
145146
if len(number) != 11:
146147
raise InvalidLength()
147148
_get_birth_date_parts(number)
149+
if not 0 <= int(number[2:4]) <= 12:
150+
raise InvalidComponent('Month must be in 1..12')
148151
return number
149152

150153

tests/test_be_bis.doctest

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
test_be_bis.doctest - more detailed doctests for stdnum.be.bis module
2+
3+
Copyright (C) 2023 Jeff Horemans
4+
5+
This library is free software; you can redistribute it and/or
6+
modify it under the terms of the GNU Lesser General Public
7+
License as published by the Free Software Foundation; either
8+
version 2.1 of the License, or (at your option) any later version.
9+
10+
This library is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public
16+
License along with this library; if not, write to the Free Software
17+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18+
02110-1301 USA
19+
20+
21+
This file contains more detailed doctests for the stdnum.be.bis module. It
22+
tries to test more corner cases and detailed functionality that is not
23+
really useful as module documentation.
24+
25+
>>> from stdnum.be import bis
26+
27+
28+
Extra tests for getting birth date, year and/or month
29+
30+
31+
>>> bis.get_birth_date('75.46.08-980.95')
32+
datetime.date(1975, 6, 8)
33+
>>> bis.get_birth_year('75.46.08-980.95')
34+
1975
35+
>>> bis.get_birth_month('75.46.08-980.95')
36+
6
37+
>>> bis.get_birth_date('01 49 07 001 85')
38+
datetime.date(2001, 9, 7)
39+
>>> bis.get_birth_year('01 49 07 001 85')
40+
2001
41+
>>> bis.get_birth_month('01 49 07 001 85')
42+
9
43+
>>> bis.get_birth_date('12345678901')
44+
Traceback (most recent call last):
45+
...
46+
InvalidChecksum: ...
47+
>>> bis.get_birth_year('12345678901')
48+
Traceback (most recent call last):
49+
...
50+
InvalidChecksum: ...
51+
>>> bis.get_birth_month('12345678901')
52+
Traceback (most recent call last):
53+
...
54+
InvalidChecksum: ...
55+
>>> bis.get_birth_date('00400100155') # Exact date of birth unknown (fictitious date case 1900-00-01)
56+
>>> bis.get_birth_year('00400100155')
57+
>>> bis.get_birth_month('00400100155')
58+
>>> bis.get_birth_date('00200100112') # Birth date and gender unknown
59+
>>> bis.get_birth_year('00200100112')
60+
>>> bis.get_birth_month('00200100112')
61+
>>> bis.get_birth_date('00400100184') # Exact date of birth unknown (fictitious date case 2000-00-01)
62+
>>> bis.get_birth_year('00400100184')
63+
>>> bis.get_birth_month('00400100184')
64+
>>> bis.get_birth_date('00200100141') # Birth date and gender unknown
65+
>>> bis.get_birth_year('00200100141')
66+
>>> bis.get_birth_month('00200100141')
67+
>>> bis.get_birth_date('00400000117') # Only birth year known (2000-00-00)
68+
>>> bis.get_birth_year('00400000117')
69+
2000
70+
>>> bis.get_birth_month('00400000117')
71+
>>> bis.get_birth_date('00200000171') # Only birth year known and gender unknown
72+
>>> bis.get_birth_year('00200000171')
73+
2000
74+
>>> bis.get_birth_month('00200000171')
75+
>>> bis.get_birth_date('00410000124') # Only birth year and month known (2000-01-00)
76+
>>> bis.get_birth_year('00410000124')
77+
2000
78+
>>> bis.get_birth_month('00410000124')
79+
1
80+
>>> bis.get_birth_date('00210000178') # Only birth year and month known (2000-01-00) and gender unknown
81+
>>> bis.get_birth_year('00210000178')
82+
2000
83+
>>> bis.get_birth_month('00210000178')
84+
1
85+
>>> bis.get_birth_date('85473500193') # Unknown day of birth date (35)
86+
>>> bis.get_birth_year('85473500193')
87+
1985
88+
>>> bis.get_birth_month('85473500193')
89+
7
90+
>>> bis.get_birth_date('85273500150') # Unknown day of birth date (35) and gender unknown
91+
>>> bis.get_birth_year('85273500150')
92+
1985
93+
>>> bis.get_birth_month('85273500150')
94+
7
95+
>>> bis.get_birth_date('85533000191') # Invalid month (13)
96+
Traceback (most recent call last):
97+
...
98+
InvalidComponent: ...
99+
>>> bis.get_birth_year('85533000191')
100+
Traceback (most recent call last):
101+
...
102+
InvalidComponent: ...
103+
>>> bis.get_birth_month('85533000191')
104+
Traceback (most recent call last):
105+
...
106+
InvalidComponent: ...
107+
>>> bis.get_birth_date('85333000148')
108+
Traceback (most recent call last):
109+
...
110+
InvalidComponent: ...
111+
>>> bis.get_birth_year('85333000148')
112+
Traceback (most recent call last):
113+
...
114+
InvalidComponent: ...
115+
>>> bis.get_birth_month('85333000148')
116+
Traceback (most recent call last):
117+
...
118+
InvalidComponent: ...
119+
120+
121+
Extra tests for getting gender.
122+
123+
>>> bis.get_gender('75.46.08-980.95')
124+
'F'
125+
>>> bis.get_gender('75.26.08-980.52') # Gender unknown (month incremented by 20)
126+
>>> bis.get_gender('85473500193')
127+
'M'
128+
>>> bis.get_gender('85273500150')
129+
130+
131+
A NN should not be considered a valid BIS number.
132+
133+
>>> bis.validate('00000100195')
134+
Traceback (most recent call last):
135+
...
136+
InvalidComponent: ...

tests/test_be_nn.doctest

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,11 @@ Extra tests for getting gender
9191

9292
>>> nn.get_gender('75.06.08-980.09')
9393
'F'
94+
95+
96+
A BIS number is not considered a valid NN.
97+
98+
>>> nn.validate('00400100184')
99+
Traceback (most recent call last):
100+
...
101+
InvalidComponent: ...

0 commit comments

Comments
 (0)