From 88eb9e5499d0dff65bd773e023321fad907c75c8 Mon Sep 17 00:00:00 2001 From: Michael Krasnyk Date: Wed, 7 Dec 2016 13:02:52 +0100 Subject: [PATCH] Added opening_hours grammar --- include/util/opening_hours.hpp | 637 ++++++++++++++++++++++ unit_tests/util/opening_hours_parsing.cpp | 279 ++++++++++ 2 files changed, 916 insertions(+) create mode 100644 include/util/opening_hours.hpp create mode 100644 unit_tests/util/opening_hours_parsing.cpp diff --git a/include/util/opening_hours.hpp b/include/util/opening_hours.hpp new file mode 100644 index 000000000..957a27442 --- /dev/null +++ b/include/util/opening_hours.hpp @@ -0,0 +1,637 @@ +#ifndef OSRM_OPENING_HOURS_HPP +#define OSRM_OPENING_HOURS_HPP + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace osrm +{ +namespace util +{ + +// Helper classes for "opening hours" format http://wiki.openstreetmap.org/wiki/Key:opening_hours +// Supported simplified features in CheckOpeningHours: +// - Year/Month/Day ranges +// - Weekday ranges +// - Time ranges +// Not supported: +// - Week numbers +// - Holidays, events, variables dates +// - Day offsets and periodic ranges +struct OpeningHours +{ + enum Modifier + { + unknown, + open, + closed, + off, + is24_7 + }; + + struct Time + { + enum Event : unsigned char + { + invalid, + none, + dawn, + sunrise, + sunset, + dusk + }; + + Event event; + std::int32_t minutes; + + Time() : event(invalid), minutes(0) {} + Time(Event event) : event(event), minutes(0) {} + Time(char hour, char min) : event(none), minutes(hour * 60 + min) {} + Time(Event event, bool positive, const Time &offset) + : event(event), minutes(positive ? offset.minutes : -offset.minutes) + { + } + }; + + struct TimeSpan + { + Time from, to; + TimeSpan() = default; + TimeSpan(const Time &from_, const Time &to_) : from(from_), to(to_) + { + if (to.minutes < from.minutes) + to.minutes += 24 * 60; + } + + bool IsInRange(const struct tm &time, bool &use_curr_day, bool &use_next_day) const + { + // TODO: events are not handled + if (from.event != OpeningHours::Time::none || to.event != OpeningHours::Time::none) + return false; + + const auto minutes = time.tm_hour * 60 + time.tm_min; + if (to.minutes > 24 * 60) + { + use_curr_day = (from.minutes <= minutes); // in range [from, 24:00) current day + use_next_day = (minutes < to.minutes - 24 * 60); // in range [00:00, to) next day + } + else + { + use_curr_day = + (from.minutes <= minutes && minutes < to.minutes); // in range [from, to) + use_next_day = false; // do not use the next day + } + + return use_curr_day || use_next_day; + } + }; + + struct WeekdayRange + { + int weekdays, overnight_weekdays; + WeekdayRange() = default; + WeekdayRange(unsigned char from, unsigned char to) + { + // weekdays mask for [from, to], e.g [2, 5] -> 0111100, [5, 2] -> 1100111, + // [3, 3] -> 0001000, [0,6] -> 1111111, [6,0] -> 1000001, [4, 3] -> 1111111 + weekdays = (from <= to) ? ((1 << (to - from + 1)) - 1) << from + : ~(((1 << (from - to - 1)) - 1) << (to + 1)); + weekdays &= 0x7f; + overnight_weekdays = (weekdays << 1) | (weekdays & 0x40 ? 1 : 0); + } + + bool IsInRange(const struct tm &time, bool use_curr_day, bool use_next_day) const + { + return (use_curr_day && weekdays & (1 << time.tm_wday)) || + (use_next_day && overnight_weekdays & (1 << time.tm_wday)); + } + }; + + struct Monthday + { + int year; + char month; + char day; + Monthday() = default; + Monthday(int year) : year(year), month(0), day(0) {} + Monthday(int year, char month, char day) : year(year), month(month), day(day) {} + + bool IsValid() const { return year > 0 || month != 0 || day != 0; } + bool operator==(const Monthday &rhs) const + { + return std::tie(year, month, day) == std::tie(rhs.year, rhs.month, rhs.day); + } + }; + + struct MonthdayRange + { + Monthday from, to; + MonthdayRange() : from(0, 0, 0), to(0, 0, 0) {} + MonthdayRange(const Monthday &from, const Monthday &to) : from(from), to(to) {} + + bool IsInRange(const struct tm &time, bool use_curr_day, bool use_next_day) const + { + using boost::gregorian::date; + using boost::gregorian::date_duration; + + const auto year = time.tm_year + 1900; + const auto month = time.tm_mon + 1; + + date date_current(year, month, time.tm_mday); + date date_from(boost::gregorian::min_date_time); + date date_to(boost::gregorian::max_date_time); + + if (from.IsValid()) + { + date_from = (from.day == 0) ? date(from.year == 0 ? year : from.year, + from.month == 0 ? month : from.month, + 1) + : date(from.year == 0 ? year : from.year, + from.month == 0 ? month : from.month, + from.day); + } + if (to.IsValid()) + { + date_to = date(to.year == 0 ? (from.year == 0 ? year : from.year) : to.year, + to.month == 0 ? (from.month == 0 ? month : from.month) : to.month, + 1); + date_to = (to.day == 0) ? date_to.end_of_month() + : date(date_to.year(), date_to.month(), to.day); + } + else if (to == Monthday()) + { + date_to = date_from; + } + + if (!use_curr_day) + date_from += date_duration(1); + if (use_next_day && date_to != date(boost::gregorian::max_date_time)) + date_to += date_duration(1); + + return date_from <= date_current && date_current <= date_to; + } + }; + + OpeningHours() : modifier(open) {} + + bool IsInRange(const struct tm &time) const + { + bool use_curr_day = true; // the first matching time uses the current day + bool use_next_day = false; // the first matching time uses the next day + return + // the value is in range if time is not specified or is in any time range + // (also modifies use_curr_day and use_next_day flags to handle overnight day ranges, + // e.g. for 22:00-03:00 and 2am -> use_curr_day = false and use_next_day = true) + (times.empty() || std::any_of(times.begin(), + times.end(), + [&time, &use_curr_day, &use_next_day](const auto &x) { + return x.IsInRange(time, use_curr_day, use_next_day); + })) + // .. and if weekdays are not specified or matches weekdays range + && (weekdays.empty() || + std::any_of(weekdays.begin(), + weekdays.end(), + [&time, use_curr_day, use_next_day](const auto &x) { + return x.IsInRange(time, use_curr_day, use_next_day); + })) + // .. and if month-day ranges are not specified or is in any month-day range + && (monthdays.empty() || + std::any_of(monthdays.begin(), + monthdays.end(), + [&time, use_curr_day, use_next_day](const auto &x) { + return x.IsInRange(time, use_curr_day, use_next_day); + })); + } + + std::vector times; + std::vector weekdays; + std::vector monthdays; + Modifier modifier; +}; + +#ifndef NDEBUG +// Debug output stream operators for use with BOOST_SPIRIT_DEBUG +inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::Modifier value) +{ + switch (value) + { + case OpeningHours::unknown: + return stream << "unknown"; + case OpeningHours::open: + return stream << "open"; + case OpeningHours::closed: + return stream << "closed"; + case OpeningHours::off: + return stream << "off"; + case OpeningHours::is24_7: + return stream << "24/7"; + } + return stream; +} + +inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::Time::Event value) +{ + switch (value) + { + case OpeningHours::Time::dawn: + return stream << "dawn"; + case OpeningHours::Time::sunrise: + return stream << "sunrise"; + case OpeningHours::Time::sunset: + return stream << "sunset"; + case OpeningHours::Time::dusk: + return stream << "dusk"; + default: + break; + } + return stream; +} + +inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::Time &value) +{ + boost::io::ios_flags_saver ifs(stream); + if (value.event == OpeningHours::Time::invalid) + return stream << "???"; + if (value.event == OpeningHours::Time::none) + return stream << std::setfill('0') << std::setw(2) << value.minutes / 60 << ":" + << std::setfill('0') << std::setw(2) << value.minutes % 60; + stream << value.event; + if (value.minutes != 0) + stream << value.minutes; + return stream; +} + +inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::TimeSpan &value) +{ + return stream << value.from << "-" << value.to; +} + +inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::Monthday &value) +{ + bool empty = true; + if (value.year != 0) + { + stream << (int)value.year; + empty = false; + }; + if (value.month != 0) + { + stream << (empty ? "" : "/") << (int)value.month; + empty = false; + }; + if (value.day != 0) + { + stream << (empty ? "" : "/") << (int)value.day; + }; + return stream; +} + +inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::WeekdayRange &value) +{ + boost::io::ios_flags_saver ifs(stream); + return stream << std::hex << std::setfill('0') << std::setw(2) << value.weekdays; +} + +inline std::ostream &operator<<(std::ostream &stream, const OpeningHours::MonthdayRange &value) +{ + return stream << value.from << "-" << value.to; +} + +inline std::ostream &operator<<(std::ostream &stream, const OpeningHours &value) +{ + if (value.modifier == OpeningHours::is24_7) + return stream << OpeningHours::is24_7; + + for (auto x : value.monthdays) + stream << x << ", "; + for (auto x : value.weekdays) + stream << x << ", "; + for (auto x : value.times) + stream << x << ", "; + return stream << " |" << value.modifier << "|"; +} +#endif + +namespace detail +{ + +namespace +{ +namespace ph = boost::phoenix; +namespace qi = boost::spirit::qi; +} + +template +struct opening_hours_grammar : qi::grammar()> +{ + // http://wiki.openstreetmap.org/wiki/Key:opening_hours/specification + opening_hours_grammar() : opening_hours_grammar::base_type(time_domain) + { + using qi::_1; + using qi::_a; + using qi::_b; + using qi::_c; + using qi::_r1; + using qi::_pass; + using qi::_val; + using qi::eoi; + using qi::lit; + using qi::char_; + using qi::uint_; + using oh = osrm::util::OpeningHours; + + // clang-format off + + // General syntax + time_domain = rule_sequence[ph::push_back(_val, _1)] % any_rule_separator; + + rule_sequence + = lit("24/7")[ph::bind(&oh::modifier, _val) = oh::is24_7] + | (selector_sequence[_val = _1] >> -rule_modifier[ph::bind(&oh::modifier, _val) = _1] >> -comment) + | comment + ; + + any_rule_separator = char_(';') | lit("||") | additional_rule_separator; + + additional_rule_separator = char_(','); + + // Rule modifiers + rule_modifier.add + ("unknown", oh::unknown) + ("open", oh::open) + ("closed", oh::closed) + ("off", oh::off) + ; + + // Selectors + selector_sequence = (wide_range_selectors(_a) || small_range_selectors(_a))[_val = _a]; + + wide_range_selectors + = (monthday_selector(_r1) + || year_selector(_r1) + || week_selector(_r1) // TODO week_selector + ) >> -lit(':'); + + small_range_selectors = (weekday_selector(_r1) >> (&~lit(',') | eoi)) || time_selector(_r1); + + // Time selector + time_selector = (timespan % ',')[ph::bind(&OpeningHours::times, _r1) = _1]; + + timespan + = (time[_a = _1] + >> -(lit('+')[_b = ph::construct(24, 0)] + | ('-' >> extended_time[_b = _1] + >> -('+' | '/' >> (minute | hour_minutes)))) + )[_val = ph::construct(_a, _b)] + ; + + time = hour_minutes | variable_time; + + extended_time = extended_hour_minutes | variable_time; + + variable_time + = event[_val = ph::construct(_1)] + | ('(' >> event[_a = _1] >> plus_or_minus[_b = _1] >> hour_minutes[_c = _1] >> ')') + [_val = ph::construct(_a, _b, _c)] + ; + + event.add + ("dawn", OpeningHours::Time::dawn) + ("sunrise", OpeningHours::Time::sunrise) + ("sunset", OpeningHours::Time::sunset) + ("dusk", OpeningHours::Time::dusk) + ; + + // Weekday selector + weekday_selector + = (holiday_sequence(_r1) >> -(char_(", ") >> weekday_sequence(_r1))) + | (weekday_sequence(_r1) >> -(char_(", ") >> holiday_sequence(_r1))) + ; + + weekday_sequence = (weekday_range % ',')[ph::bind(&OpeningHours::weekdays, _r1) = _1]; + + weekday_range + = wday[_a = _1, _b = _1] + >> -(('-' >> wday[_b = _1]) + | ('[' >> (nth_entry % ',') >> ']' >> -day_offset)) + [_val = ph::construct(_a, _b)] + ; + + holiday_sequence = (lit("SH") >> -day_offset) | lit("PH"); + + nth_entry = nth | nth >> '-' >> nth | '-' >> nth; + + nth = char_("12345"); + + day_offset = plus_or_minus >> uint_ >> lit("days"); + + // Week selector + week_selector = (lit("week ") >> week) % ','; + + week = weeknum >> -('-' >> weeknum >> -('/' >> uint_)); + + // Month selector + monthday_selector = (monthday_range % ',')[ph::bind(&OpeningHours::monthdays, _r1) = _1]; + + monthday_range + = (date_from[ph::bind(&OpeningHours::MonthdayRange::from, _val) = _1] + >> -date_offset + >> '-' + >> date_to[ph::bind(&OpeningHours::MonthdayRange::to, _val) = _1] + >> -date_offset) + | (date_from[ph::bind(&OpeningHours::MonthdayRange::from, _val) = _1] + >> -(date_offset + >> -lit('+')[ph::bind(&OpeningHours::MonthdayRange::from, _val) = ph::construct(-1)] + )) + ; + + date_offset = (plus_or_minus >> wday) | day_offset; + + date_from + = ((-year[_a = _1] >> (month[_b = _1] || (daynum[_c = _1] >> (&~lit(':') | eoi)))) + | variable_date) + [_val = ph::construct(_a, _b, _c)] + ; + + date_to + = date_from[_val = _1] + | daynum[_val = ph::construct(0, 0, _1)] + ; + + variable_date = lit("easter"); + + // Year selector + year_selector = (year_range % ',')[ph::bind(&OpeningHours::monthdays, _r1) = _1]; + + year_range + = year[ph::bind(&OpeningHours::MonthdayRange::from, _val) = ph::construct(_1)] + >> -(('-' >> year[ph::bind(&OpeningHours::MonthdayRange::to, _val) = ph::construct(_1)] + >> -('/' >> uint_)) + | lit('+')[ph::bind(&OpeningHours::MonthdayRange::to, _val) = ph::construct(-1)]); + + // Basic elements + plus_or_minus = lit('+')[_val = true] | lit('-')[_val = false]; + + hour = uint2_p[_pass = bind([](unsigned x) { return x <= 24; }, _1), _val = _1]; + + extended_hour = uint2_p[_pass = bind([](unsigned x) { return x <= 48; }, _1), _val = _1]; + + minute = uint2_p[_pass = bind([](unsigned x) { return x < 60; }, _1), _val = _1]; + + hour_minutes = + hour[_a = _1] >> ':' >> minute[_val = ph::construct(_a, _1)]; + + extended_hour_minutes = extended_hour[_a = _1] >> ':' >> + minute[_val = ph::construct(_a, _1)]; + + wday.add + ("Su", 0) + ("Mo", 1) + ("Tu", 2) + ("We", 3) + ("Th", 4) + ("Fr", 5) + ("Sa", 6) + ; + + daynum = uint2_p[_pass = bind([](unsigned x) { return 01 <= x && x <= 31; }, _1), _val = _1]; + + weeknum = uint2_p[_pass = bind([](unsigned x) { return 01 <= x && x <= 53; }, _1), _val = _1]; + + month.add + ("Jan", 1) + ("Feb", 2) + ("Mar", 3) + ("Apr", 4) + ("May", 5) + ("Jun", 6) + ("Jul", 7) + ("Aug", 8) + ("Sep", 9) + ("Oct", 10) + ("Nov", 11) + ("Dec", 12) + ; + + year = uint4_p[_pass = bind([](unsigned x) { return x > 1900; }, _1), _val = _1]; + + comment = lit('"') >> *(~qi::char_('"')) >> lit('"'); + + // clang-format on + + BOOST_SPIRIT_DEBUG_NODES((time_domain)(rule_sequence)(any_rule_separator)( + selector_sequence)(wide_range_selectors)(small_range_selectors)(time_selector)( + timespan)(time)(extended_time)(variable_time)(weekday_selector)(weekday_sequence)( + weekday_range)(holiday_sequence)(nth_entry)(nth)(day_offset)(week_selector)(week)( + monthday_selector)(monthday_range)(date_offset)(date_from)(date_to)(variable_date)( + year_selector)(year_range)(plus_or_minus)(hour_minutes)(extended_hour_minutes)(comment)( + hour)(extended_hour)(minute)(daynum)(weeknum)(year)); + } + + qi::rule()> time_domain; + qi::rule rule_sequence; + qi::rule any_rule_separator, additional_rule_separator; + qi::rule> selector_sequence; + qi::symbols rule_modifier; + qi::rule wide_range_selectors, small_range_selectors, + time_selector, weekday_selector, year_selector, monthday_selector, week_selector; + + // Time rules + qi::rule> + timespan; + + qi::rule time, extended_time; + + qi::rule> + variable_time; + + qi::rule> hour_minutes, + extended_hour_minutes; + + qi::symbols event; + + qi::rule plus_or_minus; + + // Weekday rules + qi::rule weekday_sequence, holiday_sequence; + + qi::rule> + weekday_range; + + // Monthday rules + qi::rule monthday_range; + + qi::rule> + date_from; + + qi::rule date_to; + + // Year rules + qi::rule year_range; + + // Unused rules + qi::rule nth_entry, nth, day_offset, week, date_offset, + variable_date, comment; + + // Basic rules and parsers + qi::rule hour, extended_hour, minute, daynum, weeknum, year; + qi::symbols wday, month; + qi::uint_parser uint2_p; + qi::uint_parser uint4_p; +}; +} + +inline std::vector ParseOpeningHours(const std::string &str) +{ + auto it(str.begin()), end(str.end()); + const detail::opening_hours_grammar static grammar; + + std::vector result; + bool ok = boost::spirit::qi::phrase_parse(it, end, grammar, boost::spirit::qi::blank, result); + + if (!ok || it != end) + return std::vector(); + + return result; +} + +inline bool CheckOpeningHours(const std::vector &input, const struct tm &time) +{ + bool is_open = false; + for (auto &opening_hours : input) + { + if (opening_hours.modifier == OpeningHours::is24_7) + return true; + + if (opening_hours.IsInRange(time)) + { + is_open = opening_hours.modifier == OpeningHours::open; + } + } + + return is_open; +} + +} // util +} // osrm + +#endif // OSRM_OPENING_HOURS_HPP diff --git a/unit_tests/util/opening_hours_parsing.cpp b/unit_tests/util/opening_hours_parsing.cpp new file mode 100644 index 000000000..381bddddf --- /dev/null +++ b/unit_tests/util/opening_hours_parsing.cpp @@ -0,0 +1,279 @@ +#include "util/opening_hours.hpp" + +#include +#include +#include + +BOOST_AUTO_TEST_SUITE(opening_hours) + +// convert a string representation of time to a tm structure +struct tm time(const char *str) +{ + const std::locale loc = std::locale( + std::locale::classic(), new boost::posix_time::time_input_facet("%a, %d %b %Y %H:%M:%S")); + std::istringstream is(str); + is.imbue(loc); + + boost::posix_time::ptime t; + is >> t; + return boost::posix_time::to_tm(t); +} + +BOOST_AUTO_TEST_CASE(check_opening_hours_grammar) +{ + using osrm::util::ParseOpeningHours; + + const std::string opening_hours[] = { + "Apr 10-Jun 15", + "Apr 10-15 off", + "Jun 08:00-14:00", + "24/7", + "Mo-Sa", + "Sa-Su 00:00-24:00", + "Mo-Fr 08:30-20:00", + "Mo 10:00-12:00,12:30-15:00; Tu-Fr 08:00-12:00,12:30-15:00; Sa 08:00-12:00", + "Mo-Su 08:00-18:00; Apr 10-15 off; Jun 08:00-14:00; Aug off; Dec 25 off", + "Mo-Sa 10:00-20:00; Tu off", + "Mo-Sa 10:00-20:00; Tu 10:00-14:00", + "sunrise-(sunset-01:30)", + "Su 10:00+", + "Mo-Sa 08:00-13:00,14:00-17:00 || \"by appointment\"", + "Su-Tu 11:00-01:00, We-Th 11:00-03:00, Fr 11:00-06:00, Sa 11:00-07:00", + "week 01-53/2 Fr 09:00-12:00; week 02-52/2 We 09:00-12:00", + "Mo-Su,PH 15:00-03:00; easter -2 days off", + "08:30-12:30,15:30-20:00", + "Tu,Th 16:00-20:00", + "2016 Feb-2017 Dec", + "2016-2017", + "Mo,Tu,Th,Fr 12:00-18:00;Sa 12:00-17:00; Th[3] off; Th[-1] off"}; + + for (auto &input : opening_hours) + { + BOOST_CHECK_MESSAGE(!ParseOpeningHours(input).empty(), "parsing " << input << " failed"); + } +} + +BOOST_AUTO_TEST_CASE(check_opening_hours_grammar_incorrect_correct) +{ + using osrm::util::ParseOpeningHours; + + const std::pair opening_hours[] = { + {"7/8-23", "Mo-Su 08:00-23:00"}, + {"0600-1800", "06:00-18:00"}, + {"Mo-Fr, 07:00-09:00", "Mo-Fr 07:00-09:00"}, + {"07;00-2;00pm", "07:00-14:00"}, + {"08.00-16.00, public room till 03.00 a.m", + "08:00-16:00 open, 16:00-03:00 off \"public room\""}, + {"09:00-21:00 TEL/072(360)3200", "09:00-21:00 \"call us\""}, + {"10:00 - 13:30 / 17:00 - 20:30", "10:00-13:30,17:00-20:30"}, + {"April-September; Mo-Fr 09:00-13:00, 14:00-18:00, Sa 10:00-13:00", + "Apr-Sep: Mo-Fr 09:00-13:00,14:00-18:00; Apr-Sep: Sa 10:00-13:00"}, + {"Dining in: 6am to 11pm; Drive thru: 24/7", + "06:00-23:00 open \"Dining in\" || 00:00-24:00 open \"Drive-through\""}, + {"MWThF: 1200-1800; SaSu: 1200-1700", "Mo,We,Th,Fr 12:00-18:00; Sa-Su 12:00-17:00"}, + {"BAR: Su-Mo 18:00-02:00; Tu-Th 18:00-03:00; Fr-Sa 18:00-04:00; CLUB: Tu-Th 20:00-03:00; " + "Fr-Sa 20:00-04:00", + "Tu-Th 20:00-03:00 open \"Club and bar\"; Fr-Sa 20:00-04:00 open \"Club and bar\" || " + "Su-Mo 18:00-02:00 open \"bar\" || Tu-Th 18:00-03:00 open \"bar\" || Fr-Sa 18:00-04:00 " + "open \"bar\""}}; + + for (auto &input : opening_hours) + { + BOOST_CHECK_MESSAGE(ParseOpeningHours(input.first).empty(), + "parsing succeed for incorrect input" << input.first); + BOOST_CHECK_MESSAGE(!ParseOpeningHours(input.second).empty(), + "parsing " << input.second << " failed"); + } +} + +BOOST_AUTO_TEST_CASE(check_rules_grouping) +{ + using osrm::util::ParseOpeningHours; + + const auto &result1 = ParseOpeningHours("Su-Th 11:00-03:00, Fr-Sa 11:00-05:00"); + BOOST_REQUIRE_EQUAL(result1.size(), 2); + BOOST_CHECK_EQUAL(result1.at(0).times.size(), 1); + BOOST_CHECK_EQUAL(result1.at(0).weekdays.size(), 1); + BOOST_CHECK_EQUAL(result1.at(0).monthdays.size(), 0); + BOOST_CHECK_EQUAL(result1.at(1).times.size(), 1); + BOOST_CHECK_EQUAL(result1.at(1).weekdays.size(), 1); + BOOST_CHECK_EQUAL(result1.at(1).monthdays.size(), 0); + + const auto &result2 = ParseOpeningHours("Su-Th,Fr-Sa 11:00-12:00,14:00-05:00"); + BOOST_REQUIRE_EQUAL(result2.size(), 1); + BOOST_CHECK_EQUAL(result2.at(0).times.size(), 2); + BOOST_CHECK_EQUAL(result2.at(0).weekdays.size(), 2); + BOOST_CHECK_EQUAL(result2.at(0).monthdays.size(), 0); +} + +BOOST_AUTO_TEST_CASE(check_opening_hours_time_and_weekday) +{ + using osrm::util::ParseOpeningHours; + using osrm::util::CheckOpeningHours; + + const auto &opening_hours = ParseOpeningHours("Mo-Fr 08:30-20:00"); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Wed, 14 Dec 2016 12:32:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Wed, 14 Dec 2016 21:32:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Sat, 17 Dec 2016 12:32:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Sun, 18 Dec 2016 12:32:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 19 Dec 2016 08:30:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 19 Dec 2016 08:29:59")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Fri, 23 Dec 2016 19:59:59")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Fri, 23 Dec 2016 20:00:00")), false); +} + +BOOST_AUTO_TEST_CASE(check_opening_hours_year_month) +{ + using osrm::util::ParseOpeningHours; + using osrm::util::CheckOpeningHours; + + const auto &opening_hours = ParseOpeningHours("2016 Feb-2017 Dec"); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Sun, 31 Jan 2016 12:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Wed, 14 Dec 2016 12:32:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Sun, 31 Dec 2017 12:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 01 Jan 2018 12:00:00")), false); +} + +BOOST_AUTO_TEST_CASE(check_opening_hours_year_monthday) +{ + using osrm::util::ParseOpeningHours; + using osrm::util::CheckOpeningHours; + + const auto &opening_hours = ParseOpeningHours("2019 Apr 10-Jun 15"); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Sun, 16 Apr 2017 17:59:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Tue, 09 Apr 2019 23:59:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Tue, 16 Apr 2019 00:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Sat, 15 Jun 2019 00:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Sun, 16 Jun 2019 00:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Thu, 16 Apr 2020 00:00:00")), false); +} + +BOOST_AUTO_TEST_CASE(check_opening_hours_year_monthday_time_and_weekday) +{ + using osrm::util::ParseOpeningHours; + using osrm::util::CheckOpeningHours; + + const auto &opening_hours = ParseOpeningHours("2017 Feb-May Sa-Tu 08:00-18:00"); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 12 Dec 2016 12:32:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Tue, 31 Jan 2017 23:59:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Wed, 01 Feb 2017 08:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Sat, 04 Feb 2017 09:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Tue, 07 Feb 2017 09:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Wed, 08 Feb 2017 09:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Sun, 16 Apr 2017 17:59:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Thu, 01 Jun 2017 00:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Sun, 04 Mar 2018 12:32:00")), false); +} + +BOOST_AUTO_TEST_CASE(check_opening_hours_time_plus) +{ + using osrm::util::ParseOpeningHours; + using osrm::util::CheckOpeningHours; + + const auto &opening_hours = ParseOpeningHours("08:00+"); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 12 Dec 2016 07:59:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 12 Dec 2016 08:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 12 Dec 2016 23:59:00")), true); +} + +BOOST_AUTO_TEST_CASE(check_opening_hours_off) +{ + using osrm::util::ParseOpeningHours; + using osrm::util::CheckOpeningHours; + + const auto &opening_hours = ParseOpeningHours("08:00-20:00; 12:00-14:30 off; 12:30-13:30 open"); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 12 Dec 2016 07:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 12 Dec 2016 08:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 12 Dec 2016 12:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 12 Dec 2016 13:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 12 Dec 2016 13:30:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 12 Dec 2016 14:30:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 12 Dec 2016 20:00:00")), false); +} + +BOOST_AUTO_TEST_CASE(check_opening_hours_overnight_multiple_times) +{ + using osrm::util::ParseOpeningHours; + using osrm::util::CheckOpeningHours; + + const auto &opening_hours = ParseOpeningHours("20 08:00-10:00,20:00-03:00"); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Tue, 20 Dec 2016 02:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Tue, 20 Dec 2016 07:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Tue, 20 Dec 2016 09:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Tue, 20 Dec 2016 12:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Tue, 20 Dec 2016 21:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Wed, 21 Dec 2016 02:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Wed, 21 Dec 2016 07:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Wed, 21 Dec 2016 09:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Wed, 21 Dec 2016 21:00:00")), false); +} + +BOOST_AUTO_TEST_CASE(check_opening_hours_overnight_weekdays) +{ + using osrm::util::ParseOpeningHours; + using osrm::util::CheckOpeningHours; + + const auto &opening_hours = ParseOpeningHours("Mo-Fr 20:00-03:00"); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 12 Dec 2016 02:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Sat, 17 Dec 2016 02:00:00")), true); +} + +BOOST_AUTO_TEST_CASE(check_opening_hours_overnight_days) +{ + using osrm::util::ParseOpeningHours; + using osrm::util::CheckOpeningHours; + + const auto &opening_hours = ParseOpeningHours("Dec 12-17 20:00-03:00"); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 12 Dec 2016 02:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 12 Dec 2016 20:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Sat, 17 Dec 2016 02:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Sat, 17 Dec 2016 20:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Sun, 18 Dec 2016 02:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Sun, 18 Dec 2016 20:00:00")), false); +} + +BOOST_AUTO_TEST_CASE(check_opening_hours_extended_hours_overlapping) +{ + using osrm::util::ParseOpeningHours; + using osrm::util::CheckOpeningHours; + + const auto &opening_hours = ParseOpeningHours("Dec 20 08:00-44:00"); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 19 Dec 2016 07:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Tue, 20 Dec 2016 07:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Wed, 21 Dec 2016 07:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Thu, 22 Dec 2016 07:00:00")), false); + + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 19 Dec 2016 08:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Tue, 20 Dec 2016 08:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Wed, 21 Dec 2016 08:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Thu, 22 Dec 2016 08:00:00")), false); + + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 19 Dec 2016 20:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Tue, 20 Dec 2016 20:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Wed, 21 Dec 2016 20:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Thu, 22 Dec 2016 20:00:00")), false); +} + +BOOST_AUTO_TEST_CASE(check_opening_hours_extended_hours_nonoverlapping) +{ + using osrm::util::ParseOpeningHours; + using osrm::util::CheckOpeningHours; + + const auto &opening_hours = ParseOpeningHours("Dec 20 20:00-32:00"); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 19 Dec 2016 07:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Tue, 20 Dec 2016 07:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Wed, 21 Dec 2016 07:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Thu, 22 Dec 2016 07:00:00")), false); + + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 19 Dec 2016 08:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Tue, 20 Dec 2016 08:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Wed, 21 Dec 2016 08:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Thu, 22 Dec 2016 08:00:00")), false); + + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Mon, 19 Dec 2016 20:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Tue, 20 Dec 2016 20:00:00")), true); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Wed, 21 Dec 2016 20:00:00")), false); + BOOST_CHECK_EQUAL(CheckOpeningHours(opening_hours, time("Thu, 22 Dec 2016 20:00:00")), false); +} + +BOOST_AUTO_TEST_SUITE_END()