def __contains__(self, item): """Test given datetime 'item' for containment in the time interval. Parameters ---------- item : datetime.datetime A 'datetime' object to test. Returns ------- bool Result of containment test. Notes ----- The algorithm here consists of following steps: If recurrence is set: 1. Given datetime floored to unit of 'recurrence' and stored. 2. Then given datetime floored to unit of 'unit' and stored. 3. Delta between resulting datetime objects is calculated and expressed in units of 'unit'. For example if delta is "2 days" and 'unit' is minutes, delta will be "2*24*60 minutes". If recurrence is not set: 1. Delta between date of "the beginning of time" and given date is calculated and expressed in units of 'unit'. 4. Resulting delta tested for containment in the interval. """ if isinstance(item, dt.datetime): item = (item,) elif isinstance(item, (tuple, list)): assert len(item) == 2 elif isinstance(item, Interval): item = (item.start, item.stop) elif isinstance(item, EmptyInterval): return True else: raise AssertionError if self.recurrence is None: time_in_unit = [delta(MIN, e, self.unit) for e in item] else: time_in_unit = [delta(floor(e, self.recurrence), floor(e, self.unit), self.unit) for e in item] # Because we need to count not only time # that already happened, but also time, expressed in 'unit' # that "happening" time_in_unit = [n + 1 for n in time_in_unit] if len(item) == 1: return time_in_unit[0] in self.interval elif len(item) == 2: return Interval(*time_in_unit) <= self.interval
def test_forward_recurrent_random(unit, recurrence, overlap): """`forward()` method of recurrent time intervals with and without overlapping `start` date.""" correction = BASE[unit] max_ = add_delta(MAX, -1 * N, recurrence) unit_maximum = delta(MIN, add_delta(MIN, 1, recurrence), unit) interval = Interval(*sorted(randuniq(2, correction, unit_maximum))) expected = [] initial = start = floor( add_delta(MIN, rnd.randrange(delta(MIN, max_, unit)), unit), recurrence ) if overlap: initial = start = add_delta( initial, rnd.randrange( int(interval.start), int(interval.stop) ) - correction, unit ) for i in range(N): recurrence_start = floor(start, recurrence) first = floor(add_delta(recurrence_start, interval.start - correction, unit), unit) second = add_delta(floor(add_delta(recurrence_start, interval.stop - correction, unit), unit), 1, unit) assert first < second assert start < second if start > first: first = start expected.append(Interval(first, second)) start = floor(add_delta(recurrence_start, 1, recurrence), recurrence) timeinterval = TimeInterval(interval, unit, recurrence) actual = list(islice(timeinterval.forward(initial), None, N)) assert actual == expected
def sample(unit): """Random time between between bounds of possible time. Returned time is not included in the last segment of interval measured in 'unit'. """ return floor(random_datetime_between(MIN, add_delta(MAX, -1, unit)), unit)
def __contains__(self, item): """Test given datetime 'item' for containment in the recurrent event. Parameters ---------- item : datetime.datetime A 'datetime' object to test. Returns ------- bool Result of containment test. Notes ----- The algorithm here consists of following steps: If recurrence is set: 1. Given datetime floored to unit of 'recurrence' and stored. 2. Then given datetime floored to unit of 'unit' and stored. 3. Delta between resulting datetime objects is calculated and expressed in units of 'unit'. For example if delta is "2 days" and 'unit' is minutes, delta will be "2*24*60 minutes". If recurrence is not set: 1. Delta between date of "the beginning of time" and given date is calculated and expressed in units of 'unit'. 4. Resulting delta tested for containment in the interval. """ correction = BASE[self.unit] if self.recurrence is None: time_in_unit = delta(MIN, item, self.unit) else: time_in_unit = delta(floor(item, self.recurrence), floor(item, self.unit), self.unit) time_in_unit += correction return self.start <= time_in_unit < self.stop
def forward(self, start): """Iterate time intervals starting from 'start'. Intervals returned in form of `(start, end)` pair, where `start` is a datetime object representing the start of the interval and `end` is the non-inclusive end of the interval. Parameters ---------- start : datetime.datetime A lower bound for the resulting sequence of intervals. Yields ------ start : datetime.datetime Start of an interval. end : datetime.datetime End of an interval. """ if self.recurrence is None: base = MIN else: base = floor(start, self.recurrence) correction = -1 * BASE[self.unit] def addfloor(base, delta): """Adds 'delta' to 'base' and than floors it by unit of this interval.""" return floor(add_delta(base, delta, self.unit), self.unit) # Handle possible overlap in first interval try: first = addfloor(base, self.interval.start + correction) second = addfloor(base, self.interval.stop + correction + 1) if first < start < second: yield Interval(start, second) elif start <= first: yield Interval(first, second) except OverflowError: return if self.recurrence is None: return while True: # Handle recurring intervals base = add_delta(base, 1, self.recurrence) try: first = addfloor(base, self.interval.start + correction) second = addfloor(base, self.interval.stop + correction + 1) if base > first: # In case if flooring by week resulted first = base # as a time earlier than 'base' yield Interval(first, second) except OverflowError: return
def test_forward_recurrent_random(unit, recurrence, overlap, trim): """`forward()` method of recurrent time intervals with and without overlapping `start` date.""" correction = BASE[unit] max_ = add_delta(MAX, -1 * N, recurrence) unit_maximum = delta(MIN, add_delta(MIN, 1, recurrence), unit) from_, to = sorted(randuniq(2, correction, unit_maximum)) expected = [] initial = start = floor( add_delta(MIN, rnd.randrange(delta(MIN, max_, unit)), unit), recurrence ) if overlap: initial = start = add_delta( initial, rnd.randrange( int(from_), int(to) ) - correction, unit ) for i in range(N): recurrence_start = floor(start, recurrence) recurrence_stop = floor(add_delta(recurrence_start, 1, recurrence), recurrence) first = floor(add_delta(recurrence_start, from_ - correction, unit), unit) second = floor(add_delta(recurrence_start, to - correction, unit), unit) first = max(recurrence_start, min(first, recurrence_stop)) second = min(recurrence_stop, second) assert first <= second assert start <= second if start > first and trim: first = start expected.append((first, second)) start = floor(add_delta(recurrence_start, 1, recurrence), recurrence) recurrentevent = RecurrentEvent(from_, to, unit, recurrence) actual = list(islice(recurrentevent.forward(initial, trim), None, N)) assert actual == expected
def addfloor(base, delta): """Adds 'delta' to 'base' and than floors it by unit of this interval.""" return floor(add_delta(base, delta, self.unit), self.unit)
def forward(self, start, trim=True): """Iterate time intervals starting from 'start'. Intervals returned in form of `(start, end)` pair, where `start` is a datetime object representing the start of the interval and `end` is the non-inclusive end of the interval. Parameters ---------- start : datetime.datetime A lower bound for the resulting sequence of intervals. trim : bool Whether a first interval should be trimmed by 'start' or it should be full, so it's start point may potentially be earlier, that 'start'. Yields ------ start : datetime.datetime Start of an interval. end : datetime.datetime End of an interval. """ # pylint: disable=too-many-branches if self.recurrence is None: base = MIN else: base = floor(start, self.recurrence) correction = -1 * BASE[self.unit] def addfloor(base, n): """Adds 'delta' to 'base' and than floors it by unit of this interval.""" return floor(add_delta(base, n, self.unit), self.unit) # Handle possible overlap in first interval try: first = addfloor(base, self.start + correction) if start > first: if trim: first = start # If 'unit' is week, 'first' could be earlier than # start of 'recurrence' time component corresponding to # 'start'. elif self.recurrence is not None: recurrence_start = floor(start, self.recurrence) if first < recurrence_start: first = recurrence_start if self.isgapless(): yield first, MAX return second = addfloor(base, self.stop + correction) if self.recurrence is not None: first, second = self._clamp_by_recurrence(base, first, second) yield first, second except OverflowError: return if self.recurrence is None: return while True: # Handle recurring intervals base = add_delta(base, 1, self.recurrence) try: first = addfloor(base, self.start + correction) second = addfloor(base, self.stop + correction) if base > first: # In case if flooring by week resulted first = base # as a time earlier than 'base' first, second = self._clamp_by_recurrence(base, first, second) yield first, second except OverflowError: return
def _clamp_by_recurrence(self, base, *dates): max_ = floor(add_delta(base, 1, self.recurrence), self.recurrence) return [min(d, max_) for d in dates]