Arthur de Jong

Open Source / Free Software developer

summaryrefslogtreecommitdiffstats
path: root/stdnum/iso6346.py
blob: b16fd99b805a1a4d99411bce69cd8e9ffbe3ce6f (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
# iso6346.py - functions for handling ISO 6346
#
# Copyright (C) 2014 Openlabs Technologies & Consulting (P) Limited
# Copyright (C) 2014-2017 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

"""ISO 6346 (International standard for container identification)

ISO 6346 is an international standard covering the coding, identification and
marking of intermodal (shipping) containers used within containerized
intermodal freight transport. The standard establishes a visual identification
system for every container that includes a unique serial number (with check
digit), the owner, a country code, a size, type and equipment category as well
as any operational marks. The standard is managed by the International
Container Bureau (BIC).

More information:

* https://en.wikipedia.org/wiki/ISO_6346

>>> validate('csqu3054383')
'CSQU3054383'
>>> validate('CSQU3054384')
Traceback (most recent call last):
    ...
InvalidChecksum: ...
>>> format('tasu117 000 0')
'TASU 117000 0'
"""

import re

from stdnum.exceptions import InvalidChecksum, InvalidFormat, InvalidLength, \
    ValidationError
from stdnum.util import clean


_iso6346_re = re.compile(r'^\w{3}(U|J|Z|R)\d{7}$')


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):
    """Calculate check digit and return it for the 10 digit owner code and
    serial number."""
    number = compact(number)
    alphabet = '0123456789A BCDEFGHIJK LMNOPQRSTU VWXYZ'
    return str(sum(
        alphabet.index(n) * pow(2, i)
        for i, n in enumerate(number)) % 11 % 10)


def validate(number):
    """Validate the given number (unicode) for conformity to ISO 6346."""
    number = compact(number)
    if len(number) != 11:
        raise InvalidLength()
    if not _iso6346_re.match(number):
        raise InvalidFormat()
    if calc_check_digit(number[:-1]) != number[-1]:
        raise InvalidChecksum()
    return number


def is_valid(number):
    """Check whether the number conforms to the standard ISO6346. Unlike
    the validate function, this will not raise ValidationError(s)."""
    try:
        return bool(validate(number))
    except ValidationError:
        return False


def format(number):
    """Reformat the number to the standard presentation format."""
    number = compact(number)
    return ' '.join((number[:4], number[4:-1], number[-1:]))