Skip to content

Commit e30fd32

Browse files
authored
refactor(date-utils): unifica date e date-utils (#576)
também refatora `is_holiday` e `convert_date_to_text`
1 parent 84dc8cc commit e30fd32

5 files changed

Lines changed: 124 additions & 153 deletions

File tree

brutils/__init__.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,8 @@
2323
# Currency
2424
from brutils.currency import convert_real_to_text, format_currency
2525

26-
# Date imports
27-
from brutils.date import convert_date_to_text
28-
2926
# Date Utils Import
30-
from brutils.date_utils import is_holiday
27+
from brutils.date_utils import convert_date_to_text, is_holiday
3128

3229
# Email Import
3330
from brutils.email import is_valid as is_valid_email
@@ -94,8 +91,6 @@
9491
"generate_cpf",
9592
"is_valid_cpf",
9693
"remove_symbols_cpf",
97-
# Date
98-
"convert_date_to_text",
9994
# Email
10095
"is_valid_email",
10196
# Legal Process
@@ -132,6 +127,7 @@
132127
"get_municipality_by_code",
133128
# Date Utils
134129
"is_holiday",
130+
"convert_date_to_text",
135131
# Currency
136132
"format_currency",
137133
"convert_real_to_text",

brutils/date.py

Lines changed: 0 additions & 61 deletions
This file was deleted.

brutils/date_utils.py

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
import re
12
from datetime import datetime
2-
from typing import Union
33

44
import holidays
5+
from num2words import num2words
56

7+
from brutils.data.enums.months import MonthsEnum
68

7-
def is_holiday(target_date: datetime, uf: str = None) -> Union[bool, None]:
9+
DATE_REGEX = re.compile(r"^\d{2}/\d{2}/\d{4}$")
10+
11+
12+
def is_holiday(target_date: datetime, uf: str = None) -> bool | None:
813
"""
914
Checks if the given date is a national or state holiday in Brazil.
1015
@@ -41,18 +46,50 @@ def is_holiday(target_date: datetime, uf: str = None) -> Union[bool, None]:
4146
>>> is_holiday(datetime(2024, 12, 25), uf="RJ")
4247
True
4348
"""
44-
4549
if not isinstance(target_date, datetime):
4650
return None
4751

48-
valid_ufs = holidays.Brazil().subdivisions
52+
national_holidays = holidays.Brazil(years=target_date.year)
53+
valid_ufs = national_holidays.subdivisions
54+
4955
if uf is not None and uf not in valid_ufs:
5056
return None
5157

52-
national_holidays = holidays.Brazil(years=target_date.year)
53-
5458
if uf is None:
5559
return target_date in national_holidays
5660

5761
state_holidays = holidays.Brazil(prov=uf, years=target_date.year)
5862
return target_date in state_holidays
63+
64+
65+
def convert_date_to_text(date: str) -> str | None:
66+
"""
67+
Converts a given date in Brazilian format (dd/mm/yyyy) to its textual representation.
68+
69+
This function takes a date as a string in the format dd/mm/yyyy and converts it
70+
to a string with the date written out in Brazilian Portuguese, including the full
71+
month name and the year.
72+
73+
Args:
74+
date (str): The date to be converted into text. Expected format: dd/mm/yyyy.
75+
76+
Returns:
77+
str | None: A string with the date written out in Brazilian Portuguese,
78+
or None if the date is invalid.
79+
80+
"""
81+
if not DATE_REGEX.match(date):
82+
return None
83+
84+
try:
85+
dt = datetime.strptime(date, "%d/%m/%Y")
86+
except ValueError:
87+
return None
88+
89+
day, month, year = dt.day, dt.month, dt.year
90+
91+
day_str = "Primeiro" if day == 1 else num2words(day, lang="pt")
92+
month_enum = MonthsEnum(month)
93+
year_str = num2words(year, lang="pt")
94+
95+
return f"{day_str.capitalize()} de {month_enum.month_name} de {year_str}"

tests/test_date.py

Lines changed: 0 additions & 80 deletions
This file was deleted.

tests/test_date_utils.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
from unittest import TestCase
33
from unittest.mock import patch
44

5+
from num2words import num2words
6+
7+
from brutils import convert_date_to_text
8+
from brutils.data.enums.months import MonthsEnum
59
from brutils.date_utils import is_holiday
610

711

@@ -92,3 +96,78 @@ def test_data_sem_uf(self, mock_is_holiday):
9296
self.assertFalse(
9397
is_holiday(datetime(2024, 7, 9))
9498
) # Data estadual de SP, sem UF
99+
100+
101+
class TestNum2Words(TestCase):
102+
def test_num_conversion(self) -> None:
103+
"""
104+
Smoke test of the num2words library.
105+
This test is used to guarantee that our dependency still works.
106+
"""
107+
self.assertEqual(num2words(30, lang="pt-br"), "trinta")
108+
self.assertEqual(num2words(42, lang="pt-br"), "quarenta e dois")
109+
self.assertEqual(
110+
num2words(2024, lang="pt-br"), "dois mil e vinte e quatro"
111+
)
112+
self.assertEqual(num2words(0, lang="pt-br"), "zero")
113+
self.assertEqual(num2words(-1, lang="pt-br"), "menos um")
114+
115+
116+
class TestConvertDateToText(TestCase):
117+
def test_convert_date_to_text(self):
118+
self.assertEqual(
119+
convert_date_to_text("15/08/2024"),
120+
"Quinze de agosto de dois mil e vinte e quatro",
121+
)
122+
self.assertEqual(
123+
convert_date_to_text("01/01/2000"),
124+
"Primeiro de janeiro de dois mil",
125+
)
126+
self.assertEqual(
127+
convert_date_to_text("31/12/1999"),
128+
"Trinta e um de dezembro de mil novecentos e noventa e nove",
129+
)
130+
131+
self.assertIsNone(convert_date_to_text("30/02/2020"), None)
132+
self.assertIsNone(convert_date_to_text("30/00/2020"), None)
133+
self.assertIsNone(convert_date_to_text("30/02/2000"), None)
134+
self.assertIsNone(convert_date_to_text("50/09/2000"), None)
135+
self.assertIsNone(convert_date_to_text("25/15/2000"), None)
136+
self.assertIsNone(convert_date_to_text("29/02/2019"), None)
137+
138+
# Invalid date pattern.
139+
self.assertIsNone(convert_date_to_text("Invalid"))
140+
self.assertIsNone(convert_date_to_text("25/1/2020"))
141+
self.assertIsNone(convert_date_to_text("1924/08/20"))
142+
self.assertIsNone(convert_date_to_text("5/09/2020"))
143+
self.assertIsNone(convert_date_to_text("00/09/2020"))
144+
self.assertIsNone(convert_date_to_text("32/09/2020"))
145+
146+
self.assertEqual(
147+
convert_date_to_text("29/02/2020"),
148+
"Vinte e nove de fevereiro de dois mil e vinte",
149+
)
150+
self.assertEqual(
151+
convert_date_to_text("01/01/1900"),
152+
"Primeiro de janeiro de mil e novecentos",
153+
)
154+
155+
months_year = [
156+
(1, "janeiro"),
157+
(2, "fevereiro"),
158+
(3, "marco"),
159+
(4, "abril"),
160+
(5, "maio"),
161+
(6, "junho"),
162+
(7, "julho"),
163+
(8, "agosto"),
164+
(9, "setembro"),
165+
(10, "outubro"),
166+
(11, "novembro"),
167+
(12, "dezembro"),
168+
]
169+
170+
def testMonthEnum(self):
171+
for number_month, name_month in self.months_year:
172+
month = MonthsEnum(number_month)
173+
self.assertEqual(month.month_name, name_month)

0 commit comments

Comments
 (0)