Iterating through a range of dates in Python
Solution 1
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.
Update after discussion with John Machin:
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, end_date):
for n in range(int((end_date - start_date).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()
.
Solution 2
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
Solution 3
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.
Solution 4
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)
Solution 5
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
ShawnMilo
Start-up veteran who has worked in very large and very small companies. Expert back-end developer with experience managing (hiring, mentoring, training, conducting one-on-ones, etc.). As comfortable talking to or writing for upper management as other developers.
Updated on July 25, 2022Comments
-
ShawnMilo almost 2 years
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())
Notes
- I'm not actually using this to print. That's just for demo purposes.
- The
start_date
andend_date
variables aredatetime.date
objects because I don't need the timestamps. (They're going to be used to generate a report).
Sample Output
For a start date of
2009-05-30
and an end date of2009-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
-
ShawnMilo almost 15 yearsThanks, good point. That if statement was left over from a previous version, before I was subtracting start date from end date.
-
ShawnMilo almost 15 yearsThanks Ber, that's awesome. It works perfectly and looks much better than my old mess.
-
John Machin almost 15 years-1 ... having a preliminary calculation of day_count and using range is not awesome when a simple while loop will suffice.
-
Ber almost 15 years@John Machin: Okay. I do however prever an iteration over while loops with explicit incrementation of some counter or value. The interation pattern is more pythonic (at least in my personal view) and also more general, as it allows to express an iteration while hiding the details of how that iteration is done.
-
Ber almost 15 years@John Machin: I have updated my answer to show what I mean. Hope you'll like it.
-
John Machin almost 15 years@Ber: I don't like it at all; it's DOUBLY bad. You ALREADY had an iteration! By wrapping the complained-about constructs in a generator, you have added even more execution overhead plus diverted the user's attention to somewhere else to read your 3-liner's code and/or docs. -2
-
Ber almost 15 years@John Machin: I disagree. The point is not about reducing the number of lines to the absolute minimum. After all, we're not talking Perl here. Also, my code does only one iteration (that's how the generator works, but I guess you know that). *** My point is about abstracting concepts for re-use and self explanatory code. I maintain that this is far more worthwhile than have the shortest code possible.
-
itsadok about 14 yearsYou can make this code slightly shorter if you use the datetime object's strftime method.
-
Mark Ransom about 12 yearsIf you're going for terseness you can use a generator expression:
(start_date + datetime.timedelta(n) for n in range((end_date - start_date).days))
-
Profpatsch over 10 years@JohnMachin It’s the most pythonic solution, really. Iterating over mutating values is a thing of the past and very implicit. Explicit over implicit, rule 2, remember?
-
sage over 9 yearsI like this, but I had to refresh my memory on the relevant imports:
from datetime import timedelta, date ; start_date = date(2014,1,1) ; day_count = 15
- after that, it executes for me. You might consider putting the complete example for those rusty or new to Python. -
MichaelJones about 9 yearsIt is worth noting that this doesn't seamlessly translate to time zone aware datetime objects as the start & end datetimes might be in different daylight savings zones which makes it much more fiddly. As far as I can tell, you have to reconstruct the datetime at each point to allow the time zone and pytz.localize to figure out the daylight savings.
-
Ninjakannon almost 9 yearsNote that the final date in the for loop here is inclusive of
until
whereas the final date of thedaterange
method in Ber's answer is exclusive ofend_date
. -
Zach Saucier over 8 years"much like numpy" - Pandas is built on numpy :P
-
Firma Agnes almost 7 yearsVery clear and short, but doesn't work well if you want to use continue
-
Diego Vinícius almost 7 yearsjust a little improviment:
for n in range(int(start_date.day), int ((end_date - start_date).days), step):
this way now is possible pass steps (in case want jump steps in interation) -
pyano over 6 yearsSuper lean construction ! The last line works for me with
dates = np.arange(d0, d1, dt).astype(datetime.datetime)
-
TitanFighter over 6 yearsPlease add round brackets, like
print((datetime.date.today() + datetime.timedelta(i)).isoformat())
-
user1767754 over 6 years@TitanFighter please feel free to do edits, I'll accept them.
-
TitanFighter over 6 yearsI tried. Editing requires minimum 6 chars, but in this case it is necessary to add just 2 chars, "(" and ")"
-
Vamshi G over 6 years
for n in range(int ((end_date - start_date).days+1)):
For the end_date to be included -
F.Raab over 5 years+1 for posting a generic one-liner solution which allows any timedelta, instead of a fixed rounded step such as hourly/minutely/… .
-
mr.zog about 5 years
print((datetime.date.today() + datetime.timedelta(i)))
without the .isoformat() gives exactly the same output. I need my script to print YYMMDD. Anyone know how to do that? -
user1767754 about 5 yearsJust do this in the for loop instead of the print statement
d = datetime.date.today() + datetime.timedelta(i); d.strftime("%Y%m%d")
-
qwr over 4 yearsmore modern docs dateutil.readthedocs.io/en/stable/rrule.html
-
double-beep almost 4 yearsWelcome to Stack Overflow! While this code may solve the question, including an explanation of how and why this solves the problem, especially on questions with too many good answers, would really help to improve the quality of your post, and probably result in more upvotes. Remember that you are answering the question for readers in the future, not just the person asking now. Please edit your answer to add explanations and give an indication of what limitations and assumptions apply. From Review
-
Akira over 2 yearsThis is elegant!