Skip to content

Commit

Permalink
394 - Converter data no formato brasileiro para uma representação tex…
Browse files Browse the repository at this point in the history
…tual (#415)

* Convert date to text representation

Converts a given date in brazilian format to its textual
representation.

394

* Add some coverage suggestions

* Fix test implementation for testMonthEnum
  • Loading branch information
BeneBr authored Dec 19, 2024
1 parent d7b645d commit a46eafe
Show file tree
Hide file tree
Showing 11 changed files with 292 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Generate Report
run: |
pip install coverage
pip install coverage num2words
coverage run -m unittest discover tests/
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v4
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- 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)

## [2.2.0] - 2024-09-12
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ emulate bash -c '. .../bin/activate'
Para testar se o ambiente virtual está ativo corretamente, execute o comando e verifique se a resposta é algo parecido com a seguinte:

```sh
$ poetry env inf
$ poetry env info
Virtualenv
Python: 3.x.y
Implementation: CPython
Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ False
- [generate\_phone](#generate_phone)
- [Email](#email)
- [is\_valid\_email](#is_valid_email)
- [Data](#date)
- [convert\_date\_to_text](#convert_date_to_text)
- [Placa de Carro](#placa-de-carro)
- [is\_valid\_license\_plate](#is_valid_license_plate)
- [format\_license\_plate](#format_license_plate)
Expand Down Expand Up @@ -629,6 +631,33 @@ False
False
```

## Data

## convert_date_to_text

Converte uma data em sua representação textual.

Argumentos:
- date (str): Uma string no formato dd/mm/aaaa

Retorna:
- A represetação textual da data ou None caso a data esteja mal formatada ou a data seja inválida.

Exemplo:

````python
>>> from brutils import convert_date_to_text
>>> convert_date_to_text("25/12/2000")
"Vinte e cinco de dezembro de dois mil"
>>> convert_date_to_text("31/02/2000")
None
>>> convert_date_to_text("29/02/2024")
"Vinte e nove de fevereiro de dois mil e vinte e quatro"
>>> convert_date_to_text("1/08/2024")
"Primeiro de agosto de dois mil e vinte e quatro"
````


## Placa de Carro

### is_valid_license_plate
Expand Down
28 changes: 28 additions & 0 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ False
- [generate\_cep](#generate_cep)
- [get\_address\_from\_cep](#get_address_from_cep)
- [get\_cep\_information\_from\_address](#get_cep_information_from_address)
- [Date](#date)
- [convert\_date\_to_text](#convert_date_to_text)
- [Phone](#phone)
- [is\_valid\_phone](#is_valid_phone)
- [format\_phone](#format_phone)
Expand Down Expand Up @@ -449,6 +451,32 @@ Example:
]
```

## Date

### convert_date_to_text
Convert a brazilian date (dd/mm/yyyy) format in their portuguese textual representation.

Args:
- date (str): A date in a string format dd/mm/yyyy.

Return:
- (str) | None: A portuguese textual representation of the date or None case a date is invalid.


Example:

````python
>>> from brutils import convert_date_to_text
>>> convert_date_to_text("25/12/2000")
"Vinte e cinco de dezembro de dois mil"
>>> convert_date_to_text("31/02/2000")
None
>>> convert_date_to_text("29/02/2024")
"Vinte e nove de fevereiro de dois mil e vinte e quatro"
>>> convert_date_to_text("1/08/2024")
"Primeiro de agosto de dois mil e vinte e quatro"
````

## Phone

### is_valid_phone
Expand Down
5 changes: 5 additions & 0 deletions brutils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
remove_symbols as remove_symbols_cpf,
)

# Date imports
from brutils.date import convert_date_to_text

# Email Import
from brutils.email import is_valid as is_valid_email
from brutils.ibge.municipality import (
Expand Down Expand Up @@ -144,6 +147,8 @@
"generate_cpf",
"is_valid_cpf",
"remove_symbols_cpf",
# Date
"convert_date_to_text",
# Email
"is_valid_email",
# Legal Process
Expand Down
57 changes: 57 additions & 0 deletions brutils/data/enums/months.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from brutils.data.enums.better_enum import BetterEnum


class MonthsEnum(BetterEnum):
JANEIRO = 1
FEVEREIRO = 2
MARCO = 3
ABRIL = 4
MAIO = 5
JUNHO = 6
JULHO = 7
AGOSTO = 8
SETEMBRO = 9
OUTUBRO = 10
NOVEMBRO = 11
DEZEMBRO = 12

@property
def month_name(self) -> str:
if self == MonthsEnum.JANEIRO:
return "janeiro"
elif self == MonthsEnum.FEVEREIRO:
return "fevereiro"
elif self == MonthsEnum.MARCO:
return "marco"
elif self == MonthsEnum.ABRIL:
return "abril"
elif self == MonthsEnum.MAIO:
return "maio"
elif self == MonthsEnum.JUNHO:
return "junho"
elif self == MonthsEnum.JULHO:
return "julho"
elif self == MonthsEnum.AGOSTO:
return "agosto"
elif self == MonthsEnum.SETEMBRO:
return "setembro"
elif self == MonthsEnum.OUTUBRO:
return "outubro"
elif self == MonthsEnum.NOVEMBRO:
return "novembro"
else:
return "dezembro"

@classmethod
def is_valid_month(cls, month: int) -> bool:
"""
Checks if the given month value is valid.
Args:
month (int): The month to check.
Returns:
True if the month is valid, False otherwise.
"""
return (
True if month in set(month.value for month in MonthsEnum) else False
)
64 changes: 64 additions & 0 deletions brutils/date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import re
from typing import Union

from num2words import num2words

from brutils.data.enums.months import MonthsEnum


def convert_date_to_text(date: str) -> Union[str, None]:
"""
Converts a given date in Brazilian format (dd/mm/yyyy) to its textual representation.
This function takes a date as a string in the format dd/mm/yyyy and converts it
to a string with the date written out in Brazilian Portuguese, including the full
month name and the year.
Args:
date (str): The date to be converted into text. Expected format: dd/mm/yyyy.
Returns:
str or None: A string with the date written out in Brazilian Portuguese,
or None if the date is invalid.
"""
pattern = re.compile(r"\d{2}/\d{2}/\d{4}")
if not re.match(pattern, date):
raise ValueError(
"Date is not a valid date. Please pass a date in the format dd/mm/yyyy."
)

day_str, month_str, year_str = date.split("/")
day = int(day_str)
month = int(month_str)
year = int(year_str)

if 0 <= day > 31:
return None

if not MonthsEnum.is_valid_month(month):
return None

# Leap year.
if MonthsEnum(int(month)) is MonthsEnum.FEVEREIRO:
if (int(year) % 4 == 0 and int(year) % 100 != 0) or (
int(year) % 400 == 0
):
if day > 29:
return None
else:
if day > 28:
return None

day_string = "Primeiro" if day == 1 else num2words(day, lang="pt")
month = MonthsEnum(month)
year_string = num2words(year, lang="pt")

date_string = (
day_string.capitalize()
+ " de "
+ month.month_name
+ " de "
+ year_string
)
return date_string
26 changes: 25 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ classifiers = [

[tool.poetry.dependencies]
python = "^3.8.1"
num2words = "0.5.13"

[tool.poetry.group.test.dependencies]
coverage = "^7.2.7"
Expand Down
80 changes: 80 additions & 0 deletions tests/test_date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from unittest import TestCase

from num2words import num2words

from brutils import convert_date_to_text
from brutils.data.enums.months import MonthsEnum


class TestNum2Words(TestCase):
def test_num_conversion(self) -> None:
"""
Smoke test of the num2words library.
This test is used to guarantee that our dependency still works.
"""
self.assertEqual(num2words(30, lang="pt-br"), "trinta")
self.assertEqual(num2words(42, lang="pt-br"), "quarenta e dois")
self.assertEqual(
num2words(2024, lang="pt-br"), "dois mil e vinte e quatro"
)
self.assertEqual(num2words(0, lang="pt-br"), "zero")
self.assertEqual(num2words(-1, lang="pt-br"), "menos um")


class TestDate(TestCase):
def test_convert_date_to_text(self):
self.assertEqual(
convert_date_to_text("15/08/2024"),
"Quinze de agosto de dois mil e vinte e quatro",
)
self.assertEqual(
convert_date_to_text("01/01/2000"),
"Primeiro de janeiro de dois mil",
)
self.assertEqual(
convert_date_to_text("31/12/1999"),
"Trinta e um de dezembro de mil novecentos e noventa e nove",
)

#
self.assertIsNone(convert_date_to_text("30/02/2020"), None)
self.assertIsNone(convert_date_to_text("30/00/2020"), None)
self.assertIsNone(convert_date_to_text("30/02/2000"), None)
self.assertIsNone(convert_date_to_text("50/09/2000"), None)
self.assertIsNone(convert_date_to_text("25/15/2000"), None)
self.assertIsNone(convert_date_to_text("29/02/2019"), None)

# Invalid date pattern.
self.assertRaises(ValueError, convert_date_to_text, "Invalid")
self.assertRaises(ValueError, convert_date_to_text, "25/1/2020")
self.assertRaises(ValueError, convert_date_to_text, "1924/08/20")
self.assertRaises(ValueError, convert_date_to_text, "5/09/2020")

self.assertEqual(
convert_date_to_text("29/02/2020"),
"Vinte e nove de fevereiro de dois mil e vinte",
)
self.assertEqual(
convert_date_to_text("01/01/1900"),
"Primeiro de janeiro de mil e novecentos",
)

months_year = [
(1, "janeiro"),
(2, "fevereiro"),
(3, "marco"),
(4, "abril"),
(5, "maio"),
(6, "junho"),
(7, "julho"),
(8, "agosto"),
(9, "setembro"),
(10, "outubro"),
(11, "novembro"),
(12, "dezembro"),
]

def testMonthEnum(self):
for number_month, name_month in self.months_year:
month = MonthsEnum(number_month)
self.assertEqual(month.month_name, name_month)

0 comments on commit a46eafe

Please sign in to comment.