Skip to content

Instantly share code, notes, and snippets.

@iwanbolzern
Created August 13, 2019 21:50
Show Gist options
  • Save iwanbolzern/f8eb724605ae6279ee0f77860b003816 to your computer and use it in GitHub Desktop.
Save iwanbolzern/f8eb724605ae6279ee0f77860b003816 to your computer and use it in GitHub Desktop.

Date Iterator

Have you ever wanted to iterate over the last 10 days? DateIterator provides simple solution for your problem.

Simple usage

from datetime import datetime, timedelta
from date_iterrator import DateIterator

itr = DateIterator(datetime(2019, 1, 1), datetime(2019, 1, 10), timedelta(days=1))
list(itr)
[(datetime.datetime(2019, 1, 1, 0, 0), datetime.datetime(2019, 1, 2, 0, 0)), 
(datetime.datetime(2019, 1, 2, 0, 0), datetime.datetime(2019, 1, 3, 0, 0)), 
(datetime.datetime(2019, 1, 3, 0, 0), datetime.datetime(2019, 1, 4, 0, 0)), 
(datetime.datetime(2019, 1, 4, 0, 0), datetime.datetime(2019, 1, 5, 0, 0)), 
(datetime.datetime(2019, 1, 5, 0, 0), datetime.datetime(2019, 1, 6, 0, 0)), 
(datetime.datetime(2019, 1, 6, 0, 0), datetime.datetime(2019, 1, 7, 0, 0)), 
(datetime.datetime(2019, 1, 7, 0, 0), datetime.datetime(2019, 1, 8, 0, 0)), 
(datetime.datetime(2019, 1, 8, 0, 0), datetime.datetime(2019, 1, 9, 0, 0)), 
(datetime.datetime(2019, 1, 9, 0, 0), datetime.datetime(2019, 1, 10, 0, 0))]

Difference between open and closed iterator

Open and closed iterators are equal in the case when the interval fits exactly n times between from_date and to_date. On the other side when the parameters are as follows:

itr = DateIterator(datetime(2019, 1, 1), datetime(2019, 1, 10, hour=5), timedelta(days=1))

Now there are 5 hours left at the end. An open iterator (passed as argument closed=False) will stop and ignore the 5 additional hours. Checkout the example:

itr = DateIterator(datetime(2019, 1, 1), datetime(2019, 1, 10), timedelta(days=1), closed=False)
list(itr)
[(datetime.datetime(2019, 1, 1, 0, 0), datetime.datetime(2019, 1, 2, 0, 0)), 
...
(datetime.datetime(2019, 1, 9, 0, 0), datetime.datetime(2019, 1, 10, 0, 0))]

Whereas a closed iterator will generate one more iteration and include the remaining 5 hours:

itr = DateIterator(datetime(2019, 1, 1), datetime(2019, 1, 10), timedelta(days=1), closed=True)
list(itr)
[(datetime.datetime(2019, 1, 1, 0, 0), datetime.datetime(2019, 1, 2, 0, 0)), 
...
(datetime.datetime(2019, 1, 9, 0, 0), datetime.datetime(2019, 1, 10, 0, 0)),
(datetime.datetime(2019, 1, 10, 0, 0), datetime.datetime(2019, 1, 11, 0, 0))]
from datetime import datetime, timedelta
class DateIterator:
def __init__(self, from_date: datetime, to_date: datetime, interval: timedelta, closed: bool = True):
assert (from_date < to_date), 'to_date must be greater than from_date'
self.from_date = from_date
self.to_date = to_date
self.interval = interval
self.closed = closed
self.total_times = None
self.current_position = None
self._init_iterator()
def _init_iterator(self):
self._calculate_total_times()
self.current_position = 0
def _calculate_total_times(self):
self.total_times = (self.to_date - self.from_date) / self.interval
is_exact_fit = self.total_times.is_integer()
if not is_exact_fit and self.closed:
self.total_times += 1
self.total_times = int(self.total_times)
def __iter__(self):
self._init_iterator()
return self
def __next__(self):
if self.current_position >= self.total_times:
raise StopIteration
interval_start_date = self.from_date + self.interval * self.current_position
interval_end_date = interval_start_date + self.interval
self.current_position += 1
return interval_start_date, interval_end_date
from datetime import datetime, timedelta
from unittest import TestCase
from date_iterrator import DateIterator
class TestDateIterator(TestCase):
def setUp(self) -> None:
self.from_date = datetime(2019, 1, 1)
self.to_date = datetime(2019, 1, 2)
self.interval = timedelta(hours=6)
self.expected = [(datetime(2019, 1, 1), datetime(2019, 1, 1, 6)),
(datetime(2019, 1, 1, 6), datetime(2019, 1, 1, 12)),
(datetime(2019, 1, 1, 12), datetime(2019, 1, 1, 18)),
(datetime(2019, 1, 1, 18), datetime(2019, 1, 2))]
def test_iterator_exact_fit(self):
itr_closed = DateIterator(self.from_date, self.to_date, self.interval, closed=True)
itr_open = DateIterator(self.from_date, self.to_date, self.interval, closed=False)
self.assertEqual(list(itr_closed), self.expected)
self.assertEqual(list(itr_open), self.expected)
def test_iterator_closed_not_exact_fit(self):
self.to_date = datetime(2019, 1, 2, 3)
self.expected.append((datetime(2019, 1, 2), datetime(2019, 1, 2, 6)))
itr_closed = DateIterator(self.from_date, self.to_date, self.interval, closed=True)
self.assertEqual(list(itr_closed), self.expected)
def test_iterator_open_not_exact_fit(self):
self.to_date = datetime(2019, 1, 2, 3)
itr_open = DateIterator(self.from_date, self.to_date, self.interval, closed=False)
self.assertEqual(list(itr_open), self.expected)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment