Skip to content

Commit d3ec3bd

Browse files
committed
Support new format for Brazilian CNPJ
The new format allows letters as well as numbers to identify companies. Closes #484
1 parent cd94bf1 commit d3ec3bd

File tree

1 file changed

+22
-12
lines changed

1 file changed

+22
-12
lines changed

stdnum/br/cnpj.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# cnpj.py - functions for handling CNPJ numbers
22
# coding: utf-8
33
#
4-
# Copyright (C) 2015 Arthur de Jong
4+
# Copyright (C) 2015-2026 Arthur de Jong
55
#
66
# This library is free software; you can redistribute it and/or
77
# modify it under the terms of the GNU Lesser General Public
@@ -20,12 +20,14 @@
2020

2121
"""CNPJ (Cadastro Nacional da Pessoa Jurídica, Brazilian company identifier).
2222
23-
Numbers from the national register of legal entities have 14 digits. The
24-
first 8 digits identify the company, the following 4 digits identify a
23+
Numbers from the national register of legal entities have 14 alphanumeric digits.
24+
The first 8 digits identify the company, the following 4 digits identify a
2525
business unit and the last 2 digits are check digits.
2626
2727
>>> validate('16.727.230/0001-97')
2828
'16727230000197'
29+
>>> validate('12. ABC.345 /01DE–35') # new format from July 2026 onwards
30+
'12ABC34501DE35'
2931
>>> validate('16.727.230.0001-98')
3032
Traceback (most recent call last):
3133
...
@@ -40,31 +42,39 @@
4042

4143
from __future__ import annotations
4244

45+
import re
46+
4347
from stdnum.exceptions import *
44-
from stdnum.util import clean, isdigits
48+
from stdnum.util import clean
49+
50+
51+
# Minimal regex of valid characters
52+
_cnpj_re = re.compile(r'^[\dA-Z]+$')
4553

4654

4755
def compact(number: str) -> str:
4856
"""Convert the number to the minimal representation. This strips the
4957
number of any valid separators and removes surrounding whitespace."""
50-
return clean(number, ' -./').strip()
58+
return clean(number, ' -./').strip().upper()
5159

5260

5361
def calc_check_digits(number: str) -> str:
5462
"""Calculate the check digits for the number."""
55-
d1 = (11 - sum(((3 - i) % 8 + 2) * int(n)
56-
for i, n in enumerate(number[:12]))) % 11 % 10
57-
d2 = (11 - sum(((4 - i) % 8 + 2) * int(n)
58-
for i, n in enumerate(number[:12])) -
59-
2 * d1) % 11 % 10
60-
return '%d%d' % (d1, d2)
63+
number = compact(number)
64+
values = [ord(n) - 48 for n in number[:12]]
65+
weights = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
66+
d1 = (11 - sum(w * v for w, v in zip(weights, values))) % 11 % 10
67+
values.append(d1)
68+
weights = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
69+
d2 = (11 - sum(w * v for w, v in zip(weights, values))) % 11 % 10
70+
return f'{d1}{d2}'
6171

6272

6373
def validate(number: str) -> str:
6474
"""Check if the number is a valid CNPJ. This checks the length and
6575
whether the check digits are correct."""
6676
number = compact(number)
67-
if not isdigits(number) or int(number) <= 0:
77+
if not _cnpj_re.match(number) or number.startswith('000000000000'):
6878
raise InvalidFormat()
6979
if len(number) != 14:
7080
raise InvalidLength()

0 commit comments

Comments
 (0)