Reputation: 6195
I have the following code to do this, but how can I do it better? Right now I think it's better than nested loops, but it starts to get Perl-one-linerish when you have a generator in a list comprehension.
day_count = (end_date - start_date).days + 1
for single_date in [d for d in (start_date + timedelta(n) for n in range(day_count)) if d <= end_date]:
print strftime("%Y-%m-%d", single_date.timetuple())
start_date
and end_date
variables are datetime.date
objects because I don't need the timestamps. (They're going to be used to generate a report).For a start date of 2009-05-30
and an end date of 2009-06-09
:
2009-05-30
2009-05-31
2009-06-01
2009-06-02
2009-06-03
2009-06-04
2009-06-05
2009-06-06
2009-06-07
2009-06-08
2009-06-09
Upvotes: 567
Views: 595674
Reputation: 41813
Why are there two nested iterations? For me it produces the same list of data with only one iteration:
for single_date in (start_date + timedelta(n) for n in range(day_count)):
print ...
And no list gets stored, only one generator is iterated over. Also the "if" in the generator seems to be unnecessary.
After all, a linear sequence should only require one iterator, not two.
Maybe the most elegant solution is using a generator function to completely hide/abstract the iteration over the range of dates:
from datetime import date, timedelta
def daterange(start_date: date, end_date: date):
days = int((end_date - start_date).days)
for n in range(days):
yield start_date + timedelta(n)
start_date = date(2013, 1, 1)
end_date = date(2015, 6, 2)
for single_date in daterange(start_date, end_date):
print(single_date.strftime("%Y-%m-%d"))
NB: For consistency with the built-in range()
function this iteration stops before reaching the end_date
. So for inclusive iteration use the next day, as you would with range()
.
Upvotes: 816
Reputation: 112
Answer for Python API Call Loop
using datetime and timedelta from the datetime module
from datetime import datetime, timedelta
import requests
headers = {"accept": "application/json", "x-api-key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
delta = timedelta(days=30)
start = datetime(year=2022, month=1, day=1)
num_ranges = 10
for _ in range(num_ranges):
start_str = start.strftime("%Y-%m-%d")
end = start + delta
end_str = end.strftime("%Y-%m-%d")
url = f"https://apiendpoint.com/v2/dapps/xxxx/history/uaw?dateFrom={start_str}&dateTo={end_str}"
response = requests.request("GET", url, headers=headers)
# TODO: append response json to result
start = end
Upvotes: 0
Reputation: 147
Here is an alternative solution based on leftjoin solution.
For pendulum below 3.0.0
def test_iteration():
start = pendulum.from_format('2018-01', 'YYYY-MM')
end = pendulum.from_format('2020-01', 'YYYY-MM')
interval = pendulum.period(start, end)
for dt in interval.range('months'):
print(dt.format('YYYY-MM'))
And for pendulum 3.0.0 and above (they have renamed period to interval) https://github.com/sdispater/pendulum/pull/676
def test_iteration():
start = pendulum.from_format('2018-01', 'YYYY-MM')
end = pendulum.from_format('2020-01', 'YYYY-MM')
interval = pendulum.interval(start, end)
for dt in interval.range('months'):
print(dt.format('YYYY-MM'))
You can change de range values from the list on the documentation here : https://pendulum.eustace.io/docs/#range
Supported units for range() are: years, months, weeks, days, hours, minutes, seconds and microseconds
Upvotes: 1
Reputation: 1129
in polars it can be done as well, if eager
is set to True
start = datetime.date(year=2009, month=5, day=30)
end = datetime.date(year=2009, month=6, day=9)
dates = pl.date_range(start, end, eager=True)
for date in dates:
print(date)
Upvotes: 1
Reputation: 139
If you are going to use dynamic timedelta
then you can use:
1. With while loop
def datetime_range(start: datetime, end: datetime, delta: timedelta) -> Generator[datetime, None, None]:
while start <= end:
yield start
start += delta
2. With for loop
from datetime import datetime, timedelta
from typing import Generator
def datetime_range(start: datetime, end: datetime, delta: timedelta) -> Generator[datetime, None, None]:
delta_units = int((end - start) / delta)
for _ in range(delta_units + 1):
yield start
start += delta
3. If you are using async/await
async def datetime_range(start: datetime, end: datetime, delta: timedelta) -> AsyncGenerator[datetime, None]:
delta_units = int((end - start) / delta)
for _ in range(delta_units + 1):
yield start
start += delta
4. List comprehension
def datetime_range(start: datetime, end: datetime, delta: timedelta) -> List[datetime]:
delta_units = int((end - start) / delta)
return [start + (delta * index) for index in range(delta_units + 1)]
Then 1 and 2 solutions simply can be used like this
start = datetime(2020, 10, 10, 10, 00)
end = datetime(2022, 10, 10, 18, 00)
delta = timedelta(minutes=30)
result = [time_part for time_part in datetime_range(start, end, delta)]
# or
for time_part in datetime_range(start, end, delta):
print(time_part)
3-third solution can be used like this in async context. Because it retruns an async generator object, which can be used only in async context
start = datetime(2020, 10, 10, 10, 00)
end = datetime(2022, 10, 10, 18, 00)
delta = timedelta(minutes=30)
result = [time_part async for time_part in datetime_range(start, end, delta)]
async for time_part in datetime_range(start, end, delta):
print(time_part)
The benefit of the solutions about is that all of them are using dynamic timedelta
. This can be very usefull in cases when you do not know which time delta you will have.
Upvotes: 3
Reputation: 4917
This might be more clear:
from datetime import date, timedelta
start_date = date(2019, 1, 1)
end_date = date(2020, 1, 1)
delta = timedelta(days=1)
while start_date <= end_date:
print(start_date.strftime("%Y-%m-%d"))
start_date += delta
Upvotes: 377
Reputation: 3373
You can use Arrow
:
This is example from the docs, iterating over hours:
from arrow import Arrow
>>> start = datetime(2013, 5, 5, 12, 30)
>>> end = datetime(2013, 5, 5, 17, 15)
>>> for r in Arrow.range('hour', start, end):
... print repr(r)
...
<Arrow [2013-05-05T12:30:00+00:00]>
<Arrow [2013-05-05T13:30:00+00:00]>
<Arrow [2013-05-05T14:30:00+00:00]>
<Arrow [2013-05-05T15:30:00+00:00]>
<Arrow [2013-05-05T16:30:00+00:00]>
To iterate over days, you can use like this:
>>> start = Arrow(2013, 5, 5)
>>> end = Arrow(2013, 5, 5)
>>> for r in Arrow.range('day', start, end):
... print repr(r)
(Didn't check if you can pass datetime.date
objects, but anyways Arrow
objects are easier in general)
Upvotes: 3
Reputation: 634
For those who are interested in Pythonic functional way:
from datetime import date, timedelta
from itertools import count, takewhile
for d in takewhile(lambda x: x<=date(2009,6,9), map(lambda x:date(2009,5,30)+timedelta(days=x), count())):
print(d)
Upvotes: 4
Reputation: 38325
Using pendulum.period:
import pendulum
start = pendulum.from_format('2020-05-01', 'YYYY-MM-DD', formatter='alternative')
end = pendulum.from_format('2020-05-02', 'YYYY-MM-DD', formatter='alternative')
period = pendulum.period(start, end)
for dt in period:
print(dt.to_date_string())
Upvotes: 4
Reputation: 879
from datetime import date,timedelta
delta = timedelta(days=1)
start = date(2020,1,1)
end=date(2020,9,1)
loop_date = start
while loop_date<=end:
print(loop_date)
loop_date+=delta
Upvotes: 2
Reputation: 41
import datetime
from dateutil.rrule import DAILY,rrule
date=datetime.datetime(2019,1,10)
date1=datetime.datetime(2019,2,2)
for i in rrule(DAILY , dtstart=date,until=date1):
print(i.strftime('%Y%b%d'),sep='\n')
OUTPUT:
2019Jan10
2019Jan11
2019Jan12
2019Jan13
2019Jan14
2019Jan15
2019Jan16
2019Jan17
2019Jan18
2019Jan19
2019Jan20
2019Jan21
2019Jan22
2019Jan23
2019Jan24
2019Jan25
2019Jan26
2019Jan27
2019Jan28
2019Jan29
2019Jan30
2019Jan31
2019Feb01
2019Feb02
Upvotes: 4
Reputation: 111
For completeness, Pandas also has a period_range
function for timestamps that are out of bounds:
import pandas as pd
pd.period_range(start='1/1/1626', end='1/08/1627', freq='D')
Upvotes: 10
Reputation: 26672
Slightly different approach to reversible steps by storing range
args in a tuple.
def date_range(start, stop, step=1, inclusive=False):
day_count = (stop - start).days
if inclusive:
day_count += 1
if step > 0:
range_args = (0, day_count, step)
elif step < 0:
range_args = (day_count - 1, -1, step)
else:
raise ValueError("date_range(): step arg must be non-zero")
for i in range(*range_args):
yield start + timedelta(days=i)
Upvotes: 0
Reputation: 5658
> pip install DateTimeRange
from datetimerange import DateTimeRange
def dateRange(start, end, step):
rangeList = []
time_range = DateTimeRange(start, end)
for value in time_range.range(datetime.timedelta(days=step)):
rangeList.append(value.strftime('%m/%d/%Y'))
return rangeList
dateRange("2018-09-07", "2018-12-25", 7)
Out[92]:
['09/07/2018',
'09/14/2018',
'09/21/2018',
'09/28/2018',
'10/05/2018',
'10/12/2018',
'10/19/2018',
'10/26/2018',
'11/02/2018',
'11/09/2018',
'11/16/2018',
'11/23/2018',
'11/30/2018',
'12/07/2018',
'12/14/2018',
'12/21/2018']
Upvotes: 3
Reputation: 3093
You can generate a series of date between two dates using the pandas library simply and trustfully
import pandas as pd
print pd.date_range(start='1/1/2010', end='1/08/2018', freq='M')
You can change the frequency of generating dates by setting freq as D, M, Q, Y (daily, monthly, quarterly, yearly )
Upvotes: 6
Reputation: 25094
Show the last n days from today:
import datetime
for i in range(0, 100):
print((datetime.date.today() + datetime.timedelta(i)).isoformat())
Output:
2016-06-29
2016-06-30
2016-07-01
2016-07-02
2016-07-03
2016-07-04
Upvotes: 9
Reputation: 1546
I have a similar problem, but I need to iterate monthly instead of daily.
This is my solution
import calendar
from datetime import datetime, timedelta
def days_in_month(dt):
return calendar.monthrange(dt.year, dt.month)[1]
def monthly_range(dt_start, dt_end):
forward = dt_end >= dt_start
finish = False
dt = dt_start
while not finish:
yield dt.date()
if forward:
days = days_in_month(dt)
dt = dt + timedelta(days=days)
finish = dt > dt_end
else:
_tmp_dt = dt.replace(day=1) - timedelta(days=1)
dt = (_tmp_dt.replace(day=dt.day))
finish = dt < dt_end
Example #1
date_start = datetime(2016, 6, 1)
date_end = datetime(2017, 1, 1)
for p in monthly_range(date_start, date_end):
print(p)
Output
2016-06-01
2016-07-01
2016-08-01
2016-09-01
2016-10-01
2016-11-01
2016-12-01
2017-01-01
Example #2
date_start = datetime(2017, 1, 1)
date_end = datetime(2016, 6, 1)
for p in monthly_range(date_start, date_end):
print(p)
Output
2017-01-01
2016-12-01
2016-11-01
2016-10-01
2016-09-01
2016-08-01
2016-07-01
2016-06-01
Upvotes: 4
Reputation: 485
This is the most human-readable solution I can think of.
import datetime
def daterange(start, end, step=datetime.timedelta(1)):
curr = start
while curr < end:
yield curr
curr += step
Upvotes: 20
Reputation: 3426
Here's code for a general date range function, similar to Ber's answer, but more flexible:
def count_timedelta(delta, step, seconds_in_interval):
"""Helper function for iterate. Finds the number of intervals in the timedelta."""
return int(delta.total_seconds() / (seconds_in_interval * step))
def range_dt(start, end, step=1, interval='day'):
"""Iterate over datetimes or dates, similar to builtin range."""
intervals = functools.partial(count_timedelta, (end - start), step)
if interval == 'week':
for i in range(intervals(3600 * 24 * 7)):
yield start + datetime.timedelta(weeks=i) * step
elif interval == 'day':
for i in range(intervals(3600 * 24)):
yield start + datetime.timedelta(days=i) * step
elif interval == 'hour':
for i in range(intervals(3600)):
yield start + datetime.timedelta(hours=i) * step
elif interval == 'minute':
for i in range(intervals(60)):
yield start + datetime.timedelta(minutes=i) * step
elif interval == 'second':
for i in range(intervals(1)):
yield start + datetime.timedelta(seconds=i) * step
elif interval == 'millisecond':
for i in range(intervals(1 / 1000)):
yield start + datetime.timedelta(milliseconds=i) * step
elif interval == 'microsecond':
for i in range(intervals(1e-6)):
yield start + datetime.timedelta(microseconds=i) * step
else:
raise AttributeError("Interval must be 'week', 'day', 'hour' 'second', \
'microsecond' or 'millisecond'.")
Upvotes: 2
Reputation: 757
Numpy's arange
function can be applied to dates:
import numpy as np
from datetime import datetime, timedelta
d0 = datetime(2009, 1,1)
d1 = datetime(2010, 1,1)
dt = timedelta(days = 1)
dates = np.arange(d0, d1, dt).astype(datetime)
The use of astype
is to convert from numpy.datetime64
to an array of datetime.datetime
objects.
Upvotes: 15
Reputation: 2836
This function has some extra features:
error checking in case the end is older than the start
import datetime
from datetime import timedelta
DATE_FORMAT = '%Y/%m/%d'
def daterange(start, end):
def convert(date):
try:
date = datetime.datetime.strptime(date, DATE_FORMAT)
return date.date()
except TypeError:
return date
def get_date(n):
return datetime.datetime.strftime(convert(start) + timedelta(days=n), DATE_FORMAT)
days = (convert(end) - convert(start)).days
if days <= 0:
raise ValueError('The start date must be before the end date.')
for n in range(0, days):
yield get_date(n)
start = '2014/12/1'
end = '2014/12/31'
print list(daterange(start, end))
start_ = datetime.date.today()
end = '2015/12/1'
print list(daterange(start, end))
Upvotes: 2
Reputation: 22716
Pandas is great for time series in general, and has direct support for date ranges.
import pandas as pd
daterange = pd.date_range(start_date, end_date)
You can then loop over the daterange to print the date:
for single_date in daterange:
print (single_date.strftime("%Y-%m-%d"))
It also has lots of options to make life easier. For example if you only wanted weekdays, you would just swap in bdate_range. See http://pandas.pydata.org/pandas-docs/stable/timeseries.html#generating-ranges-of-timestamps
The power of Pandas is really its dataframes, which support vectorized operations (much like numpy) that make operations across large quantities of data very fast and easy.
EDIT: You could also completely skip the for loop and just print it directly, which is easier and more efficient:
print(daterange)
Upvotes: 132
Reputation: 161
Why not try:
import datetime as dt
start_date = dt.datetime(2012, 12,1)
end_date = dt.datetime(2012, 12,5)
total_days = (end_date - start_date).days + 1 #inclusive 5 days
for day_number in range(total_days):
current_date = (start_date + dt.timedelta(days = day_number)).date()
print current_date
Upvotes: 16
Reputation: 222852
Use the dateutil
library:
from datetime import date
from dateutil.rrule import rrule, DAILY
a = date(2009, 5, 30)
b = date(2009, 6, 9)
for dt in rrule(DAILY, dtstart=a, until=b):
print dt.strftime("%Y-%m-%d")
This python library has many more advanced features, some very useful, like relative delta
s—and is implemented as a single file (module) that's easily included into a project.
Upvotes: 209
Reputation: 51
for i in range(16):
print datetime.date.today() + datetime.timedelta(days=i)
Upvotes: 5
Reputation: 2084
What about the following for doing a range incremented by days:
for d in map( lambda x: startDate+datetime.timedelta(days=x), xrange( (stopDate-startDate).days ) ):
# Do stuff here
For a generic version:
for d in map( lambda x: startTime+x*stepTime, xrange( (stopTime-startTime).total_seconds() / stepTime.total_seconds() ) ):
# Do stuff here
Note that .total_seconds() is only supported after python 2.7 If you are stuck with an earlier version you can write your own function:
def total_seconds( td ):
return float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
Upvotes: 2
Reputation: 82934
import datetime
def daterange(start, stop, step_days=1):
current = start
step = datetime.timedelta(step_days)
if step_days > 0:
while current < stop:
yield current
current += step
elif step_days < 0:
while current > stop:
yield current
current += step
else:
raise ValueError("daterange() step_days argument must not be zero")
if __name__ == "__main__":
from pprint import pprint as pp
lo = datetime.date(2008, 12, 27)
hi = datetime.date(2009, 1, 5)
pp(list(daterange(lo, hi)))
pp(list(daterange(hi, lo, -1)))
pp(list(daterange(lo, hi, 7)))
pp(list(daterange(hi, lo, -7)))
assert not list(daterange(lo, hi, -1))
assert not list(daterange(hi, lo))
assert not list(daterange(lo, hi, -7))
assert not list(daterange(hi, lo, 7))
Upvotes: 7
Reputation:
import datetime
def daterange(start, stop, step=datetime.timedelta(days=1), inclusive=False):
# inclusive=False to behave like range by default
if step.days > 0:
while start < stop:
yield start
start = start + step
# not +=! don't modify object passed in if it's mutable
# since this function is not restricted to
# only types from datetime module
elif step.days < 0:
while start > stop:
yield start
start = start + step
if inclusive and start == stop:
yield start
# ...
for date in daterange(start_date, end_date, inclusive=True):
print strftime("%Y-%m-%d", date.timetuple())
This function does more than you strictly require, by supporting negative step, etc. As long as you factor out your range logic, then you don't need the separate day_count
and most importantly the code becomes easier to read as you call the function from multiple places.
Upvotes: 19