Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View file

@ -0,0 +1,26 @@
project(opening_hours)
set(SRC
opening_hours.hpp
opening_hours.cpp
opening_hours_parsers.hpp
opening_hours_parsers_terminals.cpp
parse_opening_hours.hpp
parse_opening_hours.cpp
parse_years.cpp
parse_weekdays.cpp
parse_weeks.cpp
parse_timespans.cpp
parse_months.cpp
rules_evaluation_private.hpp
rules_evaluation.hpp
rules_evaluation.cpp
)
omim_add_library(${PROJECT_NAME} ${SRC})
target_include_directories(${PROJECT_NAME} INTERFACE .)
omim_add_test_subdirectory(opening_hours_tests)
omim_add_test_subdirectory(opening_hours_integration_tests)
omim_add_test_subdirectory(opening_hours_supported_features_tests)

View file

@ -0,0 +1,977 @@
/*
The MIT License (MIT)
Copyright (c) 2015 Mail.Ru Group
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "opening_hours.hpp"
#include "rules_evaluation.hpp"
#include "parse_opening_hours.hpp"
#include <algorithm>
#include <cstdlib>
#include <functional>
#include <iomanip>
#include <ios>
#include <ostream>
#include <sstream>
#include <tuple>
#include <type_traits>
#include <vector>
namespace
{
template <typename T, typename SeparatorExtractor>
void PrintVector(std::ostream & ost, std::vector<T> const & v,
SeparatorExtractor && sepFunc)
{
auto it = begin(v);
if (it == end(v))
return;
auto sep = sepFunc(*it);
ost << *it++;
while (it != end(v))
{
ost << sep << *it;
sep = sepFunc(*it);
++it;
}
}
template <typename T>
void PrintVector(std::ostream & ost, std::vector<T> const & v, char const * const sep = ", ")
{
PrintVector(ost, v, [&sep](T const &) { return sep; });
}
void PrintOffset(std::ostream & ost, int32_t const offset, bool const space)
{
if (offset == 0)
return;
if (space)
ost << ' ';
if (offset > 0)
ost << '+';
ost << offset;
ost << ' ' << "day";
if (std::abs(offset) > 1)
ost << 's';
}
class StreamFlagsKeeper
{
public:
explicit StreamFlagsKeeper(std::ostream & ost):
m_ost(ost),
m_flags(m_ost.flags())
{
}
~StreamFlagsKeeper()
{
m_ost.flags(m_flags);
}
private:
std::ostream & m_ost;
std::ios_base::fmtflags m_flags;
};
template <typename TNumber>
void PrintPaddedNumber(std::ostream & ost, TNumber const number, uint32_t const padding = 1)
{
static constexpr bool isChar = std::is_same_v<signed char, TNumber> ||
std::is_same_v<unsigned char, TNumber> ||
std::is_same_v<char, TNumber>;
if constexpr (isChar)
{
PrintPaddedNumber(ost, static_cast<int32_t>(number), padding);
}
else
{
static_assert(std::is_integral<TNumber>::value, "number should be of integral type.");
StreamFlagsKeeper keeper(ost);
ost << std::setw(padding) << std::setfill('0') << number;
}
}
void PrintHoursMinutes(std::ostream & ost,
std::chrono::hours::rep hours,
std::chrono::minutes::rep minutes)
{
PrintPaddedNumber(ost, hours, 2);
ost << ':';
PrintPaddedNumber(ost, minutes, 2);
}
} // namespace
namespace osmoh
{
// HourMinutes -------------------------------------------------------------------------------------
bool HourMinutes::IsExtended() const
{
return GetDuration() > 24_h;
}
void HourMinutes::SetHours(THours const hours)
{
m_empty = false;
m_hours = hours;
}
void HourMinutes::SetMinutes(TMinutes const minutes)
{
m_empty = false;
m_minutes = minutes;
}
void HourMinutes::SetDuration(TMinutes const duration)
{
SetHours(std::chrono::duration_cast<THours>(duration));
SetMinutes(duration - GetHours());
}
HourMinutes operator-(HourMinutes const & hm)
{
HourMinutes result;
result.SetHours(-hm.GetHours());
result.SetMinutes(-hm.GetMinutes());
return result;
}
std::ostream & operator<<(std::ostream & ost, HourMinutes const & hm)
{
if (hm.IsEmpty())
ost << "hh:mm";
else
PrintHoursMinutes(ost, std::abs(hm.GetHoursCount()), std::abs(hm.GetMinutesCount()));
return ost;
}
// TimeEvent ---------------------------------------------------------------------------------------
Time TimeEvent::GetEventTime() const
{
return Time(HourMinutes(0_h + 0_min)); // TODO(mgsergio): get real time
}
std::ostream & operator<<(std::ostream & ost, TimeEvent::Event const event)
{
switch (event)
{
case TimeEvent::Event::None:
ost << "None";
break;
case TimeEvent::Event::Sunrise:
ost << "sunrise";
break;
case TimeEvent::Event::Sunset:
ost << "sunset";
break;
}
return ost;
}
std::ostream & operator<<(std::ostream & ost, TimeEvent const te)
{
if (te.HasOffset())
{
ost << '(' << te.GetEvent();
auto const & offset = te.GetOffset();
if (offset.GetHoursCount() < 0)
ost << '-';
else
ost << '+';
ost << offset << ')';
}
else
{
ost << te.GetEvent();
}
return ost;
}
// Time --------------------------------------------------------------------------------------------
Time::THours Time::GetHours() const
{
if (IsEvent())
return GetEvent().GetEventTime().GetHours();
return GetHourMinutes().GetHours();
}
Time::TMinutes Time::GetMinutes() const
{
if (IsEvent())
return GetEvent().GetEventTime().GetMinutes();
return GetHourMinutes().GetMinutes();
}
void Time::AddDuration(TMinutes const duration)
{
if (IsEvent())
{
m_event.AddDurationToOffset(duration);
}
else if (IsHoursMinutes())
{
m_hourMinutes.AddDuration(duration);
}
else
{
// Undefined behaviour.
}
}
void Time::SetEvent(TimeEvent const & event)
{
m_type = Type::Event;
m_event = event;
}
void Time::SetHourMinutes(HourMinutes const & hm)
{
m_type = Type::HourMinutes;
m_hourMinutes = hm;
}
std::ostream & operator<<(std::ostream & ost, Time const & time)
{
if (time.IsEmpty())
{
ost << "hh:mm";
return ost;
}
if (time.IsEvent())
ost << time.GetEvent();
else
ost << time.GetHourMinutes();
return ost;
}
bool operator==(Time const & lhs, Time const & rhs)
{
if (lhs.IsEmpty() && rhs.IsEmpty())
return true;
return lhs.GetType() == rhs.GetType() &&
lhs.GetHours() == rhs.GetHours() &&
lhs.GetMinutes() == rhs.GetMinutes();
}
// TimespanPeriod ----------------------------------------------------------------------------------
TimespanPeriod::TimespanPeriod(HourMinutes const & hm):
m_hourMinutes(hm),
m_type(Type::HourMinutes)
{
}
TimespanPeriod::TimespanPeriod(HourMinutes::TMinutes const minutes):
m_minutes(minutes),
m_type(Type::Minutes)
{
}
std::ostream & operator<<(std::ostream & ost, TimespanPeriod const p)
{
if (p.IsEmpty())
ost << "None";
else if (p.IsHoursMinutes())
ost << p.GetHourMinutes();
else if (p.IsMinutes())
PrintPaddedNumber(ost, p.GetMinutesCount(), 2);
return ost;
}
bool operator==(TimespanPeriod const & lhs, TimespanPeriod const & rhs)
{
if (lhs.IsEmpty() && rhs.IsEmpty())
return true;
return lhs.GetType() == rhs.GetType() &&
lhs.GetHourMinutes() == rhs.GetHourMinutes() &&
lhs.GetMinutes() == rhs.GetMinutes();
}
// Timespan ----------------------------------------------------------------------------------------
bool Timespan::HasExtendedHours() const
{
bool const canHaveExtendedHours = HasStart() && HasEnd() &&
GetStart().IsHoursMinutes() &&
GetEnd().IsHoursMinutes();
if (!canHaveExtendedHours)
{
return false;
}
auto const & startHM = GetStart().GetHourMinutes();
auto const & endHM = GetEnd().GetHourMinutes();
if (endHM.IsExtended())
return true;
return endHM.GetDuration() <= startHM.GetDuration();
}
void Timespan::ExpandPlus()
{
if (HasPlus())
{
SetEnd(HourMinutes(24_h));
}
}
std::ostream & operator<<(std::ostream & ost, Timespan const & span)
{
ost << span.GetStart();
if (!span.IsOpen())
{
ost << '-' << span.GetEnd();
if (span.HasPeriod())
ost << '/' << span.GetPeriod();
}
if (span.HasPlus())
ost << '+';
return ost;
}
std::ostream & operator<<(std::ostream & ost, osmoh::TTimespans const & timespans)
{
PrintVector(ost, timespans);
return ost;
}
bool operator==(Timespan const & lhs, Timespan const & rhs)
{
if (lhs.IsEmpty() && rhs.IsEmpty())
return true;
if (lhs.IsEmpty() != rhs.IsEmpty() ||
lhs.HasStart() != rhs.HasStart() ||
lhs.HasEnd() != rhs.HasEnd() ||
lhs.HasPlus() != rhs.HasPlus() ||
lhs.HasPeriod() != rhs.HasPeriod())
{
return false;
}
return lhs.GetStart() == rhs.GetStart() &&
lhs.GetEnd() == rhs.GetEnd() &&
lhs.GetPeriod() == rhs.GetPeriod();
}
// NthWeekdayOfTheMonthEntry -----------------------------------------------------------------------
std::ostream & operator<<(std::ostream & ost, NthWeekdayOfTheMonthEntry const entry)
{
if (entry.HasStart())
ost << static_cast<uint32_t>(entry.GetStart());
if (entry.HasEnd())
ost << '-' << static_cast<uint32_t>(entry.GetEnd());
return ost;
}
bool NthWeekdayOfTheMonthEntry::operator==(NthWeekdayOfTheMonthEntry const & rhs) const
{
return m_start == rhs.m_start && m_end == rhs.m_end;
}
// WeekdayRange ------------------------------------------------------------------------------------
bool WeekdayRange::HasWday(Weekday const wday) const
{
if (IsEmpty() || wday == Weekday::None)
return false;
if (!HasEnd())
return GetStart() == wday;
return (GetStart() <= GetEnd())
? GetStart() <= wday && wday <= GetEnd()
: wday <= GetEnd() || GetStart() <= wday;
}
bool WeekdayRange::operator==(WeekdayRange const & rhs) const
{
return m_start == rhs.m_start && m_end == rhs.m_end && m_offset == rhs.m_offset &&
m_nths == rhs.m_nths;
}
std::ostream & operator<<(std::ostream & ost, Weekday wday)
{
switch (wday)
{
case Weekday::Sunday:
ost << "Su";
break;
case Weekday::Monday:
ost << "Mo";
break;
case Weekday::Tuesday:
ost << "Tu";
break;
case Weekday::Wednesday:
ost << "We";
break;
case Weekday::Thursday:
ost << "Th";
break;
case Weekday::Friday:
ost << "Fr";
break;
case Weekday::Saturday:
ost << "Sa";
break;
case Weekday::None:
ost << "None";
}
return ost;
}
std::ostream & operator<<(std::ostream & ost, WeekdayRange const & range)
{
ost << range.GetStart();
if (range.HasEnd())
{
ost << '-' << range.GetEnd();
}
else
{
if (range.HasNth())
{
ost << '[';
PrintVector(ost, range.GetNths(), ",");
ost << ']';
}
PrintOffset(ost, range.GetOffset(), true);
}
return ost;
}
std::ostream & operator<<(std::ostream & ost, TWeekdayRanges const & ranges)
{
PrintVector(ost, ranges);
return ost;
}
// Holiday -----------------------------------------------------------------------------------------
std::ostream & operator<<(std::ostream & ost, Holiday const & holiday)
{
if (holiday.IsPlural())
{
ost << "PH";
}
else
{
ost << "SH";
PrintOffset(ost, holiday.GetOffset(), true);
}
return ost;
}
std::ostream & operator<<(std::ostream & ost, THolidays const & holidays)
{
PrintVector(ost, holidays);
return ost;
}
bool Holiday::operator==(Holiday const & rhs) const
{
return m_plural == rhs.m_plural && m_offset == rhs.m_offset;
}
// Weekdays ----------------------------------------------------------------------------------------
std::ostream & operator<<(std::ostream & ost, Weekdays const & weekday)
{
ost << weekday.GetHolidays();
if (weekday.HasWeekday() && weekday.HasHolidays())
ost << ", ";
ost << weekday.GetWeekdayRanges();
return ost;
}
bool Weekdays::operator==(Weekdays const & rhs) const
{
return m_weekdayRanges == rhs.m_weekdayRanges && m_holidays == rhs.m_holidays;
}
// DateOffset --------------------------------------------------------------------------------------
std::ostream & operator<<(std::ostream & ost, DateOffset const & offset)
{
if (offset.HasWDayOffset())
{
ost << (offset.IsWDayOffsetPositive() ? '+' : '-')
<< offset.GetWDayOffset();
}
PrintOffset(ost, offset.GetOffset(), offset.HasWDayOffset());
return ost;
}
bool DateOffset::operator==(DateOffset const & rhs) const
{
return m_wdayOffest == rhs.m_wdayOffest && m_positive == rhs.m_positive &&
m_offset == rhs.m_offset;
}
bool DateOffset::operator<(DateOffset const & rhs) const
{
return std::tie(m_wdayOffest, m_positive, m_offset) <
std::tie(rhs.m_wdayOffest, rhs.m_positive, rhs.m_offset);
}
// MonthDay ----------------------------------------------------------------------------------------
std::ostream & operator<<(std::ostream & ost, MonthDay::Month const month)
{
switch (month)
{
case MonthDay::Month::None:
ost << "None";
break;
case MonthDay::Month::Jan:
ost << "Jan";
break;
case MonthDay::Month::Feb:
ost << "Feb";
break;
case MonthDay::Month::Mar:
ost << "Mar";
break;
case MonthDay::Month::Apr:
ost << "Apr";
break;
case MonthDay::Month::May:
ost << "May";
break;
case MonthDay::Month::Jun:
ost << "Jun";
break;
case MonthDay::Month::Jul:
ost << "Jul";
break;
case MonthDay::Month::Aug:
ost << "Aug";
break;
case MonthDay::Month::Sep:
ost << "Sep";
break;
case MonthDay::Month::Oct:
ost << "Oct";
break;
case MonthDay::Month::Nov:
ost << "Nov";
break;
case MonthDay::Month::Dec:
ost << "Dec";
break;
}
return ost;
}
std::ostream & operator<<(std::ostream & ost, MonthDay::VariableDate const date)
{
switch (date)
{
case MonthDay::VariableDate::None:
ost << "none";
break;
case MonthDay::VariableDate::Easter:
ost << "easter";
break;
}
return ost;
}
std::ostream & operator<<(std::ostream & ost, MonthDay const md)
{
bool space = false;
auto const putSpace = [&space, &ost] {
if (space)
ost << ' ';
space = true;
};
if (md.HasYear())
{
putSpace();
ost << md.GetYear();
}
if (md.IsVariable())
{
putSpace();
ost << md.GetVariableDate();
}
else
{
if (md.HasMonth())
{
putSpace();
ost << md.GetMonth();
}
if (md.HasDayNum())
{
putSpace();
PrintPaddedNumber(ost, md.GetDayNum(), 2);
}
}
if (md.HasOffset())
{
ost << ' ' << md.GetOffset();
}
return ost;
}
bool MonthDay::operator==(MonthDay const & rhs) const
{
return m_year == rhs.m_year && m_month == rhs.m_month && m_daynum == rhs.m_daynum &&
m_variable_date == rhs.m_variable_date && m_offset == rhs.m_offset;
}
bool MonthDay::operator<(MonthDay const & rhs) const
{
return std::tie(m_year, m_month, m_daynum, m_variable_date, m_offset) <
std::tie(rhs.m_year, rhs.m_month, rhs.m_daynum, rhs.m_variable_date, rhs.m_offset);
}
// MonthdayRange -----------------------------------------------------------------------------------
std::ostream & operator<<(std::ostream & ost, MonthdayRange const & range)
{
if (range.HasStart())
ost << range.GetStart();
if (range.HasEnd())
{
ost << '-' << range.GetEnd();
if (range.HasPeriod())
ost << '/' << range.GetPeriod();
}
else if (range.HasPlus())
ost << '+';
return ost;
}
std::ostream & operator<<(std::ostream & ost, TMonthdayRanges const & ranges)
{
PrintVector(ost, ranges);
return ost;
}
bool MonthdayRange::operator==(MonthdayRange const & rhs) const
{
return m_start == rhs.m_start && m_end == rhs.m_end && m_period == rhs.m_period &&
m_plus == rhs.m_plus;
}
// YearRange ---------------------------------------------------------------------------------------
std::ostream & operator<<(std::ostream & ost, YearRange const range)
{
if (range.IsEmpty())
return ost;
ost << range.GetStart();
if (range.HasEnd())
{
ost << '-' << range.GetEnd();
if (range.HasPeriod())
ost << '/' << range.GetPeriod();
}
else if (range.HasPlus())
{
ost << '+';
}
return ost;
}
std::ostream & operator<<(std::ostream & ost, TYearRanges const ranges)
{
PrintVector(ost, ranges);
return ost;
}
bool YearRange::operator==(YearRange const & rhs) const
{
return m_start == rhs.m_start && m_end == rhs.m_end && m_plus == rhs.m_plus &&
m_period == rhs.m_period;
}
// WeekRange ---------------------------------------------------------------------------------------
std::ostream & operator<<(std::ostream & ost, WeekRange const range)
{
if (range.IsEmpty())
return ost;
PrintPaddedNumber(ost, range.GetStart(), 2);
if (range.HasEnd())
{
ost << '-';
PrintPaddedNumber(ost, range.GetEnd(), 2);
if (range.HasPeriod())
ost << '/' << range.GetPeriod();
}
return ost;
}
std::ostream & operator<<(std::ostream & ost, TWeekRanges const ranges)
{
ost << "week ";
PrintVector(ost, ranges);
return ost;
}
bool WeekRange::operator==(WeekRange const & rhs) const
{
return m_start == rhs.m_start && m_end == rhs.m_end && m_period == rhs.m_period;
}
// RuleSequence ------------------------------------------------------------------------------------
bool RuleSequence::HasMonthDay() const
{
for (auto const & monthRange : GetMonths())
{
if (monthRange.GetStart().GetDayNum())
return true;
if (monthRange.GetEnd().GetDayNum())
return true;
}
return false;
}
bool RuleSequence::operator==(RuleSequence const & rhs) const
{
return m_twentyFourHours == rhs.m_twentyFourHours && m_years == rhs.m_years &&
m_months == rhs.m_months && m_weeks == rhs.m_weeks && m_weekdays == rhs.m_weekdays &&
m_times == rhs.m_times && m_comment == rhs.m_comment &&
m_anySeparator == rhs.m_anySeparator &&
m_separatorForReadability == rhs.m_separatorForReadability &&
m_modifier == rhs.m_modifier && m_modifierComment == rhs.m_modifierComment;
}
std::ostream & operator<<(std::ostream & ost, RuleSequence::Modifier const modifier)
{
switch (modifier)
{
case RuleSequence::Modifier::DefaultOpen:
case RuleSequence::Modifier::Comment:
break;
case RuleSequence::Modifier::Unknown:
ost << "unknown";
break;
case RuleSequence::Modifier::Closed:
ost << "closed";
break;
case RuleSequence::Modifier::Open:
ost << "open";
break;
}
return ost;
}
std::ostream & operator<<(std::ostream & ost, RuleSequence const & s)
{
bool space = false;
auto const putSpace = [&space, &ost] {
if (space)
ost << ' ';
space = true;
};
if (s.IsTwentyFourHours())
{
putSpace();
ost << "24/7";
}
else
{
if (s.HasComment())
ost << s.GetComment() << ':';
else
{
if (s.HasYears())
{
putSpace();
ost << s.GetYears();
}
if (s.HasMonths())
{
putSpace();
ost << s.GetMonths();
}
if (s.HasWeeks())
{
putSpace();
ost << s.GetWeeks();
}
if (s.HasSeparatorForReadability())
ost << ':';
if (s.HasWeekdays())
{
putSpace();
ost << s.GetWeekdays();
}
if (s.HasTimes())
{
putSpace();
ost << s.GetTimes();
}
}
}
if (s.GetModifier() != RuleSequence::Modifier::DefaultOpen &&
s.GetModifier() != RuleSequence::Modifier::Comment)
{
putSpace();
ost << s.GetModifier();
}
if (s.HasModifierComment())
{
putSpace();
ost << '"' << s.GetModifierComment() << '"';
}
return ost;
}
std::ostream & operator<<(std::ostream & ost, TRuleSequences const & s)
{
PrintVector(ost, s, [](RuleSequence const & r) {
auto const sep = r.GetAnySeparator();
return (sep == "||" ? ' ' + sep + ' ' : sep + ' ');
});
return ost;
}
// OpeningHours ------------------------------------------------------------------------------------
OpeningHours::OpeningHours(std::string const & rule):
m_valid(Parse(rule, m_rule))
{
}
OpeningHours::OpeningHours(TRuleSequences const & rule):
m_rule(rule),
m_valid(true)
{
}
bool OpeningHours::IsOpen(time_t const dateTime) const
{
return osmoh::IsOpen(m_rule, dateTime);
}
bool OpeningHours::IsClosed(time_t const dateTime) const
{
return osmoh::IsClosed(m_rule, dateTime);
}
bool OpeningHours::IsUnknown(time_t const dateTime) const
{
return osmoh::IsUnknown(m_rule, dateTime);
}
OpeningHours::InfoT OpeningHours::GetInfo(time_t const dateTime) const
{
InfoT info;
info.state = GetState(m_rule, dateTime);
if (info.state != RuleState::Unknown)
{
if (info.state == RuleState::Open)
info.nextTimeOpen = dateTime;
else
info.nextTimeOpen = osmoh::GetNextTimeState(m_rule, dateTime, RuleState::Open);
if (info.state == RuleState::Closed)
info.nextTimeClosed = dateTime;
else
info.nextTimeClosed = osmoh::GetNextTimeState(m_rule, dateTime, RuleState::Closed);
}
return info;
}
bool OpeningHours::IsValid() const
{
return m_valid;
}
bool OpeningHours::IsTwentyFourHours() const
{
return m_rule.size() == 1 && m_rule[0].IsTwentyFourHours();
}
bool OpeningHours::HasWeekdaySelector() const
{
return std::any_of(m_rule.cbegin(), m_rule.cend(), std::mem_fn(&osmoh::RuleSequence::HasWeekdays));
}
bool OpeningHours::HasMonthSelector() const
{
return std::any_of(m_rule.cbegin(), m_rule.cend(), std::mem_fn(&osmoh::RuleSequence::HasMonths));
}
bool OpeningHours::HasWeekSelector() const
{
return std::any_of(m_rule.cbegin(), m_rule.cend(), std::mem_fn(&osmoh::RuleSequence::HasWeeks));
}
bool OpeningHours::HasYearSelector() const
{
return std::any_of(m_rule.cbegin(), m_rule.cend(), std::mem_fn(&osmoh::RuleSequence::HasYears));
}
void swap(OpeningHours & lhs, OpeningHours & rhs)
{
std::swap(lhs.m_rule, rhs.m_rule);
std::swap(lhs.m_valid, rhs.m_valid);
}
bool OpeningHours::operator==(OpeningHours const & rhs) const
{
return m_valid == rhs.m_valid && m_rule == rhs.m_rule;
}
std::ostream & operator<<(std::ostream & ost, OpeningHours const & oh)
{
ost << oh.GetRule();
return ost;
}
std::string ToString(osmoh::OpeningHours const & openingHours)
{
if (!openingHours.IsValid())
return {};
std::ostringstream stream;
stream << openingHours;
return stream.str();
}
} // namespace osmoh

View file

@ -0,0 +1,745 @@
/*
The MIT License (MIT)
Copyright (c) 2015 Mail.Ru Group
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include <chrono>
#include <iostream>
#include <string>
#include <type_traits>
#include <vector>
// Implemented in accordance with the specification
// https://wiki.openstreetmap.org/wiki/Key:opening_hours/specification
namespace osmoh
{
class HourMinutes
{
public:
using THours = std::chrono::hours;
using TMinutes = std::chrono::minutes;
HourMinutes() = default;
HourMinutes(THours const duration) { SetDuration(duration); }
HourMinutes(TMinutes const duration) { SetDuration(duration); }
bool IsEmpty() const { return m_empty; }
bool IsExtended() const;
THours GetHours() const { return m_hours; }
TMinutes GetMinutes() const { return m_minutes; }
TMinutes GetDuration() const { return GetMinutes() + GetHours(); }
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) { SetDuration(GetDuration() + duration); }
private:
THours m_hours = THours::zero();
TMinutes m_minutes = TMinutes::zero();
bool m_empty = true;
};
HourMinutes operator-(HourMinutes const & hm);
std::ostream & operator<<(std::ostream & ost, HourMinutes const & hm);
inline bool operator<(HourMinutes const & a, HourMinutes const & b)
{
return a.GetDuration() < b.GetDuration();
}
inline bool operator==(HourMinutes const & a, HourMinutes const & b)
{
return a.GetDuration() == b.GetDuration();
}
class Time;
class TimeEvent
{
public:
enum class Event
{
None,
Sunrise,
Sunset
};
TimeEvent() = default;
TimeEvent(Event const event): m_event(event) {}
bool IsEmpty() const { return m_event == Event::None; }
bool HasOffset() const { return !m_offset.IsEmpty(); }
Event GetEvent() const { return m_event; }
void SetEvent(Event const event) { m_event = event; }
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;
private:
Event m_event = Event::None;
HourMinutes m_offset;
};
std::ostream & operator<<(std::ostream & ost, TimeEvent const te);
class Time
{
enum class Type
{
None,
HourMinutes,
Event,
};
public:
using THours = HourMinutes::THours;
using TMinutes = HourMinutes::TMinutes;
Time() = default;
Time(HourMinutes const & hm) { SetHourMinutes(hm); }
Time(TimeEvent const & te) { SetEvent(te); }
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; }
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 { return m_event; }
void SetEvent(TimeEvent const & event);
HourMinutes const & GetHourMinutes() const { return m_hourMinutes; }
HourMinutes & GetHourMinutes() { return m_hourMinutes; }
void SetHourMinutes(HourMinutes const & hm);
private:
HourMinutes m_hourMinutes;
TimeEvent m_event;
Type m_type = Type::None;
};
inline constexpr Time::THours operator ""_h(unsigned long long int h)
{
return Time::THours(h);
}
inline constexpr Time::TMinutes operator ""_min(unsigned long long int m)
{
return Time::TMinutes(m);
}
std::ostream & operator<<(std::ostream & ost, Time const & time);
bool operator==(Time const & lhs, Time const & rhs);
class TimespanPeriod
{
public:
enum class Type
{
None,
Minutes,
HourMinutes
};
TimespanPeriod() = default;
TimespanPeriod(HourMinutes const & hm);
TimespanPeriod(HourMinutes::TMinutes const minutes);
bool IsEmpty() const { return m_type == Type::None; }
bool IsHoursMinutes() const { return m_type == Type::HourMinutes; }
bool IsMinutes() const { return m_type == Type::Minutes; }
Type GetType() const { return m_type; }
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;
HourMinutes m_hourMinutes;
Type m_type = Type::None;
};
std::ostream & operator<<(std::ostream & ost, TimespanPeriod const p);
bool operator==(TimespanPeriod const & lhs, TimespanPeriod const & rhs);
class Timespan
{
public:
Timespan() = default;
Timespan(Time const & start, Time const & end): m_start(start), m_end(end) {}
Timespan(HourMinutes::TMinutes const & start,
HourMinutes::TMinutes const & end): m_start(start), m_end(end) {}
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 { return m_start; }
Time const & GetEnd() const { return m_end; }
Time & GetStart() { return m_start; }
Time & GetEnd() { return m_end; }
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; }
void ExpandPlus();
private:
Time m_start;
Time m_end;
TimespanPeriod m_period;
bool m_plus = false;
};
using TTimespans = std::vector<Timespan>;
std::ostream & operator<<(std::ostream & ost, Timespan const & span);
std::ostream & operator<<(std::ostream & ost, osmoh::TTimespans const & timespans);
bool operator==(Timespan const & lhs, Timespan const & rhs);
class NthWeekdayOfTheMonthEntry
{
public:
enum class NthDayOfTheMonth
{
None,
First,
Second,
Third,
Fourth,
Fifth
};
bool IsEmpty() const { return !HasStart() && !HasEnd(); }
bool HasStart() const { return GetStart() != NthDayOfTheMonth::None; }
bool HasEnd() const { return GetEnd() != NthDayOfTheMonth::None; }
NthDayOfTheMonth GetStart() const { return m_start; }
NthDayOfTheMonth GetEnd() const { return m_end; }
void SetStart(NthDayOfTheMonth const s) { m_start = s; }
void SetEnd(NthDayOfTheMonth const e) { m_end = e; }
bool operator==(NthWeekdayOfTheMonthEntry const & rhs) const;
private:
NthDayOfTheMonth m_start = NthDayOfTheMonth::None;
NthDayOfTheMonth m_end = NthDayOfTheMonth::None;
};
std::ostream & operator<<(std::ostream & ost, NthWeekdayOfTheMonthEntry const entry);
enum class Weekday
{
None,
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
};
inline constexpr Weekday ToWeekday(uint64_t day)
{
using TDay = decltype(day);
return ((day <= static_cast<TDay>(Weekday::None) ||
day > static_cast<TDay>(Weekday::Saturday))
? Weekday::None
: static_cast<Weekday>(day));
}
inline constexpr Weekday operator ""_weekday(unsigned long long int day)
{
return ToWeekday(day);
}
std::ostream & operator<<(std::ostream & ost, Weekday wday);
class WeekdayRange
{
using TNths = std::vector<NthWeekdayOfTheMonthEntry>;
public:
bool HasWday(Weekday const wday) 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 { 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 { return m_start; }
Weekday GetEnd() const { return m_end; }
void SetStart(Weekday const & wday) { m_start = wday; }
void SetEnd(Weekday const & wday) { m_end = wday; }
int32_t GetOffset() const { return m_offset; }
void SetOffset(int32_t const offset) { m_offset = offset; }
bool HasNth() const { return !m_nths.empty(); }
TNths const & GetNths() const { return m_nths; }
void AddNth(NthWeekdayOfTheMonthEntry const & entry) { m_nths.push_back(entry); }
bool operator==(WeekdayRange const & rhs) const;
private:
Weekday m_start = Weekday::None;
Weekday m_end = Weekday::None;
int32_t m_offset = 0;
TNths m_nths;
};
using TWeekdayRanges = std::vector<WeekdayRange>;
std::ostream & operator<<(std::ostream & ost, WeekdayRange const & range);
std::ostream & operator<<(std::ostream & ost, TWeekdayRanges const & ranges);
class Holiday
{
public:
bool IsPlural() const { return m_plural; }
void SetPlural(bool const plural) { m_plural = plural; }
int32_t GetOffset() const { return m_offset; }
void SetOffset(int32_t const offset) { m_offset = offset; }
bool operator==(Holiday const & rhs) const;
private:
bool m_plural = false;
int32_t m_offset = 0;
};
using THolidays = std::vector<Holiday>;
std::ostream & operator<<(std::ostream & ost, Holiday const & holiday);
std::ostream & operator<<(std::ostream & ost, THolidays const & holidys);
// Correspond to weekday_selector in osm opening hours.
class Weekdays
{
public:
bool IsEmpty() const { return GetWeekdayRanges().empty() && GetHolidays().empty(); }
bool HasWeekday() const { return !GetWeekdayRanges().empty(); }
bool HasHolidays() const { return !GetHolidays().empty(); }
TWeekdayRanges const & GetWeekdayRanges() const { return m_weekdayRanges; }
THolidays const & GetHolidays() const { return m_holidays; }
void SetWeekdayRanges(TWeekdayRanges const ranges) { m_weekdayRanges = ranges; }
void SetHolidays(THolidays const & holidays) { m_holidays = holidays; }
void AddWeekdayRange(WeekdayRange const range) { m_weekdayRanges.push_back(range); }
void AddHoliday(Holiday const & holiday) { m_holidays.push_back(holiday); }
bool operator==(Weekdays const & rhs) const;
private:
TWeekdayRanges m_weekdayRanges;
THolidays m_holidays;
};
std::ostream & operator<<(std::ostream & ost, Weekdays const & weekday);
class DateOffset
{
public:
bool IsEmpty() const { return !HasOffset() && !HasWDayOffset(); }
bool HasWDayOffset() const { return m_wdayOffest != Weekday::None; }
bool HasOffset() const { return m_offset != 0; }
bool IsWDayOffsetPositive() const { return m_positive; }
Weekday GetWDayOffset() const { return m_wdayOffest; }
int32_t GetOffset() const { return m_offset; }
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; }
bool operator==(DateOffset const & rhs) const;
bool operator!=(DateOffset const & rhs) const { return !(*this == rhs); }
bool operator<(DateOffset const & rhs) const;
private:
Weekday m_wdayOffest = Weekday::None;
bool m_positive = true;
int32_t m_offset = 0;
};
std::ostream & operator<<(std::ostream & ost, DateOffset const & offset);
class MonthDay
{
public:
enum class Month
{
None,
Jan,
Feb,
Mar,
Apr,
May,
Jun,
Jul,
Aug,
Sep,
Oct,
Nov,
Dec
};
enum class VariableDate
{
None,
Easter
};
using TYear = uint16_t;
using TDayNum = uint8_t;
bool IsEmpty() const { return !HasYear() && !HasMonth() && !HasDayNum() && !IsVariable(); }
bool IsVariable() const { return GetVariableDate() != VariableDate::None; }
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 { 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) { 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; }
bool operator==(MonthDay const & rhs) const;
bool operator<(MonthDay const & rhs) const;
bool operator<=(MonthDay const & rhs) const { return *this < rhs || *this == rhs; }
private:
TYear m_year = 0;
Month m_month = Month::None;
TDayNum m_daynum = 0;
VariableDate m_variable_date = VariableDate::None;
DateOffset m_offset;
};
inline constexpr MonthDay::Month ToMonth(uint64_t month)
{
using TMonth = decltype(month);
return ((month <= static_cast<TMonth>(MonthDay::Month::None) ||
month > static_cast<TMonth>(MonthDay::Month::Dec))
? MonthDay::Month::None
: static_cast<osmoh::MonthDay::Month>(month));
}
inline constexpr MonthDay::Month operator ""_M(unsigned long long int month)
{
return ToMonth(month);
}
std::ostream & operator<<(std::ostream & ost, MonthDay::Month const month);
std::ostream & operator<<(std::ostream & ost, MonthDay::VariableDate const date);
std::ostream & operator<<(std::ostream & ost, MonthDay const md);
class MonthdayRange
{
public:
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 { return m_start; }
MonthDay const & GetEnd() const { return m_end; }
uint32_t GetPeriod() const { return m_period; }
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; }
bool operator==(MonthdayRange const & rhs) const;
private:
MonthDay m_start;
MonthDay m_end;
uint32_t m_period = 0;
bool m_plus = false;
};
using TMonthdayRanges = std::vector<MonthdayRange>;
std::ostream & operator<<(std::ostream & ost, MonthdayRange const & range);
std::ostream & operator<<(std::ostream & ost, TMonthdayRanges const & ranges);
class YearRange
{
public:
using TYear = uint16_t;
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 { return m_start; }
TYear GetEnd() const { return m_end; }
uint32_t GetPeriod() const { return m_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; }
bool operator==(YearRange const & rhs) const;
private:
TYear m_start = 0;
TYear m_end = 0;
bool m_plus = false;
uint32_t m_period = 0;
};
using TYearRanges = std::vector<YearRange>;
std::ostream & operator<<(std::ostream & ost, YearRange const range);
std::ostream & operator<<(std::ostream & ost, TYearRanges const ranges);
class WeekRange
{
public:
using TWeek = uint8_t;
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 { return m_start; }
TWeek GetEnd() const { return m_end; }
uint32_t GetPeriod() const { return m_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; }
bool operator==(WeekRange const & rhs) const;
private:
TWeek m_start = 0;
TWeek m_end = 0;
uint32_t m_period = 0;
};
using TWeekRanges = std::vector<WeekRange>;
std::ostream & operator<<(std::ostream & ost, WeekRange const range);
std::ostream & operator<<(std::ostream & ost, TWeekRanges const ranges);
class RuleSequence
{
public:
enum class Modifier
{
DefaultOpen,
Open,
Closed,
Unknown,
Comment
};
bool IsEmpty() const { return !HasYears() && !HasMonths() && !HasWeeks() &&
!HasWeekdays() && !HasTimes(); }
bool IsTwentyFourHours() const { return m_twentyFourHours; }
bool HasYears() const { return !GetYears().empty(); }
bool HasMonths() const { return !GetMonths().empty(); }
bool HasMonthDay() const;
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 { 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 { return m_comment; }
std::string const & GetModifierComment() const { return m_modifierComment; }
std::string const & GetAnySeparator() const { return m_anySeparator; }
Modifier GetModifier() const { return m_modifier; }
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) { m_weekdays = weekdays; }
void SetTimes(TTimespans const & times) { m_times = times; }
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) { m_modifier = modifier; }
bool operator==(RuleSequence const & rhs) const;
private:
bool m_twentyFourHours{false};
TYearRanges m_years;
TMonthdayRanges m_months;
TWeekRanges m_weeks;
Weekdays m_weekdays;
TTimespans m_times;
std::string m_comment;
std::string m_anySeparator = ";";
bool m_separatorForReadability = false;
Modifier m_modifier = Modifier::DefaultOpen;
std::string m_modifierComment;
};
using TRuleSequences = std::vector<RuleSequence>;
std::ostream & operator<<(std::ostream & ost, RuleSequence::Modifier const modifier);
std::ostream & operator<<(std::ostream & ost, RuleSequence const & sequence);
std::ostream & operator<<(std::ostream & ost, TRuleSequences const & sequences);
enum class RuleState
{
Open,
Closed,
Unknown
};
class OpeningHours
{
public:
OpeningHours() = default;
OpeningHours(std::string const & rule);
OpeningHours(TRuleSequences const & rule);
bool IsOpen(time_t const dateTime) const;
bool IsClosed(time_t const dateTime) const;
bool IsUnknown(time_t const dateTime) const;
struct InfoT
{
RuleState state;
/// Calculated only if state != RuleState::Unknown.
time_t nextTimeOpen;
time_t nextTimeClosed;
};
InfoT GetInfo(time_t const dateTime) const;
bool IsValid() const;
bool IsTwentyFourHours() const;
bool HasWeekdaySelector() const;
bool HasMonthSelector() const;
bool HasWeekSelector() const;
bool HasYearSelector() const;
TRuleSequences const & GetRule() const { return m_rule; }
friend void swap(OpeningHours & lhs, OpeningHours & rhs);
bool operator==(OpeningHours const & rhs) const;
private:
TRuleSequences m_rule;
bool m_valid = false;
};
std::ostream & operator<<(std::ostream & ost, OpeningHours const & oh);
std::string ToString(osmoh::OpeningHours const & openingHours);
} // namespace osmoh

View file

@ -0,0 +1,12 @@
project(opening_hours_integration_tests)
set(SRC opening_hours_integration_tests.cpp)
omim_add_test(${PROJECT_NAME} ${SRC} BOOST_TEST)
target_link_libraries(${PROJECT_NAME} opening_hours)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/opening-count.lst" "${CMAKE_BINARY_DIR}/"
COMMENT "Copying opening-count.lst file for testing"
)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,208 @@
#include "parse_opening_hours.hpp"
#include "rules_evaluation.hpp"
#define BOOST_TEST_MODULE OpeningHoursIntegration
#include <algorithm>
#include <array>
#include <ctime>
#include <map>
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#endif
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/test/included/unit_test.hpp>
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
namespace
{
template <typename T>
std::string ToString(T const & t)
{
std::stringstream sstr;
sstr << t;
return sstr.str();
}
template <typename T>
bool HasPeriod(std::vector<T> const & v)
{
auto const hasPeriod = [](T const & t) { return t.HasPeriod(); };
return std::any_of(begin(v), end(v), hasPeriod);
}
template <typename T>
bool HasPlus(std::vector<T> const & v)
{
auto const hasPlus = [](T const & t) { return t.HasPlus(); };
return std::any_of(begin(v), end(v), hasPlus);
}
bool HasOffset(osmoh::TMonthdayRanges const & mr)
{
auto const hasOffset = [](osmoh::MonthdayRange const & md) {
return
md.GetStart().HasOffset() ||
md.GetEnd().HasOffset();
};
return std::any_of(begin(mr), end(mr), hasOffset);
}
bool HasOffset(osmoh::Weekdays const & wd)
{
auto const hasOffset = [](osmoh::WeekdayRange const & w) { return w.HasOffset(); };
return std::any_of(begin(wd.GetWeekdayRanges()), end(wd.GetWeekdayRanges()), hasOffset);
}
template <typename ParserResult>
bool CompareNormalized(std::string const & original, ParserResult const & pretendent)
{
auto originalCopy = original;
auto pretendentCopy = ToString(pretendent);
boost::to_lower(originalCopy);
boost::to_lower(pretendentCopy);
boost::replace_all(originalCopy, "off", "closed");
boost::replace_all(originalCopy, " ", "");
boost::replace_all(pretendentCopy, " ", "");
return pretendentCopy == originalCopy;
}
enum
{
Parsed,
Serialised,
Period,
Plus,
Offset,
Count_
};
using TRuleFeatures = std::array<bool, Count_>;
std::ostream & operator<<(std::ostream & ost, TRuleFeatures const & f)
{
std::copy(std::begin(f), std::end(f), std::ostream_iterator<bool>(ost, "\t"));
return ost;
}
TRuleFeatures DescribeRule(osmoh::TRuleSequences const & rule)
{
TRuleFeatures features{};
for (auto const & r : rule)
{
features[Period] |= HasPeriod(r.GetTimes());
features[Period] |= HasPeriod(r.GetMonths());
features[Period] |= HasPeriod(r.GetYears());
features[Period] |= HasPeriod(r.GetWeeks());
features[Plus] |= HasPlus(r.GetTimes());
features[Plus] |= HasPlus(r.GetMonths());
features[Plus] |= HasPlus(r.GetYears());
features[Offset] |= HasOffset(r.GetMonths());
features[Offset] |= HasOffset(r.GetWeekdays());
}
return features;
}
} // namespace
/// How to run:
/// 1. copy opening-count.lst to where the binary is
/// 2. run with --log_level=message
BOOST_AUTO_TEST_CASE(OpeningHours_CountFailed)
{
std::ifstream datalist("opening-count.lst");
BOOST_REQUIRE_MESSAGE(datalist.is_open(),
"Can't open ./opening-count.lst: " << std::strerror(errno));
std::string line;
size_t line_num = 0;
size_t num_failed = 0;
size_t num_total = 0;
std::map<size_t, size_t> hist;
std::map<TRuleFeatures, size_t> featuresDistrib;
while (std::getline(datalist, line))
{
size_t count = 1;
std::string datastr;
auto d = line.find('|');
if (d == std::string::npos)
{
BOOST_WARN_MESSAGE((d != std::string::npos),
"Incorrect line " << line_num << " format: " << line);
datastr = line;
}
else
{
count = std::stol(line.substr(0, d));
datastr = line.substr(d + 1);
}
line_num++;
osmoh::TRuleSequences rule;
auto const isParsed = Parse(datastr, rule);
TRuleFeatures features{};
if (isParsed)
features = DescribeRule(rule);
features[Parsed] = true;
features[Serialised] = true;
if (!isParsed)
{
num_failed += count;
++hist[count];
features[Parsed] = false;
features[Serialised] = false;
BOOST_TEST_MESSAGE("-- " << count << " :[" << datastr << "]");
}
else if (!CompareNormalized(datastr, rule))
{
num_failed += count;
++hist[count];
features[Serialised] = false;
BOOST_TEST_MESSAGE("- " << count << " :[" << datastr << "]");
BOOST_TEST_MESSAGE("+ " << count << " :[" << ToString(rule) << "]");
}
featuresDistrib[features] += count;
num_total += count;
}
BOOST_CHECK_MESSAGE((num_failed == 0),
"Failed " << num_failed <<
" of " << num_total <<
" (" << double(num_failed)/(double(num_total)/100) << "%)");
{
std::stringstream message;
for (auto const & e : hist)
message << "Weight: " << e.first << " Count: " << e.second << std::endl;
BOOST_TEST_MESSAGE(message.str());
}
{
std::stringstream message;
message << "Parsed\tSerialised\tPeriod\tPlus\tOffset\tCount" << std::endl;
for (auto const & e : featuresDistrib)
message << e.first << '\t' << e.second << std::endl;
BOOST_TEST_MESSAGE(message.str());
}
}

View file

@ -0,0 +1,114 @@
#pragma once
#include "opening_hours.hpp"
// #define BOOST_SPIRIT_DEBUG
#include <boost/spirit/include/qi.hpp>
namespace osmoh
{
namespace phx = boost::phoenix;
namespace parsing
{
namespace qi = boost::spirit::qi;
namespace charset = boost::spirit::standard_wide;
using space_type = charset::space_type;
using Iterator = std::string::const_iterator;
struct dash_ : public qi::symbols<char> { dash_(); };
struct event_ : public qi::symbols<char, osmoh::TimeEvent::Event> { event_(); };
struct wdays_ : qi::symbols<char, osmoh::Weekday> { wdays_(); };
struct month_ : qi::symbols<char, osmoh::MonthDay::Month> { month_(); };
struct hours_ : qi::symbols<char, osmoh::Time::THours> { hours_(); };
struct exthours_ : qi::symbols<char, osmoh::Time::THours> { exthours_(); };
struct minutes_ : qi::symbols<char, osmoh::Time::TMinutes> { minutes_(); };
struct weeknum_ : qi::symbols<char, unsigned> { weeknum_(); };
struct daynum_ : qi::symbols<char, MonthDay::TDayNum> { daynum_(); };
extern dash_ dash;
extern event_ event;
extern wdays_ wdays;
extern month_ month;
extern hours_ hours;
extern exthours_ exthours;
extern minutes_ minutes;
extern weeknum_ weeknum;
extern daynum_ daynum;
class year_selector_parser : public qi::grammar<Iterator, osmoh::TYearRanges(), space_type>
{
protected:
qi::rule<Iterator, osmoh::YearRange(), space_type> year_range;
qi::rule<Iterator, osmoh::TYearRanges(), space_type> main;
public:
year_selector_parser();
};
class week_selector_parser : public qi::grammar<Iterator, osmoh::TWeekRanges(), space_type>
{
protected:
qi::rule<Iterator, osmoh::WeekRange(), space_type> week;
qi::rule<Iterator, osmoh::TWeekRanges(), space_type> main;
public:
week_selector_parser();
};
class month_selector_parser : public qi::grammar<Iterator, TMonthdayRanges(), space_type>
{
protected:
qi::rule<Iterator, int32_t(), space_type, qi::locals<int32_t>> day_offset;
qi::rule<Iterator, DateOffset(), space_type, qi::locals<bool>> date_offset;
qi::rule<Iterator, MonthDay(), space_type> date_left;
qi::rule<Iterator, MonthDay(), space_type> date_right;
qi::rule<Iterator, MonthDay(), space_type> date_from;
qi::rule<Iterator, MonthDay(), space_type> date_to;
qi::rule<Iterator, MonthDay(), space_type> date_from_with_offset;
qi::rule<Iterator, MonthDay(), space_type> date_to_with_offset;
qi::rule<Iterator, MonthdayRange(), space_type> monthday_range;
qi::rule<Iterator, TMonthdayRanges(), space_type> main;
public:
month_selector_parser();
};
class weekday_selector_parser : public qi::grammar<Iterator, osmoh::Weekdays(), space_type>
{
protected:
qi::rule<Iterator, osmoh::NthWeekdayOfTheMonthEntry::NthDayOfTheMonth(), space_type> nth;
qi::rule<Iterator, osmoh::NthWeekdayOfTheMonthEntry(), space_type> nth_entry;
qi::rule<Iterator, int32_t(), space_type, qi::locals<int8_t>> day_offset;
qi::rule<Iterator, osmoh::WeekdayRange(), space_type> weekday_range;
qi::rule<Iterator, osmoh::TWeekdayRanges(), space_type> weekday_sequence;
qi::rule<Iterator, osmoh::Holiday(), space_type> holiday;
qi::rule<Iterator, osmoh::THolidays(), space_type> holiday_sequence;
qi::rule<Iterator, osmoh::Weekdays(), space_type> main;
public:
weekday_selector_parser();
};
class time_selector_parser : public qi::grammar<Iterator, osmoh::TTimespans(), space_type>
{
protected:
qi::rule<Iterator, osmoh::HourMinutes(), space_type> hour_minutes;
qi::rule<Iterator, osmoh::HourMinutes(), space_type> extended_hour_minutes;
qi::rule<Iterator, osmoh::TimeEvent(), space_type> variable_time;
qi::rule<Iterator, osmoh::Time(), space_type> extended_time;
qi::rule<Iterator, osmoh::Time(), space_type> time;
qi::rule<Iterator, osmoh::Timespan(), space_type> timespan;
qi::rule<Iterator, osmoh::TTimespans(), space_type> main;
public:
time_selector_parser();
};
} // namespace parsing
} // namespace osmoh

View file

@ -0,0 +1,111 @@
#include "opening_hours_parsers.hpp"
namespace osmoh
{
namespace parsing
{
dash_::dash_()
{
add
("-")
/* not standard */
// (L"")(L"—")(L"")(L"~")(L"")(L"〜")(L"to")(L"às")(L"ás")(L"as")(L"a")(L"ate")(L"bis")
;
}
event_::event_()
{
add
("dawn", osmoh::TimeEvent::Event::Sunrise)
("sunrise", osmoh::TimeEvent::Event::Sunrise)
("sunset", osmoh::TimeEvent::Event::Sunset)
("dusk", osmoh::TimeEvent::Event::Sunset)
;
}
wdays_::wdays_()
{
add
("su", 1_weekday)("mo", 2_weekday)("tu", 3_weekday)("we", 4_weekday)("th", 5_weekday)("fr", 6_weekday)("sa", 7_weekday) // en
// (L"mon", 0)(L"tue", 1)(L"wed", 2)(L"thu", 3)(L"fri", 4)(L"sat", 5)(L"sun", 6) // en
// (L"пн", 0)(L"вт", 1)(L"ср", 2)(L"чт", 3)(L"пт", 4)(L"сб", 5)(L"вс", 6) // ru
// (L"пн.", 0)(L"вт.", 1)(L"ср.", 2)(L"чт.", 3)(L"пт.", 4)(L"сб.", 5)(L"вс.", 6) // ru
// (L"lu", 0)(L"ma", 1)(L"me", 2)(L"je", 3)(L"ve", 4)(L"sa", 5)(L"di", 6) // fr
// (L"lu", 0)(L"ma", 1)(L"me", 2)(L"gi", 3)(L"ve", 4)(L"sa", 5)(L"do", 6) // it
// (L"lu", 0)(L"ma", 1)(L"mi", 2)(L"ju", 3)(L"vie", 4)(L"sá", 5)(L"do", 6) // sp
// (L"週一", 0)(L"週二", 1)(L"週三", 2)(L"週四", 3)(L"週五", 4)(L"週六", 5)(L"週日", 6) // ch traditional
// (L"senin", 0)(L"selasa", 1)(L"rabu", 2)(L"kamis", 3)(L"jum'at", 4)(L"sabtu", 5)(L"minggu", 6) // indonesian
// (L"wd", 2)
;
}
month_::month_()
{
add
("jan", 1_M)("feb", 2_M)("mar", 3_M)("apr", 4_M)("may", 5_M)("jun", 6_M)
("jul", 7_M)("aug", 8_M)("sep", 9_M)("oct", 10_M)("nov", 11_M)("dec", 12_M)
;
}
hours_::hours_()
{
add
( "0", 0_h)( "1", 1_h)( "2", 2_h)( "3", 3_h)( "4", 4_h)( "5", 5_h)( "6", 6_h)( "7", 7_h)( "8", 8_h)( "9", 9_h) /* not standard */
("00", 0_h)("01", 1_h)("02", 2_h)("03", 3_h)("04", 4_h)("05", 5_h)("06", 6_h)("07", 7_h)("08", 8_h)("09", 9_h)
("10", 10_h)("11", 11_h)("12", 12_h)("13", 13_h)("14", 14_h)("15", 15_h)("16", 16_h)("17", 17_h)("18", 18_h)("19", 19_h)
("20", 20_h)("21", 21_h)("22", 22_h)("23", 23_h)("24", 24_h)
;
}
exthours_::exthours_()
{
add
( "0", 0_h)( "1", 1_h)( "2", 2_h)( "3", 3_h)( "4", 4_h)( "5", 5_h)( "6", 6_h)( "7", 7_h)( "8", 8_h)( "9", 9_h) /* not standard */
("00", 0_h)("01", 1_h)("02", 2_h)("03", 3_h)("04", 4_h)("05", 5_h)("06", 6_h)("07", 7_h)("08", 8_h)("09", 9_h)
("10", 10_h)("11", 11_h)("12", 12_h)("13", 13_h)("14", 14_h)("15", 15_h)("16", 16_h)("17", 17_h)("18", 18_h)("19", 19_h)
("20", 20_h)("21", 21_h)("22", 22_h)("23", 23_h)("24", 24_h)("25", 25_h)("26", 26_h)("27", 27_h)("28", 28_h)("29", 29_h)
("30", 30_h)("31", 31_h)("32", 32_h)("33", 33_h)("34", 34_h)("35", 35_h)("36", 36_h)("37", 37_h)("38", 38_h)("39", 39_h)
("40", 40_h)("41", 41_h)("42", 42_h)("43", 43_h)("44", 44_h)("45", 45_h)("46", 46_h)("47", 47_h)("48", 48_h)
;
}
minutes_::minutes_()
{
add
( "0", 0_min)( "1", 1_min)( "2", 2_min)( "3", 3_min)( "4", 4_min)( "5", 5_min)( "6", 6_min)( "7", 7_min)( "8", 8_min)( "9", 9_min) /* not standard */
("00", 0_min)("01", 1_min)("02", 2_min)("03", 3_min)("04", 4_min)("05", 5_min)("06", 6_min)("07", 7_min)("08", 8_min)("09", 9_min)
("10", 10_min)("11", 11_min)("12", 12_min)("13", 13_min)("14", 14_min)("15", 15_min)("16", 16_min)("17", 17_min)("18", 18_min)("19", 19_min)
("20", 20_min)("21", 21_min)("22", 22_min)("23", 23_min)("24", 24_min)("25", 25_min)("26", 26_min)("27", 27_min)("28", 28_min)("29", 29_min)
("30", 30_min)("31", 31_min)("32", 32_min)("33", 33_min)("34", 34_min)("35", 35_min)("36", 36_min)("37", 37_min)("38", 38_min)("39", 39_min)
("40", 40_min)("41", 41_min)("42", 42_min)("43", 43_min)("44", 44_min)("45", 45_min)("46", 46_min)("47", 47_min)("48", 48_min)("49", 49_min)
("50", 50_min)("51", 51_min)("52", 52_min)("53", 53_min)("54", 54_min)("55", 55_min)("56", 56_min)("57", 57_min)("58", 58_min)("59", 59_min)
;
}
weeknum_::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)
;
}
daynum_::daynum_()
{
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)
;
}
} // namespace parsing
} // namespace osmoh

View file

@ -0,0 +1,7 @@
project(opening_hours_supported_features_tests)
set(SRC opening_hours_supported_features_tests.cpp)
omim_add_test(${PROJECT_NAME} ${SRC} BOOST_TEST)
target_link_libraries(${PROJECT_NAME} opening_hours)

View file

@ -0,0 +1,7 @@
project(opening_hours_tests)
set(SRC opening_hours_tests.cpp)
omim_add_test(${PROJECT_NAME} ${SRC} BOOST_TEST)
target_link_libraries(${PROJECT_NAME} opening_hours)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,109 @@
#include "parse_opening_hours.hpp"
#include "opening_hours_parsers.hpp"
#include <boost/phoenix/bind.hpp>
#include <boost/phoenix/operator.hpp> // operator,
namespace osmoh
{
namespace parsing
{
month_selector_parser::month_selector_parser() : month_selector_parser::base_type(main)
{
using qi::_1;
using qi::_2;
using qi::_3;
using qi::_a;
using qi::_val;
using qi::uint_;
using qi::ushort_;
using qi::lit;
using qi::double_;
using qi::lexeme;
using osmoh::DateOffset;
using osmoh::MonthDay;
using osmoh::MonthdayRange;
static const qi::int_parser<unsigned, 10, 4, 4> year = {};
day_offset = ((lit('+')[_a = 1] | lit('-')[_a = -1]) >>
ushort_ >> charset::no_case[(lit("days") | lit("day"))]) [_val = _a * _1];
date_offset = ((lit('+')[_a = true] | lit('-')[_a = false])
>> charset::no_case[wdays] >> day_offset)
[(bind(&DateOffset::SetWDayOffset, _val, _1),
bind(&DateOffset::SetOffset, _val, _2),
bind(&DateOffset::SetWDayOffsetPositive, _val, _a))]
| ((lit('+')[_a = true] | lit('-') [_a = false]) >> charset::no_case[wdays])
[(bind(&DateOffset::SetWDayOffset, _val, _1),
bind(&DateOffset::SetWDayOffsetPositive, _val, _a))]
| day_offset [bind(&DateOffset::SetOffset, _val, _1)]
;
date_left = (year >> charset::no_case[month]) [(bind(&MonthDay::SetYear, _val, _1),
bind(&MonthDay::SetMonth, _val, _2))]
| charset::no_case[month] [bind(&MonthDay::SetMonth, _val, _1)]
;
date_right = charset::no_case[month] [bind(&MonthDay::SetMonth, _val, _1)]
;
date_from = (date_left >> (daynum >> !(lit(':') >> qi::digit)))
[(_val = _1, bind(&MonthDay::SetDayNum, _val, _2))]
| (year >> charset::no_case[lit("easter")]) [(bind(&MonthDay::SetYear, _val, _1),
bind(&MonthDay::SetVariableDate, _val,
MonthDay::VariableDate::Easter))]
| charset::no_case[lit("easter")] [bind(&MonthDay::SetVariableDate, _val,
MonthDay::VariableDate::Easter)]
;
date_to = date_from [_val = _1]
| (daynum >> !(lit(':') >> qi::digit)) [bind(&MonthDay::SetDayNum, _val, _1)]
;
date_from_with_offset = (date_from >> date_offset)
[(_val = _1, bind(&MonthDay::SetOffset, _val, _2))]
| date_from [_val = _1]
;
date_to_with_offset = (date_to >> date_offset)
[(_val = _1, bind(&MonthDay::SetOffset, _val, _2))]
| date_to [_val = _1]
;
monthday_range = (date_from_with_offset >> dash >> date_to_with_offset)
[(bind(&MonthdayRange::SetStart, _val, _1),
bind(&MonthdayRange::SetEnd, _val, _2))]
| (date_from_with_offset >> '+') [(bind(&MonthdayRange::SetStart, _val, _1),
bind(&MonthdayRange::SetPlus, _val, true))]
| (date_left >> dash >> date_right >> '/' >> uint_)
[(bind(&MonthdayRange::SetStart, _val, _1),
bind(&MonthdayRange::SetEnd, _val, _2),
bind(&MonthdayRange::SetPeriod, _val, _3))]
| (date_left >> lit("-") >> date_right) [(bind(&MonthdayRange::SetStart, _val, _1),
bind(&MonthdayRange::SetEnd, _val, _2))]
| date_from [bind(&MonthdayRange::SetStart, _val, _1)]
| date_left [bind(&MonthdayRange::SetStart, _val, _1)]
;
main %= (monthday_range % ',');
BOOST_SPIRIT_DEBUG_NODE(main);
BOOST_SPIRIT_DEBUG_NODE(monthday_range);
BOOST_SPIRIT_DEBUG_NODE(day_offset);
BOOST_SPIRIT_DEBUG_NODE(date_offset);
BOOST_SPIRIT_DEBUG_NODE(date_left);
BOOST_SPIRIT_DEBUG_NODE(date_right);
BOOST_SPIRIT_DEBUG_NODE(date_from);
BOOST_SPIRIT_DEBUG_NODE(date_to);
BOOST_SPIRIT_DEBUG_NODE(date_from_with_offset);
BOOST_SPIRIT_DEBUG_NODE(date_to_with_offset);
}
}
bool Parse(std::string const & str, TMonthdayRanges & context)
{
return osmoh::ParseImpl<parsing::month_selector_parser>(str, context);
}
} // namespace osmoh

View file

@ -0,0 +1,123 @@
#include "parse_opening_hours.hpp"
#include "opening_hours_parsers.hpp"
#include <boost/phoenix/bind.hpp>
#include <boost/phoenix/operator.hpp> // operator,
#include <boost/phoenix/stl.hpp>
namespace osmoh
{
namespace parsing
{
dash_ dash;
event_ event;
wdays_ wdays;
month_ month;
hours_ hours;
exthours_ exthours;
minutes_ minutes;
weeknum_ weeknum;
daynum_ daynum;
class time_domain : public qi::grammar<Iterator, osmoh::TRuleSequences(), space_type>
{
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<Iterator, std::string()> comment;
qi::rule<Iterator, std::string(), space_type> separator;
qi::rule<Iterator, qi::unused_type(osmoh::RuleSequence &), space_type> small_range_selectors;
qi::rule<Iterator, qi::unused_type(osmoh::RuleSequence &), space_type> wide_range_selectors;
qi::rule<Iterator, qi::unused_type(osmoh::RuleSequence &), space_type> rule_modifier;
qi::rule<Iterator, osmoh::RuleSequence(), space_type> rule_sequence;
qi::rule<Iterator, osmoh::TRuleSequences(), space_type> main;
public:
time_domain() : time_domain::base_type(main)
{
using qi::lit;
using qi::lexeme;
using qi::_1;
using qi::_a;
using qi::_r1;
using qi::_val;
using charset::char_;
using qi::eps;
using qi::lazy;
using phx::back;
using phx::push_back;
using osmoh::RuleSequence;
using Modifier = RuleSequence::Modifier;
comment %= '"' >> +(char_ - '"') >> '"'
;
separator %= charset::string(";")
| charset::string("||")
| charset::string(",")
;
wide_range_selectors =
( -(year_selector [bind(&RuleSequence::SetYears, _r1, _1)]) >>
-(month_selector [bind(&RuleSequence::SetMonths, _r1, _1)]) >>
-(week_selector [bind(&RuleSequence::SetWeeks, _r1, _1)]) >>
-(lit(':') [bind(&RuleSequence::SetSeparatorForReadability, _r1, true)]))
| (comment >> ':') [bind(&RuleSequence::SetComment, _r1, _1)]
;
small_range_selectors =
( -(weekday_selector [bind(&RuleSequence::SetWeekdays, _r1, _1)]) >>
-(time_selector [bind(&RuleSequence::SetTimes, _r1, _1)]))
;
rule_modifier =
(charset::no_case[lit("open") | lit("on")]
[bind(&RuleSequence::SetModifier, _r1, Modifier::Open)] >>
-(comment [bind(&RuleSequence::SetModifierComment, _r1, _1)]))
| ((charset::no_case[lit("closed") | lit("off")])
[bind(&RuleSequence::SetModifier, _r1, Modifier::Closed)] >>
-(comment [bind(&RuleSequence::SetModifierComment, _r1, _1)]))
| (charset::no_case[lit("unknown")]
[bind(&RuleSequence::SetModifier, _r1, Modifier::Unknown)] >>
-(comment [bind(&RuleSequence::SetModifierComment, _r1, _1)]))
| comment [(bind(&RuleSequence::SetModifier, _r1, Modifier::Comment),
bind(&RuleSequence::SetModifierComment, _r1, _1))]
;
rule_sequence =
( lit("24/7") [bind(&RuleSequence::SetTwentyFourHours, _val, true)]
| ( -wide_range_selectors(_val) >>
-small_range_selectors(_val) )) >>
-rule_modifier(_val)
;
main = ( -(lit("opening_hours") >> lit('=')) >>
(rule_sequence [push_back(_val, _1)] %
(separator [phx::bind(&RuleSequence::SetAnySeparator, back(_val), _1)])))
;
BOOST_SPIRIT_DEBUG_NODE(main);
BOOST_SPIRIT_DEBUG_NODE(rule_sequence);
BOOST_SPIRIT_DEBUG_NODE(rule_modifier);
BOOST_SPIRIT_DEBUG_NODE(small_range_selectors);
BOOST_SPIRIT_DEBUG_NODE(wide_range_selectors);
}
};
} // namespace parsing
bool Parse(std::string const & str, TRuleSequences & context)
{
context.clear();
return osmoh::ParseImpl<parsing::time_domain>(str, context);
}
} // namespace osmoh

View file

@ -0,0 +1,38 @@
#pragma once
#include "opening_hours.hpp"
#include <string>
#include <boost/spirit/include/qi.hpp>
namespace osmoh
{
template<typename Parser, typename Context>
bool ParseImpl(std::string const & str, Context & context)
{
using boost::spirit::qi::phrase_parse;
using boost::spirit::standard_wide::space;
Parser parser;
#ifndef NDEBUG
boost::spirit::qi::what(parser);
#endif
auto first = begin(str);
auto const last = end(str);
auto parsed = phrase_parse(first, last, parser, space, context);
if (!parsed || first != last)
return false;
return true;
}
bool Parse(std::string const &, TTimespans &);
bool Parse(std::string const &, Weekdays &);
bool Parse(std::string const &, TMonthdayRanges &);
bool Parse(std::string const &, TYearRanges &);
bool Parse(std::string const &, TWeekRanges &);
bool Parse(std::string const &, TRuleSequences &);
} // namespace osmoh

View file

@ -0,0 +1,97 @@
#include "parse_opening_hours.hpp"
#include "opening_hours_parsers.hpp"
#include <boost/phoenix/bind.hpp>
#include <boost/phoenix/operator.hpp> // operator,
namespace osmoh
{
namespace parsing
{
time_selector_parser::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 charset::char_;
using osmoh::HourMinutes;
using osmoh::TimeEvent;
using osmoh::Time;
using osmoh::Timespan;
hour_minutes =
(hours >> lit(':') >> minutes) [(bind(&HourMinutes::AddDuration, _val, _1),
bind(&HourMinutes::AddDuration, _val, _2))]
;
extended_hour_minutes =
(exthours >> lit(':') >> minutes)[(bind(&HourMinutes::AddDuration, _val, _1),
bind(&HourMinutes::AddDuration, _val, _2))]
;
variable_time =
( lit('(')
>> charset::no_case[event] [bind(&TimeEvent::SetEvent, _val, _1)]
>> ( (lit('+') >> hour_minutes) [bind(&TimeEvent::SetOffset, _val, _1)]
| (lit('-') >> hour_minutes) [bind(&TimeEvent::SetOffset, _val, -_1)] )
>> lit(')')
)
| charset::no_case[event][bind(&TimeEvent::SetEvent, _val, _1)]
;
extended_time = extended_hour_minutes [bind(&Time::SetHourMinutes, _val, _1)]
| variable_time [bind(&Time::SetEvent, _val, _1)]
;
time = hour_minutes [bind(&Time::SetHourMinutes, _val, _1)]
| variable_time [bind(&Time::SetEvent, _val, _1)]
;
timespan =
(time >> dash >> extended_time >> '/' >> hour_minutes)
[(bind(&Timespan::SetStart, _val, _1),
bind(&Timespan::SetEnd, _val, _2),
bind(&Timespan::SetPeriod, _val, _3))]
| (time >> dash >> extended_time >> '/' >> minutes)
[(bind(&Timespan::SetStart, _val, _1),
bind(&Timespan::SetEnd, _val, _2),
bind(&Timespan::SetPeriod, _val, _3))]
| (time >> dash >> extended_time >> '+')
[(bind(&Timespan::SetStart, _val, _1),
bind(&Timespan::SetEnd, _val, _2),
bind(&Timespan::SetPlus, _val, true))]
| (time >> dash >> extended_time)
[(bind(&Timespan::SetStart, _val, _1),
bind(&Timespan::SetEnd, _val, _2))]
| (time >> '+')
[(bind(&Timespan::SetStart, _val, _1),
bind(&Timespan::SetPlus, _val, true))]
// This rule is only used for collection_times tag wish is not in our interest.
// | time[bind(&Timespan::SetStart, _val, _1)]
;
main %= timespan % ',';
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);
}
}
bool Parse(std::string const & str, TTimespans & context)
{
return osmoh::ParseImpl<parsing::time_selector_parser>(str, context);
}
} // namespace osmoh

View file

@ -0,0 +1,79 @@
#include "parse_opening_hours.hpp"
#include "opening_hours_parsers.hpp"
#include <boost/phoenix/bind.hpp>
#include <boost/phoenix/operator.hpp> // operator,
namespace osmoh
{
namespace parsing
{
weekday_selector_parser::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 osmoh::NthWeekdayOfTheMonthEntry;
using osmoh::Holiday;
using osmoh::WeekdayRange;
using osmoh::Weekdays;
nth = ushort_(1)[_val = NthWeekdayOfTheMonthEntry::NthDayOfTheMonth::First]
| ushort_(2) [_val = NthWeekdayOfTheMonthEntry::NthDayOfTheMonth::Second]
| ushort_(3) [_val = NthWeekdayOfTheMonthEntry::NthDayOfTheMonth::Third]
| ushort_(4) [_val = NthWeekdayOfTheMonthEntry::NthDayOfTheMonth::Fourth]
| ushort_(5) [_val = NthWeekdayOfTheMonthEntry::NthDayOfTheMonth::Fifth];
nth_entry = (nth >> dash >> nth) [(bind(&NthWeekdayOfTheMonthEntry::SetStart, _val, _1),
bind(&NthWeekdayOfTheMonthEntry::SetEnd, _val, _2))]
| (lit('-') >> nth) [bind(&NthWeekdayOfTheMonthEntry::SetEnd, _val, _1)]
| nth [bind(&NthWeekdayOfTheMonthEntry::SetStart, _val, _1)]
;
day_offset =
( (lit('+')[_a = 1] | lit('-') [_a = -1]) >>
ushort_ [_val = _1 * _a] >>
charset::no_case[(lit("days") | lit("day"))] )
;
holiday = (charset::no_case[lit("SH")] [bind(&Holiday::SetPlural, _val, false)]
>> -day_offset [bind(&Holiday::SetOffset, _val, _1)])
| charset::no_case[lit("PH")] [bind(&Holiday::SetPlural, _val, true)]
;
holiday_sequence %= (holiday % ',');
weekday_range =
( charset::no_case[wdays] [bind(&WeekdayRange::SetStart, _val, _1)] >>
'[' >> (nth_entry [bind(&WeekdayRange::AddNth, _val, _1)]) % ',') >> ']' >>
-(day_offset [bind(&WeekdayRange::SetOffset, _val, _1)])
| charset::no_case[(wdays >> dash >> wdays)] [(bind(&WeekdayRange::SetStart, _val, _1),
bind(&WeekdayRange::SetEnd, _val, _2))]
| charset::no_case[wdays] [bind(&WeekdayRange::SetStart, _val, _1)]
;
weekday_sequence %= (weekday_range % ',') >> !qi::no_skip[charset::alpha]
;
main = (holiday_sequence >> -lit(',') >> weekday_sequence)
[(bind(&Weekdays::SetHolidays, _val, _1),
bind(&Weekdays::SetWeekdayRanges, _val, _2))]
| holiday_sequence [bind(&Weekdays::SetHolidays, _val, _1)]
| weekday_sequence [bind(&Weekdays::SetWeekdayRanges, _val, _1)]
;
BOOST_SPIRIT_DEBUG_NODE(main);
BOOST_SPIRIT_DEBUG_NODE(weekday_sequence);
BOOST_SPIRIT_DEBUG_NODE(weekday_range);
BOOST_SPIRIT_DEBUG_NODE(holiday_sequence);
}
}
bool Parse(std::string const & str, Weekdays & context)
{
return osmoh::ParseImpl<parsing::weekday_selector_parser>(str, context);
}
} // namespace osmoh

View file

@ -0,0 +1,37 @@
#include "parse_opening_hours.hpp"
#include "opening_hours_parsers.hpp"
#include <boost/phoenix/bind.hpp>
#include <boost/phoenix/operator.hpp> // operator,
namespace osmoh
{
namespace parsing
{
week_selector_parser::week_selector_parser() : week_selector_parser::base_type(main)
{
using qi::uint_;
using qi::lit;
using qi::_1;
using qi::_2;
using qi::_3;
using qi::_val;
using osmoh::WeekRange;
week = (weeknum >> dash >> weeknum >> '/' >> uint_) [(bind(&WeekRange::SetStart, _val, _1),
bind(&WeekRange::SetEnd, _val, _2),
bind(&WeekRange::SetPeriod, _val, _3))]
| (weeknum >> dash >> weeknum) [(bind(&WeekRange::SetStart, _val, _1),
bind(&WeekRange::SetEnd, _val, _2))]
| weeknum [bind(&WeekRange::SetStart, _val, _1)]
;
main %= charset::no_case[lit("week")] >> (week % ',');
}
}
bool Parse(std::string const & str, TWeekRanges & context)
{
return osmoh::ParseImpl<parsing::week_selector_parser>(str, context);
}
} // namespace osmoh

View file

@ -0,0 +1,41 @@
#include "parse_opening_hours.hpp"
#include "opening_hours_parsers.hpp"
#include <boost/phoenix/bind.hpp>
#include <boost/phoenix/operator.hpp> // operator,
namespace osmoh
{
namespace parsing
{
year_selector_parser::year_selector_parser() : year_selector_parser::base_type(main)
{
using qi::uint_;
using qi::lit;
using qi::_1;
using qi::_2;
using qi::_3;
using qi::_val;
using osmoh::YearRange;
static const qi::int_parser<unsigned, 10, 4, 4> year = {};
year_range = (year >> dash >> year >> '/' >> uint_) [(bind(&YearRange::SetStart, _val, _1),
bind(&YearRange::SetEnd, _val, _2),
bind(&YearRange::SetPeriod, _val, _3))]
| (year >> dash >> year) [(bind(&YearRange::SetStart, _val, _1),
bind(&YearRange::SetEnd, _val, _2))]
| (year >> lit('+')) [(bind(&YearRange::SetStart, _val, _1),
bind(&YearRange::SetPlus, _val, true))]
;
main %= (year_range % ',');
}
}
bool Parse(std::string const & str, TYearRanges & context)
{
return osmoh::ParseImpl<parsing::year_selector_parser>(str, context);
}
} // namespace osmoh

View file

@ -0,0 +1,522 @@
#include "rules_evaluation.hpp"
#include "rules_evaluation_private.hpp"
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <tuple>
namespace
{
using THourMinutes = std::tuple<int, int>;
using osmoh::operator""_h;
constexpr osmoh::MonthDay::TYear kTMYearOrigin = 1900;
bool ToHourMinutes(osmoh::Time const & t, THourMinutes & hm)
{
if (!t.IsHoursMinutes())
return false;
hm = THourMinutes{t.GetHoursCount(), t.GetMinutesCount()};
return true;
}
bool ToHourMinutes(std::tm const & t, THourMinutes & hm)
{
hm = THourMinutes{t.tm_hour, t.tm_min};
return true;
}
int CompareMonthDayTimeTuple(osmoh::MonthDay const & monthDay, std::tm const & date)
{
if (monthDay.HasYear())
{
if (monthDay.GetYear() != date.tm_year + kTMYearOrigin)
return static_cast<int>(monthDay.GetYear()) - (date.tm_year + kTMYearOrigin);
}
if (monthDay.HasMonth())
{
if (monthDay.GetMonth() != osmoh::ToMonth(date.tm_mon + 1))
return static_cast<int>(monthDay.GetMonth()) - (date.tm_mon + 1);
}
if (monthDay.HasDayNum())
{
if (monthDay.GetDayNum() != date.tm_mday)
return static_cast<int>(monthDay.GetDayNum()) - date.tm_mday;
}
return 0;
}
bool operator<=(osmoh::MonthDay const & monthDay, std::tm const & date)
{
return CompareMonthDayTimeTuple(monthDay, date) < 1;
}
bool operator<=(std::tm const & date, osmoh::MonthDay const & monthDay)
{
return CompareMonthDayTimeTuple(monthDay, date) > -1;
}
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.
// 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())
result.SetYear(end.GetYear());
if (end.HasMonth())
result.SetMonth(end.GetMonth());
if (end.HasDayNum())
result.SetDayNum(end.GetDayNum());
return result;
}
uint8_t GetWeekNumber(std::tm const & date)
{
char buff[4]{};
if (strftime(&buff[0], sizeof(buff), "%V", &date) == 0)
return 0;
uint32_t weekNumber;
std::stringstream sstr(buff);
sstr >> weekNumber;
return weekNumber;
}
osmoh::RuleState ModifierToRuleState(osmoh::RuleSequence::Modifier const modifier)
{
using Modifier = osmoh::RuleSequence::Modifier;
switch(modifier)
{
case Modifier::DefaultOpen:
case Modifier::Open:
return osmoh::RuleState::Open;
case Modifier::Closed:
return osmoh::RuleState::Closed;
case Modifier::Unknown:
case Modifier::Comment:
return osmoh::RuleState::Unknown;
}
std::cerr << "Unreachable\n";
std::abort();
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;
}
// Spans can be of three different types:
// 1. Normal span - start time is less then end time and end time is less then 24h. Spans of this
// type will be added into |originalNormalizedSpans| as is, |additionalSpan| will be empty.
// 2. Extended span - start time is greater or equal to end time and end time is not equal to
// 00:00 (for ex. 08:00-08:00 or 08:00-03:00), this span will be split into two normal spans
// first will be added into |originalNormalizedSpans| and second will be saved into
// |additionalSpan|. We don't handle more than one occurence of extended span since it is an
// invalid situation.
// 3. Special case - end time is equal to 00:00 (for ex. 08:00-00:00), span of this type will be
// normalized and added into |originalNormalizedSpans|, |additionalSpan| will be empty.
//
// TODO(mgsergio): interpret 00:00 at the end of the span as normal, not extended hours.
void SplitExtendedHours(osmoh::TTimespans const & spans,
osmoh::TTimespans & originalNormalizedSpans,
osmoh::Timespan & additionalSpan)
{
originalNormalizedSpans.clear();
additionalSpan = {};
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]);
// if a span remains extended after normalization, then it will be split into two different spans.
if (splittedSpans.size() > 1)
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
{
// ADL shadows ::operator==.
using ::operator==;
bool IsActive(Timespan 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())
span.ExpandPlus();
if (span.HasStart() && span.HasEnd())
{
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;
return start <= toBeChecked && toBeChecked <= end;
}
else if (span.HasStart() && span.HasPlus())
{
// TODO(mgsergio): Not implemented yet
return false;
}
return false;
}
bool IsActive(WeekdayRange const & range, std::tm const & date)
{
if (range.IsEmpty())
return false;
auto const wday = ToWeekday(date.tm_wday + 1);
if (wday == Weekday::None)
return false;
return range.HasWday(wday);
}
bool IsActive(Holiday const & holiday, std::tm const & date)
{
return false;
}
bool IsActive(Weekdays const & weekdays, std::tm const & date)
{
for (auto const & wr : weekdays.GetWeekdayRanges())
if (IsActive(wr, date))
return true;
for (auto const & hd : weekdays.GetHolidays())
if (IsActive(hd, date))
return true;
return weekdays.GetWeekdayRanges().empty() &&
weekdays.GetHolidays().empty();
}
bool IsActive(MonthdayRange const & range, std::tm const & date)
{
if (range.IsEmpty())
return false;
if (range.HasEnd())
{
auto const & start = range.GetStart();
auto const end = NormalizeEnd(range.GetStart(), range.GetEnd());
if (start <= end)
return start <= date && date <= end;
else
return start <= date || date <= end;
}
return range.GetStart() == date;
}
bool IsActive(YearRange const & range, std::tm const & date)
{
if (range.IsEmpty())
return false;
auto const year = date.tm_year + kTMYearOrigin;
if (range.HasEnd())
return range.GetStart() <= year && year <= range.GetEnd();
return range.GetStart() == year;
}
bool IsActive(WeekRange const & range, std::tm const & date)
{
if (range.IsEmpty())
return false;
auto const weekNumber = GetWeekNumber(date);
if (range.HasEnd())
return range.GetStart() <= weekNumber && weekNumber <= range.GetEnd();
return range.GetStart() == weekNumber;
}
template <typename T>
bool IsActiveAny(std::vector<T> const & selectors, std::tm const & date)
{
for (auto const & selector : selectors)
{
if (IsActive(selector, date))
return true;
}
return selectors.empty();
}
bool IsActiveAny(Timespan const & span, std::tm const & time) { return IsActive(span, time); }
bool IsDayActive(RuleSequence const & rule, std::tm const & dt)
{
return IsActiveAny(rule.GetYears(), dt) && IsActiveAny(rule.GetMonths(), dt) &&
IsActiveAny(rule.GetWeeks(), dt) && IsActive(rule.GetWeekdays(), dt);
}
template <class TTimeSpans>
std::pair<bool, bool> MakeActiveResult(RuleSequence const & rule, std::tm const & dt, TTimeSpans const & times)
{
if (IsDayActive(rule, dt))
return { true, IsActiveAny(times, dt) };
else
return {false, false};
}
/// @return [day active, time active].
std::pair<bool, bool> IsActiveImpl(RuleSequence const & rule, time_t const timestamp)
{
if (rule.IsTwentyFourHours())
return {true, true};
auto const dateTimeTM = MakeTimetuple(timestamp);
if (!HasExtendedHours(rule))
return MakeActiveResult(rule, dateTimeTM, rule.GetTimes());
TTimespans originalNormalizedSpans;
Timespan additionalSpan;
SplitExtendedHours(rule.GetTimes(), originalNormalizedSpans, additionalSpan);
auto const res1 = MakeActiveResult(rule, dateTimeTM, originalNormalizedSpans);
if (res1.first && res1.second)
return res1;
time_t constexpr twentyFourHoursShift = 24 * 60 * 60;
auto const dateTimeTMShifted = MakeTimetuple(timestamp - twentyFourHoursShift);
auto const res2 = MakeActiveResult(rule, dateTimeTMShifted, additionalSpan);
return { res1.first || res2.first, res2.second };
}
/// Check if r1 is more general range and includes r2.
bool IsR1IncludesR2(RuleSequence const & r1, RuleSequence const & r2)
{
/// @todo Very naive implementation, but ok in most cases.
if ((r1.GetYears().empty() && !r2.GetYears().empty()) ||
(r1.GetMonths().empty() && !r2.GetMonths().empty()) ||
(r1.GetWeeks().empty() && !r2.GetWeeks().empty()) ||
(r1.GetWeekdays().IsEmpty() && !r2.GetWeekdays().IsEmpty()))
{
return true;
}
return false;
}
bool IsActive(RuleSequence const & rule, time_t const timestamp)
{
auto const res = IsActiveImpl(rule, timestamp);
return res.first && res.second;
}
time_t GetNextTimeState(TRuleSequences const & rules, time_t const dateTime, RuleState state)
{
time_t constexpr kTimeTMax = std::numeric_limits<time_t>::max();
time_t dateTimeResult = kTimeTMax;
time_t dateTimeToCheck;
// Check in the next 7 days
for (int i = 0; i < 7; i++)
{
for (auto it = rules.rbegin(); it != rules.rend(); ++it)
{
auto const & times = it->GetTimes();
// If the rule has no times specified, check at 00:00
if (times.size() == 0)
{
tm tm = MakeTimetuple(dateTime);
tm.tm_hour = 0;
tm.tm_min = 0;
dateTimeToCheck = mktime(&tm);
if (dateTimeToCheck == -1)
continue;
dateTimeToCheck += i * (24 * 60 * 60);
if (dateTimeToCheck < dateTime || dateTimeToCheck > dateTimeResult)
continue;
if (GetState(rules, dateTimeToCheck) == state)
dateTimeResult = dateTimeToCheck;
}
if ((state == RuleState::Open && it->GetModifier() == RuleSequence::Modifier::Closed) ||
(state == RuleState::Closed &&
(it->GetModifier() == RuleSequence::Modifier::Open || it->GetModifier() == RuleSequence::Modifier::DefaultOpen)))
{
// Check the ending time of each rule
for (auto const & time : times)
{
tm tm = MakeTimetuple(dateTime);
tm.tm_hour = time.GetEnd().GetHourMinutes().GetHoursCount();
tm.tm_min = time.GetEnd().GetHourMinutes().GetMinutesCount();
dateTimeToCheck = mktime(&tm);
if (dateTimeToCheck == -1)
continue;
dateTimeToCheck += i * (24 * 60 * 60) + 60; // 1 minute offset
if (dateTimeToCheck < dateTime || dateTimeToCheck > dateTimeResult)
continue;
if (GetState(rules, dateTimeToCheck) == state)
dateTimeResult = dateTimeToCheck - 60; // remove 1 minute offset
}
}
else
{
// Check the starting time of each rule
for (auto const & time : times)
{
tm tm = MakeTimetuple(dateTime);
tm.tm_hour = time.GetStart().GetHourMinutes().GetHoursCount();
tm.tm_min = time.GetStart().GetHourMinutes().GetMinutesCount();
dateTimeToCheck = mktime(&tm);
if (dateTimeToCheck == -1)
continue;
dateTimeToCheck += i * (24 * 60 * 60);
if (dateTimeToCheck < dateTime || dateTimeToCheck > dateTimeResult)
continue;
if (GetState(rules, dateTimeToCheck) == state)
dateTimeResult = dateTimeToCheck;
}
}
}
if (dateTimeResult < kTimeTMax)
return dateTimeResult;
}
return kTimeTMax;
}
RuleState GetState(TRuleSequences const & rules, time_t const timestamp)
{
RuleSequence const * emptyRule = nullptr;
RuleSequence const * dayMatchedRule = nullptr;
for (auto it = rules.rbegin(); it != rules.rend(); ++it)
{
auto const & rule = *it;
auto const res = IsActiveImpl(rule, timestamp);
if (!res.first)
continue;
bool const empty = rule.IsEmpty();
if (res.second)
{
if (empty && !emptyRule)
emptyRule = &rule;
else
{
// Should understand if previous 'rule vs dayMatchedRule' should work
// like 'general x BUT specific y' or like 'x OR y'.
if (dayMatchedRule && IsR1IncludesR2(rule, *dayMatchedRule))
return RuleState::Closed;
return ModifierToRuleState(rule.GetModifier());
}
}
else if (!empty && !dayMatchedRule && ModifierToRuleState(rule.GetModifier()) == RuleState::Open)
{
// Save recent day-matched rule with Open state, but not time-matched.
dayMatchedRule = &rule;
}
}
if (emptyRule)
{
if (emptyRule->HasComment())
return RuleState::Unknown;
else
return ModifierToRuleState(emptyRule->GetModifier());
}
return (rules.empty()
? RuleState::Unknown
: RuleState::Closed);
}
} // namespace osmoh

View file

@ -0,0 +1,43 @@
#pragma once
#include "opening_hours.hpp"
#include <ctime>
namespace osmoh
{
RuleState GetState(TRuleSequences const & rules, time_t const dateTime);
time_t GetNextTimeState(TRuleSequences const & rules, time_t const dateTime, RuleState state);
inline bool IsOpen(TRuleSequences const & rules, time_t const dateTime)
{
return GetState(rules, dateTime) == RuleState::Open;
}
inline time_t GetNextTimeOpen(TRuleSequences const & rules, time_t const dateTime)
{
if (GetState(rules, dateTime) == RuleState::Open)
return dateTime;
return GetNextTimeState(rules, dateTime, RuleState::Open);
}
inline bool IsClosed(TRuleSequences const & rules, time_t const dateTime)
{
return GetState(rules, dateTime) == RuleState::Closed;
}
inline time_t GetNextTimeClosed(TRuleSequences const & rules, time_t const dateTime)
{
if (GetState(rules, dateTime) == RuleState::Closed)
return dateTime;
return GetNextTimeState(rules, dateTime, RuleState::Closed);
}
inline bool IsUnknown(TRuleSequences const & rules, time_t const dateTime)
{
return GetState(rules, dateTime) == RuleState::Unknown;
}
} // namespace osmoh

View file

@ -0,0 +1,17 @@
#pragma once
#include "opening_hours.hpp"
#include <ctime>
namespace osmoh
{
bool IsActive(Timespan span, 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, time_t const timestamp);
} // namespace osmoh