lists.arthurdejong.org
RSS feed

python-stdnum branch master updated. 1.18-11-g7e84c05

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

python-stdnum branch master updated. 1.18-11-g7e84c05



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  7e84c05bf1535ce3e645789cc7e2baf81278f7ae (commit)
      from  bf1bdfe7823ebc1206033d852412c966a3df611b (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=7e84c05bf1535ce3e645789cc7e2baf81278f7ae

commit 7e84c05bf1535ce3e645789cc7e2baf81278f7ae
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Mar 18 16:08:52 2023 +0100

    Extend date parsing in GS1-128
    
    Some new AIs have new date formats or have changed the way optional
    components of formats are defined.

diff --git a/stdnum/gs1_128.py b/stdnum/gs1_128.py
index 0bac88d..5ebf5ea 100644
--- a/stdnum/gs1_128.py
+++ b/stdnum/gs1_128.py
@@ -1,7 +1,7 @@
 # gs1_128.py - functions for handling GS1-128 codes
 #
 # Copyright (C) 2019 Sergi Almacellas Abellana
-# Copyright (C) 2020-2021 Arthur de Jong
+# Copyright (C) 2020-2023 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
@@ -103,22 +103,32 @@ def _encode_value(fmt, _type, value):
                 _encode_value('N6', _type, value[0]),
                 _encode_value('N6', _type, value[1]))
         elif isinstance(value, datetime.date):
-            if fmt == 'N10':
+            if fmt in ('N6', 'N6..12'):
+                return value.strftime('%y%m%d')
+            elif fmt == 'N10':
                 return value.strftime('%y%m%d%H%M')
-            elif fmt == 'N8+N..4':
+            elif fmt in ('N6+N..4', 'N6[+N..4]'):
+                value = datetime.datetime.strftime(value, '%y%m%d%H%M')
+                if value.endswith('00'):
+                    value = value[:-2]
+                if value.endswith('00'):
+                    value = value[:-2]
+                return value
+            elif fmt in ('N8+N..4', 'N8[+N..4]'):
                 value = datetime.datetime.strftime(value, '%y%m%d%H%M%S')
                 if value.endswith('00'):
                     value = value[:-2]
                 if value.endswith('00'):
                     value = value[:-2]
                 return value
-            return value.strftime('%y%m%d')
+            else:  # pragma: no cover (all formats should be covered)
+                raise ValueError('unsupported format: %s' % fmt)
     return str(value)
 
 
 def _max_length(fmt, _type):
     """Determine the maximum length based on the format ad type."""
-    length = sum(int(re.match(r'^[NXY][0-9]*?[.]*([0-9]+)$', x).group(1)) for 
x in fmt.split('+'))
+    length = sum(int(re.match(r'^[NXY][0-9]*?[.]*([0-9]+)[\[\]]?$', 
x).group(1)) for x in fmt.split('+'))
     if _type == 'decimal':
         length += 1
     return length
@@ -142,21 +152,22 @@ def _decode_value(fmt, _type, value):
             value = value[:-digits] + '.' + value[-digits:]
         return decimal.Decimal(value)
     elif _type == 'date':
-        if fmt == 'N8+N..4':
-            return datetime.datetime.strptime(value, 
'%y%m%d%H%M%S'[:len(value)])
-        elif len(value) == 10:
-            return datetime.datetime.strptime(value, '%y%m%d%H%M')
-        elif len(value) == 12:
-            return (_decode_value(fmt, _type, value[:6]), _decode_value(fmt, 
_type, value[6:]))
-        elif len(value) == 6 and value[4:] == '00':
-            # When day == '00', it must be interpreted as last day of month
-            date = datetime.datetime.strptime(value[:4], '%y%m')
-            if date.month == 12:
-                date = date.replace(day=31)
+        if len(value) == 6:
+            if value[4:] == '00':
+                # When day == '00', it must be interpreted as last day of month
+                date = datetime.datetime.strptime(value[:4], '%y%m')
+                if date.month == 12:
+                    date = date.replace(day=31)
+                else:
+                    date = date.replace(month=date.month + 1, day=1) - 
datetime.timedelta(days=1)
+                return date.date()
             else:
-                date = date.replace(month=date.month + 1, day=1) - 
datetime.timedelta(days=1)
-            return date.date()
-        return datetime.datetime.strptime(value, '%y%m%d').date()
+                return datetime.datetime.strptime(value, '%y%m%d').date()
+        elif len(value) == 12 and fmt in ('N12', 'N6..12'):
+            return (_decode_value('N6', _type, value[:6]), _decode_value('N6', 
_type, value[6:]))
+        else:
+            # other lengths are interpreted as variable-length datetime values
+            return datetime.datetime.strptime(value, 
'%y%m%d%H%M%S'[:len(value)])
     elif _type == 'int':
         return int(value)
     return value.strip()
@@ -223,7 +234,7 @@ def encode(data, separator='', parentheses=False):
     fixed_values = []
     variable_values = []
     for inputai, value in sorted(data.items()):
-        ai, info = _gs1_aidb.info(inputai)[0]
+        ai, info = _gs1_aidb.info(str(inputai))[0]
         if not info:
             raise InvalidComponent()
         # validate the value if we have a custom module for it
diff --git a/stdnum/gs1_ai.dat b/stdnum/gs1_ai.dat
index fcc5d16..cd6e33e 100644
--- a/stdnum/gs1_ai.dat
+++ b/stdnum/gs1_ai.dat
@@ -1,5 +1,5 @@
 # generated from https://www.gs1.org/standards/barcodes/application-identifiers
-# on 2022-11-13 16:58:11.050076
+# on 2023-03-18 14:23:58.680562
 00 format="N18" type="str" name="SSCC" description="Serial Shipping Container 
Code (SSCC)"
 01 format="N14" type="str" name="GTIN" description="Global Trade Item Number 
(GTIN)"
 02 format="N14" type="str" name="CONTENT" description="Global Trade Item 
Number (GTIN) of contained trade items"
@@ -15,14 +15,14 @@
 22 format="X..20" type="str" fnc1="1" name="CPV" description="Consumer product 
variant"
 235 format="X..28" type="str" fnc1="1" name="TPX" description="Third Party 
Controlled, Serialised Extension of Global Trade Item Number (GTIN) (TPX)"
 240 format="X..30" type="str" fnc1="1" name="ADDITIONAL ID" 
description="Additional product identification assigned by the manufacturer"
-241 format="X..30" type="str" fnc1="1" name="CUST. PART NO." 
description="Customer part number"
+241 format="X..30" type="str" fnc1="1" name="CUST. PART No." 
description="Customer part number"
 242 format="N..6" type="str" fnc1="1" name="MTO VARIANT" 
description="Made-to-Order variation number"
 243 format="X..20" type="str" fnc1="1" name="PCN" description="Packaging 
component number"
 250 format="X..30" type="str" fnc1="1" name="SECONDARY SERIAL" 
description="Secondary serial number"
 251 format="X..30" type="str" fnc1="1" name="REF. TO SOURCE" 
description="Reference to source entity"
-253 format="N13+X..17" type="str" fnc1="1" name="GDTI" description="Global 
Document Type Identifier (GDTI)"
+253 format="N13[+X..17]" type="str" fnc1="1" name="GDTI" description="Global 
Document Type Identifier (GDTI)"
 254 format="X..20" type="str" fnc1="1" name="GLN EXTENSION COMPONENT" 
description="Global Location Number (GLN) extension component"
-255 format="N13+N..12" type="str" fnc1="1" name="GCN" description="Global 
Coupon Number (GCN)"
+255 format="N13[+N..12]" type="str" fnc1="1" name="GCN" description="Global 
Coupon Number (GCN)"
 30 format="N..8" type="int" fnc1="1" name="VAR. COUNT" description="Variable 
count of items (variable measure trade item)"
 310 format="N6" type="decimal" name="NET WEIGHT (kg)" description="Net weight, 
kilograms (variable measure trade item)"
 311 format="N6" type="decimal" name="LENGTH (m)" description="Length or first 
dimension, metres (variable measure trade item)"
@@ -92,15 +92,15 @@
 411 format="N13" type="str" name="BILL TO" description="Bill to / Invoice to 
Global Location Number (GLN)"
 412 format="N13" type="str" name="PURCHASE FROM" description="Purchased from 
Global Location Number (GLN)"
 413 format="N13" type="str" name="SHIP FOR LOC" description="Ship for / 
Deliver for - Forward to Global Location Number (GLN)"
-414 format="N13" type="str" name="LOC No" description="Identification of a 
physical location - Global Location Number (GLN)"
+414 format="N13" type="str" name="LOC No." description="Identification of a 
physical location - Global Location Number (GLN)"
 415 format="N13" type="str" name="PAY TO" description="Global Location Number 
(GLN) of the invoicing party"
 416 format="N13" type="str" name="PROD/SERV LOC" description="Global Location 
Number (GLN) of the production or service location"
 417 format="N13" type="str" name="PARTY" description="Party Global Location 
Number (GLN)"
 420 format="X..20" type="str" fnc1="1" name="SHIP TO POST" description="Ship 
to / Deliver to postal code within a single postal authority"
 421 format="N3+X..9" type="str" fnc1="1" name="SHIP TO POST" description="Ship 
to / Deliver to postal code with ISO country code"
 422 format="N3" type="int" fnc1="1" name="ORIGIN" description="Country of 
origin of a trade item"
-423 format="N3+N..12" type="str" fnc1="1" name="COUNTRY - INITIAL PROCESS." 
description="Country of initial processing"
-424 format="N3" type="int" fnc1="1" name="COUNTRY - PROCESS." 
description="Country of processing"
+423 format="N3+N..12" type="str" fnc1="1" name="COUNTRY - INITIAL PROCESS" 
description="Country of initial processing"
+424 format="N3" type="int" fnc1="1" name="COUNTRY - PROCESS" 
description="Country of processing"
 425 format="N3+N..12" type="str" fnc1="1" name="COUNTRY - DISASSEMBLY" 
description="Country of disassembly"
 426 format="N3" type="int" fnc1="1" name="COUNTRY - FULL PROCESS" 
description="Country covering full process chain"
 427 format="X..3" type="str" fnc1="1" name="ORIGIN SUBDIVISION" 
description="Country subdivision Of origin"
@@ -113,6 +113,7 @@
 4306 format="X..70" type="str" fnc1="1" name="SHIP TO REG" 
description="Ship-to / Deliver-to region"
 4307 format="X2" type="str" fnc1="1" name="SHIP TO COUNTRY" 
description="Ship-to / Deliver-to country code"
 4308 format="X..30" type="str" fnc1="1" name="SHIP TO PHONE" 
description="Ship-to / Deliver-to telephone number"
+4309 format="N20" type="str" fnc1="1" name="SHIP TO GEO" description="Ship-to 
/ Deliver-to GEO location"
 4310 format="X..35" type="str" fnc1="1" name="RTN TO COMP" 
description="Return-to company name"
 4311 format="X..35" type="str" fnc1="1" name="RTN TO NAME" 
description="Return-to contact"
 4312 format="X..70" type="str" fnc1="1" name="RTN TO ADD1" 
description="Return-to address line 1"
@@ -127,8 +128,8 @@
 4321 format="N1" type="str" fnc1="1" name="DANGEROUS GOODS" 
description="Dangerous goods flag"
 4322 format="N1" type="str" fnc1="1" name="AUTH TO LEAVE" 
description="Authority to leave"
 4323 format="N1" type="str" fnc1="1" name="SIG REQUIRED" 
description="Signature required flag"
-4324 format="N10" type="date" fnc1="1" name="NOT BEF DEL DT" description="Not 
before delivery date time"
-4325 format="N10" type="date" fnc1="1" name="NOT AFT DEL DT" description="Not 
after delivery date time"
+4324 format="N10" type="date" fnc1="1" name="NBEF DEL DT" description="Not 
before delivery date time"
+4325 format="N10" type="date" fnc1="1" name="NAFT DEL DT" description="Not 
after delivery date time"
 4326 format="N6" type="date" fnc1="1" name="REL DATE" description="Release 
date"
 7001 format="N13" type="str" fnc1="1" name="NSN" description="NATO Stock 
Number (NSN)"
 7002 format="X..30" type="str" fnc1="1" name="MEAT CUT" description="UN/ECE 
meat carcasses and cuts classification"
@@ -140,6 +141,7 @@
 7008 format="X..3" type="str" fnc1="1" name="AQUATIC SPECIES" 
description="Species for fishery purposes"
 7009 format="X..10" type="str" fnc1="1" name="FISHING GEAR TYPE" 
description="Fishing gear type"
 7010 format="X..2" type="str" fnc1="1" name="PROD METHOD" 
description="Production method"
+7011 format="N6[+N..4]" type="date" fnc1="1" name="TEST BY DATE" 
description="Test by date"
 7020 format="X..20" type="str" fnc1="1" name="REFURB LOT" 
description="Refurbishment lot ID"
 7021 format="X..20" type="str" fnc1="1" name="FUNC STAT" 
description="Functional status"
 7022 format="X..20" type="str" fnc1="1" name="REV STAT" description="Revision 
status"
@@ -173,13 +175,13 @@
 7239 format="X2+X..28" type="str" fnc1="1" name="CERT #10" 
description="Certification reference"
 7240 format="X..20" type="str" fnc1="1" name="PROTOCOL" description="Protocol 
ID"
 8001 format="N14" type="str" fnc1="1" name="DIMENSIONS" description="Roll 
products (width, length, core diameter, direction, splices)"
-8002 format="X..20" type="str" fnc1="1" name="CMT No" description="Cellular 
mobile telephone identifier"
+8002 format="X..20" type="str" fnc1="1" name="CMT No." description="Cellular 
mobile telephone identifier"
 8003 format="N14+X..16" type="str" fnc1="1" name="GRAI" description="Global 
Returnable Asset Identifier (GRAI)"
 8004 format="X..30" type="str" fnc1="1" name="GIAI" description="Global 
Individual Asset Identifier (GIAI)"
 8005 format="N6" type="str" fnc1="1" name="PRICE PER UNIT" description="Price 
per unit of measure"
 8006 format="N14+N2+N2" type="str" fnc1="1" name="ITIP" 
description="Identification of an individual trade item piece (ITIP)"
 8007 format="X..34" type="str" fnc1="1" name="IBAN" description="International 
Bank Account Number (IBAN)"
-8008 format="N8+N..4" type="date" fnc1="1" name="PROD TIME" description="Date 
and time of production"
+8008 format="N8[+N..4]" type="date" fnc1="1" name="PROD TIME" 
description="Date and time of production"
 8009 format="X..50" type="str" fnc1="1" name="OPTSEN" description="Optically 
Readable Sensor Indicator"
 8010 format="Y..30" type="str" fnc1="1" name="CPID" 
description="Component/Part Identifier (CPID)"
 8011 format="N..12" type="str" fnc1="1" name="CPID SERIAL" 
description="Component/Part Identifier serial number (CPID SERIAL)"
@@ -188,7 +190,7 @@
 8017 format="N18" type="str" fnc1="1" name="GSRN - PROVIDER" 
description="Global Service Relation Number (GSRN) to identify the relationship 
between an organisation offering services and the provider of services"
 8018 format="N18" type="str" fnc1="1" name="GSRN - RECIPIENT" 
description="Global Service Relation Number (GSRN) to identify the relationship 
between an organisation offering services and the recipient of services"
 8019 format="N..10" type="str" fnc1="1" name="SRIN" description="Service 
Relation Instance Number (SRIN)"
-8020 format="X..25" type="str" fnc1="1" name="REF No" description="Payment 
slip reference number"
+8020 format="X..25" type="str" fnc1="1" name="REF No." description="Payment 
slip reference number"
 8026 format="N14+N2+N2" type="str" fnc1="1" name="ITIP CONTENT" 
description="Identification of pieces of a trade item (ITIP) contained in a 
logistic unit"
 8110 format="X..70" type="str" fnc1="1" name="" description="Coupon code 
identification for use in North America"
 8111 format="N4" type="str" fnc1="1" name="POINTS" description="Loyalty points 
of a coupon"
diff --git a/tests/test_gs1_128.doctest b/tests/test_gs1_128.doctest
index 035f17c..3e99d52 100644
--- a/tests/test_gs1_128.doctest
+++ b/tests/test_gs1_128.doctest
@@ -44,6 +44,8 @@ will be converted to the correct representation.
 Traceback (most recent call last):
     ...
 InvalidComponent: ...
+>>> gs1_128.encode({'253': '1234567890005000123'})
+'2531234567890005000123'
 
 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).
@@ -82,6 +84,10 @@ We generate dates in various formats, depending on the AI.
 '(8008)18111912'
 >>> gs1_128.encode({'8008': datetime.datetime(2018, 11, 19, 0, 0)}, 
 >>> parentheses=True)
 '(8008)18111900'
+>>> gs1_128.encode({'7011': datetime.date(2018, 11, 19)}, parentheses=True)
+'(7011)181119'
+>>> gs1_128.encode({'7011': datetime.datetime(2018, 11, 19, 12, 45)}, 
parentheses=True)
+'(7011)1811191245'
 
 If we try to encode an invalid EAN we will get an error.
 
@@ -112,6 +118,8 @@ 
pprint.pprint(gs1_128.info('(01)38425876095074(17)181119(37)1 '))
 Traceback (most recent call last):
     ...
 InvalidComponent: ...
+>>> pprint.pprint(gs1_128.info('(253)1234567890005000123'))
+{'253': '1234567890005000123'}
 
 We can decode decimal values from various formats.
 
@@ -134,6 +142,10 @@ We an decode date files from various formats.
 {'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)}
+>>> pprint.pprint(gs1_128.info('(7011)181119'))
+{'7011': datetime.date(2018, 11, 19)}
+>>> pprint.pprint(gs1_128.info('(7011)1811191245'))
+{'7011': datetime.datetime(2018, 11, 19, 12, 45)}
 
 
 While the compact() function can clean up the number somewhat the validate()
diff --git a/update/gs1_ai.py b/update/gs1_ai.py
index 8d339ed..b642469 100755
--- a/update/gs1_ai.py
+++ b/update/gs1_ai.py
@@ -2,7 +2,7 @@
 
 # update/gs1_ai.py - script to get GS1 application identifiers
 #
-# Copyright (C) 2019 Arthur de Jong
+# Copyright (C) 2019-2023 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
@@ -72,7 +72,7 @@ if __name__ == '__main__':
     print('# on %s' % datetime.datetime.utcnow())
     for ai1, ai2, format, require_fnc1, name, description in group_ai_ranges():
         _type = 'str'
-        if re.match(r'^(N8\+)?N[0-9]*[.]*[0-9]+$', format) and 'date' in 
description.lower():
+        if re.match(r'^(N[68]\[?\+)?N[0-9]*[.]*[0-9]+\]?$', format) and 'date' 
in description.lower():
             _type = 'date'
         elif re.match(r'^N[.]*[0-9]+$', format) and 'count' in 
description.lower():
             _type = 'int'

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

Summary of changes:
 stdnum/gs1_128.py          | 51 ++++++++++++++++++++++++++++------------------
 stdnum/gs1_ai.dat          | 26 ++++++++++++-----------
 tests/test_gs1_128.doctest | 12 +++++++++++
 update/gs1_ai.py           |  4 ++--
 4 files changed, 59 insertions(+), 34 deletions(-)


hooks/post-receive
-- 
python-stdnum