Given two datetimes (`start_date` and `end_date`), I’d like to generate a list of other datetimes between these two dates, the new datetimes being separated by a variable interval. e.g. every 4 days between 2011-10-10 and 2011-12-12 or every 8 hours between now and tomorrow 19p.m.

Maybe something roughly equivalent to the Dateperiod PHP class.

What would be the most efficient way to accomplish this in Python?

``````from datetime import date, datetime, timedelta

def perdelta(start, end, delta):
curr = start
while curr < end:
yield curr
curr += delta

>>> for result in perdelta(date(2011, 10, 10), date(2011, 12, 12), timedelta(days=4)):
...     print result
...
2011-10-10
2011-10-14
2011-10-18
2011-10-22
2011-10-26
2011-10-30
2011-11-03
2011-11-07
2011-11-11
2011-11-15
2011-11-19
2011-11-23
2011-11-27
2011-12-01
2011-12-05
2011-12-09
``````

Works for both dates and datetime objects. Your second example:

``````>>> for result in perdelta(datetime.now(),
...         datetime.now().replace(hour=19) + timedelta(days=1),
...         timedelta(hours=8)):
...     print result
...
2012-05-21 17:25:47.668022
2012-05-22 01:25:47.668022
2012-05-22 09:25:47.668022
2012-05-22 17:25:47.668022
``````

Try this:

``````from datetime import datetime
from dateutil.relativedelta import relativedelta

def date_range(start_date, end_date, increment, period):
result = []
nxt = start_date
delta = relativedelta(**{period:increment})
while nxt <= end_date:
result.append(nxt)
nxt += delta
return result
``````

The example in the question, “every 8 hours between now and tomorrow 19:00” would be written like this:

``````start_date = datetime.now()
end_date = start_date + relativedelta(days=1)
end_date = end_date.replace(hour=19, minute=0, second=0, microsecond=0)
date_range(start_date, end_date, 8, 'hours')
``````

Notice that the valid values for `period` are those defined for the `relativedelta` relative information, namely: `'years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'microseconds'`.

My solution returns a list, as required in the question. If you don’t need all the elements at once you can use generators, as in @MartijnPieters answer.

I really liked both answers by @Martijn Pieters and @Óscar López.
Let me suggest my combined solution between those two answers.

``````from datetime import date, datetime, timedelta

def datetime_range(start, end, delta):
current = start
if not isinstance(delta, timedelta):
delta = timedelta(**delta)
while current < end:
yield current
current += delta

start = datetime(2015,1,1)
end = datetime(2015,1,31)

#this unlocks the following interface:
for dt in datetime_range(start, end, {'days': 2, 'hours':12}):
print dt
print dt

2015-01-01 00:00:00
2015-01-03 12:00:00
2015-01-06 00:00:00
2015-01-08 12:00:00
2015-01-11 00:00:00
2015-01-13 12:00:00
2015-01-16 00:00:00
2015-01-18 12:00:00
2015-01-21 00:00:00
2015-01-23 12:00:00
2015-01-26 00:00:00
2015-01-28 12:00:00
``````

The solutions suggested here work well for intervals of days, hours, etc. using `timedelta`, or anything that `dateutil.relativedelta` supports if you want to rely on third-party libraries. But I wanted to share my solution for the specific case of monthly intervals in the format yyyymm, asked here (but marked as a duplicate of this question).

``````def iterate_months(start_ym, end_ym):
for ym in range(int(start_ym), int(end_ym) + 1):
if ym % 100 > 12 or ym % 100 == 0:
continue
yield str(ym)

list(iterate_months('201710', '201803'))
``````

Output:

``````['201710', '201711', '201712', '201801', '201802', '201803']
``````

This solution is fairly specific to this particular need for yyyymm formatting (though it comes up frequently in my world, at least) and may not be the most efficient answer with the large number of `continue`s, but has the advantages of being concise, easy to understand, and doesn’t involve a number of libraries or date-conversion code.

All the solutions given here so far are specific to cases where start < stop, but you can easily adapt them to handle cases where stop < start using the operator module as the following code, adapted from @MartijnPieters’s answer, illustrates.

``````import datetime
import operator

def time_range(start: datetime.datetime, stop, step: datetime.timedelta):
"Imitate range function for datetimes instead of ints."
sec = step.total_seconds()
if sec == 0:
raise ValueError("step must not be 0 seconds")
if sec < 0:
compare = operator.gt
else:
compare = operator.lt
x = start
while compare(x, stop):
yield x
x += step  # immutable
``````