def recentlyCompleted(self) -> []: recentlyCompleted = [] currentArrow = arrow.now() affectedDays = list(filter(lambda day : day.date >= self.lastWorkConfirmed.date() and day.date <= currentArrow.date(), self.__days)) for affectedDay in affectedDays: timeSlots = [ts for ts in affectedDay.timeSlots if ts.taskOrAppointment is not None] if affectedDay.date == self.lastWorkConfirmed.date(): # First day of area timeSlots = list(filter(lambda ts: ts.endTime > Time(self.lastWorkConfirmed.hour, self.lastWorkConfirmed.minute), timeSlots)) # Remove all timeslots prior if affectedDay.date == currentArrow.date(): # NOT an elif, as a day can be both start and end day timeSlots = list(filter(lambda ts: ts.endTime < Time(self.lastWorkConfirmed.hour, self.lastWorkConfirmed.minute), timeSlots)) # Remove all timeslot after for timeSlot in timeSlots: recentlyCompleted.append({"dateString" : util.dateString(affectedDay.date), "timeSlot" : timeSlot, "task" : timeSlot.taskOrAppointment}) return recentlyCompleted
def timeInput(info: str) -> Time: while True: try: timeString = input(info) time = Time.fromString(timeString) return time except (IndexError, ValueError): print("Invalid Time!")
def getFreeTimeBetweenPoints(days: [Day], start: arrow.Arrow, end: arrow.Arrow) -> int: # In Minutes if start == end: return 0 sortedDays = sorted(days, key=lambda d: d.date) startDate = start.date() endDate = end.date() startTime = Time(start.hour, start.minute) endTime = Time(end.hour, end.minute) # Collect time inbetween freeMinutes = 0 for day in sortedDays: # Days before or equal to the start date if day.date < startDate: continue elif day.date == startDate: if day.date == endDate: # If it is ALSO equal to the endDate (i.e. startDate and endDate are the same day) return day.freeTimeInMinutes( after=startTime, before=endTime ) # Return immediately, as this is the only day (since startDate==endDate) else: freeMinutes += day.freeTimeInMinutes(after=startTime) continue # Days after or equal to the end date if day.date > endDate: print( f"WARNING: This scenario [debug-id: schedule_alg:AAA123] should not occur! Last day of time windows was likely not calculated correctly. DayDate: {day.date} | EndDate: {endDate}" ) print(f"Start: {start} | End: {end}") return freeMinutes elif day.date == endDate: freeMinutes += day.freeTimeInMinutes(before=endTime) return freeMinutes # This is the last valid day, return immediately # Normal day freeMinutes += day.freeTimeInMinutes() return freeMinutes
def freeTimeSlots( self, before=None, after=None ) -> [TimeSlot ]: # Returns a NEW DEEPCOPIED LIST containing new objects / copies # At this point, priority could be used to determine if appointments should get thrown out blocking_appointments = self.appointments.copy() virtualAppointments = [ ] # Blocking appointments to simulate some constraint, i.e. after/before if before is not None and before != Time(23, 59): before = Appointment("VIRTUAL_APP_BEFORE", TimeSlot(before, Time(23, 59))) virtualAppointments.append(before) if after is not None and after != Time(0, 0): after = Appointment("VIRTUAL_APP_AFTER", TimeSlot(Time(0, 0), after)) virtualAppointments.append(after) blocking_appointments += virtualAppointments freeSlots = [] for timeSlot in self.timeSlots: # TimeSlots with a TaskOrAppointment assigned to it are not free if timeSlot.taskOrAppointment is not None: continue # Calculate overlap with appointments and perhaps split up TimeSlots as necessary currentTimeSlots = [timeSlot.copy()] for appointment in blocking_appointments: newTimeSlots = [] for currentTimeSlot in currentTimeSlots: newTimeSlots += currentTimeSlot.nonOverlap( appointment.timeSlot) currentTimeSlots = newTimeSlots.copy( ) # .copy() is very important; mutation danger freeSlots += currentTimeSlots.copy() return freeSlots
def arrowToTime(a: arrow.Arrow) -> Time: return Time(a.hour, a.minute)
def exampleTimeSlots() -> [TimeSlot]: return [ TimeSlot(Time(8, 30), Time(12, 30)), TimeSlot(Time(13, 00), Time(17, 00)) ]
def timeParse(timeString: str) -> Time: hours = int(timeString.split(":")[0]) minutes = int(timeString.split(":")[1]) return Time(hours, minutes)
def calculateSchedule(globalDays: [Day], tasks: [Task], currentSchedule: Schedule, start: arrow.Arrow, debug=False) -> Schedule: currentTime = util.smoothCurrentArrow() if start < currentTime: raise Exception("Cannot calculate a schedule for the past") days = currentSchedule.days() oldDays = [day.copy() for day in days if day.date <= start.date() ] # Contains all days PRIOR to start date (last is popped off) oldTimeSlots = [] if oldDays: # On initial creation, there are no old days lastDay = oldDays.pop( ) # This day is changed after the current timeslot and kept the same before. splitTime = Time(start.hour, start.minute) for ts in lastDay.timeSlots: if ts.endTime > splitTime and ts.startTime < splitTime: # Update start time to be the end of the current timeslot # Update Arrow object only if it isn't before the smoothCurrentArrow() splitArrow = arrow.Arrow(start.year, start.month, start.day, hour=ts.endTime.hours, minute=ts.endTime.minutes) if splitArrow >= currentTime: start = splitArrow splitTime = Time(start.hour, start.minute) if ts.startTime < splitTime and ts.taskOrAppointment is not None: oldTimeSlots.append(ts) startIndex = None for i, day in enumerate(globalDays): if day.date == start.date(): startIndex = i break else: raise Exception("Error in schedule_alg 555255GGG") newDays = [day.copy() for day in globalDays[startIndex:]] # Remove TimeSlots prior to start firstNewDay = newDays[0] oldTimeSlotsToRemove = [] for ts in firstNewDay.timeSlots: if ts.startTime < splitTime: oldTimeSlotsToRemove.append(ts) for otstr in oldTimeSlotsToRemove: firstNewDay.timeSlots.remove(otstr) for oldTimeSlot in oldTimeSlots: firstNewDay.addTimeSlot(oldTimeSlot) lastWorkConfirmed = currentSchedule.lastWorkConfirmed if lastWorkConfirmed is None: lastWorkConfirmed = start.clone() # Creates a clean copy of the tasks and days, so that no evil mutation occurs newDays = sorted(newDays, key=lambda d: d.date) tmpTasks = sorted([task.copy() for task in tasks], key=lambda t: t.deadline) if isSolvable(tasks, newDays, start, useMinimum=False): happySchedule = calculateHappySchedule( tmpTasks, newDays, lastWorkConfirmed, start, debug=debug ) # Adds the history (previous schedule) to the newly calculated schedule happySchedule.addHistory(oldDays) return happySchedule elif isSolvable(tasks, newDays, start, useMinimum=True): riskySchedule = calculateSadSchedule(tmpTasks, newDays, lastWorkConfirmed, start, debug=debug) riskySchedule.addHistory(oldDays) return riskySchedule #return calculateRiskySchedule(tmpTasks, newDays, created=start) else: sadSchedule = calculateSadSchedule(tmpTasks, newDays, lastWorkConfirmed, start, debug=debug) sadSchedule.addHistory(oldDays) return sadSchedule
def scheduleTask(self, task: Task, start: arrow.Arrow, minutes=None, debug=False): # TODO: Consider minBlock if minutes is None: minutesToSchedule = task.maxRemainingTime else: minutesToSchedule = minutes if debug: print(f"Schedule is scheduling {minutesToSchedule}min for task {task}") scheduledMinutes = 0 startTime = util.arrowToTime(start) for day in self.__days: if debug: print("\n") print(day, end=" -> ") if task.maxRemainingTime == 0 or minutesToSchedule == scheduledMinutes: if debug: print("A", end="") return # Skip days prior to the startDate if day.date < start.date(): if debug: print("B", end="") continue # For the start day, consider only the times that lie after the start time if day.date == start.date(): if debug: print("C", end="") freeTimeSlots = day.freeTimeSlots(after=startTime) # Days in the future if day.date > start.date(): if debug: print("D", end="") freeTimeSlots = day.freeTimeSlots() if len(freeTimeSlots) == 0: if debug: print("E", end="") continue # For the valid TimeSlots of the future, fill them with the task until they are either all filled or "minutesToSchedule" minutes are scheduled for ts in freeTimeSlots: if debug: print("F", end="") if task.maxRemainingTime == 0 or minutesToSchedule == scheduledMinutes: if debug: print("G", end="") return # If TimeSlot is <= to what still needs to be scheduled, fill it completely if ts.durationInMinutes <= (minutesToSchedule - scheduledMinutes): if debug: print("H", end="") # Schedule the Task day.scheduleTask(ts, task, debug=debug) # Update the counters task.addCompletionTime(ts.durationInMinutes) # This mutates the ORIGINAL TASK scheduledMinutes += ts.durationInMinutes else: # If TimeSlot is bigger than what needs to be scheduled, fill the first section of it (until "minutesToSchedule" minutes are scheduled) if debug: print("I", end="") # Build the Partial TimeSlot remainingMinutesToSchedule = minutesToSchedule - scheduledMinutes length = Time.fromMinutes(remainingMinutesToSchedule) partialTimeSlot = TimeSlot(ts.startTime, ts.startTime + length) # Schedule the Task day.scheduleTask(partialTimeSlot, task, debug=debug) # Update the counters task.addCompletionTime(partialTimeSlot.durationInMinutes) # This mutates the ORIGINAL TASK scheduledMinutes += partialTimeSlot.durationInMinutes # Unnecessary. ScheduledMinutes will == MinutesToSchedule after this every time. return # Since the TimeSlot was bigger than the remaining minutesToSchedule, we are done here now. raise ImpossibleScheduleException("Unable to schedule task!")