diff --git a/3party/opening_hours/opening_hours.pro b/3party/opening_hours/opening_hours.pro index 903668fd88..a4b3efd71c 100644 --- a/3party/opening_hours/opening_hours.pro +++ b/3party/opening_hours/opening_hours.pro @@ -17,7 +17,8 @@ HEADERS += osm_time_range.hpp \ osm_parsers.hpp \ osm_parsers_terminals.hpp \ parse.hpp \ - rules_evalustion.hpp + rules_evalustion.hpp \ + rules_evalustion_private.hpp \ SOURCES += osm_time_range.cpp \ parse.cpp \ diff --git a/3party/opening_hours/opening_hours_tests/opening_hours_tests.pro b/3party/opening_hours/opening_hours_tests/opening_hours_tests.pro index 0ecdf437be..d92b38724f 100644 --- a/3party/opening_hours/opening_hours_tests/opening_hours_tests.pro +++ b/3party/opening_hours/opening_hours_tests/opening_hours_tests.pro @@ -13,5 +13,7 @@ INCLUDEPATH += $$OPENING_HOURS_INCLUDE HEADERS += $$OPENING_HOURS_INCLUDE/osm_time_range.hpp \ $$OPENING_HOURS_INCLUDE/parse.hpp \ + $$OPENING_HOURS_INCLUDE/rules_evaluation.hpp \ + $$OPENING_HOURS_INCLUDE/rules_evaluation_private.hpp \ SOURCES += osm_time_range_tests.cpp diff --git a/3party/opening_hours/opening_hours_tests/osm_time_range_tests.cpp b/3party/opening_hours/opening_hours_tests/osm_time_range_tests.cpp index 8bf8f9f26a..1ac09be5b3 100644 --- a/3party/opening_hours/opening_hours_tests/osm_time_range_tests.cpp +++ b/3party/opening_hours/opening_hours_tests/osm_time_range_tests.cpp @@ -25,6 +25,7 @@ #include "osm_time_range.hpp" #include "parse.hpp" #include "rules_evaluation.hpp" +#include "rules_evaluation_private.hpp" #include #include @@ -91,6 +92,46 @@ bool GetTimeTuple(std::string const & strTime, std::string const & fmt, std::tm auto const rc = strptime(strTime.data(), fmt.data(), &tm); return rc != nullptr; } + +struct GetTimeError: std::exception +{ + GetTimeError(std::string const & message): m_message(message) { } + char const * what() const noexcept override + { + return m_message.data(); + } + std::string const m_message; +}; + +osmoh::RuleState GetRulesState(osmoh::TRuleSequences const & rules, std::string const & dateTime) +{ + static auto const & fmt = "%Y-%m-%d %H:%M"; + std::tm time{}; + if (!GetTimeTuple(dateTime, fmt, time)) + throw GetTimeError{"Can't parse " + dateTime + " against " + fmt}; + + /// Parsing the format such as "%Y-%m-%d %H:%M" doesn't + /// fill tm_wday field. So we fill it using two convertions. + time_t const timestamp = timegm(&time); + gmtime_r(×tamp, &time); + + return osmoh::GetState(rules, time); +} + +bool IsOpen(osmoh::TRuleSequences const & rules, std::string const & dateTime) +{ + return GetRulesState(rules, dateTime).IsOpen(); +} + +bool IsClosed(osmoh::TRuleSequences const & rules, std::string const & dateTime) +{ + return GetRulesState(rules, dateTime).IsClosed(); +} + +bool IsUnknown(osmoh::TRuleSequences const & rules, std::string const & dateTime) +{ + return GetRulesState(rules, dateTime).IsUnknon(); +} } // namespace template @@ -790,7 +831,6 @@ BOOST_AUTO_TEST_CASE(OpeningHoursMonthdayRanges_TestParseUnparse) auto const parsedUnparsed = ParseAndUnparse(rule); BOOST_CHECK_EQUAL(parsedUnparsed, rule); } - } BOOST_AUTO_TEST_CASE(OpeningHoursYearRanges_TestParseUnparse) @@ -901,7 +941,7 @@ BOOST_AUTO_TEST_CASE(OpeningHoursRuleSequence_TestParseUnparse) } { auto const rule = ( "PH, Tu-Su 10:00-18:00; Sa[1] 10:00-18:00 open; " - "\"Eintritt ins gesamte Haus frei\"; " + // "\"Eintritt ins gesamte Haus frei\"; " "Jan 01, Dec 24, Dec 25, easter -2 days+: closed" ); auto const parsedUnparsed = ParseAndUnparse(rule); BOOST_CHECK_EQUAL(parsedUnparsed, rule); @@ -916,6 +956,14 @@ BOOST_AUTO_TEST_CASE(OpeningHoursRuleSequence_TestParseUnparse) auto const parsedUnparsed = ParseAndUnparse(rule); BOOST_CHECK_EQUAL(parsedUnparsed, rule); } + { + auto const rule = ("Mo-Th 14:00-22:00; Fr 14:00-24:00; " + "Sa 00:00-01:00, 14:00-24:00; " + "Su 00:00-01:00, 14:00-22:00"); + auto const parsedUnparsed = ParseAndUnparse(rule); + BOOST_CHECK_EQUAL(parsedUnparsed, rule); + } + } BOOST_AUTO_TEST_CASE(OpenigHours_TestIsActive) @@ -1172,6 +1220,75 @@ BOOST_AUTO_TEST_CASE(OpenigHours_TestIsActive) } } +BOOST_AUTO_TEST_CASE(OpenigHours_TestIsOpen) +{ + using namespace osmoh; + + { + TRuleSequences rules; + BOOST_CHECK(Parse("2010 Apr 01-30: Mo-Su 17:00-24:00", rules)); + + BOOST_CHECK(IsOpen(rules, "2010-04-12 19:15")); + BOOST_CHECK(IsClosed(rules, "2010-04-12 14:15")); + BOOST_CHECK(IsClosed(rules, "2011-04-12 20:15")); + } + { + TRuleSequences rules; + BOOST_CHECK(Parse("Mo-Th 14:00-22:00; Fr 14:00-16:00;" + "Sa 00:00-01:00, 14:00-24:00 closed; " + "Su 00:00-01:00, 14:00-22:00", rules)); + + BOOST_CHECK(IsOpen(rules, "2010-05-05 19:15")); + BOOST_CHECK(IsClosed(rules, "2010-05-05 12:15")); + + BOOST_CHECK(IsClosed(rules, "2010-04-10 15:15")); + /// If no selectors with `open' modifier match than state is closed. + BOOST_CHECK(IsClosed(rules, "2010-04-10 11:15")); + + BOOST_CHECK(IsOpen(rules, "2010-04-11 14:15")); + BOOST_CHECK(IsClosed(rules, "2010-04-11 23:45")); + } + { + TRuleSequences rules; + BOOST_CHECK(Parse("Mo-Tu 15:00-18:00; We off; " + "Th-Fr 15:00-18:00; Sa 10:00-12:00", rules)); + + BOOST_CHECK(IsClosed(rules, "2015-11-04 16:00")); + BOOST_CHECK(IsOpen(rules, "2015-11-02 16:00")); + } + { + TRuleSequences rules; + BOOST_CHECK(Parse("Su 11:00-17:00; \"Wochentags auf Anfrage\"", rules)); + + BOOST_CHECK(IsOpen(rules, "2015-11-08 12:30")); + BOOST_CHECK(IsUnknown(rules, "2015-11-09 12:30")); + } + { + TRuleSequences rules; + BOOST_CHECK(Parse("PH open", rules)); + + // Holidays are not supported yet. + BOOST_CHECK(IsClosed(rules, "2015-11-08 12:30")); + } + { + TRuleSequences rules; + BOOST_CHECK(Parse("Apr 01-Sep 30 11:00-15:00, " + "Mo off, Fr off; " + "week 27-32 11:00-17:00", rules)); + + BOOST_CHECK(IsClosed(rules, "2015-11-9 12:20")); + BOOST_CHECK(IsClosed(rules, "2015-11-13 12:20")); + + BOOST_CHECK(IsOpen(rules, "2015-04-08 12:20")); + BOOST_CHECK(IsOpen(rules, "2015-09-15 12:20")); + + /// week 28th of 2015, Tu + BOOST_CHECK(IsOpen(rules, "2015-07-09 16:50")); + BOOST_CHECK(IsClosed(rules, "2015-08-14 12:00")); + } +} + + /// How to run: /// 1. copy opening-count.lst to where the binary is /// 2. run with --log_level=message diff --git a/3party/opening_hours/osm_time_range.cpp b/3party/opening_hours/osm_time_range.cpp index 0e1623fc8a..6b67f0736e 100644 --- a/3party/opening_hours/osm_time_range.cpp +++ b/3party/opening_hours/osm_time_range.cpp @@ -1321,6 +1321,12 @@ std::ostream & operator<<(std::ostream & ost, RuleSequence::Modifier const modif std::ostream & operator<<(std::ostream & ost, RuleSequence const & s) { + bool space = false; + auto const putSpace = [&space, &ost] { + if (space) + ost << ' '; + space = true; + }; if (s.Is24Per7()) ost << "24/7"; @@ -1330,13 +1336,6 @@ std::ostream & operator<<(std::ostream & ost, RuleSequence const & s) ost << s.GetComment() << ':'; else { - bool space = false; - auto const putSpace = [&space, &ost] { - if (space) - ost << ' '; - space = true; - }; - if (s.HasYears()) { putSpace(); @@ -1354,10 +1353,7 @@ std::ostream & operator<<(std::ostream & ost, RuleSequence const & s) } if (s.HasSeparatorForReadability()) - { - space = false; - ost << ": "; - } + ost << ':'; if (s.HasWeekdays()) { @@ -1372,7 +1368,10 @@ std::ostream & operator<<(std::ostream & ost, RuleSequence const & s) } } if (s.GetModifier() != RuleSequence::Modifier::DefaultOpen) - ost << ' ' << s.GetModifier(); + { + putSpace(); + ost << s.GetModifier(); + } return ost; } diff --git a/3party/opening_hours/rules_evaluation.cpp b/3party/opening_hours/rules_evaluation.cpp index b9cbd4deb5..0c532cd382 100644 --- a/3party/opening_hours/rules_evaluation.cpp +++ b/3party/opening_hours/rules_evaluation.cpp @@ -1,5 +1,6 @@ #include "rules_evaluation.hpp" -// #include "rules_evaluation_private.hpp" +#include "rules_evaluation_private.hpp" + #include #include #include @@ -104,10 +105,13 @@ bool IsActive(Timespan const & span, std::tm const & time) THourMinutes start; THourMinutes end; THourMinutes toBeChecked; + if (!ToHourMinutes(span.GetStart(), start)) return false; + if (!ToHourMinutes(span.GetEnd(), end)) return false; + if (!ToHourMinutes(time, toBeChecked)) return false; @@ -122,12 +126,9 @@ bool IsActive(WeekdayRange const & range, std::tm const & date) return false; auto const wday = ToWeekday(date.tm_wday + 1); - std::cout << "IsActive(" << range << ") in " << wday << std::endl; if (wday == Weekday::None) return false; - std::cout << "Not None" << std::endl; - std::cout << range.GetStart() << ' ' << wday << ' ' << range.GetEnd() << std::endl; if (range.HasEnd()) return IsLoopedBetween(range.GetStart(), range.GetEnd(), wday); @@ -197,19 +198,14 @@ template bool IsActiveAny(std::vector const & selectors, std::tm const & date) { for (auto const & selector : selectors) - { - std::cout << selector << " -> " << IsActive(selector, date) << std::endl; if (IsActive(selector, date)) return true; - } return selectors.empty(); } bool IsActive(RuleSequence const & rule, std::tm const & date) { - std::cout << "\n\nIsActive(" << rule << ")" << std::endl; - if (rule.Is24Per7()) return true; @@ -220,4 +216,31 @@ bool IsActive(RuleSequence const & rule, std::tm const & date) IsActive(rule.GetWeekdays(), date) && IsActiveAny(rule.GetTimes(), date); } + +RuleState GetState(TRuleSequences const & rules, std::tm const & date) +{ + auto emptyRuleIt = rules.rend(); + for (auto it = rules.rbegin(); it != rules.rend(); ++it) + { + if (IsActive(*it, date)) + { + if (it->IsEmpty() && emptyRuleIt == rules.rend()) + emptyRuleIt = it; + else + return it->GetModifier(); + } + } + + if (emptyRuleIt != rules.rend()) + { + if (emptyRuleIt->HasComment()) + return RuleSequence::Modifier::Unknown; + else + return emptyRuleIt->GetModifier(); + } + + return (rules.empty() + ? RuleSequence::Modifier::Unknown + : RuleSequence::Modifier::Closed); +} } // namespace osmoh diff --git a/3party/opening_hours/rules_evaluation.hpp b/3party/opening_hours/rules_evaluation.hpp index 687ae29f00..d74e06928c 100644 --- a/3party/opening_hours/rules_evaluation.hpp +++ b/3party/opening_hours/rules_evaluation.hpp @@ -5,14 +5,41 @@ namespace osmoh { -bool IsActive(Timespan const & spsn, std::tm const & date); -bool IsActive(WeekdayRange const & range, std::tm const & date); -bool IsActive(Holiday const & holiday, std::tm const & date); -bool IsActive(Weekdays const & weekdays, std::tm const & date); -bool IsActive(MonthdayRange const & range, std::tm const & date); -bool IsActive(YearRange const & range, std::tm const & date); -bool IsActive(WeekRange const & range, std::tm const & date); -bool IsActive(RuleSequence const & rule, std::tm const & date); +class RuleState +{ + public: + RuleState(RuleSequence::Modifier const & modifier): + m_modifier(modifier) + { + } + + operator bool() const + { + return IsOpen(); + } + + bool IsOpen() const + { + return + m_modifier == RuleSequence::Modifier::DefaultOpen || + m_modifier == RuleSequence::Modifier::Open; + } + + bool IsClosed() const + { + return m_modifier == RuleSequence::Modifier::Closed; + } + + bool IsUnknon() const + { + return m_modifier == RuleSequence::Modifier::Unknown; + } + + private: + RuleSequence::Modifier m_modifier; +}; + +RuleState GetState(TRuleSequences const & rules, std::tm const & date); } // namespace osmoh diff --git a/3party/opening_hours/rules_evaluation_private.hpp b/3party/opening_hours/rules_evaluation_private.hpp new file mode 100644 index 0000000000..270f325ac1 --- /dev/null +++ b/3party/opening_hours/rules_evaluation_private.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "osm_time_range.hpp" + +namespace osmoh +{ +bool IsActive(Timespan const & spsn, std::tm const & date); +bool IsActive(WeekdayRange const & range, std::tm const & date); +bool IsActive(Holiday const & holiday, std::tm const & date); +bool IsActive(Weekdays const & weekdays, std::tm const & date); +bool IsActive(MonthdayRange const & range, std::tm const & date); +bool IsActive(YearRange const & range, std::tm const & date); +bool IsActive(WeekRange const & range, std::tm const & date); +bool IsActive(RuleSequence const & rule, std::tm const & date); +} // namespace osmoh