Arthur de Jong

Open Source / Free Software developer

summaryrefslogtreecommitdiffstats
path: root/stdnum/cz/rc.py
blob: 603f7ee0a5a07d290e4cb362e74445e35170432a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# rc.py - functions for handling Czech birth numbers
# coding: utf-8
#
# Copyright (C) 2012, 2013 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

"""RČ (Rodné číslo, the Czech birth number).

The birth number (RČ, Rodné číslo) is the Czech national identifier. The
number can be 9 or 10 digits long. Numbers given out after January 1st
1954 should have 10 digits. The number includes the birth date of the
person and their gender.

This number is identical to the Slovak counterpart.

>>> validate('710319/2745')
'7103192745'
>>> validate('991231123')
'991231123'
>>> validate('7103192746')  # invalid check digit
Traceback (most recent call last):
    ...
InvalidChecksum: ...
>>> validate('1103492745')  # invalid date
Traceback (most recent call last):
    ...
InvalidComponent: ...
>>> validate('590312/123')  # 9 digit number in 1959
Traceback (most recent call last):
    ...
InvalidLength: ...
>>> format('7103192745')
'710319/2745'
"""

import datetime

from stdnum.exceptions import *
from stdnum.util import clean


def compact(number):
    """Convert the number to the minimal representation. This strips the
    number of any valid separators and removes surrounding whitespace."""
    return clean(number, ' /').upper().strip()


def get_birth_date(number):
    """Split the date parts from the number and return the birth date."""
    year = 1900 + int(number[0:2])
    # females have 50 added to the month value, 20 is added when the serial
    # overflows (since 2004)
    month = int(number[2:4]) % 50 % 20
    day = int(number[4:6])
    # 9 digit numbers were used until January 1st 1954
    if len(number) == 9:
        if year >= 1980:
            year -= 100
        if year > 1953:
            raise InvalidLength('No 9 digit birth numbers after 1953.')
    elif year < 1954:
        year += 100
    try:
        return datetime.date(year, month, day)
    except ValueError:
        raise InvalidComponent()


def validate(number):
    """Check if the number is a valid birth number. This checks the length,
    formatting, embedded date and check digit."""
    number = compact(number)
    if not number.isdigit():
        raise InvalidFormat()
    if len(number) not in (9, 10):
        raise InvalidLength()
    # check if birth date is valid
    birth_date = get_birth_date(number)
    # TODO: check that the birth date is not in the future
    # check the check digit (10 digit numbers only)
    if len(number) == 10:
        check = int(number[:-1]) % 11
        # before 1985 the checksum could be 0 or 10
        if birth_date < datetime.date(1985, 1, 1):
            check = check % 10
        if number[-1] != str(check):
            raise InvalidChecksum()
    return number


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


def format(number):
    """Reformat the number to the standard presentation format."""
    number = compact(number)
    return number[:6] + '/' + number[6:]