1# kennitala.py - functions for handling Icelandic identity codes
2# coding: utf-8
3#
4# Copyright (C) 2015 Tuomas Toivonen
5# Copyright (C) 2015 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"""Kennitala (Icelandic personal and organisation identity code).
23
24Module for handling Icelandic personal and organisation identity codes
25(kennitala).
26
27>>> validate('450401-3150')  # organisation
28'4504013150'
29>>> validate('120174-3399')  # individual
30'1201743399'
31>>> validate('530575-0299')
32Traceback (most recent call last):
33    ...
34InvalidChecksum: ...
35>>> validate('320174-3399')
36Traceback (most recent call last):
37    ...
38InvalidComponent: ...
39>>> format('1201743399')
40'120174-3399'
41"""
42
43import datetime
44import re
45
46from stdnum.exceptions import *
47from stdnum.util import clean
48
49
50# Icelandic personal and organisation identity codes are composed of
51# date part, a dash, two random digits, a checksum, and a century
52# indicator where '9' for 1900-1999 and '0' for 2000 and beyond. For
53# organisations instead of birth date, the registration date is used,
54# and number 4 is added to the first digit.
55_kennitala_re = re.compile(
56    r'^(?P<day>[01234567]\d)(?P<month>[01]\d)(?P<year>\d\d)'
57    r'(?P<random>\d\d)(?P<control>\d)'
58    r'(?P<century>[09])$')
59
60
61def compact(number):
62    """Convert the kennitala to the minimal representation. This
63    strips surrounding whitespace and separation dash, and converts it
64    to upper case."""
65    return clean(number, '-').upper().strip()
66
67
68def checksum(number):
69    """Calculate the checksum."""
70    weights = (3, 2, 7, 6, 5, 4, 3, 2, 1, 0)
71    return sum(w * int(n) for w, n in zip(weights, number)) % 11
72
73
74def validate(number):
75    """Check if the number provided is a valid kennitala. It checks the
76    format, whether a valid date is given and whether the check digit is
77    correct."""
78    number = compact(number)
79    match = _kennitala_re.search(number)
80    if not match:
81        raise InvalidFormat()
82    day = int(match.group('day'))
83    month = int(match.group('month'))
84    year = int(match.group('year'))
85    if match.group('century') == '9':
86        year += 1900
87    else:
88        year += 2000
89    # check if birth date or registration data is valid
90    try:
91        if day >= 40:  # organisation
92            datetime.date(year, month, day - 40)
93        else:  # individual
94            datetime.date(year, month, day)
95    except ValueError:
96        raise InvalidComponent()
97    # validate the checksum
98    if checksum(number) != 0:
99        raise InvalidChecksum()
100    return number
101
102
103def is_valid(number):
104    """Check if the number provided is a valid HETU. It checks the format,
105    whether a valid date is given and whether the check digit is correct."""
106    try:
107        return bool(validate(number))
108    except ValidationError:
109        return False
110
111
112def format(number):
113    """Reformat the number to the standard presentation format."""
114    number = compact(number)
115    return number[:6] + '-' + number[6:]
116