forked from organicmaps/organicmaps
Implement evaluation of opening_hours rules with
extended hours. Add tests. Pass more tests in opening_hours_supported_features_tests.
This commit is contained in:
parent
a8397934df
commit
f7292c4a44
7 changed files with 212 additions and 49 deletions
|
@ -357,6 +357,11 @@ HourMinutes const & Time::GetHourMinutes() const
|
|||
return m_hourMinutes;
|
||||
}
|
||||
|
||||
HourMinutes & Time::GetHourMinutes()
|
||||
{
|
||||
return m_hourMinutes;
|
||||
}
|
||||
|
||||
void Time::SetHourMinutes(HourMinutes const & hm)
|
||||
{
|
||||
m_type = Type::HourMinutes;
|
||||
|
@ -510,6 +515,16 @@ Time const & Timespan::GetEnd() const
|
|||
return m_end;
|
||||
}
|
||||
|
||||
Time & Timespan::GetStart()
|
||||
{
|
||||
return m_start;
|
||||
}
|
||||
|
||||
Time & Timespan::GetEnd()
|
||||
{
|
||||
return m_end;
|
||||
}
|
||||
|
||||
TimespanPeriod const & Timespan::GetPeriod() const
|
||||
{
|
||||
return m_period;
|
||||
|
|
|
@ -134,6 +134,7 @@ class Time
|
|||
void SetEvent(TimeEvent const & event);
|
||||
|
||||
HourMinutes const & GetHourMinutes() const;
|
||||
HourMinutes & GetHourMinutes();
|
||||
void SetHourMinutes(HourMinutes const & hm);
|
||||
|
||||
bool IsEmpty() const;
|
||||
|
@ -204,6 +205,10 @@ public:
|
|||
|
||||
Time const & GetStart() const;
|
||||
Time const & GetEnd() const;
|
||||
|
||||
Time & GetStart();
|
||||
Time & GetEnd();
|
||||
|
||||
TimespanPeriod const & GetPeriod() const;
|
||||
|
||||
void SetStart(Time const & start);
|
||||
|
|
|
@ -83,7 +83,7 @@ enum
|
|||
Serialised,
|
||||
Period,
|
||||
Plus,
|
||||
// True if a rule has Timespen with more than 24 hours at the end
|
||||
// True if a rule has Timespan with more than 24 hours at the end
|
||||
// or and greater than start.
|
||||
// Example:
|
||||
// 12:00-29:00
|
||||
|
|
|
@ -99,15 +99,12 @@ inline osmoh::RuleState GetRulesState(osmoh::TRuleSequences const & rules, std::
|
|||
{
|
||||
static auto const & fmt = "%Y-%m-%d %H:%M";
|
||||
std::tm time = {};
|
||||
/// Parsing the format such as "%Y-%m-%d %H:%M" doesn't
|
||||
/// fill tm_wday field. It will be filled after time_t to tm convertion.
|
||||
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 = mktime(&time);
|
||||
localtime_r(×tamp, &time);
|
||||
|
||||
return osmoh::GetState(rules, time);
|
||||
return osmoh::GetState(rules, mktime(&time));
|
||||
}
|
||||
|
||||
inline bool IsOpen(osmoh::TRuleSequences const & rules, std::string const & dateTime)
|
||||
|
@ -124,6 +121,11 @@ inline bool IsUnknown(osmoh::TRuleSequences const & rules, std::string const & d
|
|||
{
|
||||
return GetRulesState(rules, dateTime) == osmoh::RuleState::Unknown;
|
||||
}
|
||||
|
||||
inline bool IsActive(osmoh::RuleSequence const & rule, std::tm tm)
|
||||
{
|
||||
return IsActive(rule, mktime(&tm));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
BOOST_AUTO_TEST_CASE(OpeningHours_TestHourMinutes)
|
||||
|
@ -1289,11 +1291,11 @@ BOOST_AUTO_TEST_CASE(OpeningHours_TestIsActive)
|
|||
BOOST_CHECK(Parse("Mo-We 17:00-18:00, Th,Fr 15:00-16:00", rules));
|
||||
|
||||
std::tm time{};
|
||||
auto const fmt = "%w %H:%M";
|
||||
BOOST_CHECK(GetTimeTuple("2 17:35", fmt, time));
|
||||
auto const fmt = "%Y-%m-%d %H:%M";
|
||||
BOOST_CHECK(GetTimeTuple("2015-10-6 17:35", fmt, time));
|
||||
BOOST_CHECK(IsActive(rules[0], time));
|
||||
|
||||
BOOST_CHECK(GetTimeTuple("4 15:35", fmt, time));
|
||||
BOOST_CHECK(GetTimeTuple("2015-10-8 15:35", fmt, time));
|
||||
BOOST_CHECK(IsActive(rules[1], time));
|
||||
}
|
||||
{
|
||||
|
@ -1313,14 +1315,14 @@ BOOST_AUTO_TEST_CASE(OpeningHours_TestIsActive)
|
|||
BOOST_CHECK(Parse("Apr-Sep Su 14:30-17:00", rules));
|
||||
|
||||
std::tm time{};
|
||||
auto const fmt = "%m-%w %H:%M";
|
||||
BOOST_CHECK(GetTimeTuple("5-0 15:35", fmt, time));
|
||||
auto const fmt = "%Y-%m-%d %H:%M";
|
||||
BOOST_CHECK(GetTimeTuple("2015-04-12 15:35", fmt, time));
|
||||
BOOST_CHECK(IsActive(rules[0], time));
|
||||
|
||||
BOOST_CHECK(GetTimeTuple("5-1 15:35", fmt, time));
|
||||
BOOST_CHECK(GetTimeTuple("2015-04-13 15:35", fmt, time));
|
||||
BOOST_CHECK(!IsActive(rules[0], time));
|
||||
|
||||
BOOST_CHECK(GetTimeTuple("10-0 15:35", fmt, time));
|
||||
BOOST_CHECK(GetTimeTuple("2015-10-11 15:35", fmt, time));
|
||||
BOOST_CHECK(!IsActive(rules[0], time));
|
||||
}
|
||||
{
|
||||
|
@ -1332,6 +1334,57 @@ BOOST_AUTO_TEST_CASE(OpeningHours_TestIsActive)
|
|||
BOOST_CHECK(GetTimeTuple("2010-4-20-0 18:15", fmt, time));
|
||||
BOOST_CHECK(IsActive(rules[0], time));
|
||||
}
|
||||
{
|
||||
TRuleSequences rules;
|
||||
BOOST_CHECK(Parse("Mo 09:00-06:00", rules));
|
||||
|
||||
std::tm time{};
|
||||
auto const fmt = "%Y-%m-%d %H:%M";
|
||||
BOOST_CHECK(GetTimeTuple("2015-11-10 05:35", fmt, time));
|
||||
BOOST_CHECK(IsActive(rules[0], time));
|
||||
|
||||
BOOST_CHECK(GetTimeTuple("2015-11-11 05:35", fmt, time));
|
||||
BOOST_CHECK(!IsActive(rules[0], time));
|
||||
|
||||
BOOST_CHECK(GetTimeTuple("2015-11-11 06:01", fmt, time));
|
||||
BOOST_CHECK(!IsActive(rules[0], time));
|
||||
}
|
||||
{
|
||||
TRuleSequences rules;
|
||||
BOOST_CHECK(Parse("Mo 09:00-32:00", rules));
|
||||
|
||||
std::tm time{};
|
||||
auto const fmt = "%Y-%m-%d %H:%M";
|
||||
BOOST_CHECK(GetTimeTuple("2015-11-10 05:35", fmt, time));
|
||||
BOOST_CHECK(IsActive(rules[0], time));
|
||||
|
||||
BOOST_CHECK(GetTimeTuple("2015-11-11 05:35", fmt, time));
|
||||
BOOST_CHECK(!IsActive(rules[0], time));
|
||||
|
||||
BOOST_CHECK(GetTimeTuple("2015-11-11 06:01", fmt, time));
|
||||
BOOST_CHECK(!IsActive(rules[0], time));
|
||||
}
|
||||
{
|
||||
TRuleSequences rules;
|
||||
BOOST_CHECK(Parse("Mo 09:00-48:00", rules));
|
||||
|
||||
std::tm time{};
|
||||
auto const fmt = "%Y-%m-%d %H:%M";
|
||||
BOOST_CHECK(GetTimeTuple("2015-11-10 05:35", fmt, time));
|
||||
BOOST_CHECK(IsActive(rules[0], time));
|
||||
|
||||
BOOST_CHECK(GetTimeTuple("2015-11-10 15:35", fmt, time));
|
||||
BOOST_CHECK(IsActive(rules[0], time));
|
||||
|
||||
BOOST_CHECK(GetTimeTuple("2015-11-10 23:35", fmt, time));
|
||||
BOOST_CHECK(IsActive(rules[0], time));
|
||||
|
||||
BOOST_CHECK(GetTimeTuple("2015-11-11 05:35", fmt, time));
|
||||
BOOST_CHECK(!IsActive(rules[0], time));
|
||||
|
||||
BOOST_CHECK(GetTimeTuple("2015-11-11 06:01", fmt, time));
|
||||
BOOST_CHECK(!IsActive(rules[0], time));
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(OpeningHours_TestIsOpen)
|
||||
|
@ -1429,6 +1482,16 @@ BOOST_AUTO_TEST_CASE(OpeningHours_TestIsOpen)
|
|||
BOOST_CHECK(IsOpen(rules, "2015-11-06 18:40"));
|
||||
BOOST_CHECK(!IsClosed(rules, "2015-11-06 18:40"));
|
||||
}
|
||||
{
|
||||
TRuleSequences rules;
|
||||
BOOST_CHECK(Parse("2015 Apr 01-30: Mo-Fr 17:00-08:00", rules));
|
||||
|
||||
BOOST_CHECK(IsOpen(rules, "2015-04-10 07:15"));
|
||||
BOOST_CHECK(IsOpen(rules, "2015-05-01 07:15"));
|
||||
BOOST_CHECK(IsOpen(rules, "2015-04-11 07:15"));
|
||||
BOOST_CHECK(IsClosed(rules, "2015-04-12 14:15"));
|
||||
BOOST_CHECK(IsClosed(rules, "2016-04-12 20:15"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "rules_evaluation.hpp"
|
||||
#include "rules_evaluation_private.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
@ -9,6 +10,7 @@
|
|||
namespace
|
||||
{
|
||||
using THourMinutes = std::tuple<int, int>;
|
||||
using osmoh::operator""_h;
|
||||
|
||||
constexpr osmoh::MonthDay::TYear kTMYearOrigin = 1900;
|
||||
|
||||
|
@ -116,12 +118,89 @@ inline osmoh::RuleState ModifierToRuleState(osmoh::RuleSequence::Modifier const
|
|||
return osmoh::RuleState::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
// Transform timspan with extended end of the form of
|
||||
// time less than 24 hours to extended form, i.e from 25 to 48 hours.
|
||||
// Example: 12:15-06:00 -> 12:15-30:00.
|
||||
inline void NormalizeExtendedEnd(osmoh::Timespan & span)
|
||||
{
|
||||
auto & endHouminutes = span.GetEnd().GetHourMinutes();
|
||||
auto const duration = endHouminutes.GetDuration();
|
||||
if (duration < 24_h)
|
||||
endHouminutes.SetDuration(duration + 24_h);
|
||||
}
|
||||
|
||||
inline osmoh::TTimespans SplitExtendedHours(osmoh::Timespan span)
|
||||
{
|
||||
osmoh::TTimespans result;
|
||||
NormalizeExtendedEnd(span);
|
||||
|
||||
auto spanToBeSplit = span;
|
||||
if (spanToBeSplit.HasExtendedHours())
|
||||
{
|
||||
osmoh::Timespan normalSpan;
|
||||
normalSpan.SetStart(spanToBeSplit.GetStart());
|
||||
normalSpan.SetEnd(osmoh::HourMinutes(24_h));
|
||||
result.push_back(normalSpan);
|
||||
|
||||
spanToBeSplit.SetStart(osmoh::HourMinutes(0_h));
|
||||
spanToBeSplit.GetEnd().AddDuration(-24_h);
|
||||
}
|
||||
result.push_back(spanToBeSplit);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void SplitExtendedHours(osmoh::TTimespans const & spans,
|
||||
osmoh::TTimespans & originalNormilizedSpans,
|
||||
osmoh::Timespan & additionalSpan)
|
||||
{
|
||||
// We don't handle more than one occurence of extended span
|
||||
// since it is an invalid situation.
|
||||
for (auto it = begin(spans); it != end(spans); ++it)
|
||||
{
|
||||
if (!it->HasExtendedHours())
|
||||
{
|
||||
originalNormilizedSpans.push_back(*it);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const splittedSpans = SplitExtendedHours(*it);
|
||||
originalNormilizedSpans.push_back(splittedSpans[0]);
|
||||
additionalSpan = splittedSpans[1];
|
||||
|
||||
++it;
|
||||
std::copy(it, end(spans), back_inserter(originalNormilizedSpans));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool HasExtendedHours(osmoh::RuleSequence const & rule)
|
||||
{
|
||||
for (auto const & timespan : rule.GetTimes())
|
||||
if (timespan.HasExtendedHours())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::tm MakeTimetuple(time_t const timestamp)
|
||||
{
|
||||
std::tm tm{};
|
||||
localtime_r(×tamp, &tm);
|
||||
return tm;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace osmoh
|
||||
{
|
||||
bool IsActive(Timespan const & span, std::tm const & time)
|
||||
{
|
||||
// Timespan with e.h. should be split into parts with no e.h.
|
||||
// before calling IsActive().
|
||||
// TODO(mgsergio): set assert(!span.HasExtendedHours())
|
||||
|
||||
if (span.HasStart() && span.HasEnd())
|
||||
{
|
||||
THourMinutes start;
|
||||
|
@ -137,17 +216,6 @@ bool IsActive(Timespan const & span, std::tm const & time)
|
|||
if (!ToHourMinutes(time, toBeChecked))
|
||||
return false;
|
||||
|
||||
// TODO(mgsergio): We don't handle extended hours yet.
|
||||
// Extended hours handling could be implemented through
|
||||
// splitting rule with extended hours into separated rules.
|
||||
// See https://trello.com/c/Efsvs6PP/23-opening-hours-extended-hours
|
||||
if (end <= start || end > THourMinutes{24, 00})
|
||||
// It's better to say we are open, cause
|
||||
// in search result page only `closed' lables are shown.
|
||||
// So from user's perspective `unknown' and `open'
|
||||
// mean same as `not closed'.
|
||||
return true;
|
||||
|
||||
return start <= toBeChecked && toBeChecked <= end;
|
||||
}
|
||||
else if (span.HasStart() && span.HasPlus())
|
||||
|
@ -188,9 +256,8 @@ bool IsActive(Weekdays const & weekdays, std::tm const & date)
|
|||
if (IsActive(hd, date))
|
||||
return true;
|
||||
|
||||
return
|
||||
weekdays.GetWeekdayRanges().empty() &&
|
||||
weekdays.GetHolidays().empty();
|
||||
return weekdays.GetWeekdayRanges().empty() &&
|
||||
weekdays.GetHolidays().empty();
|
||||
}
|
||||
|
||||
bool IsActive(MonthdayRange const & range, std::tm const & date)
|
||||
|
@ -199,9 +266,8 @@ bool IsActive(MonthdayRange const & range, std::tm const & date)
|
|||
return false;
|
||||
|
||||
if (range.HasEnd())
|
||||
return
|
||||
range.GetStart() <= date &&
|
||||
date <= NormalizeEnd(range.GetStart(), range.GetEnd());
|
||||
return range.GetStart() <= date &&
|
||||
date <= NormalizeEnd(range.GetStart(), range.GetEnd());
|
||||
|
||||
return range.GetStart() == date;
|
||||
}
|
||||
|
@ -244,25 +310,44 @@ bool IsActiveAny(std::vector<T> const & selectors, std::tm const & date)
|
|||
return selectors.empty();
|
||||
}
|
||||
|
||||
bool IsActive(RuleSequence const & rule, std::tm const & date)
|
||||
bool IsActive(RuleSequence const & rule, time_t const timestamp)
|
||||
{
|
||||
if (rule.IsTwentyFourHours())
|
||||
return true;
|
||||
|
||||
return
|
||||
IsActiveAny(rule.GetYears(), date) &&
|
||||
IsActiveAny(rule.GetMonths(), date) &&
|
||||
IsActiveAny(rule.GetWeeks(), date) &&
|
||||
IsActive(rule.GetWeekdays(), date) &&
|
||||
IsActiveAny(rule.GetTimes(), date);
|
||||
auto const checkIsActive = [](RuleSequence const & rule, std::tm const & dt)
|
||||
{
|
||||
return IsActiveAny(rule.GetYears(), dt) && IsActiveAny(rule.GetMonths(), dt) &&
|
||||
IsActiveAny(rule.GetWeeks(), dt) && IsActive(rule.GetWeekdays(), dt);
|
||||
};
|
||||
|
||||
auto const dateTimeTM = MakeTimetuple(timestamp);
|
||||
if (!HasExtendedHours(rule))
|
||||
return checkIsActive(rule, dateTimeTM) && IsActiveAny(rule.GetTimes(), dateTimeTM);
|
||||
|
||||
TTimespans originalNormalizedSpans;
|
||||
Timespan additionalSpan;
|
||||
SplitExtendedHours(rule.GetTimes(), originalNormalizedSpans, additionalSpan);
|
||||
|
||||
if (checkIsActive(rule, dateTimeTM) && IsActiveAny(originalNormalizedSpans, dateTimeTM))
|
||||
return true;
|
||||
|
||||
time_t constexpr twentyFourHoursShift = 24 * 60 * 60;
|
||||
auto const dateTimeTMShifted = MakeTimetuple(timestamp - twentyFourHoursShift);
|
||||
|
||||
if (checkIsActive(rule, dateTimeTMShifted) &&
|
||||
IsActive(additionalSpan, dateTimeTMShifted))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
RuleState GetState(TRuleSequences const & rules, std::tm const & date)
|
||||
RuleState GetState(TRuleSequences const & rules, time_t const timestamp)
|
||||
{
|
||||
auto emptyRuleIt = rules.rend();
|
||||
for (auto it = rules.rbegin(); it != rules.rend(); ++it)
|
||||
{
|
||||
if (IsActive(*it, date))
|
||||
if (IsActive(*it, timestamp))
|
||||
{
|
||||
if (it->IsEmpty() && emptyRuleIt == rules.rend())
|
||||
emptyRuleIt = it;
|
||||
|
@ -283,11 +368,4 @@ RuleState GetState(TRuleSequences const & rules, std::tm const & date)
|
|||
? RuleState::Unknown
|
||||
: RuleState::Closed);
|
||||
}
|
||||
|
||||
RuleState GetState(TRuleSequences const & rules, time_t const dateTime)
|
||||
{
|
||||
std::tm tm{};
|
||||
localtime_r(&dateTime, &tm);
|
||||
return GetState(rules, tm);
|
||||
}
|
||||
} // namespace osmoh
|
||||
|
|
|
@ -12,7 +12,7 @@ enum class RuleState
|
|||
Closed,
|
||||
Unknown
|
||||
};
|
||||
RuleState GetState(TRuleSequences const & rules, std::tm const & date);
|
||||
|
||||
RuleState GetState(TRuleSequences const & rules, time_t const dateTime);
|
||||
|
||||
inline bool IsOpen(TRuleSequences const & rules, time_t const dateTime)
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include "opening_hours.hpp"
|
||||
|
||||
#include <ctime>
|
||||
|
||||
namespace osmoh
|
||||
{
|
||||
bool IsActive(Timespan const & spsn, std::tm const & date);
|
||||
|
@ -11,5 +13,5 @@ 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);
|
||||
bool IsActive(RuleSequence const & rule, time_t const timestamp);
|
||||
} // namespace osmoh
|
||||
|
|
Loading…
Add table
Reference in a new issue