Skip to content

Commit 2535bbf

Browse files
weberdakarthurdejong
authored andcommitted
Add European Community (EC) Number
Closes #422
1 parent 2478483 commit 2535bbf

File tree

2 files changed

+280
-0
lines changed

2 files changed

+280
-0
lines changed

stdnum/eu/ecnumber.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# ecnumber.py - functions for handling European Community Numbers
2+
3+
# Copyright (C) 2023 Daniel Weber
4+
#
5+
# This library is free software; you can redistribute it and/or
6+
# modify it under the terms of the GNU Lesser General Public
7+
# License as published by the Free Software Foundation; either
8+
# version 2.1 of the License, or (at your option) any later version.
9+
#
10+
# This library is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
# Lesser General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU Lesser General Public
16+
# License along with this library; if not, write to the Free Software
17+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18+
# 02110-1301 USA
19+
20+
"""EC Number (European Community number).
21+
22+
The EC Number is a unique seven-digit number assigned to chemical substances
23+
for regulatory purposes within the European Union by the European Commission.
24+
25+
More information:
26+
27+
* https://en.wikipedia.org/wiki/European_Community_number
28+
29+
>>> validate('200-001-8')
30+
'200-001-8'
31+
>>> validate('200-001-9')
32+
Traceback (most recent call last):
33+
...
34+
InvalidChecksum: ...
35+
>>> validate('20-0001-8')
36+
Traceback (most recent call last):
37+
...
38+
InvalidFormat: ...
39+
"""
40+
41+
import re
42+
43+
from stdnum.exceptions import *
44+
from stdnum.util import clean
45+
46+
47+
_ec_number_re = re.compile(r'^[0-9]{3}-[0-9]{3}-[0-9]$')
48+
49+
50+
def compact(number):
51+
"""Convert the number to the minimal representation."""
52+
number = clean(number, ' ').strip()
53+
if '-' not in number:
54+
number = '-'.join((number[:3], number[3:6], number[6:]))
55+
return number
56+
57+
58+
def calc_check_digit(number):
59+
"""Calculate the check digit for the number. The passed number should not
60+
have the check digit included."""
61+
number = compact(number).replace('-', '')
62+
return str(
63+
sum((i + 1) * int(n) for i, n in enumerate(number)) % 11)[0]
64+
65+
66+
def validate(number):
67+
"""Check if the number provided is a valid EC Number."""
68+
number = compact(number)
69+
if not len(number) == 9:
70+
raise InvalidLength()
71+
if not _ec_number_re.match(number):
72+
raise InvalidFormat()
73+
if number[-1] != calc_check_digit(number[:-1]):
74+
raise InvalidChecksum()
75+
return number
76+
77+
78+
def is_valid(number):
79+
"""Check if the number provided is a valid EC Number."""
80+
try:
81+
return bool(validate(number))
82+
except ValidationError:
83+
return False

tests/test_eu_ecnumber.doctest

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
test_eu_ecnumber.doctest - more detailed doctests for the stdnum.eu.ecnumber module
2+
3+
Copyright (C) 2023 Daniel Weber
4+
5+
This library is free software; you can redistribute it and/or
6+
modify it under the terms of the GNU Lesser General Public
7+
License as published by the Free Software Foundation; either
8+
version 2.1 of the License, or (at your option) any later version.
9+
10+
This library is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public
16+
License along with this library; if not, write to the Free Software
17+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18+
02110-1301 USA
19+
20+
21+
This file contains more detailed doctests for the stdnum.eu.ecnumber module. It
22+
contains some corner case tests and tries to validate numbers that have been
23+
found online.
24+
25+
>>> from stdnum.eu import ecnumber
26+
>>> from stdnum.exceptions import *
27+
28+
29+
EC Numbers always include separators and will be introduced if they are not
30+
present. Validation will fail if separators are in the incorrect place.
31+
32+
>>> ecnumber.validate('200-112-1')
33+
'200-112-1'
34+
>>> ecnumber.validate('2001121')
35+
'200-112-1'
36+
>>> ecnumber.validate('20-0112-1')
37+
Traceback (most recent call last):
38+
...
39+
InvalidFormat: ...
40+
>>> ecnumber.validate('2000112-1')
41+
Traceback (most recent call last):
42+
...
43+
InvalidFormat: ...
44+
45+
46+
The number should only have two separators.
47+
48+
>>> ecnumber.validate('20--112-1')
49+
Traceback (most recent call last):
50+
...
51+
InvalidFormat: ...
52+
53+
54+
Only numeric characters between separators.
55+
56+
>>> ecnumber.validate('20A-112-1')
57+
Traceback (most recent call last):
58+
...
59+
InvalidFormat: ...
60+
61+
62+
EC Numbers are always nine characters long (including hyphens).
63+
64+
>>> ecnumber.validate('2000-112-1')
65+
Traceback (most recent call last):
66+
...
67+
InvalidLength: ...
68+
>>> ecnumber.validate('20001121')
69+
Traceback (most recent call last):
70+
...
71+
InvalidLength: ...
72+
>>> ecnumber.validate('201121')
73+
Traceback (most recent call last):
74+
...
75+
InvalidLength: ...
76+
77+
78+
The final character must have the correct check digit.
79+
80+
>>> ecnumber.validate('200-112-2')
81+
Traceback (most recent call last):
82+
...
83+
InvalidChecksum: ...
84+
>>> ecnumber.validate('2001122')
85+
Traceback (most recent call last):
86+
...
87+
InvalidChecksum: ...
88+
89+
90+
These are randomly selected from the EC Inventory should be valid EC Numbers.
91+
92+
>>> numbers = '''
93+
...
94+
... 200-662-2
95+
... 200-897-0
96+
... 203-499-5
97+
... 204-282-8
98+
... 206-777-4
99+
... 207-296-2
100+
... 207-631-2
101+
... 207-952-8
102+
... 211-043-1
103+
... 212-948-4
104+
... 215-429-0
105+
... 216-155-4
106+
... 217-593-9
107+
... 217-931-5
108+
... 219-941-5
109+
... 220-575-3
110+
... 221-531-6
111+
... 222-700-7
112+
... 222-729-5
113+
... 223-550-5
114+
... 226-307-1
115+
... 228-426-4
116+
... 233-748-3
117+
... 235-556-5
118+
... 236-325-1
119+
... 238-475-3
120+
... 238-769-1
121+
... 239-367-9
122+
... 239-530-4
123+
... 241-289-5
124+
... 242-807-2
125+
... 243-154-6
126+
... 244-556-4
127+
... 244-886-9
128+
... 245-704-0
129+
... 247-214-2
130+
... 248-170-7
131+
... 249-213-2
132+
... 249-244-1
133+
... 249-469-5
134+
... 250-046-2
135+
... 250-140-3
136+
... 250-478-1
137+
... 251-186-7
138+
... 251-412-4
139+
... 252-552-9
140+
... 252-796-6
141+
... 254-323-9
142+
... 254-324-4
143+
... 255-524-4
144+
... 255-597-2
145+
... 256-980-7
146+
... 257-228-0
147+
... 257-308-5
148+
... 259-660-5
149+
... 262-758-0
150+
... 263-157-6
151+
... 263-543-4
152+
... 266-556-3
153+
... 266-597-7
154+
... 266-708-9
155+
... 267-064-1
156+
... 271-104-3
157+
... 271-556-1
158+
... 273-972-9
159+
... 274-112-5
160+
... 274-741-5
161+
... 274-747-8
162+
... 276-796-0
163+
... 280-279-5
164+
... 280-851-4
165+
... 280-947-6
166+
... 281-719-9
167+
... 281-919-6
168+
... 282-848-3
169+
... 282-944-5
170+
... 284-690-0
171+
... 286-712-4
172+
... 287-761-4
173+
... 287-900-9
174+
... 288-360-7
175+
... 295-191-2
176+
... 296-057-6
177+
... 297-119-5
178+
... 297-362-7
179+
... 300-706-1
180+
... 301-691-4
181+
... 301-916-6
182+
... 302-175-1
183+
... 302-331-9
184+
... 304-512-8
185+
... 304-902-8
186+
... 307-269-6
187+
... 307-415-9
188+
... 307-692-6
189+
... 310-159-0
190+
... 414-380-4
191+
... 421-750-9
192+
... 424-870-1
193+
... 500-464-9
194+
...
195+
... '''
196+
>>> [x for x in numbers.splitlines() if x and not ecnumber.is_valid(x)]
197+
[]

0 commit comments

Comments
 (0)