Traversing a series of dates in Python

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())

note

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

Sample output

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

#1 building

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().

#2 building

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

#3 building

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.

#4 building

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.

#5 building

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)) 

Tags: Python

Posted on Wed, 11 Mar 2020 00:00:30 -0400 by tommix