def allocate(self, start, effort): if not self.sheet: self.sheet = [I.Interval(start)] ret = self.allocate(start, effort) self.sheet = [] return ret logger.debug( f"allocate: allocating effort {effort} @{self.name} from {start}") last_iv = I.Interval(datetime.date(datetime.MAXYEAR, 12, 31)) for i, (left, right) in enumerate(zip(self.sheet, self.sheet[1:] + [last_iv])): if start >= right.start: continue gap = I.Interval(max(left.finish, start), right.start) logger.debug(f"allocate: found free slot {gap}") ok, iv = self.cal.allows_effort(gap, effort) if not ok: logger.debug(f"allocate: slot {gap} rejected due to holidays") continue logger.debug(f"allocate: updated due to holidays: {iv}") fragmentation = iv.start - left.finish if gap.finish != sys.maxsize: fragmentation += gap.finish - iv.finish return i + 1, iv, fragmentation raise ValueError("unreachable")
def read_date2(s, loc): """Parse date duration e.g. "2015-02-01" or "2015-02-01 - 2015-02-03".""" start, rest = read_date(s, loc) finish = start if rest and rest[0] == '-': finish, rest = read_date(rest[1:], loc) return I.Interval(start, finish, True)
def _set_goal(self, goal): self.goal = goal self.prio = goal.prio self.complete = goal.complete() if goal.completion_date is not None: self.duration = I.Interval(goal.completion_date) self.deadline = self.goal.deadline
def __init__(self, loc): self.name = 'Unknown' self.loc = loc year = datetime.date.today().year self.duration = I.Interval(datetime.date(year, 1, 1), datetime.date(year, 12, 31), True) self.members = [] self.members_map = {} self.teams = {} self.teams_map = {} self.holidays = [] self.tracker_link = 'http://jira.localhost/browse/%s' self.pr_link = None
def allows_effort(self, iv, effort): """Checks whether we have enough working hours in interval of time.""" ndays = (iv.finish - iv.start).days # Fast check if ndays * 8 < effort: return False, None # Slow check # TODO: do this faster # TODO: hour-based precision start = None for day in range(ndays): d = iv.start + datetime.timedelta(days=day) if d.weekday() < 5 and not self.holidays.contains(d): if start is None: start = d effort -= 8 if effort <= 0: return True, I.Interval(start, d, closed=True) return False, None
def test_add(): seq = I.Seq([I.Interval(d1, d2), I.Interval(d5, d6)]) seq.add(I.Interval(d3, d4)) assert len(seq.ivs) == 3 and seq.ivs[1] == I.Interval(d3, d4) seq = I.Seq([I.Interval(d1, d2), I.Interval(d5, d6)]) with pytest.raises(Exception): seq.add(I.Interval(d1, d2)) seq = I.Seq([I.Interval(d1, d3), I.Interval(d5, d6)]) with pytest.raises(Exception): seq.add(I.Interval(d2, d4)) seq = I.Seq([I.Interval(d1, d2), I.Interval(d5, d6)]) with pytest.raises(Exception): seq.add(I.Interval(d1, d3)) seq = I.Seq([I.Interval(d1, d2), I.Interval(d3, d4)]) seq.add(I.Interval(d5, d6)) assert len(seq.ivs) == 3 and seq.ivs[2] == I.Interval(d5, d6) seq = I.Seq([I.Interval(d1, d2), I.Interval(d3, d4)]) seq.add(I.Interval(d4, d6)) assert len(seq.ivs) == 3 and seq.ivs[2] == I.Interval(d4, d6)
def test_union(): assert I.Interval(d1, d3).union(I.Interval(d2, d4)) == I.Interval(d1, d4) assert I.Interval(d1, d2).union(I.Interval(d3, d4)) is None
def test_overlaps(): assert I.Interval(d1, d3).overlaps(I.Interval(d2, d4)) assert not I.Interval(d1, d2).overlaps(I.Interval(d3, d4))
def test_after(): assert I.Interval(d2, d3).after(I.Interval(d1, d2)) assert not I.Interval(d1, d2).after(I.Interval(d1, d2)) assert not I.Interval(d1, d2).after(I.Interval(d2, d3))
def test_before(): assert I.Interval(d1, d2).before(I.Interval(d2, d3)) assert not I.Interval(d1, d2).before(I.Interval(d1, d2)) assert not I.Interval(d2, d3).before(I.Interval(d1, d2))
def test_create(): with pytest.raises(Exception): I.Interval(d2, d1)