Created
October 19, 2009 12:31
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""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