def working_datetime_ranges_of_date(d, special_working_hours={}, week_working_hours={}, merge_tomorrow=True): """ Returns a list of datetimes tuples (datetime_range), indicating contiguous working periods of given date, if merge_tomorrow check if first period of tomorrow is contiguous and merge with last of today. """ # curried on working hours whs_by_date = partial(working_hours_of_date, special_working_hours=special_working_hours, week_working_hours=week_working_hours) # curried on date whs_to_dt_ranges = partial(working_hours_to_datetime_ranges, d) today_working_hours = whs_by_date(d) if not len(today_working_hours): return [] if not merge_tomorrow: return whs_to_dt_ranges(today_working_hours) tomorrow_working_hours = whs_by_date(tomorrow(d)) if are_working_hours_contiguous(today_working_hours, tomorrow_working_hours): # last range of today become a merged range between # the last of today and the first of tomorrow next_day = tomorrow(d) # when tomorrow working hour end at 00:00, certainly is (00:00, 00:00) # because is a contiguous with today working hours, in this case # we add a day to current date because end at 00:00 of day after # this cover 24/7 like situation if tomorrow_working_hours[0][1] == time(0): next_day = tomorrow(next_day) last_period = ( datetime.combine(d, today_working_hours[-1][0]), datetime.combine(next_day, tomorrow_working_hours[0][1]) ) return whs_to_dt_ranges(today_working_hours[:-1]) + [last_period] return whs_to_dt_ranges(today_working_hours)
def is_datetime_range_available(dt_range, availability={}): """ Checks if a datetime_range is compatible with availability. """ a = defaulitize_availability(availability) start_date, end_date = dt_range[0].date(), dt_range[1].date() # check if working_hours of date by current availability # contains current datetime_range def contains_dt_range_in_wh_of_date(d, merge_tomorrow=True): working_dt_ranges = working_datetime_ranges_of_date( d, a['special_working_hours'], a['week_working_hours'], merge_tomorrow=merge_tomorrow) return any_match(partial(flip(contains), dt_range), working_dt_ranges) if (is_date_available(start_date, a) and contains_dt_range_in_wh_of_date( start_date, merge_tomorrow=is_date_available(tomorrow(start_date), a))): return True if is_same_date(dt_range): return (is_date_available(yesterday(start_date), a) and contains_dt_range_in_wh_of_date(yesterday(start_date), merge_tomorrow=False)) return False
def by_time_range(time_range, d): """ Create a new datetime_range by a time_range and date object. """ start_time, end_time = time_range if end_time <= start_time: return datetime.combine(d, start_time), datetime.combine(tomorrow(d), end_time) else: return datetime.combine(d, start_time), datetime.combine(d, end_time)
def nearest_working_datetime_range(dt_range, availability={}): """ Nearest working datetime_range by datetime_range. """ a = defaulitize_availability(availability) start_date = dt_range[0].date() if not is_date_available(start_date, a): return None tomorrow_available = is_date_available(tomorrow(start_date), a) working_dt_ranges = working_datetime_ranges_of_date( start_date, a['special_working_hours'], a['week_working_hours'], merge_tomorrow=tomorrow_available) is_near = partial(flip(end_after_or_eq), dt_range) return first_match(is_near, working_dt_ranges)