Reputation: 2889
I want to create a list (or array or whathever) of a given number of dates at monthly intervals.
Basically what I want is this
>>>some_function(start_date=date(2005, 5, 14), periods=4, freq='M')
['2005-05-14', '2005-06-14', '2005-07-14', '2005-08-14']
and if the day of the startmonth is close to end of the month I want this
>>>some_function(start_date=date(2007, 12, 31), periods=4, freq='M')
['2007-12-31', '2008-01-31', '2008-02-29', '2008-03-31']
I am aware of the pandas date_range function, however it produces this
pd.date_range(date(2005, 5, 14), periods=4, freq='M')
Out[1]: DatetimeIndex(['2005-05-31', '2005-06-30', '2005-07-31', '2005-08-31'],
dtype='datetime64[ns]', freq='M')
i.e. it sets the month end as the day. Which is not what I want.
Obviously, this could be produced iterating over the number of periods, but this creates a hassle when the day of the startmonth is close to the last day of the month.
Does anybody know of a function producing this or is the method outlined above the only way?
Upvotes: 7
Views: 7101
Reputation: 319
I crafted the following:
from datetime import datetime, date
from datetime import timedelta
def next_month_generator(d):
while True:
if d.month == 12:
# on Dec, need to change year
d = d.replace(year=d.year+1, month=1)
else:
try:
# get next month, same date
d = d.replace(month=d.month+1)
except ValueError:
# get next month, last months date
d = date(year=d.year, month=d.month+2, day=1) - timedelta(days=1)
yield d
start_date=date(2017, 1, 31)
nm = next_month_generator(start_date)
for _ in range(13):
print(nm.next())
>> 2017-02-28
>> 2017-03-28
>> 2017-04-28
>> 2017-05-28
>> 2017-06-28
>> 2017-07-28
>> 2017-08-28
>> 2017-09-28
>> 2017-10-28
>> 2017-11-28
>> 2017-12-28
>> 2018-01-28
>> 2018-02-28
If Python 3, use:
for _ in range(13):
print(next(nm))
Upvotes: 6
Reputation: 346
I think the behavior you're after is, you want a date range where all the dates are on the same day of the month as your start date, except use the last day of the month for months which have fewer days in the month.
You can do that by using pandas.DateOffset(months=1, day=day_of_month)
as the freq
argument, where day_of_month
is the day of month you want each date to be on. This will automatically use the last day of the month for months whose last day is less than day_of_month
.
In [68]: pandas.date_range('2005-05-14', periods=4, freq=pandas.DateOffset(months=1, day=14))
Out[68]: DatetimeIndex(['2005-05-14', '2005-06-14', '2005-07-14', '2005-08-14'], dtype='datetime64[ns]', freq='<DateOffset: day=14, months=1>')
In [69]: pandas.date_range('2007-12-31', periods=4, freq=pandas.DateOffset(months=1, day=31))
Out[69]: DatetimeIndex(['2007-12-31', '2008-01-31', '2008-02-29', '2008-03-31'], dtype='datetime64[ns]', freq='<DateOffset: day=31, months=1>')
Upvotes: 10
Reputation: 164843
This should work. add_months
function is via @DaveWebb in How to increment datetime by custom months in python without using library.
import datetime
import calendar
start_date = '2018-02-02'
def add_months(sourcedate, months):
month = sourcedate.month - 1 + months
year = sourcedate.year + month // 12
month = month % 12 + 1
day = min(sourcedate.day, calendar.monthrange(year, month)[1])
return datetime.date(year, month, day)
def range_of_months(sourcedate, months):
return [add_months(sourcedate, m) for m in range(months+1)]
start = datetime.date.today()
range_of_months(start, 5)
# [datetime.date(2018, 2, 2),
# datetime.date(2018, 3, 2),
# datetime.date(2018, 4, 2),
# datetime.date(2018, 5, 2),
# datetime.date(2018, 6, 2),
# datetime.date(2018, 7, 2)]
Upvotes: 3