Skip to content

Instantly share code, notes, and snippets.

@obeattie
Created October 19, 2009 12:31
Show Gist options
  • Save obeattie/213328 to your computer and use it in GitHub Desktop.
Save obeattie/213328 to your computer and use it in GitHub Desktop.
A datetime range class for Python, for (obviously) dealing with ranges of datetimes.
"""Provides a `DateTimeRange` class, which is used for managing ranges of datetimes."""
import datetime
class DateTimeRange(object):
"""Represents a range of datetimes, with a start and (optionally) an end.
Basically implements most of the methods on a standard sequence data type to provide
some lovely syntactic sugar. Specifically, you can iterate on this, index it, slice it,
use the in operator, reverse it, and use it in a boolean context to see if there is any
time in between the start and end."""
DEFAULT_STEP = datetime.timedelta(seconds=1)
def __init__(self, start, end=None, step=DEFAULT_STEP, *args, **kwargs):
self.start = start
self.end = end
self.step = step
return super(DateTimeRange, self).__init__(*args, **kwargs)
def __contains__(self, item):
"""Returns whether or not the passed datetime is within the range. Does not take into
account the stride length from `self.step` -- if you need that use dateutil's rrule
instead."""
if self.end is None:
# The range never ends, so we just need to check `item` is beyond the start
return (self.start <= item)
else:
return (self.start <= item <= self.end)
def __iter__(self):
"""Returns a generator which will yield datetime objects within the range, incrementing
with `self.step` as its stride length on each iteration."""
value = self.start
while (value in self):
yield value
value += self.step
def __reversed__(self):
"""Reverse iterator yielding the datetime objects within the range in reverse. Similarly
to the forward-iterator, decrements (rather than increments) by `self.step` each time.
This can only be called if an end is defined."""
assert self.end is not None, 'Reverse iteration is not supported without an end'
value = self.end
while (value in self):
yield value
value -= self.step
def __nonzero__(self):
"""Returns whether the date range covers a length of time (i.e. the end value is beyond
the start). If no end is defined, always returns True as the range continues forever."""
return ((not self.end) or (self.end > self.start))
def __get_slice(self, start, stop, step=None):
"""Internal method for slicing the date range. Use the standard slicing syntax as the
external interface."""
indices = (xrange(start, stop, step) if step is not None else xrange(start, stop))
result = []
for index in indices:
try:
result.append(self[index])
except IndexError:
pass
return result
def __getitem__(self, key):
"""Returns the n'th datetime from the range, using `self.step` to determine the
increment. Does not calculate every datetime up until the index, but rather
multiplies the step value by the index to achieve the same result more efficiently.
Negative indexing is only supported if an end is defined. Also supports slicing -- the
same rule regarding negative indexing still applying."""
if isinstance(key, tuple):
# Multiple indices
return [self[i] for i in key]
elif isinstance(key, slice):
# Slicing
return self.__get_slice(start=key.start, stop=key.stop, step=key.step)
else:
# Regular indexing
if key < 0:
# Reverse-indexing
assert self.end is not None, 'Negative indexing is not supported without an end'
value = (self.end - (self.step * key))
else:
# Forward-indexing
value = (self.start + (self.step * key))
# Check that the value is in the range; return it if it is, raise IndexError if not
if value in self:
return value
else:
raise IndexError, 'index out of range'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment