def test_forward_non_recurrent_random(unit, overlap, trim): """`forward()` method of non-recurrent time intervals with and without overlapping `start` date.""" correction = BASE[unit] max_ = add_delta(MAX, -1 * N, unit) unit_maximum = delta(MIN, max_, unit) interval = sorted(randuniq(2, correction, unit_maximum)) if overlap: start = add_delta( MIN, rnd.randrange(int(interval[0]), int(interval[1])) - correction, unit ) else: start = add_delta(MIN, int(interval[0]) - correction, unit) if trim: expected_start = start else: expected_start = add_delta(MIN, int(interval[0]) - correction, unit) stop = add_delta(MIN, int(interval[1]) - correction, unit) expected = [(expected_start, stop)] recurrentevent = RecurrentEvent(interval[0], interval[1], unit, None) actual = list(islice(recurrentevent.forward(start, trim), None, N)) assert actual == expected
def test_forward_non_recurrent_random(unit, overlap): """`forward()` method of non-recurrent time intervals with and without overlapping `start` date.""" correction = BASE[unit] max_ = add_delta(MAX, -1 * N, unit) unit_maximum = delta(MIN, max_, unit) interval = Interval(*sorted(randuniq(2, correction, unit_maximum))) if overlap: start = add_delta( MIN, rnd.randrange( int(interval.start), int(interval.stop) ) - correction, unit ) else: start = add_delta(MIN, int(interval.start) - correction, unit) stop = add_delta(MIN, int(interval.stop) - correction + 1, unit) expected = [Interval(start, stop)] timeinterval = TimeInterval(interval, unit, None) actual = list(islice(timeinterval.forward(start), None, N)) assert actual == expected
def test_interval_containment(unit, recurrence, sample, inclusion, expected): """Cases for containment test of an dates intervals.""" correction = BASE[unit] lower, upper = unit_span(unit, recurrence, sample if recurrence is not None else None) if inclusion == 'included': test = lambda outer_start, outer_stop, inner_start, inner_stop: ( lower <= outer_start <= inner_start <= inner_stop < outer_stop < upper ) elif inclusion == 'not_included': test = lambda outer_start, outer_stop, inner_start, inner_stop: ( lower <= inner_start < outer_start <= outer_stop < inner_stop < upper ) else: raise NotImplementedError outer_start, outer_stop, inner_start, inner_stop = guess(lower, upper, 4, test) timeinterval = TimeInterval(Interval(outer_start, outer_stop), unit, recurrence) if recurrence is not None: first = add_delta(sample, inner_start - correction, unit) second = add_delta(sample, inner_stop - correction, unit) else: first = add_delta(MIN, inner_start - correction, unit) second = add_delta(MIN, inner_stop - correction, unit) assert ((first, second) in timeinterval) == expected
def test_add_delta_raises_overflow_exception(unit): """'add_delta' raises `OverflowError` in case if resulting datetime is greater than `tempo.unit.MAX` or lesser than `tempo.unit.MIN`""" with pytest.raises(OverflowError): add_delta(MAX, 1, unit) with pytest.raises(OverflowError): add_delta(MIN, -1, unit)
def unit_span(unit, recurrence=None, sample=None): """How much 'unit''s in 'recurrence'.""" lower = BASE[unit] assert ((recurrence is None and sample is None) or (recurrence is not None and sample is not None)) if recurrence is not None: upper = delta(sample, add_delta(sample, 1, recurrence), unit) else: upper = delta(MIN, add_delta(MAX, -1, unit), unit) assert lower != upper and lower < upper return lower, upper
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 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 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): """`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 test_add_delta_raises_value_error_on_wrong_unit(): with pytest.raises(ValueError): add_delta(MIN, -1, 'wrong_unit')
def test_add_delta(datetime, delta, unit, expected): """Cases for `add_delta`.""" assert add_delta(datetime, delta, unit) == 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]