mortysporty
mortysporty

Reputation: 2889

Pandas create date range at certain dates

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

Answers (3)

serkef
serkef

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

user3311658
user3311658

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

jpp
jpp

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

Related Questions