test_gs1_128.doctest - more detailed doctests for the stdnum.gs1_128 module

Copyright (C) 2019 Sergi Almacellas Abellaan
Copyright (C) 2020 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.gs1_128 module. It
tries to test more corner cases and detailed functionality that is not
really useful as module documentation.

>>> from decimal import Decimal
>>> import datetime
>>> import pprint
>>> from stdnum import gs1_128


>>> gs1_128.compact('(01)38425876095074(17)181119(37)1 ')
'013842587609507417181119371'


We can create a GS1-128 code based on data we provide. Various data types
will be converted to the correct representation.

>>> gs1_128.encode({'01': '38425876095074', '17': datetime.date(2018, 11, 19), '37': 1}, parentheses=True)
'(01)38425876095074(17)181119(37)1'
>>> gs1_128.encode({'02': '98412345678908', '310': 17.23, '37': 32})
'029841234567890831020017233732'
>>> gs1_128.encode({'03': '1234'})  # unknown AI
Traceback (most recent call last):
    ...
InvalidComponent: ...

If we have a separator we use it to separate variable-length values, otherwise
we pad all variable-length values to the maximum length (except the last one).

>>> gs1_128.encode({'01': '58425876097843', '10': '123456', '37': 18, '390': 42, '392': 12}, parentheses=True)
'(01)58425876097843(10)123456              (37)00000018(390)0000000000000042(392)012'
>>> gs1_128.encode({'01': '58425876097843', '10': '123456', '37': 18, '390': 42, '392': 12}, parentheses=True, separator='[FNC1]')
'(01)58425876097843(10)123456[FNC1](37)18[FNC1](390)042[FNC1](392)012'

Numeric values can be provided in several forms and precision is encoded
properly.

>>> gs1_128.encode({
...     '310': 17.23,  # float
...     '311': 456,  # int
...     '312': 1.0 / 3.0,  # float with lots of digits
...     '313': '123.456',  # str
...     '391': ('123', Decimal('123.456')),  # currency number combo
... }, parentheses=True)
'(310)2001723(311)0000456(312)5033333(313)3123456(391)3123123456'

We generate dates in various formats, depending on the AI.

>>> gs1_128.encode({
...     '11': datetime.datetime(2018, 11, 19, 0, 0, 0),
...     '12': '181119',  # if you provide a string value, it is expected to be correct
...     '7003': datetime.datetime(2018, 11, 19, 12, 45, 13),
...     '7007': (datetime.date(2018, 11, 19), datetime.date(2018, 11, 21)),
... }, parentheses=True)
'(11)181119(12)181119(7003)1811191245(7007)181119181121'
>>> gs1_128.encode({'8008': datetime.datetime(2018, 11, 19, 12, 45, 13)}, parentheses=True)
'(8008)181119124513'
>>> gs1_128.encode({'8008': datetime.datetime(2018, 11, 19, 12, 45)}, parentheses=True)
'(8008)1811191245'
>>> gs1_128.encode({'8008': datetime.datetime(2018, 11, 19, 12, 0)}, parentheses=True)
'(8008)18111912'
>>> gs1_128.encode({'8008': datetime.datetime(2018, 11, 19, 0, 0)}, parentheses=True)
'(8008)18111900'

If we try to encode an invalid EAN we will get an error.

>>> gs1_128.encode({'01': '38425876095079'}, parentheses=True)
Traceback (most recent call last):
    ...
InvalidChecksum: ...


We can decode (parse) the GS1-128 code to a dictionary with information about
the structure of the number.

pprint.pprint(gs1_128.info('(01)38425876095074(17)181119(37)1 '))
{'01': '38425876095074', '17': datetime.date(2018, 11, 19), '37': 1}
>>> pprint.pprint(gs1_128.info('013842587609507417181119371'))
{'01': '38425876095074', '17': datetime.date(2018, 11, 19), '37': 1}
>>> pprint.pprint(gs1_128.info('(02)98412345678908(310)3017230(37)32'))
{'02': '98412345678908', '310': Decimal('17.230'), '37': 32}
>>> pprint.pprint(gs1_128.info('(01)58425876097843(10)123456              (17)181119(37)18'))
{'01': '58425876097843', '10': '123456', '17': datetime.date(2018, 11, 19), '37': 18}
>>> pprint.pprint(gs1_128.info('|(01)58425876097843|(10)123456|(17)181119(37)18', separator='|'))
{'01': '58425876097843', '10': '123456', '17': datetime.date(2018, 11, 19), '37': 18}
>>> pprint.pprint(gs1_128.info('(01)38425876095074(17)260400(10)123456'))
{'01': '38425876095074', '10': '123456', '17': datetime.date(2026, 4, 30)}
>>> pprint.pprint(gs1_128.info('(01)38425876095074(17)261200(10)123456'))
{'01': '38425876095074', '10': '123456', '17': datetime.date(2026, 12, 31)}
>>> gs1_128.info('(03)38425876095074')  # unknown AI
Traceback (most recent call last):
    ...
InvalidComponent: ...

We can decode decimal values from various formats.

>>> pprint.pprint(gs1_128.info('(310)5033333'))
{'310': Decimal('0.33333')}
>>> pprint.pprint(gs1_128.info('(310)0033333'))
{'310': Decimal('33333')}
>>> pprint.pprint(gs1_128.info('(391)3123123456'))
{'391': ('123', Decimal('123.456'))}

We an decode date files from various formats.

>>> pprint.pprint(gs1_128.info('(11)181119'))
{'11': datetime.date(2018, 11, 19)}
>>> pprint.pprint(gs1_128.info('(7003)1811191245'))
{'7003': datetime.datetime(2018, 11, 19, 12, 45)}
>>> pprint.pprint(gs1_128.info('(7007)181119'))
{'7007': datetime.date(2018, 11, 19)}
>>> pprint.pprint(gs1_128.info('(7007)181119181121'))
{'7007': (datetime.date(2018, 11, 19), datetime.date(2018, 11, 21))}
>>> pprint.pprint(gs1_128.info('(8008)18111912'))
{'8008': datetime.datetime(2018, 11, 19, 12, 0)}


While the compact() function can clean up the number somewhat the validate()
function calls info() and then encode() to ensure an even more compact and
consistent format.

>>> gs1_128.compact('(01)58425876097843(10)123456              (37)00000018')
'015842587609784310123456              3700000018'
>>> gs1_128.validate('(01)58425876097843(10)123456              (37)00000018')
'015842587609784310123456              3718'
>>> gs1_128.validate('(01)58425876097843(10)123456              (37)00000018', separator='|')
'015842587609784310123456|3718'
>>> gs1_128.validate('30aa')
Traceback (most recent call last):
    ...
InvalidFormat: ...