diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bb57c98..a4851ba6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Utilitário `convert_code_to_uf` [#397](https://github.com/brazilian-utils/brutils-python/pull/410) - Utilitário `convert_date_to_text`[#394](https://github.com/brazilian-utils/brutils-python/pull/415) - Utilitário `get_municipality_by_code` [412](https://github.com/brazilian-utils/brutils-python/pull/412) - - Utilitário `get_code_by_municipality_name` [#399](https://github.com/brazilian-utils/brutils-python/issues/399) +- Utilitário `get_code_by_municipality_name` [#399](https://github.com/brazilian-utils/brutils-python/issues/399) +- Utilitário `format_currency` [#426](https://github.com/brazilian-utils/brutils-python/issues/426) ## [2.2.0] - 2024-09-12 diff --git a/README.md b/README.md index 0308eba5..8c35ffdf 100644 --- a/README.md +++ b/README.md @@ -81,18 +81,20 @@ False - [remove\_symbols\_pis](#remove_symbols_pis) - [generate\_pis](#generate_pis) - [Processo Jurídico](#processo-jurídico) - - [is\_valid\_legal\_process](#is_valid_legal_process) +- [is\_valid\_legal\_process](#is_valid_legal_process) - [format\_legal\_process](#format_legal_process) - [remove\_symbols\_legal\_process](#remove_symbols_legal_process) - [generate\_legal\_process](#generate_legal_process) -- [Título Eleitoral](#titulo-eleitoral) - - [is_valid_voter_id](#is_valid_voter_id) - - [format_voter_id](#format_voter_id) - - [generate_voter_id](#generate_voter_id) +- [Titulo Eleitoral](#titulo-eleitoral) + - [is\_valid\_voter\_id](#is_valid_voter_id) + - [format\_voter\_id](#format_voter_id) + - [generate\_voter\_id](#generate_voter_id) - [IBGE](#ibge) - [get_code_by_municipality_name](#get_code_by_municipality_name) - [convert_code_to_uf](#convert_code_to_uf) - [get\_municipality\_by\_code](#get_municipality_by_code) +- [Monetário](#monetário) + - [format\_currency](#format_currency) ## CPF @@ -1190,6 +1192,33 @@ None >>> get_code_by_municipality_name("Municipio Inexistente", "RS") None ``` + +## Monetário + +### format_currency + +Formata um número seguindo o padrão monetário brasileiro. O número será formatado +adicionando o símbolo R$ como prefixo, vírgula como separador decimal, e ponto como +agrupador de milhar. + +Argumentos: + * float ou Decimal: Um número com ou sem casas decimais. + +Retorna: + * str ou None: O número formatado seguindo o padrão brasileiro. + +Exemplo: + +```python +>>> from brutils.currency import format_currency +>>> format_currency(1259.03) +'R$ 1.259,03' +>>> format_currency(0) +'R$ 0,00' +>>> format_currency("not a number") +None +``` + # Novos Utilitários e Reportar Bugs Caso queira sugerir novas funcionalidades ou reportar bugs, basta criar diff --git a/README_EN.md b/README_EN.md index a3fc4926..4cd1813d 100644 --- a/README_EN.md +++ b/README_EN.md @@ -93,6 +93,8 @@ False - [convert_code_to_uf](#convert_code_to_uf) - [get\_municipality\_by\_code](#get_municipality_by_code) - [get_code_by_municipality_name](#get_code_by_municipality_name) +- [Monetary](#monetary) + - [format_currency](#format_currency) ## CPF @@ -1195,6 +1197,32 @@ None None ``` +## Monetary + +### format_currency + +Formats a number following the Brazilian monetary standard. The number will be +formatted by adding the R$ symbol as a prefix, a comma as a decimal separator, and a +period as a thousands grouper. + +Args: + * float or Decimal: A number with or without decimal places. + +Returns: + * str or None: The number formatted following the Brazilian standard. + +Example: + +```python +>>> from brutils.currency import format_currency +>>> format_currency(1259.03) +'R$ 1.259,03' +>>> format_currency(0) +'R$ 0,00' +>>> format_currency("not a number") +None +``` + # Feature Request and Bug Report If you want to suggest new features or report bugs, simply create diff --git a/brutils/__init__.py b/brutils/__init__.py index b781f0de..8717e1dd 100644 --- a/brutils/__init__.py +++ b/brutils/__init__.py @@ -4,43 +4,24 @@ get_address_from_cep, get_cep_information_from_address, ) -from brutils.cep import ( - generate as generate_cep, -) -from brutils.cep import ( - is_valid as is_valid_cep, -) -from brutils.cep import ( - remove_symbols as remove_symbols_cep, -) +from brutils.cep import generate as generate_cep +from brutils.cep import is_valid as is_valid_cep +from brutils.cep import remove_symbols as remove_symbols_cep # CNPJ Imports -from brutils.cnpj import ( - format_cnpj, -) -from brutils.cnpj import ( - generate as generate_cnpj, -) -from brutils.cnpj import ( - is_valid as is_valid_cnpj, -) -from brutils.cnpj import ( - remove_symbols as remove_symbols_cnpj, -) +from brutils.cnpj import format_cnpj +from brutils.cnpj import generate as generate_cnpj +from brutils.cnpj import is_valid as is_valid_cnpj +from brutils.cnpj import remove_symbols as remove_symbols_cnpj # CPF Imports -from brutils.cpf import ( - format_cpf, -) -from brutils.cpf import ( - generate as generate_cpf, -) -from brutils.cpf import ( - is_valid as is_valid_cpf, -) -from brutils.cpf import ( - remove_symbols as remove_symbols_cpf, -) +from brutils.cpf import format_cpf +from brutils.cpf import generate as generate_cpf +from brutils.cpf import is_valid as is_valid_cpf +from brutils.cpf import remove_symbols as remove_symbols_cpf + +# Currency +from brutils.currency import format_currency # Date imports from brutils.date import convert_date_to_text @@ -53,43 +34,23 @@ get_code_by_municipality_name, get_municipality_by_code, ) -from brutils.ibge.uf import ( - convert_code_to_uf, -) +from brutils.ibge.uf import convert_code_to_uf # Legal Process Imports -from brutils.legal_process import ( - format_legal_process, -) -from brutils.legal_process import ( - generate as generate_legal_process, -) -from brutils.legal_process import ( - is_valid as is_valid_legal_process, -) -from brutils.legal_process import ( - remove_symbols as remove_symbols_legal_process, -) +from brutils.legal_process import format_legal_process +from brutils.legal_process import generate as generate_legal_process +from brutils.legal_process import is_valid as is_valid_legal_process +from brutils.legal_process import remove_symbols as remove_symbols_legal_process # License Plate Imports from brutils.license_plate import ( convert_to_mercosul as convert_license_plate_to_mercosul, ) -from brutils.license_plate import ( - format_license_plate, -) -from brutils.license_plate import ( - generate as generate_license_plate, -) -from brutils.license_plate import ( - get_format as get_format_license_plate, -) -from brutils.license_plate import ( - is_valid as is_valid_license_plate, -) -from brutils.license_plate import ( - remove_symbols as remove_symbols_license_plate, -) +from brutils.license_plate import format_license_plate +from brutils.license_plate import generate as generate_license_plate +from brutils.license_plate import get_format as get_format_license_plate +from brutils.license_plate import is_valid as is_valid_license_plate +from brutils.license_plate import remove_symbols as remove_symbols_license_plate # Phone Imports from brutils.phone import ( @@ -97,37 +58,19 @@ remove_international_dialing_code, remove_symbols_phone, ) -from brutils.phone import ( - generate as generate_phone, -) -from brutils.phone import ( - is_valid as is_valid_phone, -) +from brutils.phone import generate as generate_phone +from brutils.phone import is_valid as is_valid_phone # PIS Imports -from brutils.pis import ( - format_pis, -) -from brutils.pis import ( - generate as generate_pis, -) -from brutils.pis import ( - is_valid as is_valid_pis, -) -from brutils.pis import ( - remove_symbols as remove_symbols_pis, -) +from brutils.pis import format_pis +from brutils.pis import generate as generate_pis +from brutils.pis import is_valid as is_valid_pis +from brutils.pis import remove_symbols as remove_symbols_pis # Voter ID Imports -from brutils.voter_id import ( - format_voter_id, -) -from brutils.voter_id import ( - generate as generate_voter_id, -) -from brutils.voter_id import ( - is_valid as is_valid_voter_id, -) +from brutils.voter_id import format_voter_id +from brutils.voter_id import generate as generate_voter_id +from brutils.voter_id import is_valid as is_valid_voter_id # Defining __all__ to expose the public methods __all__ = [ @@ -183,4 +126,6 @@ "convert_code_to_uf", "get_municipality_by_code", "get_code_by_municipality_name", + # Currency + "format_currency", ] diff --git a/brutils/currency.py b/brutils/currency.py new file mode 100644 index 00000000..f731e1a1 --- /dev/null +++ b/brutils/currency.py @@ -0,0 +1,40 @@ +from decimal import Decimal, InvalidOperation + + +def format_currency(value): # type: (float) -> str | None + """ + Formats a given float as Brazilian currency (R$). + + This function takes a float value and returns a formatted string representing + the value as Brazilian currency, using the correct thousand and decimal separators. + + Args: + value (float or Decimal): The numeric value to be formatted. + + Returns: + str or None: A string formatted as Brazilian currency, or None if the input is invalid. + + Example: + >>> format_currency(1234.56) + "R$ 1.234,56" + >>> format_currency(0) + "R$ 0,00" + >>> format_currency(-9876.54) + "R$ -9.876,54" + >>> format_currency(None) + None + >>> format_currency("not a number") + None + """ + + try: + decimal_value = Decimal(value) + formatted_value = ( + f"R$ {decimal_value:,.2f}".replace(",", "_") + .replace(".", ",") + .replace("_", ".") + ) + + return formatted_value + except InvalidOperation: + return None diff --git a/tests/test_currency.py b/tests/test_currency.py new file mode 100644 index 00000000..7996f2f8 --- /dev/null +++ b/tests/test_currency.py @@ -0,0 +1,27 @@ +from decimal import Decimal +from unittest import TestCase + +from brutils.currency import format_currency + + +class TestFormatCurrency(TestCase): + def test_when_value_is_a_decimal_value(self): + assert format_currency(Decimal("123236.70")) == "R$ 123.236,70" + + def test_when_value_is_a_float_value(self): + assert format_currency(123236.70) == "R$ 123.236,70" + + def test_when_value_is_negative(self): + assert format_currency(-123236.70) == "R$ -123.236,70" + + def test_when_value_is_zero(self): + print(format_currency(0)) + assert format_currency(0) == "R$ 0,00" + + def test_value_decimal_replace_rounding(self): + assert format_currency(-123236.7676) == "R$ -123.236,77" + + def test_when_value_is_not_a_valid_currency(self): + assert format_currency("not a number") is None + assert format_currency("09809,87") is None + assert format_currency("897L") is None