diff --git a/3party/3party.pro b/3party/3party.pro index af69765182..30c4a05b43 100644 --- a/3party/3party.pro +++ b/3party/3party.pro @@ -3,7 +3,8 @@ TEMPLATE = subdirs SUBDIRS = freetype fribidi zlib bzip2 jansson tomcrypt protobuf osrm expat \ - succinct + succinct opening_hours \ + !iphone*:!tizen*:!android* { SUBDIRS += gflags \ diff --git a/3party/opening_hours/opening_hours.pro b/3party/opening_hours/opening_hours.pro new file mode 100644 index 0000000000..d4df281427 --- /dev/null +++ b/3party/opening_hours/opening_hours.pro @@ -0,0 +1,17 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2015-03-27T13:21:18 +# +#------------------------------------------------- + +ROOT_DIR = ../.. + +TARGET = opening_hours +TEMPLATE = lib +CONFIG += staticlib + +include($$ROOT_DIR/common.pri) + +SOURCES += osm_time_range.cpp + +HEADERS += osm_time_range.hpp diff --git a/3party/opening_hours/osm_time_range.cpp b/3party/opening_hours/osm_time_range.cpp new file mode 100644 index 0000000000..f264cdecc4 --- /dev/null +++ b/3party/opening_hours/osm_time_range.cpp @@ -0,0 +1,885 @@ +#include "osm_time_range.hpp" + +#include +#include +#include +#include + +//#define BOOST_SPIRIT_DEBUG 1 +#define BOOST_SPIRIT_USE_PHOENIX_V3 +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wshorten-64-to-32" +#include +#include +#pragma clang diagnostic pop + + + +namespace osmoh { + + std::ostream & operator << (std::ostream & s, Time const & t) + { + bool event = t.flags & Time::eSunrise || t.flags & Time::eSunset; + if (event) + s << ((t.flags & Time::eSunrise) ? "sunrise" : "sunset") << " ("; + std::ios_base::fmtflags sf = s.flags(); + if (t.flags & (Time::ePlus | Time::eMinus)) + s << ((t.flags & Time::ePlus) ? "+" : "-"); + if (t.flags & Time::eHours) + s << std::setw(2) << std::setfill('0') << (int)t.hours; + if (t.flags & Time::eMinutes) + s << ":" << std::setw(2) << std::setfill('0') << (int)t.minutes; + s.flags(sf); + if (event) + s << ")"; + return s; + } + + std::ostream & operator << (std::ostream & s, TimeSpan const & span) + { + s << span.from; + if (span.to.flags) + s << '-' << span.to; + if (span.flags == Time::ePlus) + s << "..."; + if (span.flags == Time::eExt) + s << '/' << span.period; + + return s; + } + + std::ostream & operator << (std::ostream & s, Weekdays const & w) + { + char const * wdays[] = {"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}; + for (size_t i = 0; i < 7; ++i) { + if (w.weekdays & (1 << i)) { + if (w.weekdays & ((1 << i) - 1)) + s << ','; + s << wdays[i]; + } + } + + if (w.nth) { + s << "["; + + uint8_t a = w.nth & 0xFF; + for (size_t i = 0; i < 4; ++i) { + if (a & (1 << i)) { + if (a & ((1 << i) - 1)) + s << ','; + s << (i + 1); + } + } + + a = (w.nth >> 8) & 0xFF; + for (size_t i = 0; i < 4; ++i) { + if (a & (1 << i)) { + if (a & ((1 << i) - 1)) + s << ','; + s << '-' << (i + 1); + } + } + + s << "]"; + } + + if (w.offset) + s << ' ' << w.offset << " day(s)"; + return s; + } + + std::ostream & operator << (std::ostream & s, State const & w) + { + char const * st[] = {"unknown", "closed", "open"}; + s << ' ' << st[w.state] << " " << w.comment; + return s; + } + + std::ostream & operator << (std::ostream & s, TimeRule const & w) + { + for (auto const & e : w.weekdays) + s << e; + if (!w.weekdays.empty() && !w.timespan.empty()) + s << ' '; + for (auto const & e : w.timespan) + s << e; + + return s << w.state; + } + + boost::posix_time::time_period make_time_period(boost::gregorian::date const & d, osmoh::TimeSpan const & ts) + { + using boost::posix_time::ptime; + using boost::posix_time::hours; + using boost::posix_time::minutes; + using boost::posix_time::time_period; + + ptime sunrise(d, hours(6)); + ptime sunset(d, hours(19)); + + ptime t1, t2; + + if (ts.from.flags & osmoh::Time::eSunrise) + t1 = sunrise; + else if (ts.from.flags & osmoh::Time::eSunset) + t1 = sunset; + else + t1 = ptime(d, hours((ts.from.flags & osmoh::Time::eHours) ? ts.from.hours : 0) + minutes((ts.from.flags & osmoh::Time::eMinutes) ? ts.from.minutes : 0)); + + t2 = t1; + + if (ts.to.flags & osmoh::Time::eSunrise) + t2 = sunrise; + else if (ts.to.flags & osmoh::Time::eSunset) + t2 = sunset; + else + { + t2 = ptime(d, hours((ts.to.flags & osmoh::Time::eHours) ? ts.to.hours : 24) + minutes((ts.to.flags & osmoh::Time::eMinutes) ? ts.to.minutes : 0)); + if (t2 < t1) + t2 += hours(24); + } + + return time_period(t1, t2); + } + +} // namespace osmoh + + +BOOST_FUSION_ADAPT_STRUCT +( + osmoh::Time, + (uint8_t, hours) + (uint8_t, minutes) + (uint8_t, flags) +) + +BOOST_FUSION_ADAPT_STRUCT +( + osmoh::TimeSpan, + (osmoh::Time, from) + (osmoh::Time, to) + (uint8_t, flags) + (osmoh::Time, period) +) + +BOOST_FUSION_ADAPT_STRUCT +( + osmoh::Weekdays, + (uint8_t, weekdays) + (uint16_t, nth) + (int32_t, offset) +) + +BOOST_FUSION_ADAPT_STRUCT +( + osmoh::State, + (uint8_t, state) + (std::string, comment) +) + +BOOST_FUSION_ADAPT_STRUCT +( + osmoh::TimeRule, + (std::vector, weekdays) + (std::vector, timespan) + (osmoh::State, state) + (uint8_t, int_flags) +) + +namespace { + namespace qi = boost::spirit::qi; + namespace phx = boost::phoenix; + namespace repo = boost::spirit::repository; + + namespace charset = boost::spirit::standard_wide; + using space_type = charset::space_type; + + + class test_impl + { + public: + template + struct result { typedef void type; }; + + template + void operator() (const Arg & a) const + { + std::cout << a << " \t(" << typeid(a).name() << ")" << std::endl; + } + }; + phx::function const test = test_impl(); + + class dash_ : public qi::symbols + { + public: + dash_() + { + add + ("-") + /* not standard */ + ("–")("~")("~")("〜") + ; + } + } dash; + + class event_ : public qi::symbols + { + public: + event_() + { + add + ("dawn", osmoh::Time::eSunrise)("sunrise", osmoh::Time::eSunrise)("sunset", osmoh::Time::eSunset)("dusk", osmoh::Time::eSunset) + ; + } + } event; + + struct wdays_ : qi::symbols + { + wdays_() + { + add + ("Mo", 0)("Tu", 1)("We", 2)("Th", 3)("Fr", 4)("Sa", 5)("Su", 6) + /* not standard */ + ("MO", 0)("TU", 1)("WE", 2)("TH", 3)("FR", 4)("SA", 5)("SU", 6) + ; + } + } wdays; + + struct month_ : qi::symbols + { + 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) + ; + } + } month; + + struct hours_ : qi::symbols + { + hours_() + { + add + ( "0", 0)( "1", 1)( "2", 2)( "3", 3)( "4", 4)( "5", 5)( "6", 6)( "7", 7)( "8", 8)( "9", 9) /* not standard */ + ("00", 0)("01", 1)("02", 2)("03", 3)("04", 4)("05", 5)("06", 6)("07", 7)("08", 8)("09", 9) + ("10", 10)("11", 11)("12", 12)("13", 13)("14", 14)("15", 15)("16", 16)("17", 17)("18", 18)("19", 19) + ("20", 20)("21", 21)("22", 22)("23", 23)("24", 24) + ; + } + } hours; + + struct exthours_ : qi::symbols + { + exthours_() + { + add + ( "0", 0)( "1", 1)( "2", 2)( "3", 3)( "4", 4)( "5", 5)( "6", 6)( "7", 7)( "8", 8)( "9", 9) /* not standard */ + ("00", 0)("01", 1)("02", 2)("03", 3)("04", 4)("05", 5)("06", 6)("07", 7)("08", 8)("09", 9) + ("10", 10)("11", 11)("12", 12)("13", 13)("14", 14)("15", 15)("16", 16)("17", 17)("18", 18)("19", 19) + ("20", 20)("21", 21)("22", 22)("23", 23)("24", 24)("25", 25)("26", 26)("27", 27)("28", 28)("29", 29) + ("30", 30)("31", 31)("32", 32)("33", 33)("34", 34)("35", 35)("36", 36)("37", 37)("38", 38)("39", 39) + ("40", 40)("41", 41)("42", 42)("43", 43)("44", 44)("45", 45)("46", 46)("47", 47)("48", 48) + ; + } + } exthours; + + struct minutes_ : qi::symbols + { + minutes_() + { + add + ( "0", 0)( "1", 1)( "2", 2)( "3", 3)( "4", 4)( "5", 5)( "6", 6)( "7", 7)( "8", 8)( "9", 9) /* not standard */ + ("00", 0)("01", 1)("02", 2)("03", 3)("04", 4)("05", 5)("06", 6)("07", 7)("08", 8)("09", 9) + ("10", 10)("11", 11)("12", 12)("13", 13)("14", 14)("15", 15)("16", 16)("17", 17)("18", 18)("19", 19) + ("20", 20)("21", 21)("22", 22)("23", 23)("24", 24)("25", 25)("26", 26)("27", 27)("28", 28)("29", 29) + ("30", 30)("31", 31)("32", 32)("33", 33)("34", 34)("35", 35)("36", 36)("37", 37)("38", 38)("39", 39) + ("40", 40)("41", 41)("42", 42)("43", 43)("44", 44)("45", 45)("46", 46)("47", 47)("48", 48)("49", 49) + ("50", 50)("51", 51)("52", 52)("53", 53)("54", 54)("55", 55)("56", 56)("57", 57)("58", 58)("59", 59) + ; + } + } minutes; + + struct weeknum_ : qi::symbols + { + weeknum_() + { + add ( "1", 1)( "2", 2)( "3", 3)( "4", 4)( "5", 5)( "6", 6)( "7", 7)( "8", 8)( "9", 9) + ("01", 1)("02", 2)("03", 3)("04", 4)("05", 5)("06", 6)("07", 7)("08", 8)("09", 9) + ("10", 10)("11", 11)("12", 12)("13", 13)("14", 14)("15", 15)("16", 16)("17", 17)("18", 18)("19", 19) + ("20", 20)("21", 21)("22", 22)("23", 23)("24", 24)("25", 25)("26", 26)("27", 27)("28", 28)("29", 29) + ("30", 30)("31", 31)("32", 32)("33", 33)("34", 34)("35", 35)("36", 36)("37", 37)("38", 38)("39", 39) + ("40", 40)("41", 41)("42", 42)("43", 43)("44", 44)("45", 45)("46", 46)("47", 47)("48", 48)("49", 49) + ("50", 50)("51", 51)("52", 52)("53", 53) + ; + } + } weeknum; + + struct daynum_ : qi::symbols + { + daynum_() + { + add ("1", 1)("2", 2)("3", 3)("4", 4)("5", 5)("6", 6)("7", 7)("8", 8)("9", 9) + ("10", 10)("11", 11)("12", 12)("13", 13)("14", 14)("15", 15)("16", 16)("17", 17)("18", 18)("19", 19) + ("20", 20)("21", 21)("22", 22)("23", 23)("24", 24)("25", 25)("26", 26)("27", 27)("28", 28)("29", 29) + ("30", 30)("31", 31) + ; + } + } daynum; + + template + class year_selector_parser : public qi::grammar + { + protected: + qi::rule year; + qi::rule year_range; + qi::rule main; + public: + year_selector_parser() : year_selector_parser::base_type(main) + { + using qi::uint_; + using qi::lit; + using charset::char_; + + static const qi::int_parser _4digit = {}; + + year %= _4digit; + year_range %= + (year >> dash >> year >> '/' >> uint_) + | (year >> dash >> year) + | year >> char_('+') + | year + ; + main %= year_range % ','; + } + }; + + template + class week_selector_parser : public qi::grammar + { + protected: + qi::rule week; + qi::rule year_range; + qi::rule main; + public: + week_selector_parser() : week_selector_parser::base_type(main) + { + using qi::uint_; + using qi::lit; + using charset::char_; + + week %= (weeknum >> dash >> weeknum >> '/' >> uint_) + | (weeknum >> dash >> weeknum) + | weeknum + ; + + main %= charset::no_case[lit("week")] >> week % ','; + } + }; + + template + class month_selector_parser : public qi::grammar + { + protected: + qi::rule date; + qi::rule day_offset; + qi::rule date_with_offsets; + qi::rule monthday_range; + qi::rule month_range; + qi::rule main; + public: + month_selector_parser() : month_selector_parser::base_type(main) + { + using qi::int_; + using qi::lit; + using qi::double_; + using qi::lexeme; + using charset::char_; + + static const qi::int_parser year = {}; + + day_offset %= (char_('+') | char_('-')) >> int_ >> charset::no_case[(lit("days") | lit("day"))]; + + date %= charset::no_case[(-year >> month >> daynum)] + | (-year >> charset::no_case[lit("easter")]) + | daynum >> lit(' ') + ; + + date_with_offsets %= date >> -((char_('+') | char_('-')) >> wdays) >> -day_offset; + + monthday_range %= (date_with_offsets >> dash >> date_with_offsets) + | (date_with_offsets >> '+') + | date_with_offsets + | charset::no_case[(-year >> month >> dash >> month >> '/' >> int_)] + | charset::no_case[(-year >> month >> dash >> month)] + | charset::no_case[(-year >> month)] + ; + + month_range %= charset::no_case[(month >> dash >> month)] + | charset::no_case[month] + ; + + main %= (monthday_range % ',') + | (month_range % ',') + ; + + BOOST_SPIRIT_DEBUG_NODE(main); + BOOST_SPIRIT_DEBUG_NODE(month_range); + BOOST_SPIRIT_DEBUG_NODE(monthday_range); + BOOST_SPIRIT_DEBUG_NODE(date_with_offsets); + BOOST_SPIRIT_DEBUG_NODE(date); + BOOST_SPIRIT_DEBUG_NODE(day_offset); + + } + }; + + + template + class weekday_selector_parser : public qi::grammar(), space_type> + { + protected: + qi::rule nth; + qi::rule nth_entry; + qi::rule> day_offset; + qi::rule holyday; + qi::rule holiday_sequence; + qi::rule weekday_range; + qi::rule(), space_type> weekday_sequence; + qi::rule(), space_type> main; + public: + weekday_selector_parser() : weekday_selector_parser::base_type(main) + { + using qi::_a; + using qi::_1; + using qi::_2; + using qi::_val; + using qi::lit; + using qi::ushort_; + using boost::phoenix::at_c; + + nth %= ushort_(1) | ushort_(2) | ushort_(3) | ushort_(4) | ushort_(5); + + nth_entry = + (nth >> dash >> nth) [_val |= ((2 << ((_2-1)-(_1-1))) - 1) << (_1-1)] + | (lit('-') >> nth) [_val |= (0x0100 << (_1 - 1))] + | nth [_val |= (1 << (_1 - 1))] + ; + + day_offset = (lit('+')[_a = 1] | lit('-') [_a = -1]) >> ushort_[_val = _1*_a] >> charset::no_case[(lit("days") | lit("day"))]; + holyday %= (charset::no_case[lit("SH")] >> -day_offset) | charset::no_case[lit("PH")]; + holiday_sequence %= holyday % ','; + weekday_range = + (wdays[at_c<0>(_val) |= (1<<_1)] >> '[' >> nth_entry[at_c<1>(_val) |= _1] % ',' >> ']' >> day_offset[at_c<2>(_val) = _1]) + | (wdays[at_c<0>(_val) |= (1<<_1)] >> '[' >> nth_entry[at_c<1>(_val) |= _1] % ',' >> ']') + | (wdays >> dash >> wdays) [at_c<0>(_val) |= ((2 << ((_2)-(_1))) - 1) << (_1)] + | wdays[at_c<0>(_val) |= (1<<_1)] + ; + weekday_sequence %= weekday_range % ','; + main = + (holiday_sequence >> -lit(',') >> weekday_sequence[_val = _1]) + | weekday_sequence[_val = _1] >> -(-lit(',') >> holiday_sequence) + | holiday_sequence + ; + + BOOST_SPIRIT_DEBUG_NODE(main); + BOOST_SPIRIT_DEBUG_NODE(weekday_sequence); + BOOST_SPIRIT_DEBUG_NODE(weekday_range); + BOOST_SPIRIT_DEBUG_NODE(holiday_sequence); + + } + }; + + template + class time_selector_parser : public qi::grammar(), space_type> + { + protected: + qi::rule hour_minutes; + qi::rule extended_hour_minutes; + qi::rule variable_time; + qi::rule extended_time; + qi::rule time; + qi::rule timespan; + qi::rule(), space_type> main; + + class validate_timespan_impl + { + public: + template + struct result { typedef bool type; }; + + bool operator() (osmoh::TimeSpan const & ts) const + { + using boost::posix_time::ptime; + using boost::posix_time::time_duration; + using boost::posix_time::hours; + using boost::posix_time::minutes; + using boost::posix_time::time_period; + + bool result = true; + if (ts.period.flags) + { + time_period tp = osmoh::make_time_period(boost::gregorian::day_clock::local_day(), ts); + result = (tp.length() >= time_duration(ts.period.hours, ts.period.minutes, 0 /* seconds */)); + } + + return result; + } + }; + + public: + time_selector_parser() : time_selector_parser::base_type(main) + { + using qi::int_; + using qi::_1; + using qi::_2; + using qi::_3; + using qi::_a; + using qi::_val; + using qi::lit; + using qi::_pass; + using charset::char_; + using boost::phoenix::at_c; + + phx::function const validate_timespan = validate_timespan_impl(); + + hour_minutes = + hours[at_c<0>(_val) = _1, + at_c<2>(_val) |= osmoh::Time::eHours] + >> (lit(':') | lit(":") | lit('.')) + >> minutes[at_c<1>(_val) = _1, + at_c<2>(_val) |= osmoh::Time::eMinutes] + ; + + extended_hour_minutes = + exthours[at_c<0>(_val) = _1, + at_c<2>(_val) |= osmoh::Time::eHours] + >> (lit(':') | lit(":") | lit('.')) + >> minutes[at_c<1>(_val) = _1, + at_c<2>(_val) |= osmoh::Time::eMinutes] + ; + + variable_time = + (lit('(') + >> event[at_c<2>(_val) |= _1] + >> ( + char_('+')[at_c<2>(_val) |= osmoh::Time::ePlus] + | char_('-')[at_c<2>(_val) |= osmoh::Time::eMinus] + ) + >> hour_minutes[at_c<2>(_1) |= at_c<2>(_val), _val = _1] + >> lit(')') + ) + | event[at_c<2>(_val) |= _1] + ; + + extended_time %= + extended_hour_minutes + | variable_time + ; + + time %= + hour_minutes + | variable_time + ; + + + timespan = + (time >> lit('-') >> extended_time >> '/' >> hour_minutes) + [at_c<0>(_val) = _1, at_c<1>(_val) = _2, at_c<2>(_val) |= osmoh::Time::eExt, + at_c<3>(_val) = _3] + | (time >> lit('-') >> extended_time >> '/' >> minutes) + [at_c<0>(_val) = _1, at_c<1>(_val) = _2, at_c<2>(_val) |= osmoh::Time::eExt, + at_c<1>(at_c<3>(_val)) = _3, at_c<2>(at_c<3>(_val)) = osmoh::Time::eMinutes] + | (time >> lit('-') >> extended_time >> char_('+')) + [at_c<0>(_val) = _1, at_c<1>(_val) = _2, at_c<2>(_val) |= osmoh::Time::ePlus] + | (time >> dash >> extended_time) + [at_c<0>(_val) = _1, at_c<1>(_val) = _2] + | (time >> char_('+')) + [at_c<0>(_val) = _1, at_c<2>(_val) |= osmoh::Time::ePlus] + | time [at_c<0>(_val) = _1] + ; + main %= timespan[_pass = validate_timespan(_1)] % ','; + + BOOST_SPIRIT_DEBUG_NODE(main); + BOOST_SPIRIT_DEBUG_NODE(timespan); + BOOST_SPIRIT_DEBUG_NODE(time); + BOOST_SPIRIT_DEBUG_NODE(extended_time); + BOOST_SPIRIT_DEBUG_NODE(variable_time); + BOOST_SPIRIT_DEBUG_NODE(extended_hour_minutes); + } + }; + + template + class selectors_parser : public qi::grammar + { + protected: + weekday_selector_parser weekday_selector; + time_selector_parser time_selector; + year_selector_parser year_selector; + month_selector_parser month_selector; + week_selector_parser week_selector; + + qi::rule comment; + qi::rule small_range_selectors; + qi::rule wide_range_selectors; + qi::rule main; + public: + selectors_parser() : selectors_parser::base_type(main) + { + using qi::_1; + using qi::_val; + using qi::lit; + using qi::lexeme; + using charset::char_; + using boost::phoenix::at_c; + using osmoh::State; + + + comment %= lexeme['"' >> +(char_ - '"') >> '"']; + wide_range_selectors = -year_selector >> -month_selector >> -week_selector >> -lit(':') | (comment >> ':'); + small_range_selectors = -weekday_selector[at_c<0>(_val) = _1] >> -time_selector[at_c<1>(_val) = _1]; + + main = + lit("24/7")[at_c<0>(at_c<2>(_val)) = State::eOpen] + | (wide_range_selectors >> small_range_selectors[_val = _1, at_c<0>(at_c<2>(_val)) = State::eOpen]) + ; + BOOST_SPIRIT_DEBUG_NODE(main); + BOOST_SPIRIT_DEBUG_NODE(small_range_selectors); + BOOST_SPIRIT_DEBUG_NODE(wide_range_selectors); + } + }; + + template + class time_domain_parser : public qi::grammar(), space_type, qi::locals*>> + { + protected: + selectors_parser selector_sequence; + + qi::rule comment; + qi::rule separator; + qi::rule base_separator; + qi::rule rule_sequence; + qi::rule rule_modifier; + qi::rule(), space_type, qi::locals*>> main; + + public: + time_domain_parser() : time_domain_parser::base_type(main) + { + using qi::lit; + using qi::lexeme; + using qi::_1; + using qi::_a; + using qi::_val; + using charset::char_; + using boost::phoenix::at_c; + using qi::lazy; + using qi::eps; + using osmoh::State; + + comment %= lexeme['"' >> +(char_ - '"') >> '"'] | lexeme['(' >> +(char_ - ')') >> ')']; + base_separator = lit(';') | lit("||"); + separator = lit(';') | lit("||") | lit(','); + + rule_modifier = + (charset::no_case[lit("open")][at_c<0>(_val) = State::eOpen] >> -comment[at_c<1>(_val) = _1]) + | ((charset::no_case[lit("closed") | lit("off")])[at_c<0>(_val) = State::eClosed] >> -comment[at_c<1>(_val) = _1]) + | (charset::no_case[lit("unknown")][at_c<0>(_val) = State::eUnknown] >> -comment[at_c<1>(_val) = _1]) + | comment[at_c<0>(_val) = State::eUnknown, at_c<1>(_val) = _1] + ; + + rule_sequence = + selector_sequence[_val = _1] >> -rule_modifier[at_c<2>(_val) = _1, at_c<3>(_val) = 1]; + + main %= rule_sequence[_a = phx::val(&base_separator), phx::if_(at_c<3>(_1) || phx::size(at_c<1>(_1)))[_a = phx::val(&separator)]] % lazy(*_a); + + BOOST_SPIRIT_DEBUG_NODE(main); + BOOST_SPIRIT_DEBUG_NODE(rule_sequence); + BOOST_SPIRIT_DEBUG_NODE(rule_modifier); + } + }; + + template + bool parse_timerange(Iterator first, Iterator last, std::vector & context) + { + using qi::double_; + using qi::phrase_parse; + using charset::space; + + time_domain_parser time_domain; + + bool r = phrase_parse( + first, /* start iterator */ + last, /* end iterator */ + time_domain, /* the parser */ + space, /* the skip-parser */ + context /* result storage */ + ); + + if (first != last) // fail if we did not get a full match + return false; + return r; + } + + bool check_weekday(osmoh::Weekdays const & wd, boost::gregorian::date const & d) + { + using namespace boost::gregorian; + + bool hit = false; + typedef nth_day_of_the_week_in_month nth_dow; + if (wd.nth) + { + for (uint8_t i = 0; (wd.weekdays & (0xFF ^ ((1 << i) - 1))); ++i) + { + if (!(wd.weekdays & (1 << i))) + continue; + + uint8_t a = wd.nth & 0xFF; + for (size_t j = 0; (a & (0xFF ^ ((1 << j) - 1))); ++j) + { + if (a & (1 << j)) + { + nth_dow ndm(nth_dow::week_num(j + 1), nth_dow::day_of_week_type((i + 1 == 7) ? 0 : (i + 1)), d.month()); + hit |= (d == ndm.get_date(d.year())); + } + } + a = (wd.nth >> 8) & 0xFF; + for (size_t j = 0; (a & (0xFF ^ ((1 << j) - 1))); ++j) + { + if (a & (1 << j)) + { + last_day_of_the_week_in_month lwdm(nth_dow::day_of_week_type((i + 1 == 7) ? 0 : (i + 1)), d.month()); + hit |= (d == ((lwdm.get_date(d.year()) - weeks(j)) + days(wd.offset))); + } + } + } + } + else + { + for (uint8_t i = 0; (wd.weekdays & (0xFF ^ ((1 << i) - 1))); ++i) + { + if (!(wd.weekdays & (1 << i))) + continue; + hit |= (d.day_of_week() == ((i + 1 == 7) ? 0 : (i + 1))); + } + } +// std::cout << d.day_of_week() << " " << d << " --> " << wd << (hit ? " hit" : " miss") << std::endl; // very useful in debug + return hit; + } + + bool check_timespan(osmoh::TimeSpan const &ts, boost::gregorian::date const & d, boost::posix_time::ptime const & p) + { + using boost::posix_time::ptime; + using boost::posix_time::hours; + using boost::posix_time::minutes; + using boost::posix_time::time_period; + + time_period tp = osmoh::make_time_period(d, ts); +// std::cout << ts << "\t" << tp << "(" << p << ")" << (tp.contains(p) ? " hit" : " miss") << std::endl; // very useful in debug + return tp.contains(p); + } + + bool check_rule(osmoh::TimeRule const & r, std::tm const & stm, std::ostream * hitcontext = nullptr) + { + bool next = false; + + // check 24/7 + if (r.weekdays.empty() && r.timespan.empty() && r.state.state == osmoh::State::eOpen) + return true; + + boost::gregorian::date date = boost::gregorian::date_from_tm(stm); + boost::posix_time::ptime pt = boost::posix_time::ptime_from_tm(stm); + + next = r.weekdays.empty(); + for (auto const & wd : r.weekdays) + { + if (check_weekday(wd, date)) + { + if (hitcontext) + *hitcontext << wd << " "; + next = true; + } + } + if (!next) + return next; + + next = r.timespan.empty(); + for (auto const & ts : r.timespan) + { + if (check_timespan(ts, date, pt)) + { + if (hitcontext) + *hitcontext << ts << " "; + next = true; + } + } + return next && !(r.timespan.empty() && r.weekdays.empty()); + } + + +} // anonymouse namespace + +OSMTimeRange::OSMTimeRange(std::string const & rules) +: m_sourceString(rules) +, m_valid(false) +, m_state(osmoh::State::eUnknown) +{ + parse(); +} + +void OSMTimeRange::parse() +{ + m_valid = parse_timerange(m_sourceString.begin(), m_sourceString.end(), m_rules); +} + +OSMTimeRange & OSMTimeRange::operator () (time_t timestamp) +{ + std::tm stm = *localtime(×tamp); + + osmoh::State::EState true_state[3][3] = { + {osmoh::State::eUnknown, osmoh::State::eClosed, osmoh::State::eOpen}, + {osmoh::State::eClosed , osmoh::State::eClosed, osmoh::State::eOpen}, + {osmoh::State::eOpen , osmoh::State::eClosed, osmoh::State::eOpen} + }; + + osmoh::State::EState false_state[3][3] = { + {osmoh::State::eUnknown, osmoh::State::eOpen , osmoh::State::eClosed}, + {osmoh::State::eClosed , osmoh::State::eClosed , osmoh::State::eClosed}, + {osmoh::State::eOpen , osmoh::State::eOpen , osmoh::State::eOpen} + }; + + m_state = osmoh::State::eUnknown; + m_comment = std::string(); + + for (auto const & el : m_rules) + { + bool hit = false; + if ((hit = check_rule(el, stm))) + { + m_state = true_state[m_state][el.state.state]; + m_comment = el.state.comment; + } + else + { + m_state = false_state[m_state][el.state.state]; + } +// char const * st[] = {"unknown", "closed", "open"}; +// std::cout << "-[" << hit << "]-------------------[" << el << "]: " << st[m_state] << "--------------------" << std::endl; // very useful in debug + } + return *this; +} + +OSMTimeRange & OSMTimeRange::operator () (std::string const & timestr, char const * timefmt) +{ + std::tm when = {0}; + std::stringstream ss(timestr); + ss >> std::get_time(&when, timefmt); + return this->operator()(std::mktime(&when)); +} diff --git a/3party/opening_hours/osm_time_range.hpp b/3party/opening_hours/osm_time_range.hpp new file mode 100644 index 0000000000..91e82eeaf3 --- /dev/null +++ b/3party/opening_hours/osm_time_range.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include +#include +#include + +namespace osmoh { + class Time { + public: + enum EFlags { + eNone = 0, + eHours = 1, + eMinutes = 2, + ePlus = 4, + eMinus = 8, + eExt = 16, + eSunrise = 32, + eSunset = 64 + }; + + uint8_t hours; + uint8_t minutes; + uint8_t flags; + + Time() : hours(0), minutes(0), flags(eNone) {} + + friend std::ostream & operator << (std::ostream & s, Time const & t); + }; + + class TimeSpan + { + public: + Time from; + Time to; + uint8_t flags; + Time period; + + TimeSpan() : flags(Time::eNone) {} + + friend std::ostream & operator << (std::ostream & s, TimeSpan const & span); + }; + + class Weekdays + { + public: + uint8_t weekdays; + uint16_t nth; + int32_t offset; + + Weekdays() : weekdays(0), nth(0), offset(0) {} + + friend std::ostream & operator << (std::ostream & s, Weekdays const & w); + }; + + class State + { + public: + enum EState { + eUnknown = 0, + eClosed = 1, + eOpen = 2 + }; + + uint8_t state; + std::string comment; + + State() : state(eUnknown) {} + }; + + class TimeRule + { + public: + std::vector weekdays; + std::vector timespan; + State state; + uint8_t int_flags = 0; + }; + +} // namespace osmoh + +class OSMTimeRange +{ + std::string m_sourceString; + bool m_valid; + osmoh::State::EState m_state; + std::vector m_rules; + std::string m_comment; + +public: + OSMTimeRange(std::string const & rules); + + inline bool IsValid() const { return m_valid; } + inline bool IsOpen() const { return m_state == osmoh::State::eOpen; } + inline bool IsClosed() const { return m_state == osmoh::State::eClosed; } + inline bool IsUnknown() const { return m_state == osmoh::State::eUnknown; } + inline std::string const & Comment() const { return m_comment; } + + OSMTimeRange & operator()(time_t timestamp); + OSMTimeRange & operator()(std::string const & timestr, char const * timefmt="%d-%m-%Y %R"); + +private: + void parse(); +}; + diff --git a/3party/opening_hours/osm_time_range_tests.cpp b/3party/opening_hours/osm_time_range_tests.cpp new file mode 100644 index 0000000000..31e19d84bd --- /dev/null +++ b/3party/opening_hours/osm_time_range_tests.cpp @@ -0,0 +1,63 @@ +#include "osm_time_range.hpp" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wshorten-64-to-32" +#define BOOST_TEST_MODULE OpeningHours +#include +#pragma clang diagnostic pop + + +BOOST_AUTO_TEST_CASE(OpeningHours_StaticSet) +{ + { + OSMTimeRange oh("09:00-19:00;Sa 09:00-18:00;Tu,Su,PH OFF"); // symbols case + BOOST_CHECK(oh.IsValid()); + } + { + OSMTimeRange oh("09:00-19:00;Sa 09:00-18:00;Tu,Su,ph Off"); // symbols case + BOOST_CHECK(oh.IsValid()); + } + { + OSMTimeRange oh("05:00 – 22:00"); // long dash instead minus + BOOST_CHECK(oh.IsValid()); + } + { + OSMTimeRange oh("05:00 - 22:00"); // minus + BOOST_CHECK(oh.IsValid()); + } + { + OSMTimeRange oh("09:00-20:00 open \"Bei schönem Wetter. Falls unklar kann angerufen werden\""); // charset + BOOST_CHECK(oh.IsValid()); + } + { + OSMTimeRange oh("09:00-22:00; Tu off; dec 31 off; Jan 1 off"); // symbols case + BOOST_CHECK(oh.IsValid()); + } + { + OSMTimeRange oh("9:00-22:00"); // leading zeros + BOOST_CHECK(oh.IsValid()); + } + { + OSMTimeRange oh("09:00-9:30"); // leading zeros + BOOST_CHECK(oh.IsValid()); + } + { + OSMTimeRange oh("Mo 08:00-11:00,14:00-17:00; Tu 08:00-11:00, 14:00-17:00; We 08:00-11:00; Th 08:00-11:00, 14:00-16:00; Fr 08:00-11:00"); + BOOST_CHECK(oh.IsValid()); + } + +} + +BOOST_AUTO_TEST_CASE( OpeningHours_FromFile ) +{ +// BOOST_REQUIRE(false); + std::ifstream datalist("opening.lst"); + BOOST_REQUIRE_MESSAGE(datalist.is_open(), "Can't open ./opening.lst: " << std::strerror(errno)); + + std::string line; + while (std::getline(datalist, line)) + { + OSMTimeRange oh(line); + BOOST_CHECK_MESSAGE(oh.IsValid(), "Can't parse: [" << line << "]"); + } +} \ No newline at end of file diff --git a/indexer/indexer_tests/indexer_tests.pro b/indexer/indexer_tests/indexer_tests.pro index fb2b05f02c..833adc4381 100644 --- a/indexer/indexer_tests/indexer_tests.pro +++ b/indexer/indexer_tests/indexer_tests.pro @@ -4,7 +4,7 @@ CONFIG -= app_bundle TEMPLATE = app ROOT_DIR = ../.. -DEPENDENCIES = indexer platform geometry coding base protobuf tomcrypt +DEPENDENCIES = indexer platform geometry coding base protobuf tomcrypt opening_hours include($$ROOT_DIR/common.pri) QT *= core @@ -32,6 +32,7 @@ SOURCES += \ interval_index_test.cpp \ mercator_test.cpp \ mwm_set_test.cpp \ + opening_hours_test.cpp \ point_to_int64_test.cpp \ scales_test.cpp \ search_string_utils_test.cpp \ diff --git a/indexer/indexer_tests/opening_hours_test.cpp b/indexer/indexer_tests/opening_hours_test.cpp new file mode 100644 index 0000000000..723dbb2160 --- /dev/null +++ b/indexer/indexer_tests/opening_hours_test.cpp @@ -0,0 +1,174 @@ +#include "../../testing/testing.hpp" + +#include "../../3party/opening_hours/osm_time_range.hpp" + +UNIT_TEST(OpeningHours_Parse) +{ + { + OSMTimeRange oh("sunrise-sunset"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("Su-Th sunset-24:00, 04:00-sunrise; Fr-Sa sunset-sunrise"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("Apr-Sep Su [1,3] 14:30-17:00"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("06:00+"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("06:00-07:00+"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("06:00-07:00/03"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("24/7"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("06:13-15:00"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("Mo-Su 08:00-23:00"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("(sunrise+02:00)-(sunset-04:12)"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("Mo-Sa; PH off"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("Jan-Mar 07:00-19:00;Apr-Sep 07:00-22:00;Oct-Dec 07:00-19:00"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("Mo closed"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("06:00-23:00 open \"Dining in\""); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("06:00-23:00 open \"Dining in\" || 00:00-24:00 open \"Drive-through\""); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("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\""); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("09:00-21:00 \"call us\""); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("10:00-13:30,17:00-20:30"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("Apr-Sep: Mo-Fr 09:00-13:00,14:00-18:00; Apr-Sep: Sa 10:00-13:00"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("Mo,We,Th,Fr 12:00-18:00; Sa-Su 12:00-17:00"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("Su-Th 11:00-03:00, Fr-Sa 11:00-05:00"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("Mo-We 17:00-01:00, Th,Fr 15:00-01:00; PH off"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + /* test disabled because we go out from DSL definition in some cases */ + // OSMTimeRange oh("Tu-Su, Ph 10:00-18:00"); + // TEST(oh.IsValid() == false, ("Broken parser")); + } + { + OSMTimeRange oh("Tu-Su 10:00-18:00, Mo 12:00-17:00"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } + { + OSMTimeRange oh("06:00-07:00/21:03"); + TEST(oh.IsValid() == false, ("Period can't be large then interval")); + } + { + OSMTimeRange oh("sunset-sunrise"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + } +} + +UNIT_TEST(OpeningHours_TimeHit) +{ + { + OSMTimeRange oh("06:13-15:00; 16:30+"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + TEST(oh("12-12-2013 7:00").IsOpen(), ()); + TEST(oh("12-12-2013 16:00").IsClosed(), ()); + TEST(oh("12-12-2013 20:00").IsOpen(), ()); + } + { + OSMTimeRange oh("We-Sa; Mo[1,3] closed; Su[-1,-2] closed; Fr[2] open; Fr[-2], Fr open; Su[-2] -2 days"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + TEST(oh("20-03-2015 18:00").IsOpen(), ()); + TEST(oh("17-03-2015 18:00").IsClosed(), ()); + } + + { + OSMTimeRange oh("We-Fr; Mo[1,3] closed; Su[-1,-2] closed"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + TEST(oh("20-03-2015 18:00").IsOpen(), ()); + TEST(oh("17-03-2015 18:00").IsClosed(), ()); + } + { + OSMTimeRange oh("We-Fr; Mo[1,3] +1 day closed; Su[-1,-2] -3 days closed"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + TEST(oh("20-03-2015 18:00").IsOpen(), ()); + TEST(oh("17-03-2015 18:00").IsClosed(), ()); + } + { + OSMTimeRange oh("Mo-Su 14:30-17:00; Mo[1] closed; Su[-1] closed"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + TEST(oh("09-03-2015 16:00").IsOpen(), ()); + TEST(oh("02-03-2015 16:00").IsClosed(), ()); + TEST(oh("22-03-2015 16:00").IsOpen(), ()); + TEST(oh("29-03-2015 16:00").IsClosed(), ()); + } + { + OSMTimeRange oh("PH,Tu-Su 10:00-18:00; Sa[1] 10:00-18:00 open \"Eintritt ins gesamte Haus frei\"; Jan 1,Dec 24,Dec 25,easter -2 days: closed"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + TEST(oh("03-03-2015 16:00").IsOpen(), ()); + TEST(oh.Comment().empty(), ()); + TEST(oh("07-03-2015 16:00").IsOpen(), ()); + TEST(oh.Comment().empty() == false, ()); + } + { + OSMTimeRange oh("Mo-Su 11:00+; Mo [1,3] off"); + TEST(oh.IsValid(), ("Incorrect schedule string")); + TEST(oh("04-03-2015 16:00").IsOpen(), ()); + TEST(oh("09-03-2015 16:00").IsOpen(), ()); + TEST(oh("02-03-2015 16:00").IsClosed(), ()); + TEST(oh("16-03-2015 16:00").IsClosed(), ()); + } + { + OSMTimeRange oh("08:00-16:00 open, 16:00-03:00 open \"public room\""); + TEST(oh.IsValid(), ("Incorrect schedule string")); + TEST(oh("01-03-2015 20:00").IsOpen(), ()); + TEST(oh("01-03-2015 20:00").Comment() == "public room", ()); + } +} + + diff --git a/std/chrono.hpp b/std/chrono.hpp index 140a053396..07cc6ec1c0 100644 --- a/std/chrono.hpp +++ b/std/chrono.hpp @@ -11,6 +11,7 @@ using std::chrono::duration_cast; using std::chrono::high_resolution_clock; using std::chrono::milliseconds; using std::chrono::nanoseconds; +using std::chrono::system_clock; #ifdef DEBUG_NEW #define new DEBUG_NEW