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:
Sergey Magidovich 2015-11-14 23:45:38 +03:00
parent a8397934df
commit f7292c4a44
7 changed files with 212 additions and 49 deletions

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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(&timestamp, &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"));
}
}

View file

@ -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(&timestamp, &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

View file

@ -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)

View file

@ -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