Iterating through a range of dates in Python

417,290

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 deltas—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
Share:
417,290
ShawnMilo
Author by

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, 2022

Comments

  • ShawnMilo
    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 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).

    Sample Output

    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
    
  • ShawnMilo
    ShawnMilo almost 15 years
    Thanks, good point. That if statement was left over from a previous version, before I was subtracting start date from end date.
  • ShawnMilo
    ShawnMilo almost 15 years
    Thanks Ber, that's awesome. It works perfectly and looks much better than my old mess.
  • John Machin
    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
    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
    Ber almost 15 years
    @John Machin: I have updated my answer to show what I mean. Hope you'll like it.
  • John Machin
    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
    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
    itsadok about 14 years
    You can make this code slightly shorter if you use the datetime object's strftime method.
  • Mark Ransom
    Mark Ransom about 12 years
    If 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
    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
    sage over 9 years
    I 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
    MichaelJones about 9 years
    It 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
    Ninjakannon almost 9 years
    Note that the final date in the for loop here is inclusive of until whereas the final date of the daterange method in Ber's answer is exclusive of end_date.
  • Zach Saucier
    Zach Saucier over 8 years
    "much like numpy" - Pandas is built on numpy :P
  • Firma Agnes
    Firma Agnes almost 7 years
    Very clear and short, but doesn't work well if you want to use continue
  • Diego Vinícius
    Diego Vinícius almost 7 years
    just 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
    pyano over 6 years
    Super lean construction ! The last line works for me with dates = np.arange(d0, d1, dt).astype(datetime.datetime)
  • TitanFighter
    TitanFighter over 6 years
    Please add round brackets, like print((datetime.date.today() + datetime.timedelta(i)).isoformat())
  • user1767754
    user1767754 over 6 years
    @TitanFighter please feel free to do edits, I'll accept them.
  • TitanFighter
    TitanFighter over 6 years
    I tried. Editing requires minimum 6 chars, but in this case it is necessary to add just 2 chars, "(" and ")"
  • Vamshi G
    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
    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
    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
    user1767754 about 5 years
    Just do this in the for loop instead of the print statement d = datetime.date.today() + datetime.timedelta(i); d.strftime("%Y%m%d")
  • qwr
    qwr over 4 years
  • double-beep
    double-beep almost 4 years
    Welcome 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
    Akira over 2 years
    This is elegant!