lists.arthurdejong.org
RSS feed

python-stdnum branch master updated. 1.10-6-gb77c9cd

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

python-stdnum branch master updated. 1.10-6-gb77c9cd



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  b77c9cd25ff1bb714db0e42baa50bb9ecaf83375 (commit)
       via  d11e5c40adb94010c90f6b956cf1a494cd34f345 (commit)
      from  bb24c2fc8b2cdfe59c9acf35d0e6ca962c0984b2 (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 -----------------------------------------------------------------
https://arthurdejong.org/git/python-stdnum/commit/?id=b77c9cd25ff1bb714db0e42baa50bb9ecaf83375

commit b77c9cd25ff1bb714db0e42baa50bb9ecaf83375
Author: Ilya Vihtinsky <iliaviht@gmail.com>
Date:   Tue Nov 27 17:59:14 2018 +0300

    Add Swedish Personnummer
    
    Closes https://github.com/arthurdejong/python-stdnum/pull/88

diff --git a/stdnum/se/__init__.py b/stdnum/se/__init__.py
index 6becd7b..76c4a0f 100644
--- a/stdnum/se/__init__.py
+++ b/stdnum/se/__init__.py
@@ -19,3 +19,6 @@
 # 02110-1301 USA
 
 """Collection of Swedish numbers."""
+
+# provide aliases
+from stdnum.se import personnummer as personalid  # noqa: F401
diff --git a/stdnum/se/personnummer.py b/stdnum/se/personnummer.py
new file mode 100644
index 0000000..fc8f345
--- /dev/null
+++ b/stdnum/se/personnummer.py
@@ -0,0 +1,113 @@
+# personnummer.py - functions for handling Swedish Personal identity numbers
+# coding: utf-8
+#
+# Copyright (C) 2018 Ilya Vihtinsky
+# Copyright (C) 2018 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
+
+"""Personnummer (Swedish personal identity number).
+
+The Swedish Personnummer is assigned at birth to all Swedish nationals and to
+immigrants for tax and identification purposes. The number consists of 10 or
+12 digits and starts with the birth date, followed by a serial and a check
+digit.
+
+More information:
+
+* https://en.wikipedia.org/wiki/Personal_identity_number_(Sweden)
+
+>>> validate('880320-0016')
+'8803200016'
+>>> validate('880320-0018')
+Traceback (most recent call last):
+    ...
+InvalidChecksum: ...
+>>> get_gender('890102-3286')
+'F'
+>>> get_birth_date('811228-9841')
+datetime.date(1981, 12, 28)
+>>> format('8803200016')
+'880320-0016'
+"""
+
+import datetime
+
+from stdnum import luhn
+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, ' -+:')
+
+
+def get_birth_date(number):
+    """Guess the birth date from the number.
+
+    Note that it may be 100 years off because the number has only the last
+    two digits of the year."""
+    number = compact(number)
+    if len(number) == 12:
+        year = int(number[0:4])
+        month = int(number[4:6])
+        day = int(number[6:8])
+    else:
+        year = datetime.date.today().year
+        year = year - (year - int(number[0:2])) % 100
+        month = int(number[2:4])
+        day = int(number[4:6])
+    try:
+        return datetime.date(year, month, day)
+    except ValueError:
+        raise InvalidComponent()
+
+
+def get_gender(number):
+    """Get the person's birth gender ('M' or 'F')."""
+    number = compact(number)
+    if int(number[-2]) % 2:
+        return 'M'
+    else:
+        return 'F'
+
+
+def validate(number):
+    """Check if the number is a valid identity number."""
+    number = compact(number)
+    if len(number) not in (10, 12):
+        raise InvalidLength()
+    if not number.isdigit():
+        raise InvalidFormat()
+    get_birth_date(number)
+    luhn.validate(number[-10:])
+    return number
+
+
+def is_valid(number):
+    """Check if the number is a valid identity 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:]
diff --git a/tests/test_se_personnummer.doctest 
b/tests/test_se_personnummer.doctest
new file mode 100644
index 0000000..0e57de1
--- /dev/null
+++ b/tests/test_se_personnummer.doctest
@@ -0,0 +1,77 @@
+test_se_personnummer.doctest - more detailed doctests for 
stdnum.se.personnummer module
+
+Copyright (C) 2018 Ilya Vihtinsky
+Copyright (C) 2018 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.se.personnummer
+module. It tries to cover more corner cases and detailed functionality that
+is not really useful as module documentation.
+
+>>> from stdnum.se import personnummer
+
+
+Test for non-digit number.
+
+>>> personnummer.validate('a' * 10)
+Traceback (most recent call last):
+    ...
+InvalidFormat: ...
+
+
+These numbers should be detected as male or female.
+
+>>> personnummer.get_gender('670919-9530')
+'M'
+>>> personnummer.get_gender('8803200420')
+'F'
+
+
+The birth date can be extracted from the number and invalid dates are
+rejected.
+
+>>> personnummer.get_birth_date('8803200420')
+datetime.date(1988, 3, 20)
+>>> personnummer.get_birth_date('191705120424')
+datetime.date(1917, 5, 12)
+>>> personnummer.validate('8899200425')
+Traceback (most recent call last):
+    ...
+InvalidComponent: ...
+
+
+These have been found online and should all be valid numbers.
+
+>>> numbers = '''
+...
+... 670919-9530
+... 811228-9874
+... 880320-0016
+... 880320-0057
+... 8803200073
+... 8803200099
+... 8803200115
+... 8803200131
+... 8803200156
+... 8803200172
+... 8803200198
+... 8803200420
+...
+... '''
+>>> [x for x in numbers.splitlines() if x and not personnummer.is_valid(x)]
+[]

https://arthurdejong.org/git/python-stdnum/commit/?id=d11e5c40adb94010c90f6b956cf1a494cd34f345

commit d11e5c40adb94010c90f6b956cf1a494cd34f345
Author: Ilya Vihtinsky <iliaviht@gmail.com>
Date:   Tue Nov 27 16:01:51 2018 +0300

    Add Norwegian Fødselsnummer
    
    Closes https://github.com/arthurdejong/python-stdnum/pull/88

diff --git a/stdnum/no/__init__.py b/stdnum/no/__init__.py
index fff0eb3..2a9e88d 100644
--- a/stdnum/no/__init__.py
+++ b/stdnum/no/__init__.py
@@ -20,5 +20,6 @@
 
 """Collection of Norwegian numbers."""
 
-# provide vat as an alias
+# provide aliases
+from stdnum.no import fodselsnummer as personalid  # noqa: F401
 from stdnum.no import mva as vat  # noqa: F401
diff --git a/stdnum/no/fodselsnummer.py b/stdnum/no/fodselsnummer.py
new file mode 100644
index 0000000..2d9b842
--- /dev/null
+++ b/stdnum/no/fodselsnummer.py
@@ -0,0 +1,100 @@
+# fodselsnummer.py - functions for handling Norwegian birth numbers
+# coding: utf-8
+#
+# Copyright (C) 2018 Ilya Vihtinsky
+# Copyright (C) 2018 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
+
+"""Fødselsnummer (Norwegian birth number, the national identity number).
+
+The Fødselsnummer is an eleven-digit number that is built up of the date of
+birth of the person, a serial number and two check digits.
+
+More information:
+
+* https://no.wikipedia.org/wiki/F%C3%B8dselsnummer
+* https://en.wikipedia.org/wiki/National_identification_number#Norway
+
+>>> validate('684131 52112')
+'68413152112'
+>>> get_gender('684131 52112')
+'M'
+>>> validate('684131 52123')
+Traceback (most recent call last):
+    ...
+InvalidChecksum: ...
+>>> format('68413152112')
+'684131 52112'
+"""
+
+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, ' -:')
+
+
+def calc_check_digit1(number):
+    """Calculate the first check digit for the number."""
+    weights = (3, 7, 6, 1, 8, 9, 4, 5, 2)
+    return str((11 - sum(w * int(n) for w, n in zip(weights, number))) % 11)
+
+
+def calc_check_digit2(number):
+    """Calculate the second check digit for the number."""
+    weights = (5, 4, 3, 2, 7, 6, 5, 4, 3, 2)
+    return str((11 - sum(w * int(n) for w, n in zip(weights, number))) % 11)
+
+
+def get_gender(number):
+    """Get the person's birth gender ('M' or 'F')."""
+    number = compact(number)
+    if int(number[8]) % 2:
+        return 'M'
+    else:
+        return 'F'
+
+
+def validate(number):
+    """Check if the number is a valid birth number."""
+    number = compact(number)
+    if len(number) != 11:
+        raise InvalidLength()
+    if not number.isdigit():
+        raise InvalidFormat()
+    if number[-2] != calc_check_digit1(number):
+        raise InvalidChecksum()
+    if number[-1] != calc_check_digit2(number):
+        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:]
diff --git a/tests/test_no_fodselsnummer.doctest 
b/tests/test_no_fodselsnummer.doctest
new file mode 100644
index 0000000..abec35e
--- /dev/null
+++ b/tests/test_no_fodselsnummer.doctest
@@ -0,0 +1,112 @@
+test_no_fodselsnummer.doctest - more detailed doctests for 
stdnum.no.fodselsnummer module
+
+Copyright (C) 2018 Ilya Vihtinsky
+Copyright (C) 2018 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.no.fodselsnummer
+module. It tries to cover more corner cases and detailed functionality that
+is not really useful as module documentation.
+
+>>> from stdnum.no import fodselsnummer
+
+
+These numbers should be detected as male or female.
+
+>>> fodselsnummer.get_gender('70624830529')
+'M'
+>>> fodselsnummer.get_gender('56403643756')
+'M'
+>>> fodselsnummer.get_gender('70341666064')
+'F'
+>>> fodselsnummer.get_gender('82938389280')
+'F'
+
+
+The last two check digits are validated independently of each other.
+
+>>> fodselsnummer.is_valid('42485176432')
+True
+>>> fodselsnummer.is_valid('42485176431')  # only change last digit
+False
+>>> fodselsnummer.is_valid('42485176412')  # only change first digit
+False
+>>> fodselsnummer.is_valid('42485176416')  # change first, correct second
+False
+
+
+Length and format are also validated.
+
+>>> fodselsnummer.validate('42485176432123')
+Traceback (most recent call last):
+    ...
+InvalidLength: ...
+>>> fodselsnummer.validate('a0gzaB30529')
+Traceback (most recent call last):
+    ...
+InvalidFormat: ...
+
+
+These have been found online and should all be valid numbers.
+
+>>> numbers = '''
+...
+... 11027794191
+... 11051996811
+... 19575770838
+... 21918484865
+... 24396859900
+... 27213364885
+... 27389446152
+... 30383131118
+... 30777674125
+... 31042639152
+... 34831582121
+... 39043009846
+... 40070897972
+... 40673759612
+... 42115114470
+... 42485176432
+... 42957044500
+... 44207789809
+... 45014054018
+... 46929323343
+... 50067834221
+... 56403643756
+... 56653047547
+... 63282310041
+... 68413152112
+... 70031073454
+... 70341666064
+... 70624830529
+... 71494457037
+... 71946503120
+... 75442702381
+... 79189404641
+... 79318270827
+... 82938389280
+... 83814827871
+... 89829529360
+... 92782833709
+... 95700625908
+... 96517753502
+... 98576936818
+...
+... '''
+>>> [x for x in numbers.splitlines() if x and not fodselsnummer.is_valid(x)]
+[]

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

Summary of changes:
 stdnum/no/__init__.py                      |   3 +-
 stdnum/{ar/cuit.py => no/fodselsnummer.py} |  74 +++++++++++--------
 stdnum/se/__init__.py                      |   3 +
 stdnum/se/personnummer.py                  | 113 +++++++++++++++++++++++++++++
 tests/test_no_fodselsnummer.doctest        | 112 ++++++++++++++++++++++++++++
 tests/test_se_personnummer.doctest         |  77 ++++++++++++++++++++
 6 files changed, 350 insertions(+), 32 deletions(-)
 copy stdnum/{ar/cuit.py => no/fodselsnummer.py} (50%)
 create mode 100644 stdnum/se/personnummer.py
 create mode 100644 tests/test_no_fodselsnummer.doctest
 create mode 100644 tests/test_se_personnummer.doctest


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