diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e5a7ee..a40c1a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Utilitário `convert_name_to_uf` +- Utilitário `is_valid_cnh` [#651](https://github.com/brazilian-utils/brutils-python/pull/651) ### Fixed diff --git a/README.md b/README.md index 0b4defb..6743cf8 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,8 @@ False - [is\_valid\_email](#is_valid_email) - [Data](#date) - [convert\_date\_to_text](#convert_date_to_text) +- [CNH](#cnh) + - [is\_valid\_cnh](#is_valid_cnh) - [Placa de Carro](#placa-de-carro) - [is\_valid\_license\_plate](#is_valid_license_plate) - [format\_license\_plate](#format_license_plate) @@ -666,6 +668,37 @@ None "Primeiro de agosto de dois mil e vinte e quatro" ```` +## CNH + +### is_valid_cnh + +Verifica se o número de registro de CNH (Carteira de Habilitação Nacional) brasileiro é válido. +Para que um número de CNH seja considerado válido, a entrada deve ser uma string contendo +exatamente 11 dígitos numéricos. Esta função não verifica se o número da CNH é real, apenas +valida os dígitos verificadores. + +Argumentos: + +- cnh (str): A string contendo o número de registro de CNH a ser verificado. + +Retorno: + +- bool: True se o número de registro da CNHN for válido (11 dígitos), False caso contrário. + +Exemplo: + +```python +>>> from brutils import is_valid_cnh +>>> is_valid_cnh("12345678901") +False +>>> is_valid_cnh("A2C45678901") +False +>>> is_valid_cnh("98765432100") +True +>>> is_valid_cnh("987654321-00") +True +``` + ## Placa de Carro diff --git a/README_EN.md b/README_EN.md index 3bdc364..b6d6ccf 100644 --- a/README_EN.md +++ b/README_EN.md @@ -68,6 +68,8 @@ False - [generate\_phone](#generate_phone) - [Email](#email) - [is\_valid\_email](#is_valid_email) +- [CNH](#cnh) + - [is\_valid\_cnh](#is_valid_cnh) - [License Plate](#license-plate) - [is\_valid\_license\_plate](#is_valid_license_plate) - [format\_license\_plate](#format_license_plate) @@ -659,6 +661,37 @@ False False ``` + +## CNH + +### is_valid_cnh + +Checks if the registration number of a brazilian CNH (Carteira de Habilitação Nacional or Driver's License in En.) is valid. +To be considered valid, the input must be a string containing exactly 11 digits. This function does not verify if the registration number of the CNH is a real one, it only validates it's verification numbers. + +Argumentos: + +- cnh (str): A string containing the registration nunber of the CNH to be checked. + +Retorno: + +- bool: True if the CNH number is valid (11 digits), False otherwise. + +Exemplo: + +```python +>>> from brutils import is_valid_cnh +>>> is_valid_cnh("123456789") +False +>>> is_valid_cnh("A2C45678901") +False +>>> is_valid_cnh("98765432100") +True +>>> is_valid_cnh("987654321-00") +True +``` + + ## License Plate ### is_valid_license_plate diff --git a/brutils/__init__.py b/brutils/__init__.py index 1bdcfd6..b2fe994 100644 --- a/brutils/__init__.py +++ b/brutils/__init__.py @@ -8,6 +8,9 @@ from brutils.cep import is_valid as is_valid_cep from brutils.cep import remove_symbols as remove_symbols_cep +# CNH Imports +from brutils.cnh import is_valid_cnh as is_valid_cnh + # CNPJ Imports from brutils.cnpj import format_cnpj from brutils.cnpj import generate as generate_cnpj @@ -95,6 +98,8 @@ "generate_cpf", "is_valid_cpf", "remove_symbols_cpf", + # CNH + "is_valid_cnh", # Email "is_valid_email", # Legal Process diff --git a/brutils/cnh.py b/brutils/cnh.py new file mode 100644 index 0000000..f7347ac --- /dev/null +++ b/brutils/cnh.py @@ -0,0 +1,86 @@ +def is_valid_cnh(cnh: str) -> bool: + """ + Validates the registration number for the Brazilian CNH (Carteira Nacional de Habilitação) that was created in 2022. + Previous versions of the CNH are not supported in this version. + This function checks if the given CNH is valid based on the format and allowed characters, + verifying the verification digits. + + Args: + cnh (str): CNH string (symbols will be ignored). + + Returns: + bool: True if CNH has a valid format. + + Examples: + >>> is_valid_cnh("12345678901") + False + >>> is_valid_cnh("A2C45678901") + False + >>> is_valid_cnh("98765432100") + True + >>> is_valid_cnh("987654321-00") + True + """ + cnh = "".join( + filter(str.isdigit, cnh) + ) # clean the input and check for numbers only + + if not cnh: + return False + + if len(cnh) != 11: + return False + + # Reject sequences as "00000000000", "11111111111", etc. + if cnh == cnh[0] * 11: + return False + + # cast digits to list of integers + digits: list[int] = [int(ch) for ch in cnh] + first_verificator = digits[9] + second_verificator = digits[10] + + if not _check_first_verificator( + digits, first_verificator + ): # checking the 10th digit + return False + + return _check_second_verificator( + digits, second_verificator, first_verificator + ) # checking the 11th digit + + +def _check_first_verificator(digits: list[int], first_verificator: int) -> bool: + """ + Generates the first verification digit and uses it to verify the 10th digit of the CNH + """ + + sum = 0 + for i in range(9): + sum += digits[i] * (9 - i) + + sum = sum % 11 + result = 0 if sum > 9 else sum + + return result == first_verificator + + +def _check_second_verificator( + digits: list[int], second_verificator: int, first_verificator: int +) -> bool: + """ + Generates the second verification and uses it to verify the 11th digit of the CNH + """ + sum = 0 + for i in range(9): + sum += digits[i] * (i + 1) + + result = sum % 11 + + if first_verificator > 9: + result = result + 9 if (result - 2) < 0 else result - 2 + + if result > 9: + result = 0 + + return result == second_verificator diff --git a/tests/test_cnh.py b/tests/test_cnh.py new file mode 100644 index 0000000..a1049be --- /dev/null +++ b/tests/test_cnh.py @@ -0,0 +1,12 @@ +from unittest import TestCase + +from brutils.cnh import is_valid_cnh + + +class TestCNH(TestCase): + def test_is_valid_cnh(self): + self.assertFalse(is_valid_cnh("22222222222")) + self.assertFalse(is_valid_cnh("ABC70304734")) + self.assertFalse(is_valid_cnh("6619558737912")) + self.assertTrue(is_valid_cnh("097703047-34")) + self.assertTrue(is_valid_cnh("09770304734"))