1
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")
3
  • 1
    I ran the black auto-formatter on your code to make it more legible. I also fixed some spacing issues, and spread the comment over multiple lines (this was making the autoformatted result very janky with one very long comment line). Try to stick to the PEP8 style conventions to make your code more legible by other people: peps.python.org/pep-0008 Commented Dec 3, 2022 at 8:47
  • 2
    You can use dateutil.relativedelta to add months Commented Dec 3, 2022 at 8:50
  • Anyway, it's because you never update datestr. Commented Dec 3, 2022 at 8:54

2 Answers 2

1

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

Sign up to request clarification or add additional context in comments.

9 Comments

Really thanks for the help! And the recomendations
@MatiasNicolasRodriguez see my note at the bottom about the result not being what one would expect from this operation, at least not what I would
@MatiasNicolasRodriguez 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]....
@MatiasNicolasRodriguez you have to use the next month's number of days
@MatiasNicolasRodriguez again, use an int object, not a string, to manipulate numerical values. Also note, I moved the leap-year logic into a helper function and the dictionary as a global variable to make the logic more legible. Please, please plese you need to care about legibility. So mathematically, if you want the month after month 12, you add 1, and then what?
|
1

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'

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.