def fill_gaps(current_time, current_day_start, current_day_end, next_block, blocks): """ @brief fills the range r for current_times < r < next_block.start_time @param current_time(arrow): the end_time of the last placed TimeBlock @param current_day_start(arrow): the start_time of a day within our date/time range @param current_day_end(arrow): the end_time of a day within our date/time range @param next_block(TimeBlock): The next busy block we will encounter @param blocks(list): list of TimeBlock Objects @return None, updates the list 'blocks' by adding free TimeBlock Objects """ next_day = int(next_block.get_start_time().format("DD")) while int(current_time.format("DD")) != next_day: free_block = timeblocks.TimeBlock(current_time, current_day_end) if current_time != current_day_end: blocks.append(free_block) current_day_start = current_day_start.shift(days=+1) current_day_end = current_day_end.shift(days=+1) current_time = current_day_start # Fill our the rest of the days time with a free block if current_time != next_block.get_start_time(): free_block = timeblocks.TimeBlock(current_time, next_block.get_start_time()) blocks.append(free_block) return None
def test_within(): block1 = timeblocks.TimeBlock(arrow.get("2017-11-07T12:00:00-08:00"),arrow.get("2017-11-07T15:00:00-08:00"), "Block 1") block2 = timeblocks.TimeBlock(arrow.get("2017-11-07T15:01:00-08:00"),arrow.get("2017-11-07T17:00:00-08:00"), "Block 2") block3 = timeblocks.TimeBlock(arrow.get("2017-11-07T11:00:00-08:00"),arrow.get("2017-11-07T16:00:00-08:00"), "Block 3") assert(block3.is_within(block2)) assert(block3.is_within(block1)) assert(not (block2.is_within(block1)))
def get_busy(service,calendar_id,begin_date,end_date,unique_id = None): """ @brief creates a list of busy times using events for ONE Google Calendar @param service: Google 'service' object @param calendar_id: a specific Google calendar's id @param begin_date (arrow): beginning of busy times date range @param end_date (arrow): end of busy times date range @param unique_id: specific id of a recurring Google calendar event @return a list of events from a calendar(event_description,start_str,end_str) that which a person is marked "busy" """ busy_events = [] page_token = None i = 0 while True: # Events: list (https://developers.google.com/google-apps/calendar/v3/reference/events/list#examples) if unique_id: events = service.events().instances(calendarId=calendar_id, eventId=unique_id,pageToken=page_token).execute() else: events = service.events().list(calendarId=calendar_id,pageToken=page_token).execute() for event in events['items']: event_description = None # the 'transparency' key will only be present for calendar events marked: Show me as Available # Thus the condition below with result true when an event is marked # Show me as: Busy if "transparency" in event: continue # The "recurrence" key will be present in events that repeat multiple times (i.e. once a week). We must individually # loop thorugh each of these events by their unique_id in order to get the appropriate times and dates if "recurrence" in event: busy_events += get_busy(service,calendar_id,begin_date,end_date,event["id"]) continue # Get a text field that describes the event try: event_description = event["summary"] # the 'date' field will only be present for events that last a whole day. # Otherwise a 'datetime' will be present if "date" in event['start']: start = event['start']['date'] # Date format is YYYY-DD-MM end = event['start']['date'] # Date format is YYYY-DD-MM else: start = event['start']['dateTime'] # dateTime format is YYYY-MM-DD HH:mm:ss ZZ end = event['end']['dateTime'] # dateTime format is YYYY-MM-DD HH:mm:ss ZZ # Create arrow objects for event start, end and the daterange event_start = arrow.get(start) event_end = arrow.get(end) window = timeblocks.TimeBlock(begin_date,end_date) event = timeblocks.TimeBlock(event_start,event_end,event_description) if event.is_within(window): busy_events.append(event.get_overlap(window)) except KeyError: print("Key Error") page_token = events.get('nextPageToken') if not page_token: break return busy_events
def consolidate(busy_blocks): """ @brief consolidates the list of TimeBlocks @param busy_blocks: list of TimeBlock Objects, representing busy times @return a list of busy_blocks where overlapping TimeBlocks have been merged together """ consolidated = [] prev = None for block in busy_blocks: if prev == None: prev = block # Case: There is a 'gap' between when the last block ended and the current starts elif prev.get_end_time() < block.get_start_time(): consolidated.append(prev) prev = block # Case: There is some overlap between the two blocks else: description = prev.merge_descriptions(block) greater_end = max(prev.get_end_time(), block.get_end_time()) block = timeblocks.TimeBlock(prev.get_start_time(), greater_end, description) prev = block consolidated.append(prev) return consolidated
def test_overlap(): block1 = timeblocks.TimeBlock(arrow.get("2017-11-07T12:00:00-08:00"),arrow.get("2017-11-07T15:00:00-08:00"), "Block 1") block2 = timeblocks.TimeBlock(arrow.get("2017-11-07T10:00:00-08:00"),arrow.get("2017-11-07T12:01:00-08:00"), "Block 2") block3 = timeblocks.TimeBlock(arrow.get("2017-11-07T09:00:00-08:00"),arrow.get("2017-11-07T16:00:00-08:00")) block4 = timeblocks.TimeBlock(arrow.get("2017-11-07T15:00:00-08:00"),arrow.get("2017-11-07T23:00:00-08:00")) assert(block1.get_overlap(block2) == timeblocks.TimeBlock(arrow.get("2017-11-07T12:00:00-08:00"), arrow.get("2017-11-07T12:01:00-08:00"), "Block 1, Block 2")) assert(block2.get_overlap(block3) == timeblocks.TimeBlock(arrow.get("2017-11-07T10:00:00-08:00"),arrow.get("2017-11-07T12:01:00-08:00"),"Block 2")) assert(block4.get_overlap(block1) == timeblocks.TimeBlock(arrow.get("2017-11-07T15:00:00-08:00"),arrow.get("2017-11-07T15:00:00-08:00"),"Block 1"))
def test_consolidate(): block1 = timeblocks.TimeBlock(arrow.get("2017-11-07T07:00:00-08:00"), arrow.get("2017-11-07T10:15:00-08:00"), "Block 1") block2 = timeblocks.TimeBlock(arrow.get("2017-11-07T10:15:00-08:00"), arrow.get("2017-11-07T13:45:00-08:00"), "Block 2") block3 = timeblocks.TimeBlock(arrow.get("2017-11-07T10:30:00-08:00"), arrow.get("2017-11-07T11:30:00-08:00"), "Block 3") block4 = timeblocks.TimeBlock(arrow.get("2017-11-08T08:30:00-08:00"), arrow.get("2017-11-08T10:30:00-08:00"), "Block 4") block5 = timeblocks.TimeBlock(arrow.get("2017-11-08T09:30:00-08:00"), arrow.get("2017-11-08T12:00:00-08:00")) block6 = timeblocks.TimeBlock(arrow.get("2017-11-08T15:30:00-08:00"), arrow.get("2017-11-08T17:00:00-08:00"), "Block 6") block_list = [block1, block2, block3, block4, block5, block6] c_list = calc_free_times.consolidate(block_list) correct = [ timeblocks.TimeBlock(arrow.get("2017-11-07T07:00:00-08:00"), arrow.get("2017-11-07T13:45:00-08:00"), "Block 1, Block 2, Block 3"), timeblocks.TimeBlock(arrow.get("2017-11-08T08:30:00-08:00"), arrow.get("2017-11-08T12:00:00-08:00"), "Block 4"), timeblocks.TimeBlock(arrow.get("2017-11-08T15:30:00-08:00"), arrow.get("2017-11-08T17:00:00-08:00"), "Block 6") ] assert (correct == c_list) assert (calc_free_times)
def get_time_blocks(busy_blocks, begin_date, end_date, begin_time, end_time): """ @brief Calculates a list of TimeBlocks (Free and Busy) @param busy_blocks(list of TimeBlock Objects) The list of busy times @param begin_date (str): beginning date of date range. ISO formated date EX. 2017-11-15T00:00:00-08:00 @param end_date (str): ending day of date range. ISO formated date EX. 2017-11-21T00:00:00-08:00 @param begin_time (str): beginning time of time range. Format HH:mm EX. 08:00 @param end_time (str): ending time of time range. Format HH:mm EX. 17:00 @return A List of TimeBlocks (free and busy sorted in ascending order) """ # Setup arrow objects for time and date range begin_hr, begin_min = list(map(int, begin_time.split(":"))) end_hr, end_min = list(map(int, end_time.split(":"))) open_time = arrow.get(begin_date).replace(hour=begin_hr, minute=begin_min) open_year, open_month, open_day = list( map(int, open_time.format("YYYY:MM:DD").split(":"))) end_time = arrow.get(end_date).replace(hour=end_hr, minute=end_min) # consolidate and trim the list of busy_blocks consolidated = consolidate(busy_blocks) trimmed = trim_blocks(consolidated, open_time, end_time) # add free TimeBlocks pointer = open_time current_day_start = open_time current_day_end = end_time.replace(year=open_year, month=open_month, day=open_day) times = [] # blocked times for block in trimmed: # Case: our pointer is at the end of a day's time range if pointer == current_day_end: current_day_start = current_day_start.shift(days=+1) current_day_end = current_day_end.shift(days=+1) pointer = current_day_start if pointer.time == block.get_start_time(): pointer.time == block.get_end_time() # Pointer and current event are on same day if int(pointer.format("DD")) == int( block.get_start_time().format("DD")): free_block = timeblocks.TimeBlock(pointer, block.get_start_time()) pointer = block.get_end_time() times.append(free_block) # Idea: stall going onto next block unitl pointer reaches current block_end and has filled that space with free time blocks else: fill_gaps(pointer, current_day_start, current_day_end, block, times) pointer = block.get_end_time() y, m, d = list(map(int, pointer.format("YYYY:MM:DD").split(":"))) current_day_start = current_day_start.replace(year=y, month=m, day=d) current_day_end = current_day_end.replace(year=y, month=m, day=d) times.append(block) # If the last Event ends before the window closes, make the rest free TimeBlocks if pointer != end_time: fill_gaps(pointer, current_day_start, current_day_end, timeblocks.TimeBlock(end_time, None), times) return times
def trim_day(event_block, day_slices, event_slices): """ @brief Trims a TimeBlock so that it properly fits the time and date range @param event_block(TimeBlock) A TimeBlock object @param day_slices (list): created by the function "create_daily_slices" @return a list of TimeBlocks, each trimmed to fit the bounds of time range. For Example. A timeblock from 8:00 am - 4:00 pm would be trimmed down to 9:00am - 3:00pm when the given timerange is 9:00am-3:00pm. Addittionally, one event that spans over multiple days will be split into separate time blocks. One for each day """ trimmed_blocks = [] day_start = day_slices[0] event_start = event_slices[0] l = 0 r = 0 while day_start.format("YYYY/MM/DD") != event_start.format("YYYY/MM/DD"): if day_start < event_start: l += 1 day_start = day_start.shift(days=+1) else: r += 1 event_start = event_start.shift(days=+1) l *= 2 r *= 2 while l < len(day_slices) and r < len(event_slices): day_start = day_slices[l] day_end = day_slices[l + 1] event_start = event_slices[r] event_end = event_slices[r + 1] # The event_block will lay within no more of our slices if day_start > event_end: break # The event starts before and ends after our date/time range, just add the whole date/time range elif event_start <= day_start and event_end >= day_end: trimmed_blocks.append( timeblocks.TimeBlock(day_start, day_end, event_block.get_description())) # The event is completely within our slice elif day_start <= event_start and day_end >= event_end: trimmed_blocks.append( timeblocks.TimeBlock(event_start, event_end, event_block.get_description())) # break elif (event_start <= day_start) and (event_end <= day_end) and ( event_end >= day_start): trimmed_blocks.append( timeblocks.TimeBlock(day_start, event_end, event_block.get_description())) # break elif day_start <= event_start and event_end >= day_end and event_start <= day_end: trimmed_blocks.append( timeblocks.TimeBlock(event_start, day_end, event_block.get_description())) l += 2 r += 2 return trimmed_blocks
def test_get(): block1 = timeblocks.TimeBlock(arrow.get("2017-11-07T14:00:00-08:00"),arrow.get("2017-11-07T15:00:00-08:00"), "Block 1") assert(block1.get_description() == "Block 1") assert(block1.get_start_time() == arrow.get("2017-11-07T14:00:00-08:00")) assert(block1.get_end_time() == arrow.get("2017-11-07T15:00:00-08:00"))
def test_append(): block1 = timeblocks.TimeBlock(arrow.get("2017-11-07T12:00:00-08:00"),arrow.get("2017-11-07T15:00:00-08:00"), "Block 1") block2 = timeblocks.TimeBlock(arrow.get("2017-11-07T15:00:00-08:00"),arrow.get("2017-11-07T17:00:00-08:00"), "Block 2") assert(block1.append_block(block2) == timeblocks.TimeBlock(arrow.get("2017-11-07T12:00:00-08:00"),arrow.get("2017-11-07T17:00:00-08:00"),"Block 1, Block 2"))
def test_trim(): ### Test Cases for trim_day timerange_open = arrow.get("2017-10-08T08:20:00-08:00") timerange_close = arrow.get("2017-10-18T11:59:00-08:00") block = timeblocks.TimeBlock(arrow.get("2017-10-07T06:30:00-08:00"), arrow.get("2017-10-14T10:08:00-08:00"), "Away on Holiday") slices = calc_free_times.create_daily_slices(timerange_open, timerange_close) event_slices = calc_free_times.create_daily_slices(block.get_start_time(), block.get_end_time()) trimmed = calc_free_times.trim_day(block, slices, event_slices) correct = [ timeblocks.TimeBlock(arrow.get("2017-10-08T08:20:00-08:00"), arrow.get("2017-10-08T10:08:00-08:00"), "Away on Holiday"), timeblocks.TimeBlock(arrow.get("2017-10-09T08:20:00-08:00"), arrow.get("2017-10-09T10:08:00-08:00"), "Away on Holiday"), timeblocks.TimeBlock(arrow.get("2017-10-10T08:20:00-08:00"), arrow.get("2017-10-10T10:08:00-08:00"), "Away on Holiday"), timeblocks.TimeBlock(arrow.get("2017-10-11T08:20:00-08:00"), arrow.get("2017-10-11T10:08:00-08:00"), "Away on Holiday"), timeblocks.TimeBlock(arrow.get("2017-10-12T08:20:00-08:00"), arrow.get("2017-10-12T10:08:00-08:00"), "Away on Holiday"), timeblocks.TimeBlock(arrow.get("2017-10-13T08:20:00-08:00"), arrow.get("2017-10-13T10:08:00-08:00"), "Away on Holiday"), timeblocks.TimeBlock(arrow.get("2017-10-14T08:20:00-08:00"), arrow.get("2017-10-14T10:08:00-08:00"), "Away on Holiday") ] assert (trimmed == correct) block2 = timeblocks.TimeBlock(arrow.get("2017-10-14T05:30:00-08:00"), arrow.get("2017-10-14T20:15:00-08:00"), "Busy All Day") event_slices = calc_free_times.create_daily_slices(block2.get_start_time(), block2.get_end_time()) trimmed = calc_free_times.trim_day(block2, slices, event_slices) correct = [ timeblocks.TimeBlock(arrow.get("2017-10-14T08:20:00-08:00"), arrow.get("2017-10-14T11:59:00-08:00"), "Busy All Day") ] assert (trimmed == correct) block3 = timeblocks.TimeBlock(arrow.get("2017-10-18T10:30:00-08:00"), arrow.get("2017-10-18T20:15:00-08:00"), "Busy 1/2 Day") event_slices = calc_free_times.create_daily_slices(block3.get_start_time(), block3.get_end_time()) trimmed = calc_free_times.trim_day(block2, slices, event_slices) correct = [ timeblocks.TimeBlock(arrow.get("2017-10-18T10:30:00-08:00"), arrow.get("2017-10-18T11:59:00-08:00"), "Busy 1/2 Day") ] ### Test case for trim_blocks timerange_open = arrow.get("2017-11-07T10:20:00-08:00") timerange_close = arrow.get("2017-11-11T15:59:00-08:00") blocks = [ timeblocks.TimeBlock(arrow.get("2017-11-07T07:00:00-08:00"), arrow.get("2017-11-07T13:45:00-08:00"), "Block 1, Block 2, Block 3"), timeblocks.TimeBlock(arrow.get("2017-11-10T15:30:00-08:00"), arrow.get("2017-11-12T17:00:00-08:00"), "Block 6") ] trimmed = calc_free_times.trim_blocks(blocks, timerange_open, timerange_close) correct = [ timeblocks.TimeBlock(arrow.get("2017-11-07T10:20:00-08:00"), arrow.get("2017-11-07T13:45:00-08:00"), "Block 1, Block 2, Block 3"), timeblocks.TimeBlock(arrow.get("2017-11-10T15:30:00-08:00"), arrow.get("2017-11-10T15:59:00-08:00"), "Block 6"), timeblocks.TimeBlock(arrow.get("2017-11-11T15:30:00-08:00"), arrow.get("2017-11-11T15:59:00-08:00"), "Block 6") ] assert (trimmed == correct)
def test_free_times(): begin_date = "2017-10-07T00:00:00-08:00" begin_time = "06:15" end_date = "2017-10-11T00:00:00-08:00" end_time = "15:59" busy_blocks = [ timeblocks.TimeBlock(arrow.get("2017-10-08T08:20:00-08:00"), arrow.get("2017-10-08T10:00:00-08:00"), "B2"), timeblocks.TimeBlock(arrow.get("2017-10-09T06:15:00-08:00"), arrow.get("2017-10-09T15:59:00-08:00"), "B1"), timeblocks.TimeBlock(arrow.get("2017-10-10T13:55:00-08:00"), arrow.get("2017-10-10T15:59:00-08:00"), "B4") ] blocks = calc_free_times.get_time_blocks(busy_blocks, begin_date, end_date, begin_time, end_time) correct = [ timeblocks.TimeBlock(arrow.get("2017-10-07T06:15:00-08:00"), arrow.get("2017-10-07T15:59:00-08:00")), timeblocks.TimeBlock(arrow.get("2017-10-08T06:15:00-08:00"), arrow.get("2017-10-08T08:20:00-08:00")), timeblocks.TimeBlock(arrow.get("2017-10-08T08:20:00-08:00"), arrow.get("2017-10-08T10:00:00-08:00"), "B2"), timeblocks.TimeBlock(arrow.get("2017-10-08T10:00:00-08:00"), arrow.get("2017-10-08T15:59:00-08:00")), timeblocks.TimeBlock(arrow.get("2017-10-09T06:15:00-08:00"), arrow.get("2017-10-09T15:59:00-08:00"), "B1"), timeblocks.TimeBlock(arrow.get("2017-10-10T06:15:00-08:00"), arrow.get("2017-10-10T13:55:00-08:00")), timeblocks.TimeBlock(arrow.get("2017-10-10T13:55:00-08:00"), arrow.get("2017-10-10T15:59:00-08:00"), "B4"), timeblocks.TimeBlock(arrow.get("2017-10-11T06:15:00-08:00"), arrow.get("2017-10-11T15:59:00-08:00")) ] assert (blocks == correct)