8000 Add Norwegian Fødselsnummer · yyht/python-stdnum@d11e5c4 · GitHub
[go: up one dir, main page]

Skip to content

Commit d11e5c4

Browse files
vihtinskyarthurdejong
authored andcommitted
Add Norwegian Fødselsnummer
Closes arthurdejong#88
1 parent bb24c2f commit d11e5c4

File tree

3 files changed

+214
-1
lines changed

3 files changed

+214
-1
lines changed

stdnum/no/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@
2020

2121
"""Collection of Norwegian numbers."""
2222

23-
# provide vat as an alias
23+
# provide aliases
24+
from stdnum.no import fodselsnummer as personalid # noqa: F401
2425
from stdnum.no import mva as vat # noqa: F401

stdnum/no/fodselsnummer.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# fodselsnummer.py - functions for handling Norwegian birth numbers
2+
# coding: utf-8
3+
#
4+
# Copyright (C) 2018 Ilya Vihtinsky
5+
# Copyright (C) 2018 Arthur de Jong
6+
#
7+
# This library is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU Lesser General Public
9+
# License as published by the Free Software Foundation; either
10+
# version 2.1 of the License, or (at your option) any later version.
11+
#
12+
# This library is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
# Lesser General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU Lesser General Public
18+
# License along with this library; if not, write to the Free Software
19+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20+
# 02110-1301 USA
21+
22+
"""Fødselsnummer (Norwegian birth number, the national identity number).
23+
24+
The Fødselsnummer is an eleven-digit number that is built up of the date of
25+
birth of the person, a serial number and two check digits.
26+
27+
More information:
28+
29+
* https://no.wikipedia.org/wiki/F%C3%B8dselsnummer
30+
* https://en.wikipedia.org/wiki/National_identification_number#Norway
31+
32+
>>> validate('684131 52112')
33+
'68413152112'
34+
>>> get_gender('684131 52112')
35+
'M'
36+
>>> validate('684131 52123')
37+
Traceback (most recent call last):
38+
...
39+
InvalidChecksum: ...
40+
>>> format('68413152112')
41+
'684131 52112'
42+
"""
43+
44+
from stdnum.exceptions import *
45+
from stdnum.util import clean
46+
47+
48+
def compact(number):
49+
"""Convert the number to the minimal representation. This strips the
50+
number of any valid separators and removes surrounding whitespace."""
51+
return clean(number, ' -:')
52+
53+
54+
def calc_check_digit1(number):
55+
"""Calculate the first check digit for the number."""
56+
weights = (3, 7, 6, 1, 8, 9, 4, 5, 2)
57+
return str((11 - sum(w * int(n) for w, n in zip(weights, number))) % 11)
58+
59+
60+
def calc_check_digit2(number):
61+
"""Calculate the second check digit for the number."""
62+
weights = (5, 4, 3, 2, 7, 6, 5, 4, 3, 2)
63+
return str((11 - sum(w * int(n) for w, n in zip(weights, number))) % 11)
64+
65+
66+
def get_gender(number):
67+
"""Get the person's birth gender ('M' or 'F')."""
68+
number = compact(number)
69+
if int(number[8]) % 2:
70+
return 'M'
71+
else:
72+
return 'F'
73+
74+
75+
def validate(number):
76+
"""Check if the number is a valid birth number."""
77+
number = compact(number)
78+
if len(number) != 11:
79+
raise InvalidLength()
80+
if not number.isdigit():
81+
raise InvalidFormat()
82+
if number[-2] != calc_check_digit1(number):
83+
raise InvalidChecksum()
84+
if number[-1] != calc_check_digit2(number):
85+
raise InvalidChecksum()
86+
return number
87+
88+
89+
def is_valid(number):
90+
"""Check if the number is a valid birth number."""
91+
try:
92+
return bool(validate(number))
93+
except ValidationError:
94+
return False
95+
96+
97+
def format(number):
98+
"""Reformat the number to the standard presentation format."""
99+
number = compact(number)
100+
return number[:6] + ' ' + number[6:]

tests/test_no_fodselsnummer.doctest

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
test_no_fodselsnummer.doctest - more detailed doctests for stdnum.no.fodselsnummer module
2+
3+
Copyright (C) 2018 Ilya Vihtinsky
4+
Copyright (C) 2018 Arthur de Jong
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+
22+
This file contains more detailed doctests for the stdnum.no.fodselsnummer
23+
module. It tries to cover more corner cases and detailed functionality that
24+
is not really useful as module documentation.
25+
26+
>>> from stdnum.no import fodselsnummer
27+
28+
29+
These numbers should be detected as male or female.
30+
31+
>>> fodselsnummer.get_gender('70624830529')
32+
'M'
33+
>>> fodselsnummer.get_gender('56403643756')
34+
'M'
35+
>>> fodselsnummer.get_gender('70341666064')
36+
'F'
37+
>>> fodselsnummer.get_gender('82938389280')
38+
'F'
39+
40+
41+
The last two check digits are validated independently of each other.
42+
43+
>>> fodselsnummer.is_valid('42485176432')
44+
True
45+
>>> fodselsnummer.is_valid('42485176431') # only change last digit
46+
False
47+
>>> fodselsnummer.is_valid('42485176412') # only change first digit
48+
False
49+
>>> fodselsnummer.is_valid('42485176416') # change first, correct second
50+
False
51+
52+
53+
Length and format are also validated.
54+
55+
>>> fodselsnummer.validate('42485176432123')
56+
Traceback (most recent call last):
57+
...
58+
InvalidLength: ...
59+
>>> fodselsnummer.validate('a0gzaB30529')
60+
Traceback (most recent call last):
61+
...
62+
InvalidFormat: ...
63+
64+
65+
These have been found online and should all be valid numbers.
66+
67+
>>> numbers = '''
68+
...
69+
... 11027794191
70+
... 11051996811
71+
... 19575770838
72+
... 21918484865
73+
... 24396859900
74+
... 27213364885
75+
... 27389446152
76+
... 30383131118
77+
... 30777674125
78+
... 31042639152
79+
... 34831582121
80+
... 39043009846
81+
... 40070897972
82+
... 40673759612
83+
... 42115114470
84+
... 42485176432
85+
... 42957044500
86+
... 44207789809
87+
... 45014054018
88+
... 46929323343
89+
... 50067834221
90+
... 56403643756
91+
... 56653047547
92+
... 63282310041
93+
... 68413152112
94+
... 70031073454
95+
... 70341666064
96+
... 70624830529
97+
... 71494457037
98+
... 71946503120
99+
... 75442702381
100+
... 79189404641
101+
... 79318270827
102+
... 82938389280
103+
... 83814827871
104+
... 89829529360
105+
... 92782833709
106+
... 95700625908
107+
... 96517753502
108+
... 98576936818
109+
...
110+
... '''
111+
>>> [x for x in numbers.splitlines() if x and not fodselsnummer.is_valid(x)]
112+
[]

0 commit comments

Comments
 (0)
0