import datetime
year_format = ["%Y"]
month_year_format = ["%m/%y", "%m/%Y", "%M/%Y", "%b/%Y", "%b/%y", "%B/%Y", "%B/%y"]
day_month_year_format = ["%d/%m/%Y"]
slash_formats = year_format + month_year_format + day_month_year_format
dash_formats = [fmt.replace("/", "-") for fmt in slash_formats]
blank_formats = [fmt.replace("/", "") for fmt in slash_formats]
allowed_fmts = slash_formats + dash_formats + blank_formats
[docs]def parse_date(date_str, api, start=True):
"""
Auxiliary function to convert different dates strings
to the format required by an API.
Also, when it is an end date, if day is unspecificed,
the day will be the last day of the month.
Similarly, if month is not specified, the month must be 12.
Note that the date string is assumed to start with day
or month. Otherwise, unexpected results can arise.
Parameters
----------
date_str : str
String to be parsed.
api : str
Name of the api. Possible values are "ibge", "ipea" and "bcb".
start : bool
If it is a start date.
Returns
-------
str
Appropriate date string for a given API.
Raises
------
ValueError
If the string could not be converted to a
datetime.datetime object by strptime.
Examples
--------
>>> dates.parse_date("012017", api="bcb")
'01/01/2017'
>>> dates.parse_date("01-2017", api="ipea")
'2017-01-01T00:00:00-00:00'
>>> dates.parse_date("01/2017", api="ibge")
'201701'
"""
assert isinstance(date_str, str), "You didn't give a string as a date."
for fmt in allowed_fmts:
try:
date = datetime.datetime.strptime(date_str, fmt)
if not start:
# if an end date,
# "2011" -> "31-12-2011"
# "01-2011" -> "31-01-2011"
# "02-01-2011" -> "02-01-2011"
date = date.replace(
month=12 if fmt in year_format else date.month,
day=last_day_of_month(date) if fmt.find("%d") == -1 else date.day,
)
return api_date_format_of(date, api)
except ValueError:
continue
raise ValueError("Not a valid date format.")
[docs]def parse_dates(start, end, api):
"""
Auxiliary function to call dates functions.
Returns
-------
str
Date format as required by an API.
"""
if start:
start = parse_date(start, api, start=True)
else:
start = api_date_format_of(very_old_date(), api)
if end:
end = parse_date(end, api, start=False)
else:
end = api_date_format_of(today_date(), api)
return start, end
[docs]def last_day_of_month(date):
"""
Auxiliary function that returns the last day of the month,
if no day is specified.
See: https://stackoverflow.com/questions/42950/get-last-day-of-the-month
"""
next_month = date.replace(day=28) + datetime.timedelta(days=4)
return (next_month - datetime.timedelta(days=next_month.day)).day
[docs]def very_old_date():
"""
Returns very old date (01/01/1900).
Returns
-------
datetime.datetime
"""
return datetime.datetime(1900, 1, 1)
[docs]def today_date():
"""
Returns today's date.
Returns
-------
datetime.datetime
Today's date.
"""
return datetime.datetime.today()
[docs]def month_to_quarter(date, fmt=None):
"""
Convert month to quarter, i.e.,
12 -> 4, 6 -> 2, 7 -> 3 etc.
Parameters
----------
date : datetime.datetime
Returns
-------
datetime.datetime
Examples
--------
>>> dates.month_to_quarter(datetime.datetime(2019, 12, 1))
datetime.datetime(2019, 4, 1, 0, 0)
"""
if isinstance(date, str) and fmt:
date = datetime.datetime.strptime(date, fmt)
quarter = divmod(date.month, 3)[0] + bool(divmod(date.month, 3)[1])
return date.replace(month=quarter)
[docs]def check_if_quarter(dates):
"""
Check if month of a datetime
object is less than 4, i.e., if
it is a quarter.
"""
for date in dates:
date_obj = datetime.datetime.strptime(date, "%Y%m")
if date_obj.month > 4:
error_msg = f"This is a quarterly time series."
error_msg += (
f" {date_obj.month} is not a quarter, choose a number between 1 and 4."
)
raise ValueError(error_msg)