Merge pull request #542 from mgsergio/handle_extended_hours

Handle extended hours
This commit is contained in:
Sergey Yershov 2015-11-18 11:28:01 +03:00
commit 724d75a915
7 changed files with 524 additions and 1146 deletions

File diff suppressed because it is too large Load diff

View file

@ -40,24 +40,25 @@ public:
using TMinutes = std::chrono::minutes;
HourMinutes() = default;
explicit HourMinutes(THours const duration);
explicit HourMinutes(TMinutes const duration);
explicit HourMinutes(THours const duration) { SetDuration(duration); }
explicit HourMinutes(TMinutes const duration) { SetDuration(duration); }
bool IsEmpty() const;
bool IsEmpty() const { return m_empty; }
bool IsExtended() const;
THours GetHours() const;
TMinutes GetMinutes() const;
TMinutes GetDuration() const;
THours GetHours() const { return m_hours; }
TMinutes GetMinutes() const { return m_minutes; }
TMinutes GetDuration() const { return GetMinutes() + GetHours(); }
THours::rep GetHoursCount() const;
TMinutes::rep GetMinutesCount() const;
TMinutes::rep GetDurationCount() const;
THours::rep GetHoursCount() const { return GetHours().count(); }
TMinutes::rep GetMinutesCount() const { return GetMinutes().count(); }
TMinutes::rep GetDurationCount() const { return GetDuration().count(); }
void SetHours(THours const hours);
void SetMinutes(TMinutes const minutes);
void SetDuration(TMinutes const duration);
void AddDuration(TMinutes const duration);
void AddDuration(TMinutes const duration) { SetDuration(GetDuration() + duration); }
private:
THours m_hours = THours::zero();
@ -81,17 +82,17 @@ public:
};
TimeEvent() = default;
TimeEvent(Event const event);
TimeEvent(Event const event): m_event(event) {}
bool IsEmpty() const;
bool HasOffset() const;
bool IsEmpty() const { return m_event == Event::None; }
bool HasOffset() const { return !m_offset.IsEmpty(); }
Event GetEvent() const;
void SetEvent(Event const event);
Event GetEvent() const { return m_event; }
void SetEvent(Event const event) { m_event = event; }
HourMinutes const & GetOffset() const;
void SetOffset(HourMinutes const & offset);
void AddDurationToOffset(HourMinutes::TMinutes const duration);
HourMinutes const & GetOffset() const { return m_offset; }
void SetOffset(HourMinutes const & offset) { m_offset = offset; }
void AddDurationToOffset(HourMinutes::TMinutes const duration) { m_offset.AddDuration(duration); }
Time GetEventTime() const;
@ -116,30 +117,31 @@ class Time
using TMinutes = HourMinutes::TMinutes;
Time() = default;
Time(HourMinutes const & hm);
Time(TimeEvent const & te);
Time(HourMinutes const & hm) { SetHourMinutes(hm); }
Time(TimeEvent const & te) { SetEvent(te); }
Type GetType() const;
bool IsEmpty() const { return GetType() == Type::None; }
bool IsTime() const { return IsHoursMinutes() || IsEvent(); }
bool IsEvent() const { return GetType() == Type::Event; }
bool IsHoursMinutes() const { return GetType() == Type::HourMinutes; }
THours::rep GetHoursCount() const;
TMinutes::rep GetMinutesCount() const;
Type GetType() const { return m_type; }
THours::rep GetHoursCount() const { return GetHours().count(); }
TMinutes::rep GetMinutesCount() const { return GetMinutes().count(); }
THours GetHours() const;
TMinutes GetMinutes() const;
void AddDuration(TMinutes const duration);
TimeEvent const & GetEvent() const;
TimeEvent const & GetEvent() const { return m_event; }
void SetEvent(TimeEvent const & event);
HourMinutes const & GetHourMinutes() const;
HourMinutes const & GetHourMinutes() const { return m_hourMinutes; }
HourMinutes & GetHourMinutes() { return m_hourMinutes; }
void SetHourMinutes(HourMinutes const & hm);
bool IsEmpty() const;
bool IsTime() const;
bool IsEvent() const;
bool IsHoursMinutes() const;
private:
HourMinutes m_hourMinutes;
TimeEvent m_event;
@ -173,13 +175,13 @@ public:
TimespanPeriod(HourMinutes const & hm);
TimespanPeriod(HourMinutes::TMinutes const minutes);
bool IsEmpty() const;
bool IsHoursMinutes() const;
bool IsMinutes() const;
bool IsEmpty() const { return m_type == Type::None; }
bool IsHoursMinutes() const { return m_type == Type::HourMinutes; }
bool IsMinutes() const { return m_type == Type::Minutes; }
HourMinutes const & GetHourMinutes() const;
HourMinutes::TMinutes GetMinutes() const;
HourMinutes::TMinutes::rep GetMinutesCount() const;
HourMinutes const & GetHourMinutes() const { return m_hourMinutes; }
HourMinutes::TMinutes GetMinutes() const { return m_minutes; }
HourMinutes::TMinutes::rep GetMinutesCount() const { return GetMinutes().count(); }
private:
HourMinutes::TMinutes m_minutes;
@ -193,23 +195,26 @@ std::ostream & operator<<(std::ostream & ost, TimespanPeriod const p);
class Timespan
{
public:
bool IsEmpty() const;
bool IsOpen() const;
bool HasStart() const;
bool HasEnd() const;
bool HasPlus() const;
bool HasPeriod() const;
bool IsEmpty() const { return !HasStart() && !HasEnd(); }
bool IsOpen() const { return HasStart() && !HasEnd(); }
bool HasStart() const { return !GetStart().IsEmpty(); }
bool HasEnd() const { return !GetEnd().IsEmpty(); }
bool HasPlus() const { return m_plus; }
bool HasPeriod() const { return !m_period.IsEmpty(); }
bool HasExtendedHours() const;
Time const & GetStart() const;
Time const & GetEnd() const;
TimespanPeriod const & GetPeriod() const;
Time const & GetStart() const { return m_start; }
Time const & GetEnd() const { return m_end; }
void SetStart(Time const & start);
void SetEnd(Time const & end);
void SetPeriod(TimespanPeriod const & period);
void SetPlus(bool const plus);
Time & GetStart() { return m_start; }
Time & GetEnd() { return m_end; }
bool IsValid() const;
TimespanPeriod const & GetPeriod() const { return m_period; }
void SetStart(Time const & start) { m_start = start; }
void SetEnd(Time const & end) { m_end = end; }
void SetPeriod(TimespanPeriod const & period) { m_period = period; }
void SetPlus(bool const plus) { m_plus = plus; }
private:
Time m_start;
@ -236,15 +241,15 @@ public:
Fifth
};
bool IsEmpty() const;
bool HasStart() const;
bool HasEnd() const;
bool IsEmpty() const { return !HasStart() && !HasEnd(); }
bool HasStart() const { return GetStart() != NthDayOfTheMonth::None; }
bool HasEnd() const { return GetEnd() != NthDayOfTheMonth::None; }
NthDayOfTheMonth GetStart() const;
NthDayOfTheMonth GetEnd() const;
NthDayOfTheMonth GetStart() const { return m_start; }
NthDayOfTheMonth GetEnd() const { return m_end; }
void SetStart(NthDayOfTheMonth const s);
void SetEnd(NthDayOfTheMonth const e);
void SetStart(NthDayOfTheMonth const s) { m_start = s; }
void SetEnd(NthDayOfTheMonth const e) { m_end = e; }
private:
NthDayOfTheMonth m_start = NthDayOfTheMonth::None;
@ -288,32 +293,33 @@ class WeekdayRange
public:
bool HasWday(Weekday const & wday) const;
bool HasSunday() const;
bool HasMonday() const;
bool HasTuesday() const;
bool HasWednesday() const;
bool HasThursday() const;
bool HasFriday() const;
bool HasSaturday() const;
bool HasSunday() const { return HasWday(Weekday::Sunday); }
bool HasMonday() const { return HasWday(Weekday::Monday); }
bool HasTuesday() const { return HasWday(Weekday::Tuesday); }
bool HasWednesday() const { return HasWday(Weekday::Wednesday); }
bool HasThursday() const { return HasWday(Weekday::Thursday); }
bool HasFriday() const { return HasWday(Weekday::Friday); }
bool HasSaturday() const { return HasWday(Weekday::Saturday); }
bool HasStart() const;
bool HasEnd() const;
bool HasOffset() const;
bool IsEmpty() const;
bool HasStart() const { return GetStart() != Weekday::None; }
bool HasEnd() const {return GetEnd() != Weekday::None; }
bool HasOffset() const { return GetOffset() != 0; }
bool IsEmpty() const { return GetStart() == Weekday::None &&
GetEnd() == Weekday::None; }
Weekday GetStart() const;
Weekday GetEnd() const;
Weekday GetStart() const { return m_start; }
Weekday GetEnd() const { return m_end; }
void SetStart(Weekday const & wday);
void SetEnd(Weekday const & wday);
void SetStart(Weekday const & wday) { m_start = wday; }
void SetEnd(Weekday const & wday) { m_end = wday; }
int32_t GetOffset() const;
void SetOffset(int32_t const offset);
int32_t GetOffset() const { return m_offset; }
void SetOffset(int32_t const offset) { m_offset = offset; }
bool HasNth() const;
TNths const & GetNths() const;
bool HasNth() const { return !m_nths.empty(); }
TNths const & GetNths() const { return m_nths; }
void AddNth(NthWeekdayOfTheMonthEntry const & entry);
void AddNth(NthWeekdayOfTheMonthEntry const & entry) { m_nths.push_back(entry); }
private:
Weekday m_start = Weekday::None;
@ -330,11 +336,11 @@ std::ostream & operator<<(std::ostream & ost, TWeekdayRanges const & ranges);
class Holiday
{
public:
bool IsPlural() const;
void SetPlural(bool const plural);
bool IsPlural() const { return m_plural; }
void SetPlural(bool const plural) { m_plural = plural; }
int32_t GetOffset() const;
void SetOffset(int32_t const offset);
int32_t GetOffset() const { return m_offset; }
void SetOffset(int32_t const offset) { m_offset = offset; }
private:
bool m_plural = false;
@ -350,18 +356,18 @@ std::ostream & operator<<(std::ostream & ost, THolidays const & holidys);
class Weekdays
{
public:
bool IsEmpty() const;
bool HasWeekday() const;
bool HasHolidays() const;
bool IsEmpty() const { return GetWeekdayRanges().empty() && GetHolidays().empty(); }
bool HasWeekday() const { return !GetWeekdayRanges().empty(); }
bool HasHolidays() const { return !GetHolidays().empty(); }
TWeekdayRanges const & GetWeekdayRanges() const;
THolidays const & GetHolidays() const;
TWeekdayRanges const & GetWeekdayRanges() const { return m_weekdayRanges; }
THolidays const & GetHolidays() const { return m_holidays; }
void SetWeekdayRanges(TWeekdayRanges const ranges);
void SetHolidays(THolidays const & holidays);
void SetWeekdayRanges(TWeekdayRanges const ranges) { m_weekdayRanges = ranges; }
void SetHolidays(THolidays const & holidays) { m_holidays = holidays; }
void AddWeekdayRange(WeekdayRange const range);
void AddHoliday(Holiday const & holiday);
void AddWeekdayRange(WeekdayRange const range) { m_weekdayRanges.push_back(range); }
void AddHoliday(Holiday const & holiday) { m_holidays.push_back(holiday); }
private:
TWeekdayRanges m_weekdayRanges;
@ -373,18 +379,18 @@ std::ostream & operator<<(std::ostream & ost, Weekdays const & weekday);
class DateOffset
{
public:
bool IsEmpty() const;
bool HasWDayOffset() const;
bool HasOffset() const;
bool IsEmpty() const { return !HasOffset() && !HasWDayOffset(); }
bool HasWDayOffset() const { return m_wdayOffest != Weekday::None; }
bool HasOffset() const { return m_offset != 0; }
bool IsWDayOffsetPositive() const;
bool IsWDayOffsetPositive() const { return m_positive; }
Weekday GetWDayOffset() const;
int32_t GetOffset() const;
Weekday GetWDayOffset() const { return m_wdayOffest; }
int32_t GetOffset() const { return m_offset; }
void SetWDayOffset(Weekday const wday);
void SetOffset(int32_t const offset);
void SetWDayOffsetPositive(bool const on);
void SetWDayOffset(Weekday const wday) { m_wdayOffest = wday; }
void SetOffset(int32_t const offset) { m_offset = offset; }
void SetWDayOffsetPositive(bool const on) { m_positive = on; }
private:
Weekday m_wdayOffest = Weekday::None;
@ -423,25 +429,25 @@ public:
using TYear = uint16_t;
using TDayNum = uint8_t;
bool IsEmpty() const;
bool IsVariable() const;
bool IsEmpty() const { return !HasYear() && !HasMonth() && !HasDayNum() && !IsVariable(); }
bool IsVariable() const { return GetVariableDate() != VariableDate::None; }
bool HasYear() const;
bool HasMonth() const;
bool HasDayNum() const;
bool HasOffset() const;
bool HasYear() const { return GetYear() != 0; }
bool HasMonth() const { return GetMonth() != Month::None; }
bool HasDayNum() const { return GetDayNum() != 0; }
bool HasOffset() const { return !GetOffset().IsEmpty(); }
TYear GetYear() const;
Month GetMonth() const;
TDayNum GetDayNum() const;
DateOffset const & GetOffset() const;
VariableDate GetVariableDate() const;
TYear GetYear() const { return m_year; }
Month GetMonth() const { return m_month; }
TDayNum GetDayNum() const { return m_daynum; }
DateOffset const & GetOffset() const { return m_offset; }
VariableDate GetVariableDate() const { return m_variable_date; }
void SetYear(TYear const year);
void SetMonth(Month const month);
void SetDayNum(TDayNum const daynum);
void SetOffset(DateOffset const & offset);
void SetVariableDate(VariableDate const date);
void SetYear(TYear const year) { m_year = year; }
void SetMonth(Month const month) { m_month = month; }
void SetDayNum(TDayNum const daynum) { m_daynum = daynum; }
void SetOffset(DateOffset const & offset) { m_offset = offset; }
void SetVariableDate(VariableDate const date) { m_variable_date = date; }
private:
TYear m_year = 0;
@ -472,20 +478,20 @@ std::ostream & operator<<(std::ostream & ost, MonthDay const md);
class MonthdayRange
{
public:
bool IsEmpty() const;
bool HasStart() const;
bool HasEnd() const;
bool HasPeriod() const;
bool HasPlus() const;
bool IsEmpty() const { return !HasStart() && !HasEnd(); }
bool HasStart() const { return !GetStart().IsEmpty(); }
bool HasEnd() const { return !GetEnd().IsEmpty() || GetEnd().HasDayNum(); }
bool HasPeriod() const { return m_period != 0; }
bool HasPlus() const { return m_plus; }
MonthDay const & GetStart() const;
MonthDay const & GetEnd() const;
uint32_t GetPeriod() const;
MonthDay const & GetStart() const { return m_start; }
MonthDay const & GetEnd() const { return m_end; }
uint32_t GetPeriod() const { return m_period; }
void SetStart(MonthDay const & start);
void SetEnd(MonthDay const & end);
void SetPeriod(uint32_t const period);
void SetPlus(bool const plus);
void SetStart(MonthDay const & start) { m_start = start; }
void SetEnd(MonthDay const & end) { m_end = end; }
void SetPeriod(uint32_t const period) { m_period = period; }
void SetPlus(bool const plus) { m_plus = plus; }
private:
MonthDay m_start;
@ -504,21 +510,21 @@ class YearRange
public:
using TYear = uint16_t;
bool IsEmpty() const;
bool IsOpen() const;
bool HasStart() const;
bool HasEnd() const;
bool HasPlus() const;
bool HasPeriod() const;
bool IsEmpty() const { return !HasStart() && !HasEnd(); }
bool IsOpen() const { return HasStart() && !HasEnd(); }
bool HasStart() const { return GetStart() != 0; }
bool HasEnd() const { return GetEnd() != 0; }
bool HasPlus() const { return m_plus; }
bool HasPeriod() const { return GetPeriod() != 0; }
TYear GetStart() const;
TYear GetEnd() const;
uint32_t GetPeriod() const;
TYear GetStart() const { return m_start; }
TYear GetEnd() const { return m_end; }
uint32_t GetPeriod() const { return m_period; }
void SetStart(TYear const start);
void SetEnd(TYear const end);
void SetPlus(bool const plus);
void SetPeriod(uint32_t const period);
void SetStart(TYear const start) { m_start = start; }
void SetEnd(TYear const end) { m_end = end; }
void SetPlus(bool const plus) { m_plus = plus; }
void SetPeriod(uint32_t const period) { m_period = period; }
private:
TYear m_start = 0;
@ -537,19 +543,19 @@ class WeekRange
public:
using TWeek = uint8_t;
bool IsEmpty() const;
bool IsOpen() const;
bool HasStart() const;
bool HasEnd() const;
bool HasPeriod() const;
bool IsEmpty() const { return !HasStart() && !HasEnd(); }
bool IsOpen() const { return HasStart() && !HasEnd(); }
bool HasStart() const { return GetStart() != 0; }
bool HasEnd() const { return GetEnd() != 0; }
bool HasPeriod() const { return GetPeriod() != 0; }
TWeek GetStart() const;
TWeek GetEnd() const;
uint32_t GetPeriod() const;
TWeek GetStart() const { return m_start; }
TWeek GetEnd() const { return m_end; }
uint32_t GetPeriod() const { return m_period; }
void SetStart(TWeek const start);
void SetEnd(TWeek const end);
void SetPeriod(uint32_t const period);
void SetStart(TWeek const start) { m_start = start; }
void SetEnd(TWeek const end) { m_end = end; }
void SetPeriod(uint32_t const period) { m_period = period; }
private:
TWeek m_start = 0;
@ -574,44 +580,45 @@ public:
Comment
};
bool IsEmpty() const;
bool IsTwentyFourHours() const;
bool IsEmpty() const { return !HasYears() && !HasMonths() && !HasWeeks() &&
!HasWeekdays() && !HasTimes(); }
bool IsTwentyFourHours() const { return m_twentyFourHours; }
bool HasYears() const;
bool HasMonths() const;
bool HasWeeks() const;
bool HasWeekdays() const;
bool HasTimes() const;
bool HasComment() const;
bool HasModifierComment() const;
bool HasSeparatorForReadability() const;
bool HasYears() const { return !GetYears().empty(); }
bool HasMonths() const { return !GetMonths().empty(); }
bool HasWeeks() const { return !GetWeeks().empty(); }
bool HasWeekdays() const { return !GetWeekdays().IsEmpty(); }
bool HasTimes() const { return !GetTimes().empty(); }
bool HasComment() const { return !GetComment().empty(); }
bool HasModifierComment() const { return !GetModifierComment().empty(); }
bool HasSeparatorForReadability() const { return m_separatorForReadability; }
TYearRanges const & GetYears() const;
TMonthdayRanges const & GetMonths() const;
TWeekRanges const & GetWeeks() const;
Weekdays const & GetWeekdays() const;
TTimespans const & GetTimes() const;
TYearRanges const & GetYears() const { return m_years; }
TMonthdayRanges const & GetMonths() const { return m_months; }
TWeekRanges const & GetWeeks() const { return m_weeks; }
Weekdays const & GetWeekdays() const { return m_weekdays; }
TTimespans const & GetTimes() const { return m_times; }
std::string const & GetComment() const;
std::string const & GetModifierComment() const;
std::string const & GetAnySeparator() const;
std::string const & GetComment() const { return m_comment; }
std::string const & GetModifierComment() const { return m_modifierComment; }
std::string const & GetAnySeparator() const { return m_anySeparator; }
Modifier GetModifier() const;
Modifier GetModifier() const { return m_modifier; }
void SetTwentyFourHours(bool const on);
void SetYears(TYearRanges const & years);
void SetMonths(TMonthdayRanges const & months);
void SetWeeks(TWeekRanges const & weeks);
void SetTwentyFourHours(bool const on) { m_twentyFourHours = on; }
void SetYears(TYearRanges const & years) { m_years = years; }
void SetMonths(TMonthdayRanges const & months) { m_months = months; }
void SetWeeks(TWeekRanges const & weeks) { m_weeks = weeks; }
void SetWeekdays(Weekdays const & weekdays);
void SetTimes(TTimespans const & times);
void SetWeekdays(Weekdays const & weekdays) { m_weekdays = weekdays; }
void SetTimes(TTimespans const & times) { m_times = times; }
void SetComment(std::string const & comment);
void SetModifierComment(std::string & comment);
void SetAnySeparator(std::string const & separator);
void SetSeparatorForReadability(bool const on);
void SetComment(std::string const & comment) { m_comment = comment; }
void SetModifierComment(std::string & comment) { m_modifierComment = comment; }
void SetAnySeparator(std::string const & separator) { m_anySeparator = separator; }
void SetSeparatorForReadability(bool const on) { m_separatorForReadability = on; }
void SetModifier(Modifier const modifier);
void SetModifier(Modifier const modifier) { m_modifier = modifier; }
private:
bool m_twentyFourHours{false};

View file

@ -35,21 +35,6 @@ bool HasPlus(std::vector<T> const & v)
return std::any_of(begin(v), end(v), hasPlus);
}
bool HasExtendedHours(osmoh::TTimespans const & spans)
{
auto const hasExtendedHours = [](osmoh::Timespan const & s) -> bool
{
if (!s.HasEnd())
return false;
auto const startDuration = s.GetStart().GetMinutes() + s.GetStart().GetHours();
auto const endDuration = s.GetEnd().GetMinutes() + s.GetEnd().GetHours();
return endDuration > 24 * std::chrono::minutes(60) || startDuration > endDuration;
};
return std::any_of(begin(spans), end(spans), hasExtendedHours);
}
bool HasOffset(osmoh::TMonthdayRanges const & mr)
{
auto const hasOffset = [](osmoh::MonthdayRange const & md) {
@ -89,12 +74,6 @@ enum
Serialised,
Period,
Plus,
// True if a rule has Timespen with more than 24 hours at the end
// or and greater than start.
// Example:
// 12:00-29:00
// 13:12-06:15
ExtendedHours,
Offset,
Count_
};
@ -122,8 +101,6 @@ TRuleFeatures DescribeRule(osmoh::TRuleSequences const & rule)
features[Offset] |= HasOffset(r.GetMonths());
features[Offset] |= HasOffset(r.GetWeekdays());
features[ExtendedHours] |= HasExtendedHours(r.GetTimes());
}
return features;
@ -212,7 +189,7 @@ BOOST_AUTO_TEST_CASE(OpeningHours_CountFailed)
}
{
std::stringstream message;
message << "Parsed\tSerialised\tPeriod\tPlus\tExtendedHours\tOffset\tCount" << std::endl;
message << "Parsed\tSerialised\tPeriod\tPlus\tOffset\tCount" << std::endl;
for (auto const & e : featuresDistrib)
message << e.first << '\t' << e.second << std::endl;

View file

@ -99,15 +99,12 @@ osmoh::RuleState GetRulesState(osmoh::TRuleSequences const & rules, std::string
{
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));
}
bool IsOpen(osmoh::TRuleSequences const & rules, std::string const & dateTime)
@ -124,6 +121,11 @@ bool IsUnknown(osmoh::TRuleSequences const & rules, std::string const & dateTime
{
return GetRulesState(rules, dateTime) == osmoh::RuleState::Unknown;
}
bool IsActive(osmoh::RuleSequence const & rule, std::tm tm)
{
return IsActive(rule, mktime(&tm));
}
} // namespace
BOOST_AUTO_TEST_CASE(OpeningHours_TestHourMinutes)
@ -140,7 +142,7 @@ BOOST_AUTO_TEST_CASE(OpeningHours_TestHourMinutes)
BOOST_CHECK_EQUAL(ToString(hm), "00:10");
}
{
HourMinutes hm{100_min};
HourMinutes hm(100_min);
BOOST_CHECK(!hm.IsEmpty());
BOOST_CHECK_EQUAL(hm.GetHoursCount(), 1);
BOOST_CHECK_EQUAL(hm.GetMinutesCount(), 40);
@ -148,16 +150,29 @@ BOOST_AUTO_TEST_CASE(OpeningHours_TestHourMinutes)
BOOST_CHECK_EQUAL(ToString(hm), "01:40");
}
{
HourMinutes hm{};
HourMinutes hm;
hm.SetHours(22_h);
hm.SetMinutes(15_min);
BOOST_CHECK(!hm.IsEmpty());
BOOST_CHECK(!hm.IsExtended());
BOOST_CHECK_EQUAL(hm.GetHoursCount(), 22);
BOOST_CHECK_EQUAL(hm.GetMinutesCount(), 15);
BOOST_CHECK_EQUAL(ToString(hm), "22:15");
}
{
HourMinutes hm;
hm.SetHours(39_h);
hm.SetMinutes(15_min);
BOOST_CHECK(!hm.IsEmpty());
BOOST_CHECK(hm.IsExtended());
BOOST_CHECK_EQUAL(hm.GetHoursCount(), 39);
BOOST_CHECK_EQUAL(hm.GetMinutesCount(), 15);
BOOST_CHECK_EQUAL(ToString(hm), "39:15");
}
}
BOOST_AUTO_TEST_CASE(OpeningHours_TestTimeEvent)
@ -253,16 +268,19 @@ BOOST_AUTO_TEST_CASE(OpeningHours_TestTimespan)
BOOST_CHECK(span.IsEmpty());
BOOST_CHECK(!span.HasStart());
BOOST_CHECK(!span.HasEnd());
BOOST_CHECK(!span.HasExtendedHours());
BOOST_CHECK_EQUAL(ToString(span), "hh:mm-hh:mm");
span.SetStart(HourMinutes(10_h));
BOOST_CHECK(span.HasStart());
BOOST_CHECK(span.IsOpen());
BOOST_CHECK(!span.HasExtendedHours());
BOOST_CHECK_EQUAL(ToString(span), "10:00");
span.SetEnd(HourMinutes(12_h));
BOOST_CHECK(span.HasEnd());
BOOST_CHECK(!span.IsOpen());
BOOST_CHECK(!span.HasExtendedHours());
BOOST_CHECK_EQUAL(ToString(span), "10:00-12:00");
BOOST_CHECK(!span.HasPeriod());
@ -270,6 +288,34 @@ BOOST_AUTO_TEST_CASE(OpeningHours_TestTimespan)
BOOST_CHECK(span.HasPeriod());
BOOST_CHECK_EQUAL(ToString(span), "10:00-12:00/10");
}
{
Timespan span;
span.SetStart(HourMinutes(10_h));
span.SetEnd(HourMinutes(47_h));
BOOST_CHECK(span.HasExtendedHours());
BOOST_CHECK_EQUAL(ToString(span), "10:00-47:00");
}
{
Timespan span;
span.SetStart(HourMinutes(10_h));
span.SetEnd(HourMinutes(06_h));
BOOST_CHECK(span.HasExtendedHours());
BOOST_CHECK_EQUAL(ToString(span), "10:00-06:00");
}
{
Timespan span;
span.SetStart(HourMinutes(10_h));
span.SetEnd(HourMinutes(00_h));
BOOST_CHECK(!span.HasExtendedHours());
BOOST_CHECK_EQUAL(ToString(span), "10:00-00:00");
}
}
BOOST_AUTO_TEST_CASE(OpeningHours_TestNthWeekdayOfTheMonthEntry)
@ -1147,6 +1193,32 @@ BOOST_AUTO_TEST_CASE(OpeningHours_TestIsActive)
BOOST_CHECK(GetTimeTuple("2015-08", fmt, time));
BOOST_CHECK(!IsActive(ranges[0], time));
}
{
TMonthdayRanges ranges;
BOOST_CHECK(Parse("Sep 01", ranges));
std::tm time{};
auto const fmt = "%Y-%m-%d";
BOOST_CHECK(GetTimeTuple("2015-09-01", fmt, time));
BOOST_CHECK(IsActive(ranges[0], time));
BOOST_CHECK(GetTimeTuple("2014-09-02", fmt, time));
BOOST_CHECK(!IsActive(ranges[0], time));
}
{
TMonthdayRanges ranges;
BOOST_CHECK(Parse("2015 Sep 01", ranges));
std::tm time{};
auto const fmt = "%Y-%m-%d";
BOOST_CHECK(GetTimeTuple("2015-09-01", fmt, time));
BOOST_CHECK(IsActive(ranges[0], time));
BOOST_CHECK(GetTimeTuple("2014-09-01", fmt, time));
BOOST_CHECK(!IsActive(ranges[0], time));
}
{
TYearRanges ranges;
BOOST_CHECK(Parse("2011-2014", ranges));
@ -1214,18 +1286,6 @@ BOOST_AUTO_TEST_CASE(OpeningHours_TestIsActive)
BOOST_CHECK(GetTimeTuple("2015 14 3", fmt, time));
BOOST_CHECK(!IsActive(ranges[0], time));
}
{
TRuleSequences rules;
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));
BOOST_CHECK(IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("4 15:35", fmt, time));
BOOST_CHECK(IsActive(rules[1], time));
}
{
TRuleSequences rules;
BOOST_CHECK(Parse("09:00-14:00", rules));
@ -1240,28 +1300,109 @@ BOOST_AUTO_TEST_CASE(OpeningHours_TestIsActive)
}
{
TRuleSequences rules;
BOOST_CHECK(Parse("Apr-Sep Su 14:30-17:00", rules));
BOOST_CHECK(Parse("Mo-We 17:00-18:00, Th,Fr 15:00-16: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-10-6 17:35", fmt, time));
BOOST_CHECK(IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("5-1 15:35", fmt, time));
BOOST_CHECK(!IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("10-0 15:35", fmt, time));
BOOST_CHECK(!IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("2015-10-8 15:35", fmt, time));
BOOST_CHECK(IsActive(rules[1], time));
}
auto const kDateTimeFmt = "%Y-%m-%d %H:%M";
{
TRuleSequences rules;
BOOST_CHECK(Parse("2010 Apr 01-30: Mo-Su 17:00-24:00", rules));
std::tm time{};
auto const fmt = "%Y-%m-%d-%w %H:%M";
BOOST_CHECK(GetTimeTuple("2010-4-20-0 18:15", fmt, time));
BOOST_CHECK(GetTimeTuple("2010-4-20 18:15", kDateTimeFmt, time));
BOOST_CHECK(IsActive(rules[0], time));
}
{
TRuleSequences rules;
BOOST_CHECK(Parse("2010 Apr 02 - May 21: Mo-Su 17:00-24:00", rules));
std::tm time{};
BOOST_CHECK(GetTimeTuple("2010-4-20 18:15", kDateTimeFmt, time));
BOOST_CHECK(IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("2010-5-20 18:15", kDateTimeFmt, time));
BOOST_CHECK(IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("2010-4-01 18:15", kDateTimeFmt, time));
BOOST_CHECK(!IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("2010-5-22 18:15", kDateTimeFmt, time));
BOOST_CHECK(!IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("2010-6-01 18:15", kDateTimeFmt, time));
BOOST_CHECK(!IsActive(rules[0], time));
}
{
TRuleSequences rules;
BOOST_CHECK(Parse("Apr-Sep Su 14:30-17:00", rules));
std::tm time{};
BOOST_CHECK(GetTimeTuple("2015-04-12 15:35", kDateTimeFmt, time));
BOOST_CHECK(IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("2015-04-13 15:35", kDateTimeFmt, time));
BOOST_CHECK(!IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("2015-10-11 15:35", kDateTimeFmt, time));
BOOST_CHECK(!IsActive(rules[0], time));
}
{
TRuleSequences rules;
BOOST_CHECK(Parse("Mo 09:00-06:00", rules));
std::tm time{};
BOOST_CHECK(GetTimeTuple("2015-11-10 05:35", kDateTimeFmt, time));
BOOST_CHECK(IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("2015-11-11 05:35", kDateTimeFmt, time));
BOOST_CHECK(!IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("2015-11-11 06:01", kDateTimeFmt, time));
BOOST_CHECK(!IsActive(rules[0], time));
}
{
TRuleSequences rules;
BOOST_CHECK(Parse("Mo 09:00-32:00", rules));
std::tm time{};
BOOST_CHECK(GetTimeTuple("2015-11-10 05:35", kDateTimeFmt, time));
BOOST_CHECK(IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("2015-11-11 05:35", kDateTimeFmt, time));
BOOST_CHECK(!IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("2015-11-11 06:01", kDateTimeFmt, time));
BOOST_CHECK(!IsActive(rules[0], time));
}
{
TRuleSequences rules;
BOOST_CHECK(Parse("Mo 09:00-48:00", rules));
std::tm time{};
BOOST_CHECK(GetTimeTuple("2015-11-10 05:35", kDateTimeFmt, time));
BOOST_CHECK(IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("2015-11-10 15:35", kDateTimeFmt, time));
BOOST_CHECK(IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("2015-11-10 23:35", kDateTimeFmt, time));
BOOST_CHECK(IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("2015-11-11 05:35", kDateTimeFmt, time));
BOOST_CHECK(!IsActive(rules[0], time));
BOOST_CHECK(GetTimeTuple("2015-11-11 06:01", kDateTimeFmt, time));
BOOST_CHECK(!IsActive(rules[0], time));
}
}
BOOST_AUTO_TEST_CASE(OpeningHours_TestIsOpen)
@ -1359,6 +1500,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"));
}
}
@ -1385,4 +1536,8 @@ BOOST_AUTO_TEST_CASE(OpeningHours_TestOpeningHours)
BOOST_CHECK(GetTimeTuple("2015-11-10 12:30", fmt, time));
BOOST_CHECK(oh.IsClosed(mktime(&time)));
}
{
OpeningHours oh("Nov +1");
BOOST_CHECK(!oh.IsValid());
}
}

View file

@ -1,6 +1,7 @@
#include "rules_evaluation.hpp"
#include "rules_evaluation_private.hpp"
#include <algorithm>
#include <ctime>
#include <iomanip>
#include <sstream>
@ -9,10 +10,11 @@
namespace
{
using THourMinutes = std::tuple<int, int>;
using osmoh::operator""_h;
constexpr osmoh::MonthDay::TYear kTMYearOrigin = 1900;
inline bool ToHourMinutes(osmoh::Time const & t, THourMinutes & hm)
bool ToHourMinutes(osmoh::Time const & t, THourMinutes & hm)
{
if (!t.IsHoursMinutes())
return false;
@ -20,13 +22,13 @@ inline bool ToHourMinutes(osmoh::Time const & t, THourMinutes & hm)
return true;
}
inline bool ToHourMinutes(std::tm const & t, THourMinutes & hm)
bool ToHourMinutes(std::tm const & t, THourMinutes & hm)
{
hm = THourMinutes{t.tm_hour, t.tm_min};
return true;
}
inline int CompareMonthDayTimeTuple(osmoh::MonthDay const & monthDay, std::tm const & date)
int CompareMonthDayTimeTuple(osmoh::MonthDay const & monthDay, std::tm const & date)
{
if (monthDay.HasYear())
{
@ -49,23 +51,25 @@ inline int CompareMonthDayTimeTuple(osmoh::MonthDay const & monthDay, std::tm co
return 0;
}
inline bool operator<=(osmoh::MonthDay const & monthDay, std::tm const & date)
bool operator<=(osmoh::MonthDay const & monthDay, std::tm const & date)
{
return CompareMonthDayTimeTuple(monthDay, date) < 1;
}
inline bool operator<=(std::tm const & date, osmoh::MonthDay const & monthDay)
bool operator<=(std::tm const & date, osmoh::MonthDay const & monthDay)
{
return CompareMonthDayTimeTuple(monthDay, date) > -1;
}
inline bool operator==(osmoh::MonthDay const & monthDay, std::tm const & date)
bool operator==(osmoh::MonthDay const & monthDay, std::tm const & date)
{
return CompareMonthDayTimeTuple(monthDay, date) == 0;
}
// Fill result with fields that present in start and missing in end.
inline osmoh::MonthDay NormalizeEnd(osmoh::MonthDay const & start, osmoh::MonthDay const & end)
// 2015 Jan 30 - Feb 20 <=> 2015 Jan 30 - 2015 Feb 20
// 2015 Jan 01 - 22 <=> 2015 Jan 01 - 2015 Jan 22
osmoh::MonthDay NormalizeEnd(osmoh::MonthDay const & start, osmoh::MonthDay const & end)
{
osmoh::MonthDay result = start;
if (end.HasYear())
@ -77,7 +81,7 @@ inline osmoh::MonthDay NormalizeEnd(osmoh::MonthDay const & start, osmoh::MonthD
return result;
}
inline uint8_t GetWeekNumber(std::tm const & date)
uint8_t GetWeekNumber(std::tm const & date)
{
char buff[4]{};
if (strftime(&buff[0], sizeof(buff), "%V", &date) == 0)
@ -89,16 +93,16 @@ inline uint8_t GetWeekNumber(std::tm const & date)
return weekNumber;
}
inline bool IsBetweenLooped(osmoh::Weekday const start,
osmoh::Weekday const end,
osmoh::Weekday const p)
bool IsBetweenLooped(osmoh::Weekday const start,
osmoh::Weekday const end,
osmoh::Weekday const p)
{
if (start <= end)
return start <= p && p <= end;
return p >= end || start <= p;
}
inline osmoh::RuleState ModifierToRuleState(osmoh::RuleSequence::Modifier const modifier)
osmoh::RuleState ModifierToRuleState(osmoh::RuleSequence::Modifier const modifier)
{
using Modifier = osmoh::RuleSequence::Modifier;
@ -116,12 +120,88 @@ 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.
void NormalizeExtendedEnd(osmoh::Timespan & span)
{
auto & endHourMinutes = span.GetEnd().GetHourMinutes();
auto const duration = endHourMinutes.GetDuration();
if (duration < 24_h)
endHourMinutes.SetDuration(duration + 24_h);
}
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;
}
void SplitExtendedHours(osmoh::TTimespans const & spans,
osmoh::TTimespans & originalNormalizedSpans,
osmoh::Timespan & additionalSpan)
{
// We don't handle more than one occurence of extended span
// since it is an invalid situation.
auto it = begin(spans);
for (; it != end(spans) && !it->HasExtendedHours(); ++it)
originalNormalizedSpans.push_back(*it);
if (it == end(spans))
return;
auto const splittedSpans = SplitExtendedHours(*it);
originalNormalizedSpans.push_back(splittedSpans[0]);
additionalSpan = splittedSpans[1];
++it;
std::copy(it, end(spans), back_inserter(originalNormalizedSpans));
}
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 +217,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 +257,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 +267,10 @@ 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 +313,47 @@ 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 +374,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