How do you add "3 months" to a datetime.date object in python?

Question:

Python date calculations, where art thou?

I have a python app that needs to plot out dates every three months for several years. It’s important that the dates occur exactly 4 times a year, and that the dates occur on the same day each year as much as possible, and that the dates occur on the same day of the month as much as possible, and that the dates be as close to “3 months” apart as they can be (which is a moving target, especially on leap year). Unfortunately, datetime.timedelta doesn’t support months!

Is there a “standard” way to do this calculation in python???

The SQL way?

If worst comes to worst, I will punt and have my app ask PostgreSQL, who does have nice built-in support for date calculations, for the answer like this:

# select ('2010-11-29'::date + interval '3 months')::date;
    date    
------------
 2011-02-28
(1 row)
Asked By: Nathan Stocks

||

Answers:

import datetime

some_date = datetime.date.today()
three_months = datetime.timedelta(3*365/12)
print (some_date + three_months).isoformat()
# => '2012-06-01'

Then “normalize” every new year to the original date’s day (unless Feb 29)

Answered By: mVChr

If you’re looking for exact or “more precise” dates, you’re probably better off checking out dateutil.

Quick example:

>>> from dateutil.relativedelta import relativedelta
>>> import datetime
>>> TODAY = datetime.date.today()
>>> TODAY
datetime.date(2012, 3, 6)

Now add 3 months to TODAY, observe that it matches the day exactly (Note that relativedelta(months=3) and relativedelta(month=3) have different behaviors. Make sure to use months for these examples!).

>>> three_mon_rel = relativedelta(months=3)
>>> TODAY + three_mon_rel
datetime.date(2012, 6, 6)

And it stays consistent throughout the course of a year. Literally every three months, on the day (had to keep adding because for some reason multiplying a relativedelta and adding it to a datetime.date object throws a TypeError):

>>> TODAY + three_mon_rel + three_mon_rel
datetime.date(2012, 9, 6)
>>> TODAY + three_mon_rel + three_mon_rel + three_mon_rel
datetime.date(2012, 12, 6)
>>> TODAY + three_mon_rel + three_mon_rel + three_mon_rel + three_mon_rel
datetime.date(2013, 3, 6)

Whereas the mVChr‘s suggested solution, while definitely “good enough”, drifts slightly over time:

>>> three_mon_timedelta = datetime.timedelta(days=3 * 365/12)
>>> TODAY + three_mon_timedelta
datetime.date(2012, 6, 5)

And over the course of a year, the day of month keeps sliding:

>>> TODAY + three_mon_timedelta * 2
datetime.date(2012, 9, 4)
>>> TODAY + three_mon_timedelta * 3
datetime.date(2012, 12, 4)
>>> TODAY + three_mon_timedelta * 4
datetime.date(2013, 3, 5)
Answered By: jathanism

Using Python standard libraries, i.e. without dateutil or others, and solving the ‘February 31st’ problem:

import datetime
import calendar

def add_months(date, months):
    months_count = date.month + months

    # Calculate the year
    year = date.year + int(months_count / 12)

    # Calculate the month
    month = (months_count % 12)
    if month == 0:
        month = 12

    # Calculate the day
    day = date.day
    last_day_of_month = calendar.monthrange(year, month)[1]
    if day > last_day_of_month:
        day = last_day_of_month

    new_date = datetime.date(year, month, day)
    return new_date

Testing:

>>>date = datetime.date(2018, 11, 30)

>>>print(date, add_months(date, 3))
(datetime.date(2018, 11, 30), datetime.date(2019, 2, 28))

>>>print(date, add_months(date, 14))
(datetime.date(2018, 12, 31), datetime.date(2020, 2, 29))
Answered By: David Ragazzi

I don’t have enough reputation to comment. So, I am just going to write up a solution that fixes a bug in the solution that David Ragazzi posted.

The error occurs when you add enough months to get to a December date. The year is 1 too many.

For example, add_months(date.fromisoformat('2020-01-29'), 11) returns 2021 instead of 2020. I fixed the issue by changing the line starting with year =.

    import datetime
    import calendar
    def add_months(dateInput, months):
        months_count = dateInput.month + months

        # Calculate the year
        year = dateInput.year + int((months_count-1) / 12)

        # Calculate the month
        month = (months_count % 12)
        if month == 0:
            month = 12

        # Calculate the day
        day = dateInput.day
        last_day_of_month = calendar.monthrange(year, month)[1]
        if day > last_day_of_month:
            day = last_day_of_month

        new_date = date(year, month, day)
        return new_date
Answered By: mikedugas77

The answers from @david-ragazzi and @mikedugas77 work well with positive values for months, but not if months <= -date.month. Here’s a modification that should work even for negative month offsets:

import calendar
import datetime

def add_months(date, months):
    months_count = date.year * 12 + date.month + months - 1

    # Calculate the year
    year = months_count // 12

    # Calculate the month
    month = months_count % 12 + 1

    # Calculate the day
    day = date.day
    last_day_of_month = calendar.monthrange(year, month)[1]
    if day > last_day_of_month:
        day = last_day_of_month

    new_date = datetime.date(year, month, day)
    return new_date

With some tests using today’s date:

date = datetime.date(2020, 7, 30)
assert add_months(date, -12) == datetime.date(2019, 7, 30)
assert add_months(date, -11) == datetime.date(2019, 8, 30)
assert add_months(date, -10) == datetime.date(2019, 9, 30)
assert add_months(date, -9) == datetime.date(2019, 10, 30)
assert add_months(date, -8) == datetime.date(2019, 11, 30)
assert add_months(date, -7) == datetime.date(2019, 12, 30)
assert add_months(date, -6) == datetime.date(2020, 1, 30)
assert add_months(date, -5) == datetime.date(2020, 2, 29)
assert add_months(date, -4) == datetime.date(2020, 3, 30)
assert add_months(date, -3) == datetime.date(2020, 4, 30)
assert add_months(date, -2) == datetime.date(2020, 5, 30)
assert add_months(date, -1) == datetime.date(2020, 6, 30)
assert add_months(date, 0) == datetime.date(2020, 7, 30)
assert add_months(date, 1) == datetime.date(2020, 8, 30)
assert add_months(date, 2) == datetime.date(2020, 9, 30)
assert add_months(date, 3) == datetime.date(2020, 10, 30)
assert add_months(date, 4) == datetime.date(2020, 11, 30)
assert add_months(date, 5) == datetime.date(2020, 12, 30)
assert add_months(date, 6) == datetime.date(2021, 1, 30)
assert add_months(date, 7) == datetime.date(2021, 2, 28)
assert add_months(date, 8) == datetime.date(2021, 3, 30)
assert add_months(date, 9) == datetime.date(2021, 4, 30)
assert add_months(date, 10) == datetime.date(2021, 5, 30)
assert add_months(date, 11) == datetime.date(2021, 6, 30)
assert add_months(date, 12) == datetime.date(2021, 7, 30)
Answered By: AlexM

I wrote this function that may be able to help you:

import datetime
import calendar

def add_months(date, months):

    # Determine the month and year of the new date
    month, year = (date + relativedelta(months=months)).month, (date + relativedelta(months=months)).year

    # Determine the day of the new date
    # If the day of the current date is at the end of the month,
    # the day of the new date should also be at the end of the month
    if(date.day == calendar.monthrange(date.year, date.month)[1]):
        day = calendar.monthrange(year, month)[1]
    else:
        day = date.day

    new_date = datetime.datetime(year, month, day)
    return new_date

It supports adding negative months as well (i.e. subtracting months).

Here is some sample usage that illustrates how to obtain 2021 and 2022 dates as per your specs:

import datetime

a = datetime.datetime(2020, 1, 1)

# Initialse a list to hold the dates
dates = [0]*8

# Obtain the dates
for i in range(0, len(dates)):
    dates[i] = add_months(a, 3*i)

dates
Answered By: slater2809
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.