def _test_time_range(): # Tests that should fail fail_inputs = [ "", # Empty _string "asdf", # No valid input "9:45 am", # Only one _time "9:45 am - ", # Only one _time with separator ] for input in fail_inputs: assert _time_range(input) is None # Tests that should pass pass_without_tail_input = "9:45 am - 10:15 pm" (data, rest) = _time_range(pass_without_tail_input) assert data == [{ "open_time": DatetimeModWeek(0, 9, 45), "close_time": DatetimeModWeek(0, 22, 15) }] assert rest == "" pass_with_tail_input = "4:15 pm - 2:38 am Monday" (data, rest) = _time_range(pass_with_tail_input) assert data == [{ "open_time": DatetimeModWeek(0, 16, 15), "close_time": DatetimeModWeek(0, 2, 38) }] assert rest == " Monday"
def _test_weekday(): fail_inputs = ["", "notaweekday"] for fail_input in fail_inputs: assert _weekday(fail_input) is None for (index, day) in enumerate(list(calendar.day_abbr)): (data, rest) = _weekday(day) assert data == [{"days": [DatetimeModWeek(index, 0, 0)]}] assert rest == "" tail = "-Fri" for (index, day) in enumerate(list(calendar.day_abbr)): (data, rest) = _weekday(day + tail) assert data == [{"days": [DatetimeModWeek(index, 0, 0)]}] assert rest == tail
def _test_sequence(): parsers = [_weekday, _char("-"), _weekday] fail_inputs = ["", "Mon?"] for fail_input in fail_inputs: assert _sequence(parsers)(fail_input) is None pass_input = "Mon-Fri" (data, rest) = _sequence(parsers)(pass_input) assert data == [{ "days": [DatetimeModWeek(0, 0, 0)] }, { "days": [DatetimeModWeek(4, 0, 0)] }] assert rest == ""
def _time(input): if result := _sequence([ _either([_sequence([_hour, _char(":"), _minute]), _hour]), _char(" "), _either([_string("am"), _string("pm")]) ])(input): (data, rest) = result is_pm = data.pop()["string"] == "pm" if "minute" in data[-1]: found_minute = data.pop()["minute"] else: found_minute = 0 found_hour = data.pop()["hour"] if found_hour == 12: # PM spans [12, 1, ... 10, 11]. # Make PM actually span [1...12] is_pm = not is_pm if is_pm: found_hour = (found_hour + 12) % 24 # Convert to 24 _hour clock with range [0, 23] found_time = DatetimeModWeek(0, found_hour, found_minute) return ([{"time": found_time}], rest)
def find_open_restaurants(csv_filename, search_datetime): with open(csv_filename, newline="") as f: entries = list(csv.reader(f)) search_datetime_modular = DatetimeModWeek(search_datetime.weekday(), search_datetime.hour, search_datetime.minute) # Rearrange CSV into useful data structure restaurants = [] for entry in entries: restaurants.append({"name": entry[0], "hours_string": entry[1]}) for (i, rest) in enumerate(restaurants): result = parse(rest["hours_string"]) assert result is not None (data, rest) = result assert rest == "" restaurants[i]["hours_datetimes"] = data open_restaurants = [] for rest in restaurants: for hour_range in rest["hours_datetimes"]: if datetime_in_range(hour_range["open_datetime"], hour_range["close_datetime"], search_datetime_modular): open_restaurants.append(rest["name"]) return open_restaurants
def _test_n_or_more(): # Tests that should fail: fail_inputs = ["", "a", "ab"] for fail_input in fail_inputs: assert _n_or_more(_char("a"), 2)(fail_input) is None # Tests that should pass pass_without_tail_input = "aaaaaa" (data, rest) = _n_or_more(_char("a"), 6)(pass_without_tail_input) assert data == [] assert rest == "" pass_with_tail_input = "aaaaaaBanana" (data, rest) = _n_or_more(_char("a"), 6)(pass_with_tail_input) assert data == [] assert rest == "Banana" pass_with_return_data_input = "MonTueWed" (data, rest) = _n_or_more(_weekday, 2)(pass_with_return_data_input) assert data == [{ "days": [DatetimeModWeek(0, 0, 0)] }, { "days": [DatetimeModWeek(1, 0, 0)] }, { "days": [DatetimeModWeek(2, 0, 0)] }] assert rest == "" n_equals_zero_on_empty_string_input = "" (data, rest) = _n_or_more(_char("a"), 0)(n_equals_zero_on_empty_string_input) assert data == [] assert rest == "" n_equals_zero_on_tail_input = "sdfg" (data, rest) = _n_or_more(_char("a"), 0)(n_equals_zero_on_tail_input) assert data == [] assert rest == "sdfg"
def _datetime(input): if result := _sequence([_days, _char(" "), _time_range])(input): (data, rest) = result times_found = data.pop() days_all_found = data.pop()["days_all"] hours = [] if times_found["close_time"] < times_found["open_time"]: day_rollover = DatetimeModWeek(1, 0, 0) else: day_rollover = DatetimeModWeek(0, 0, 0) for day_found in days_all_found: hours.append({ "open_datetime": day_found + times_found["open_time"], "close_datetime": day_found + day_rollover + times_found["close_time"], }) return (hours, rest)
def _weekday(input): if input == "": return None if input[0:3] in list(calendar.day_abbr): day_num = list(calendar.day_abbr).index(input[0:3]) return ( [{ "days": [DatetimeModWeek(day_num, 0, 0)] }], input[3:], ) else: return None
def _day_range(input): if result := _sequence([_weekday, _char("-"), _weekday])(input): (data, rest) = result start_day = data[0]["days"][0] end_day = data[1]["days"][0] # Make range with modular arithmetic one_day = DatetimeModWeek(1, 0, 0) current_day = start_day _days = [] # Add one_day to include end_day in generated range while current_day != (end_day + one_day): _days.append(current_day) current_day += one_day data = [{"days": _days}] return (data, rest)
def _test_time(): # Tests that should fail fail_inputs = ["", "abcde", "12cde", "12:45 cde"] for fail_input in fail_inputs: assert _time(fail_input) is None # Tests that should pass single_digit_input = "1:02 am" (data, rest) = _time(single_digit_input) assert data == [{"time": DatetimeModWeek(0, 1, 2)}] assert rest == "" single_digit_with_tail_input = "3:05 am banana" (data, rest) = _time(single_digit_with_tail_input) assert data == [{"time": DatetimeModWeek(0, 3, 5)}] assert rest == " banana" double_digit_input = "10:56 am" (data, rest) = _time(double_digit_input) assert data == [{"time": DatetimeModWeek(0, 10, 56)}] assert rest == "" pm_input = "6:24 pm" (data, rest) = _time(pm_input) assert data == [{"time": DatetimeModWeek(0, 18, 24)}] assert rest == "" noon_pm_input = "12:56 pm" (data, rest) = _time(noon_pm_input) assert data == [{"time": DatetimeModWeek(0, 12, 56)}] assert rest == "" midnight_am_input = "12:43 am" (data, rest) = _time(midnight_am_input) assert data == [{"time": DatetimeModWeek(0, 0, 43)}] assert rest == "" no_minute_input = "9 am" (data, rest) = _time(no_minute_input) assert data == [{"time": DatetimeModWeek(0, 9, 0)}] assert rest == ""
def _test_parse(): # Tests that should fail fail_inputs = [ "", # Empty _string "asdf", # No valid input "Mon, Wed-Fri", # Just _days "Tue-Thu, Sat 9:45 am", # Missing end _time ] for fail_input in fail_inputs: assert parse(fail_input) is None # Tests that should pass single_datetime_input = "Mon 9 am - 4 pm" (data, rest) = parse(single_datetime_input) assert data == [{ "open_datetime": DatetimeModWeek(0, 9, 0), "close_datetime": DatetimeModWeek(0, 16, 0) }] assert rest == "" datetime_with_tail_input = "Tue-Thu 8 am - 9 pm Banana" (data, rest) = parse(datetime_with_tail_input) assert data == [{ "open_datetime": DatetimeModWeek(1, 8, 0), "close_datetime": DatetimeModWeek(1, 21, 0) }, { "open_datetime": DatetimeModWeek(2, 8, 0), "close_datetime": DatetimeModWeek(2, 21, 0) }, { "open_datetime": DatetimeModWeek(3, 8, 0), "close_datetime": DatetimeModWeek(3, 21, 0) }] assert rest == " Banana" multiple_datetimes_input = "Mon-Wed, Fri 8:00 am - 4:30 pm / Sat 10 am - 2:30 pm" (data, rest) = parse(multiple_datetimes_input) assert data == [ { "open_datetime": DatetimeModWeek(0, 8, 0), "close_datetime": DatetimeModWeek(0, 16, 30) }, { "open_datetime": DatetimeModWeek(1, 8, 0), "close_datetime": DatetimeModWeek(1, 16, 30) }, { "open_datetime": DatetimeModWeek(2, 8, 0), "close_datetime": DatetimeModWeek(2, 16, 30) }, { "open_datetime": DatetimeModWeek(4, 8, 0), "close_datetime": DatetimeModWeek(4, 16, 30) }, { "open_datetime": DatetimeModWeek(5, 10, 0), "close_datetime": DatetimeModWeek(5, 14, 30) }, ] assert rest == ""
def _test_datetime(): # Tests that should fail fail_inputs = [ "", # Empty _string "asdf", # No valid input "Mon, Wed-Fri ", # Just _days "Tue-Thu, Sat 9:45 am", # Missing end _time ] for fail_input in fail_inputs: assert _datetime(fail_input) is None # Tests that should pass single_day_input = "Mon 9:45 am - 6 pm" (data, rest) = _datetime(single_day_input) assert data == [{ "open_datetime": DatetimeModWeek(0, 9, 45), "close_datetime": DatetimeModWeek(0, 18, 0) }] assert rest == "" multiple_day_input = "Mon-Wed, Fri 10:15 am - 5 pm" (data, rest) = _datetime(multiple_day_input) assert data == [{ "open_datetime": DatetimeModWeek(0, 10, 15), "close_datetime": DatetimeModWeek(0, 17, 0) }, { "open_datetime": DatetimeModWeek(1, 10, 15), "close_datetime": DatetimeModWeek(1, 17, 0) }, { "open_datetime": DatetimeModWeek(2, 10, 15), "close_datetime": DatetimeModWeek(2, 17, 0) }, { "open_datetime": DatetimeModWeek(4, 10, 15), "close_datetime": DatetimeModWeek(4, 17, 0) }] assert rest == "" day_overflow_input = "Mon 1 pm - 2:30 am" (data, rest) = _datetime(day_overflow_input) assert data == [{ "open_datetime": DatetimeModWeek(0, 13, 0), "close_datetime": DatetimeModWeek(1, 2, 30) }] assert rest == "" week_overflow_input = "Sun 11 am - 4:15 am" (data, rest) = _datetime(week_overflow_input) assert data == [{ "open_datetime": DatetimeModWeek(6, 11, 0), "close_datetime": DatetimeModWeek(0, 4, 15) }] assert rest == ""
def _test_days(): # Tests that should fail fail_inputs = ["", " Mon"] for fail_input in fail_inputs: assert _days(fail_input) is None # Tests that should pass single_day_input = "Wed" (data, rest) = _days(single_day_input) assert data == [{"days_all": [DatetimeModWeek(2, 0, 0)]}] assert rest == "" day_range_input = "Mon-Fri" (data, rest) = _days(day_range_input) assert data == [{ "days_all": [ DatetimeModWeek(0, 0, 0), DatetimeModWeek(1, 0, 0), DatetimeModWeek(2, 0, 0), DatetimeModWeek(3, 0, 0), DatetimeModWeek(4, 0, 0), ] }] assert rest == "" days_input = "Mon-Wed, Fri" (data, rest) = _days(days_input) assert data == [{ "days_all": [ DatetimeModWeek(0, 0, 0), DatetimeModWeek(1, 0, 0), DatetimeModWeek(2, 0, 0), DatetimeModWeek(4, 0, 0), ] }] assert rest == "" days_with_rollover_input = "Wed, Sat-Tue" (data, rest) = _days(days_with_rollover_input) assert data == [{ "days_all": [ DatetimeModWeek(2, 0, 0), DatetimeModWeek(5, 0, 0), DatetimeModWeek(6, 0, 0), DatetimeModWeek(0, 0, 0), DatetimeModWeek(1, 0, 0) ] }] assert rest == "" pass_with_tail_input = "Mon-Tue, Thu, Sat-Sun 9:00" (data, rest) = _days(pass_with_tail_input) assert data == [{ "days_all": [ DatetimeModWeek(0, 0, 0), DatetimeModWeek(1, 0, 0), DatetimeModWeek(3, 0, 0), DatetimeModWeek(5, 0, 0), DatetimeModWeek(6, 0, 0), ] }] assert rest == " 9:00"
def _test_day_range(): fail_input = "Mon-Cat" assert _day_range(fail_input) is None pass_without_tail_input = "Wed-Sat" (data, rest) = _day_range(pass_without_tail_input) assert data == [{ "days": [ DatetimeModWeek(2, 0, 0), DatetimeModWeek(3, 0, 0), DatetimeModWeek(4, 0, 0), DatetimeModWeek(5, 0, 0), ] }] assert rest == "" pass_with_tail_input = "Mon-Fri " (data, rest) = _day_range(pass_with_tail_input) assert data == [{ "days": [ DatetimeModWeek(0, 0, 0), DatetimeModWeek(1, 0, 0), DatetimeModWeek(2, 0, 0), DatetimeModWeek(3, 0, 0), DatetimeModWeek(4, 0, 0), ] }] assert rest == " " pass_with_overflow_input = "Sat-Tue" (data, rest) = _day_range(pass_with_overflow_input) assert data == [{ "days": [ DatetimeModWeek(5, 0, 0), DatetimeModWeek(6, 0, 0), DatetimeModWeek(0, 0, 0), DatetimeModWeek(1, 0, 0), ] }] assert rest == ""