jbrown
jbrown

Reputation: 7996

How to create a date range until some date inclusive with python dateutils?

I'm trying to get a list of months in the given range. Specifically, given the range 2015-12-31 - 2016-01-01, I want to get the list 2015-12 and 2016-01.

I'm using dateutil's rrule, but the until parameter isn't really helping since it seems to be looking for complete months.

E.g.:

from dateutil.relativedelta import relativedelta
from dateutil.rrule import rrule, MONTHLY

list(rrule(MONTHLY, dtstart=datetime(2015, 12, 31), until=datetime(2016,01,01)))

This only returns [datetime.datetime(2015, 12, 31, 0, 0)], not [datetime.datetime(2015, 12, 31, 0, 0), datetime.datetime(2016, 01, 01, 0, 0)]

Likewise, I want the same output for any start day in December, and any end day in January. That's to say, I only care about the fact that the months are 2015-12 and 2016-01. The day part is not important for me, but I only want a single datetime per month (a maximum of 2 for these inputs, with any value for the day component).

Is there a simple solution, or will I have to increment DAILY and build my own set of months?

Upvotes: 3

Views: 2915

Answers (4)

AChampion
AChampion

Reputation: 30288

Just use the difference in months as a count:

start = datetime(2015, 12, 31)
end = datetime(2016,01,01)
diff_month = lambda e, s: (e.year - s.year)*12 + e.month - s.month
list(rrule(MONTHLY, count=diff_month(end,start)+1, dtstart=start))

Upvotes: 0

Andy
Andy

Reputation: 50640

The reason your list(rrule(MONTHLY, dtstart=datetime(2015, 12, 31), until=datetime(2016,01,01))) isn't returning January is because there isn't a month (based on the MONTHLY parameter) between December 31 and January 1.

Since you don't need the day component for this, I'm going to perform a little date manipulation:

def date_list(start, end):
    start = datetime.date(start.year, start.month, 1)
    end = datetime.date(end.year, end.month, 1)
    dates = [dt for dt in rrule(MONTHLY, dtstart=start, until=end)]
    return dates

What this does is accept any start and end date and then resets both of those to be the first of the month. This ensures that a full month exists. It will return a list of dates and each date will be the first of the month.

For example, with your input

>>> print date_list(datetime.date(2015,12,28), datetime.date(2016,1,1))
[datetime.datetime(2015, 12, 1, 0, 0), datetime.datetime(2016, 1, 1, 0, 0)]

With the same month:

>>> print date_list(datetime.date(2015,12,28), datetime.date(2015,12,31))
[datetime.datetime(2015, 12, 1, 0, 0)]

With an invalid range (end before start):

>>> print date_list(datetime.date(2015,12,28), datetime.date(2015,11,30))
[]

With a longer range:

>>> print date_list(datetime.date(2015,12,28), datetime.date(2016,8,3))
[datetime.datetime(2015, 12, 1, 0, 0), 
datetime.datetime(2016, 1, 1, 0, 0), 
datetime.datetime(2016, 2, 1, 0, 0), 
datetime.datetime(2016, 3, 1, 0, 0), 
datetime.datetime(2016, 4, 1, 0, 0), 
datetime.datetime(2016, 5, 1, 0, 0), 
datetime.datetime(2016, 6, 1, 0, 0), 
datetime.datetime(2016, 7, 1, 0, 0), 
datetime.datetime(2016, 8, 1, 0, 0)]

Upvotes: 3

gtlambert
gtlambert

Reputation: 11971

I think you will have to perform some sort of iteration. For example, using deltas, the following will do the trick:

import datetime

start = datetime.datetime(2015, 12, 31)
stop = datetime.datetime(2016, 1, 8)

delta = stop - start

answer = [start+datetime.timedelta(days=idx) for idx in range(delta.days+1)]
print(answer)

Output

[datetime.datetime(2015, 12, 31, 0, 0),
 datetime.datetime(2016, 1, 1, 0, 0),
 datetime.datetime(2016, 1, 2, 0, 0),
 datetime.datetime(2016, 1, 3, 0, 0),
 datetime.datetime(2016, 1, 4, 0, 0),
 datetime.datetime(2016, 1, 5, 0, 0),
 datetime.datetime(2016, 1, 6, 0, 0),
 datetime.datetime(2016, 1, 7, 0, 0),
 datetime.datetime(2016, 1, 8, 0, 0)]

Upvotes: 0

jbrown
jbrown

Reputation: 7996

I've done it using sets:

months = set()

for dt in rrule(DAILY, dtstart=start_date, until=end_date):
    months.add(dt.strftime('%Y-%m-.*'))

Upvotes: 1

Related Questions