From 73636f30ffb2bdabbe3e41a0451b34e3e51fcc3c Mon Sep 17 00:00:00 2001 From: DMZ22 Date: Fri, 24 Jul 2026 01:21:06 +0530 Subject: [PATCH] =?UTF-8?q?Add=20Czech=20and=20Slovak=20I=C4=8CO=20validat?= =?UTF-8?q?ion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IČO (Identifikační číslo osoby / Identifikačné číslo organizácie) is the 8-digit number that identifies a legal entity or sole trader in the Czech Republic and Slovakia, registered in the ARES and ORSR/RPO business registers. It carries a trailing weighted-modulo-11 check digit. For a legal entity the digits are the same as the Czech VAT number (DIČ) without the CZ prefix, so cz/ico reuses the check-digit algorithm already present in cz/dic for the 8-digit legal-entity form. The Slovak number is identical to the Czech one (until 1993 they were one country), so sk/ico delegates to cz/ico following the existing sk/rc -> cz/rc pattern. Detailed doctests validate 21 real Czech and 21 real Slovak numbers. Implements #450. --- stdnum/cz/ico.py | 92 +++++++++++++++++++++++++++++++++++++++ stdnum/sk/ico.py | 52 ++++++++++++++++++++++ tests/test_cz_ico.doctest | 80 ++++++++++++++++++++++++++++++++++ tests/test_sk_ico.doctest | 78 +++++++++++++++++++++++++++++++++ 4 files changed, 302 insertions(+) create mode 100644 stdnum/cz/ico.py create mode 100644 stdnum/sk/ico.py create mode 100644 tests/test_cz_ico.doctest create mode 100644 tests/test_sk_ico.doctest diff --git a/stdnum/cz/ico.py b/stdnum/cz/ico.py new file mode 100644 index 00000000..87395588 --- /dev/null +++ b/stdnum/cz/ico.py @@ -0,0 +1,92 @@ +# ico.py - functions for handling Czech organisation identifiers +# coding: utf-8 +# +# Copyright (C) 2026 Devashish Moghe +# +# 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, see . + +"""IČO (Identifikační číslo osoby, Czech organisation identifier). + +The IČO (Identifikační číslo osoby, also abbreviated IČ) is an 8-digit +number (including a trailing check digit) that uniquely identifies a legal +entity or sole trader registered in the Czech Republic. It is assigned by +the Czech Statistical Office and is listed in the public business register +(ARES). + +The number is the same as the digits of the legal entity's Czech VAT number +(DIČ, see stdnum.cz.dic) without the ``CZ`` prefix. + +More information: + +* https://cs.wikipedia.org/wiki/Identifikační_číslo_osoby +* https://ares.gov.cz/ + +>>> validate('00177041') +'00177041' +>>> validate('001 77 041') +'00177041' +>>> validate('00177042') # invalid check digit +Traceback (most recent call last): + ... +InvalidChecksum: ... +>>> validate('0017704') # too short +Traceback (most recent call last): + ... +InvalidLength: ... +>>> validate('0017704X') +Traceback (most recent call last): + ... +InvalidFormat: ... +""" + +from __future__ import annotations + +from stdnum.exceptions import * +from stdnum.util import clean, isdigits + + +def compact(number: str) -> str: + """Convert the number to the minimal representation. This strips the + number of any valid separators and removes surrounding whitespace.""" + return clean(number, ' /').strip() + + +def calc_check_digit(number: str) -> str: + """Calculate the check digit. The number passed should not have the + check digit included.""" + # weighted modulo 11 checksum; this is the same algorithm used for the + # 8-digit (legal entity) form of the Czech DIČ, see stdnum.cz.dic + check = (11 - sum((8 - i) * int(n) for i, n in enumerate(number))) % 11 + return str((check or 1) % 10) + + +def validate(number: str) -> str: + """Check if the number is a valid IČO. This checks the length, + formatting and check digit.""" + number = compact(number) + if not isdigits(number): + raise InvalidFormat() + if len(number) != 8: + raise InvalidLength() + if number[-1] != calc_check_digit(number[:-1]): + raise InvalidChecksum() + return number + + +def is_valid(number: str) -> bool: + """Check if the number is a valid IČO.""" + try: + return bool(validate(number)) + except ValidationError: + return False diff --git a/stdnum/sk/ico.py b/stdnum/sk/ico.py new file mode 100644 index 00000000..bab59124 --- /dev/null +++ b/stdnum/sk/ico.py @@ -0,0 +1,52 @@ +# ico.py - functions for handling Slovak organisation identifiers +# coding: utf-8 +# +# Copyright (C) 2026 Devashish Moghe +# +# 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, see . + +"""IČO (Identifikačné číslo organizácie, Slovak organisation identifier). + +The IČO (Identifikačné číslo organizácie) is an 8-digit number (including a +trailing check digit) that identifies an organisation or sole trader +registered in Slovakia. It is listed in the Slovak business register (ORSR) +and the Register of Legal Entities (RPO). + +This number is identical to the Czech counterpart (until 1993 the Czech +Republic and Slovakia were one country) and uses the same format and check +digit. + +>>> validate('31322832') +'31322832' +>>> validate('31 322 832') +'31322832' +>>> validate('31322833') # invalid check digit +Traceback (most recent call last): + ... +InvalidChecksum: ... +>>> validate('313228') # too short +Traceback (most recent call last): + ... +InvalidLength: ... +""" + +# since this number is essentially the same as the Czech counterpart +# (until 1993 the Czech Republic and Slovakia were one country) + +from __future__ import annotations + +from stdnum.cz.ico import calc_check_digit, compact, is_valid, validate + + +__all__ = ['compact', 'calc_check_digit', 'validate', 'is_valid'] diff --git a/tests/test_cz_ico.doctest b/tests/test_cz_ico.doctest new file mode 100644 index 00000000..f772702d --- /dev/null +++ b/tests/test_cz_ico.doctest @@ -0,0 +1,80 @@ +test_cz_ico.doctest - more detailed doctests for the stdnum.cz.ico module + +Copyright (C) 2026 Devashish Moghe + +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, see . + + +This file contains more detailed doctests for the stdnum.cz.ico module. It +tries to validate a number of numbers that have been found online. + +>>> from stdnum.cz import ico +>>> from stdnum.exceptions import * + + +Tests for some corner cases. + +>>> ico.validate('00177041') +'00177041' +>>> ico.validate('001 77 041') +'00177041' +>>> ico.compact('001 77 041') +'00177041' +>>> ico.is_valid('00177041') +True +>>> ico.is_valid('00177042') +False +>>> ico.validate('00177042') +Traceback (most recent call last): + ... +InvalidChecksum: ... +>>> ico.validate('123') +Traceback (most recent call last): + ... +InvalidLength: ... +>>> ico.validate('2616868A') +Traceback (most recent call last): + ... +InvalidFormat: ... + + +These have been found online and should all be valid numbers. + +>>> numbers = ''' +... +... 00001350 +... 00006947 +... 00177041 +... 00514152 +... 01377281 +... 24704415 +... 25063677 +... 25672720 +... 26168685 +... 26185610 +... 28195329 +... 27082440 +... 45244782 +... 45272956 +... 45274649 +... 45317054 +... 45357366 +... 47114983 +... 49240901 +... 60193336 +... 70994226 +... +... ''' +>>> [x for x in numbers.splitlines() if x and not ico.is_valid(x)] +[] diff --git a/tests/test_sk_ico.doctest b/tests/test_sk_ico.doctest new file mode 100644 index 00000000..1469291d --- /dev/null +++ b/tests/test_sk_ico.doctest @@ -0,0 +1,78 @@ +test_sk_ico.doctest - more detailed doctests for the stdnum.sk.ico module + +Copyright (C) 2026 Devashish Moghe + +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, see . + + +This file contains more detailed doctests for the stdnum.sk.ico module. It +tries to validate a number of numbers that have been found online. + +>>> from stdnum.sk import ico +>>> from stdnum.exceptions import * + + +Tests for some corner cases. + +>>> ico.validate('31322832') +'31322832' +>>> ico.validate('31 322 832') +'31322832' +>>> ico.is_valid('31322832') +True +>>> ico.is_valid('31322833') +False +>>> ico.validate('31322833') +Traceback (most recent call last): + ... +InvalidChecksum: ... +>>> ico.validate('313228') +Traceback (most recent call last): + ... +InvalidLength: ... +>>> ico.validate('3132283A') +Traceback (most recent call last): + ... +InvalidFormat: ... + + +These have been found online and should all be valid numbers. + +>>> numbers = ''' +... +... 00151653 +... 00151742 +... 00686930 +... 31320155 +... 31321828 +... 31322832 +... 31333532 +... 31364501 +... 35697270 +... 35743565 +... 35757442 +... 35763469 +... 35790253 +... 35815256 +... 35829052 +... 35848863 +... 35876832 +... 35971894 +... 36199222 +... 36631124 +... 44483767 +... +... ''' +>>> [x for x in numbers.splitlines() if x and not ico.is_valid(x)] +[]