Search code examples
pythonpython-3.xloopsfor-loopvariables

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?


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

In this edit I have modified some details and redundancies, and also fixed some bugs that are present in the original code.

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 ) or ( int(ref_year) % 400 == 0 ): ref_year_is_leap_year = True 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")

Solution

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