How to set a variable, that isnt iter variable, to increases in each iteration and doesnt always return to its value prior to its entry into for loop?

Question:

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

Answers:

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'
Answered By: Alexander

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]….

Answered By: juanpa.arrivillaga