Adding or subtracting a month to a Python datetime.date
or datetime.datetime
is a little bit of a pain. Here is the code I use for that. These functions return the same datetime type as given. They preserve time of day data (if that is at all important to you).
See also:
- Recipe 476197: First / Last Day of the Month.
- monthdelta module
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | def add_one_month(t):
"""Return a `datetime.date` or `datetime.datetime` (as given) that is
one month earlier.
Note that the resultant day of the month might change if the following
month has fewer days:
>>> add_one_month(datetime.date(2010, 1, 31))
datetime.date(2010, 2, 28)
"""
import datetime
one_day = datetime.timedelta(days=1)
one_month_later = t + one_day
while one_month_later.month == t.month: # advance to start of next month
one_month_later += one_day
target_month = one_month_later.month
while one_month_later.day < t.day: # advance to appropriate day
one_month_later += one_day
if one_month_later.month != target_month: # gone too far
one_month_later -= one_day
break
return one_month_later
def subtract_one_month(t):
"""Return a `datetime.date` or `datetime.datetime` (as given) that is
one month later.
Note that the resultant day of the month might change if the following
month has fewer days:
>>> subtract_one_month(datetime.date(2010, 3, 31))
datetime.date(2010, 2, 28)
"""
import datetime
one_day = datetime.timedelta(days=1)
one_month_earlier = t - one_day
while one_month_earlier.month == t.month or one_month_earlier.day > t.day:
one_month_earlier -= one_day
return one_month_earlier
|
There is a much simpler way. For finding the previous month you go to the first, then subtract one day:
For finding the next month's first you advance to the next month. By adding 32 days from the first of a month you will always reach the next month.
@andreas: Yup, thanks. For some of my use cases, preserving the day of month was important.
What about this as an alternative; the range of months is limited by date or datetime, both are supported, and all data (day, and hour/minute/second if it's a datetime) are maintained. The code is also much shorter, and negative numbers are supported for subtracting months.
Note: months may be positive, or negative, but must be an integer.
def addmonths(date,months): targetmonth=months+date.month try: date.replace(year=date.year+int(targetmonth/12),month=(targetmonth%12)) except: # There is an exception if the day of the month we're in does not exist in the target month # Go to the FIRST of the month AFTER, then go back one day. date.replace(year=date.year+int((targetmonth+1)/12),month=((targetmonth+1)%12),day=1) date+=datetime.timedelta(days=-1)
Lets try that again:
I think the code of jort.bloom is on the right track, but I believe errors will also always be generated when the target month is a multiple of 12, which returns a modulo remainder of 0. An error in the month will mess up the result. For example, addmonths(12/5/2011, 0) i.e., December 5, 2011 minus no months, will return 12/31/2011, not 12/5/2011, because an error will occur when month=(targetmonth%12) tries to set month=0, not month=12.
Also, when the input date is the last day of the month, often I favor using the end of the month of the offset date. So, while 7/31/2012 - 1 month = 6/30/2012 is good, 6/30/2012 - 1 month = 5/30/2012 is bad (I want 5/31/2012). A variable indicating that I want to favor the end of the month should be included so that I can specify if I want an end of the month date to offset to the closest matching day number in a month with more days (i.e, 30 in a 30 day month = 30 in a 31 day month) or to the closest end of the month day in a month with more days (30 in a 30 day month = 31 in a 31 day month).
Here is the way I would revise the code to deal with these issues:
There was a missing else block in the code I posted. Here it is corrected.
Oops. I caught a variable name spelling error. I wish I could edit my previous comments, but since I can't, here is the corrected code again:
My own: