lists.arthurdejong.org
RSS feed

python-stdnum branch master updated. 0.9-2-g1ac00a0

[Date Prev][Date Next] [Thread Prev][Thread Next]

python-stdnum branch master updated. 0.9-2-g1ac00a0



This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "python-stdnum".

The branch, master has been updated
       via  1ac00a0b51204b953fa1f1bb0b87aee16d078dbc (commit)
      from  c3d669ca074557bcf57a4742b83d178364b08465 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
http://arthurdejong.org/git/python-stdnum/commit/?id=1ac00a0b51204b953fa1f1bb0b87aee16d078dbc

commit 1ac00a0b51204b953fa1f1bb0b87aee16d078dbc
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sun Feb 2 21:22:10 2014 +0100

    Add an Italian Codice Fiscale module
    
    This module validates 16 digit Italian tax codes for individuals.
    https://en.wikipedia.org/wiki/Italian_fiscal_code_card
    
    It is based on the pycodicefiscale module that can be found here:
    https://github.com/baxeico/pycodicefiscale
    
    Functions have been renamed to follow the stdnum naming scheme:
    isvalid() -> is_valid(), control_code -> calc_check_digit(),
    get_birthday() -> get_birth_date(), get_sex() -> get_gender(). The
    build() function for generating tax codes (based on name, birth place
    and date) has been left out because this number cannot be uniquely
    constructed with this information alone (numbers are issued by the
    Italian tax office with a mechanism handle duplicates).
    
    Addresses trac ticket #9.

diff --git a/stdnum/it/codicefiscale.py b/stdnum/it/codicefiscale.py
new file mode 100644
index 0000000..63b7f6e
--- /dev/null
+++ b/stdnum/it/codicefiscale.py
@@ -0,0 +1,152 @@
+# codicefiscale.py - library for Italian fiscal code
+#
+# This file is based on code from pycodicefiscale, a Python library for
+# working with Italian fiscal code numbers officially known as Italy's
+# Codice Fiscale.
+# https://github.com/baxeico/pycodicefiscale
+#
+# Copyright (C) 2009-2013 Emanuele Rocca
+# Copyright (C) 2014 Augusto Destrero
+# Copyright (C) 2014 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
+
+"""Codice Fiscale (Italian tax code for individuals).
+
+The Codice Fiscale is an alphanumeric code of 16 characters used to
+identify individuals residing in Italy. The number consists of three
+characters derived from the person's last name, three from the person's
+first name, five that hold information on the person's gender and birth
+date, four that represent the person's place of birth and one check digit.
+
+>>> validate('RCCMNL83S18D969H')
+'RCCMNL83S18D969H'
+>>> validate('RCCMNL83S18D969')
+Traceback (most recent call last):
+    ...
+InvalidLength: ...
+>>> calc_check_digit('RCCMNL83S18D969')
+'H'
+"""
+
+import re
+import datetime
+
+from stdnum.exceptions import *
+from stdnum.util import clean
+
+
+# regular expression for matching fiscal codes
+_code_re = re.compile(
+    '^[A-Z]{6}'
+    '[0-9LMNPQRSTUV]{2}[ABCDEHLMPRST]{1}[0-9LMNPQRSTUV]{2}'
+    '[A-Z]{1}[0-9LMNPQRSTUV]{3}[A-Z]{1}$')
+
+# encoding of birth day and year values (usually numeric but some letters
+# may be substituted on clashes)
+_date_digits = dict((x, n) for n, x in enumerate('0123456789'))
+_date_digits.update(dict((x, n) for n, x in enumerate('LMNPQRSTUV')))
+
+# encoding of month values (A = January, etc.)
+_month_digits = dict((x, n) for n, x in enumerate('ABCDEHLMPRST'))
+
+# values of characters in even positions for checksum calculation
+_even_values = dict((x, n) for n, x in enumerate('0123456789'))
+_even_values.update(
+    dict((x, n) for n, x in enumerate('ABCDEFGHIJKLMNOPQRSTUVWXYZ')))
+
+# values of characters in odd positions for checksum calculation
+values = [1, 0, 5, 7, 9, 13, 15, 17, 19, 21, 2, 4, 18, 20, 11, 3, 6, 8,
+          12, 14, 16, 10, 22, 25, 24, 23]
+_odd_values = dict((x, values[n]) for n, x in enumerate('0123456789'))
+_odd_values.update(
+    dict((x, values[n]) for n, x in enumerate('ABCDEFGHIJKLMNOPQRSTUVWXYZ')))
+del values
+
+
+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, ' ').strip().upper()
+
+
+def calc_check_digit(number):
+    """Compute the control code for the given number. The passed number
+    should be the first 15 characters of a fiscal code."""
+    code = sum(_odd_values[x] if n % 2 == 0 else _even_values[x]
+               for n, x in enumerate(number))
+    return 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[code % 26]
+
+
+def get_birth_date(number, minyear=1920):
+    """Get the birth date from the person's whose fiscal code.
+
+    Only the last two digits of the year are stured in the number. The
+    dates will be returned in the range from minyear to minyear + 100.
+
+    >>> get_birth_date('RCCMNL83S18D969H')
+    datetime.date(1983, 11, 18)
+    >>> get_birth_date('RCCMNL83S18D969H', minyear=1990)
+    datetime.date(2083, 11, 18)
+    """
+    number = compact(number)
+    day = (_date_digits[number[9]] * 10 + _date_digits[number[10]]) % 40
+    month = _month_digits[number[8]] + 1
+    year = _date_digits[number[6]] * 10 + _date_digits[number[7]]
+    # find four-digit year
+    year += (minyear // 100) * 100
+    if year < minyear:
+        year += 100
+    try:
+        return datetime.date(year, month, day)
+    except ValueError:
+        raise InvalidComponent()
+
+
+def get_gender(number):
+    """Get the gender of the person's provided fiscal code.
+
+    >>> get_gender('RCCMNL83S18D969H')
+    'M'
+    >>> get_gender('CNTCHR83T41D969D')
+    'F'
+    """
+    number = compact(number)
+    return 'M' if int(number[9:11]) < 32 else 'F'
+
+
+def validate(number):
+    """Checks to see if the given fiscal code is valid. This checks the
+    length and whether the check digit is correct."""
+    number = compact(number)
+    if len(number) != 16:
+        raise InvalidLength()
+    if not _code_re.match(number):
+        raise InvalidFormat()
+    if calc_check_digit(number[:-1]) != number[-1]:
+        raise InvalidChecksum()
+    # check if birth date is valid
+    birth_date = get_birth_date(number)
+    return number
+
+
+def is_valid(number):
+    """Checks to see if the given fiscal code is valid. This checks the
+    length and whether the check digit is correct."""
+    try:
+        return bool(validate(number))
+    except ValidationError:
+        return False
diff --git a/tests/test_it_codicefiscale.doctest 
b/tests/test_it_codicefiscale.doctest
new file mode 100644
index 0000000..0dc033a
--- /dev/null
+++ b/tests/test_it_codicefiscale.doctest
@@ -0,0 +1,129 @@
+test_it_codicefiscale.doctest - tests for the stdnum.it.codicefiscale module
+
+Copyright (C) 2009-2013 Emanuele Rocca
+Copyright (C) 2014 Augusto Destrero
+Copyright (C) 2014 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
+
+
+This file contains more detailed doctests for the stdnum.it.codicefiscale
+module. It tries to validate a number of tax codes that have been found
+online.
+
+>>> from stdnum.it import codicefiscale
+>>> from stdnum.exceptions import *
+
+
+Some valid numbers
+
+>>> numbers = '''
+... MRTNTN23M02D969P
+... RCCMNL83S18D969H
+... MRSMSR81D60Z611H
+... CNTCHR83T41D969D
+... FOXDRA26C24H872Y
+... MAILCU91A25F839D
+... RSSMRA45C12F205C
+... RSSMRA45C12F20RX
+... RSSMRA45C12F2L5N
+... RSSMRA45C12F2LRI
+... RSSMRAQRCMNFNLRG
+... '''
+>>> [x for x in numbers.splitlines() if x and not codicefiscale.is_valid(x)]
+[]
+
+
+These should be invalid:
+
+>>> codicefiscale.validate('CSTNGL22I10D086I')  # the first 'I' shouldn't be 
there
+Traceback (most recent call last):
+    ...
+InvalidFormat: ...
+>>> codicefiscale.validate('FOXDRA26C24H872A')
+Traceback (most recent call last):
+    ...
+InvalidChecksum: ...
+>>> codicefiscale.validate('CNTCHR83T32D969H')  # invalid date
+Traceback (most recent call last):
+    ...
+InvalidComponent: ...
+
+
+Test getting the birth date.
+
+>>> codicefiscale.get_birth_date('MRTNTN23M02D969P')
+datetime.date(1923, 8, 2)
+>>> codicefiscale.get_birth_date('RCCMNL83S18D969H')
+datetime.date(1983, 11, 18)
+>>> codicefiscale.get_birth_date('MRSMSR81D60Z611H')
+datetime.date(1981, 4, 20)
+>>> codicefiscale.get_birth_date('CNTCHR83T41D969D')
+datetime.date(1983, 12, 1)
+>>> codicefiscale.get_birth_date('FOXDRA26C24H872Y')
+datetime.date(1926, 3, 24)
+>>> codicefiscale.get_birth_date('MAILCU91A25F839D')
+datetime.date(1991, 1, 25)
+>>> codicefiscale.get_birth_date('RSSMRA45C12F205C')
+datetime.date(1945, 3, 12)
+>>> codicefiscale.get_birth_date('RSSMRA45C12F20RX')
+datetime.date(1945, 3, 12)
+>>> codicefiscale.get_birth_date('RSSMRA45C12F2L5N')
+datetime.date(1945, 3, 12)
+>>> codicefiscale.get_birth_date('RSSMRA45C12F2LRI')
+datetime.date(1945, 3, 12)
+>>> codicefiscale.get_birth_date('RSSMRAQRCMNFNLRG')
+datetime.date(1945, 3, 12)
+>>> codicefiscale.get_birth_date('MRTNTN23M02D969P')
+datetime.date(1923, 8, 2)
+
+
+Test getting the gender.
+
+>>> codicefiscale.get_gender('MRTNTN23M02D969P')
+'M'
+>>> codicefiscale.get_gender('RCCMNL83S18D969H')
+'M'
+>>> codicefiscale.get_gender('RCDLSN84S16D969Z')
+'M'
+>>> codicefiscale.get_gender('MRSMSR81D60Z611H')
+'F'
+>>> codicefiscale.get_gender('CNTCHR83T41D969D')
+'F'
+>>> codicefiscale.get_gender('FOXDRA26C24H872Y')
+'M'
+>>> codicefiscale.get_gender('MAILCU91A25F839D')
+'M'
+
+
+Test calculating the check digit.
+
+>>> codicefiscale.calc_check_digit('MRTNTN23M02D969')
+'P'
+>>> codicefiscale.calc_check_digit('MRSMSR81D60Z611')
+'H'
+>>> codicefiscale.calc_check_digit('RCDLSN84S16D969')
+'Z'
+>>> codicefiscale.calc_check_digit('CNTCHR83T41D969')
+'D'
+>>> codicefiscale.calc_check_digit('BNCSFN85T58G702')
+'W'
+>>> codicefiscale.calc_check_digit('RCCMNL83S18D969')
+'H'
+>>> codicefiscale.calc_check_digit('FOXDRA26C24H872')
+'Y'
+>>> codicefiscale.calc_check_digit('MAILCU91A25F839')
+'D'

-----------------------------------------------------------------------

Summary of changes:
 stdnum/it/codicefiscale.py          |  152 +++++++++++++++++++++++++++++++++++
 tests/test_it_codicefiscale.doctest |  129 +++++++++++++++++++++++++++++
 2 files changed, 281 insertions(+)
 create mode 100644 stdnum/it/codicefiscale.py
 create mode 100644 tests/test_it_codicefiscale.doctest


hooks/post-receive
-- 
python-stdnum
-- 
To unsubscribe send an email to
python-stdnum-commits-unsubscribe@lists.arthurdejong.org or see
http://lists.arthurdejong.org/python-stdnum-commits/