import re, datetime
def add_months(datestr, months):
ref_year, ref_month = "", ""
ref_year_is_leap_year = False
aux_date = str(datetime.datetime.strptime(datestr, "%Y-%m-%d"))
print(repr(aux_date))
for i_month in range(int(months)):
# I add a unit since the months are "numerical quantities",
# that is, they are expressed in natural numbers, so I need it
# to start from 1 and not from 0 like the iter variable in python
i_month = i_month 1
m1 = re.search(
r"(?P<year>\d*)-(?P<month>\d{2})-(?P<startDay>\d{2})",
aux_date,
re.IGNORECASE,
)
if m1:
ref_year, ref_month = (
str(m1.groups()[0]).strip(),
str(m1.groups()[1]).strip(),
)
number_of_days_in_each_month = {
"01": "31",
"02": "28",
"03": "31",
"04": "30",
"05": "31",
"06": "30",
"07": "31",
"08": "31",
"09": "30",
"10": "31",
"11": "30",
"12": "31",
}
n_days_in_this_i_month = number_of_days_in_each_month[ref_month]
print(n_days_in_this_i_month) # nro days to increment in each i month iteration
if (
int(ref_year) % 4 == 0
and int(ref_year) % 100 == 0
and int(ref_year) % 400 != 0
):
ref_year_is_leap_year = True # divisible entre 4 y 10 y no entre 400, para determinar que sea un año bisciesto
if ref_year_is_leap_year == True and ref_month == "02":
n_days_in_this_i_month = str(int(n_days_in_this_i_month) 1) # 28 --> 29
aux_date = (
datetime.datetime.strptime(datestr, "%Y-%m-%d")
datetime.timedelta(days=int(n_days_in_this_i_month))
).strftime("%Y-%m-%d")
print(repr(aux_date))
return aux_date
print(repr(add_months("2022-12-30", "3")))
Why does the aux_date
variable, instead of progressively increasing the number of days of the elapsed months, only limit itself to adding 31 days of that month of January, and then add them back to the original amount, staying stuck there instead of advancing each iteration of this for
loop?
The objective of this for
loop is to achieve an incremental iteration loop where the days are added and not one that always returns to the original amount to add the same content over and over again.
Updated function Algorithm
def add_months(datestr, months):
ref_year, ref_month = "", ""
ref_year_is_leap_year = False #condicional booleano, cuya logica binaria intenta establecer si es o no bisiesto el año tomado como referencia
aux_date = datetime.datetime.strptime(datestr, "%Y-%m-%d")
for i_month in range(int(months)):
i_month = i_month 1 # I add a unit since the months are "numerical quantities", that is, they are expressed in natural numbers, so I need it to start from 1 and not from 0 like the iter variable in python
m1 = re.search( r"(?P<year>\d*)-(?P<month>\d{2})-(?P<startDay>\d{2})", str(aux_date), re.IGNORECASE, )
if m1:
ref_year, ref_month = ( str(m1.groups()[0]).strip(), str( int(m1.groups()[1]) 1).strip(), )
if( len(ref_month) == 1 ): ref_month = "0" ref_month
if( int(ref_month) > 12 ): ref_month = "01"
print(ref_month)
number_of_days_in_each_month = {
"01": "31",
"02": "28",
"03": "31",
"04": "30",
"05": "31",
"06": "30",
"07": "31",
"08": "31",
"09": "30",
"10": "31",
"11": "30",
"12": "31",
}
n_days_in_this_i_month = number_of_days_in_each_month[ref_month]
if (int(ref_year) % 4 == 0 and int(ref_year) % 100 == 0 and int(ref_year) % 400 != 0): ref_year_is_leap_year = True # divisible entre 4 y 10 y no entre 400, para determinar que sea un año bisciesto
if ref_year_is_leap_year == True and ref_month == "02": n_days_in_this_i_month = str(int(n_days_in_this_i_month) 1) # 28 --> 29
print(n_days_in_this_i_month) # nro days to increment in each i month iteration
aux_date = aux_date datetime.timedelta(days=int(n_days_in_this_i_month))
return datetime.datetime.strftime(aux_date, "%Y-%m-%d")
CodePudding user response:
So, as Alexander's answer already establishes, you weren't updating the date, so you were always adding to the same beginning date on each iteration. I took the liberty to clean up your code, using regex and converting to strings and back and for with the int's is the totally wrong approach here -- it misses the entire point of date-time objects, which is to encapsulate the information in a date. Just use those objects, not strings. Here is the same approach as your code using only datetime.datetime
objects:
import datetime
def add_months(datestr, months):
number_of_days_in_each_month = {
1 : 31,
2 : 28,
3 : 31,
4: 30,
5: 31,
6: 30,
7: 31,
8: 31,
9: 30,
10: 31,
11: 30,
12: 31,
}
date = datetime.datetime.strptime(datestr, "%Y-%m-%d")
is_leap_year = False
for i_month in range(1, int(months) 1):
ref_year, ref_month = date.year, date.month
n_days = number_of_days_in_each_month[ref_month]
if (
ref_year % 4 == 0
and ref_year % 100 == 0
and ref_year % 400 != 0
):
is_leap_year = True # divisible entre 4 y 10 y no entre 400, para determinar que sea un año bisciesto
if is_leap_year and ref_month == 2: # febrero
n_days = 1 # 28 --> 29
date = datetime.timedelta(days=n_days)
return date.strftime("%Y-%m-%d")
print(add_months("2022-12-30", "3"))
I also made some stylistic changes to variable names. This is an art not a science, naming variables, and it always comes down to subjective opinion, but may I humbly submit my opinion about more legible names.
Also note, you had a comment to the effect of:
I need the iter variable to start from 1 and not from 0 like the iter variable in python
The iterating variable starts where you tell it to start, given the iterable you iterate over. range(N)
will always start at zero, but it doesn't have to. You could iterate over [1, 2, 3]
, or better yet, range(1, N 1)
.
Note!
Your algorithm is not working quite how one might expect, the output one would naturally expect is 2023-03-30
I'll give you a hint, though, think about precisely which month's days you need to add to the current month.... n_days = number_of_days_in_each_month[ref_month]
....
CodePudding user response:
Because at the end of every iteration of your for loop you are reconverting the value that is given in the parameter datestr
and that value is never updated. You are also converting it to a string while trying to add a timedelta
object. You should leave the value as a datetime object and convert to string once the for loop has finished if you still need to.
Just change the variable used in the bottom assignment to aux_date
to aux_date
and remove all of the string conversions, that should at least get you going in the right direction.
for example:
import re, datetime
def add_months(datestr, months):
ref_year, ref_month = "", ""
ref_year_is_leap_year = False # condicional booleano, cuya logica binaria intenta establecer si es o no bisiesto el año tomado como referencia
aux_date = datetime.datetime.strptime(datestr, "%Y-%m-%d")
print(repr(aux_date))
for i_month in range(int(months)):
i_month = (
i_month 1
) # I add a unit since the months are "numerical quantities", that is, they are expressed in natural numbers, so I need it to start from 1 and not from 0 like the iter variable in python
m1 = re.search(
r"(?P<year>\d*)-(?P<month>\d{2})-(?P<startDay>\d{2})",
str(aux_date),
re.IGNORECASE,
)
if m1:
ref_year, ref_month = (
str(m1.groups()[0]).strip(),
str(m1.groups()[1]).strip(),
)
number_of_days_in_each_month = {
"01": "31",
"02": "28",
"03": "31",
"04": "30",
"05": "31",
"06": "30",
"07": "31",
"08": "31",
"09": "30",
"10": "31",
"11": "30",
"12": "31",
}
n_days_in_this_i_month = number_of_days_in_each_month[ref_month]
print(n_days_in_this_i_month) # nro days to increment in each i month iteration
if (
int(ref_year) % 4 == 0
and int(ref_year) % 100 == 0
and int(ref_year) % 400 != 0
):
ref_year_is_leap_year = True # divisible entre 4 y 10 y no entre 400, para determinar que sea un año bisciesto
if ref_year_is_leap_year == True and ref_month == "02":
n_days_in_this_i_month = str(int(n_days_in_this_i_month) 1) # 28 --> 29
aux_date = aux_date datetime.timedelta(days=int(n_days_in_this_i_month))
print(repr(aux_date))
return datetime.datetime.strftime(aux_date, "%Y-%m-%d")
print(repr(add_months("2022-12-30", "3")))
Output:
datetime.datetime(2022, 12, 30, 0, 0)
31
datetime.datetime(2023, 1, 30, 0, 0)
31
datetime.datetime(2023, 3, 2, 0, 0)
31
datetime.datetime(2023, 4, 2, 0, 0)
datetime.datetime(2023, 4, 2, 0, 0)
'2023-04-02'