Python – How to add “3 months” to a datetime.date object in python

postgresqlpythonsql

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)

Best Solution

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)