I have the following code to do this, but how can I do better? Now, I think it's better than nested loops, but when the list interpreter includes generators, it starts to become Perl - linear rish.
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())
- I didn't actually use it to print. This is for demonstration purposes only.
- The start date and end date variables are datetime.date objects because I don't need a timestamp. (they will be used to generate reports).
For start date 2009-05-30 and end date 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
Why are there two nested iterations? For me, it takes only one iteration to generate the same data list:
for single_date in (start_date + timedelta(n) for n in range(day_count)): print ...
There is no list stored, only one generator iterated. Again, "if" in the generator does not seem necessary.
After all, a linear sequence requires only one iterator, not two.
Update after discussion with John Machin:
Perhaps the most elegant solution is to completely hide / abstract the iteration within the date range using the generator function:
from datetime import timedelta, date 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"))
Note: in order to be consistent with the built-in range() function, this iteration stops before reaching the end [date]. So for inclusive iterations, use the next day, just like range().
This may be clearer:
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
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())
By supporting negative steps and so on, this function can complete the work beyond your strict requirements. As long as you consider the scope logic, you don't need a separate day_count. Most importantly, when you call a function from it, the code becomes easier to read multiple places.
Use 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")
The python library has many more advanced functions, some of which are very useful (for example, relative delta, which is implemented as a single file (module), and easily included in the project.
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))