diff --git a/3party/boost/boost/proto/transform/default.hpp b/3party/boost/boost/proto/transform/default.hpp index cac2d6eee8..a78afcc64a 100644 --- a/3party/boost/boost/proto/transform/default.hpp +++ b/3party/boost/boost/proto/transform/default.hpp @@ -545,7 +545,7 @@ namespace boost { namespace proto BOOST_PROTO_USE_GET_POINTER(); typedef typename detail::class_member_traits::class_type class_type; return ( - BOOST_PROTO_GET_POINTER(class_type, (BOOST_PROTO_DEFAULT_EVAL(~, 1, e))) ->* + BOOST_PROTO_GET_POINTER(class_type, (BOOST_PROTO_DEFAULT_EVAL(~, 1, e))) ->* BOOST_PROTO_DEFAULT_EVAL(~, 0, e) )(); } diff --git a/3party/opening_hours/opening_hours.cpp b/3party/opening_hours/opening_hours.cpp index 548d3bd7cf..44b25ffe8f 100644 --- a/3party/opening_hours/opening_hours.cpp +++ b/3party/opening_hours/opening_hours.cpp @@ -23,6 +23,8 @@ */ #include "opening_hours.hpp" +#include "rules_evaluation.hpp" +#include "parse_opening_hours.hpp" #include #include @@ -56,7 +58,6 @@ void PrintVector(std::ostream & ost, std::vector const & v, char const * cons PrintVector(ost, v, [&sep](T const &) { return sep; }); } - void PrintOffset(std::ostream & ost, int32_t const offset, bool const space) { if (offset == 0) @@ -75,7 +76,7 @@ void PrintOffset(std::ostream & ost, int32_t const offset, bool const space) class StreamFlagsKeeper { public: - StreamFlagsKeeper(std::ostream & ost): + explicit StreamFlagsKeeper(std::ostream & ost): m_ost(ost), m_flags(m_ost.flags()) { @@ -93,23 +94,206 @@ class StreamFlagsKeeper void PrintPaddedNumber(std::ostream & ost, uint32_t const number, uint32_t const padding = 1) { - StreamFlagsKeeper keeper{ost}; + 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 { -Time::Time(THours const hours) +// HourMinutes ------------------------------------------------------------------------------------- +HourMinutes::HourMinutes(THours const duration) { - SetHours(hours); + SetDuration(duration); } -Time::Time(TMinutes const minutes) +HourMinutes::HourMinutes(TMinutes const duration) { - SetMinutes(minutes); + SetDuration(duration); +} + +bool HourMinutes::IsEmpty() const +{ + return m_empty; +} + +HourMinutes::THours HourMinutes::GetHours() const +{ + return m_hours; +} + +HourMinutes::TMinutes HourMinutes::GetMinutes() const +{ + return m_minutes; +} + +HourMinutes::TMinutes HourMinutes::GetDuration() const +{ + return GetMinutes() + GetHours(); +} + +HourMinutes::THours::rep HourMinutes::GetHoursCount() const +{ + return GetHours().count(); +} + +HourMinutes::TMinutes::rep HourMinutes::GetMinutesCount() const +{ + return GetMinutes().count(); +} + +HourMinutes::TMinutes::rep HourMinutes::GetDurationCount() const +{ + return GetDuration().count(); +} + +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(duration)); + SetMinutes(duration - GetHours()); +} + +void HourMinutes::AddDuration(TMinutes const duration) +{ + SetDuration(GetDuration() + duration); +} + +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 --------------------------------------------------------------------------------------- +TimeEvent::TimeEvent(Event const event): m_event(event) {} + +bool TimeEvent::IsEmpty() const +{ + return m_event == Event::None; +} + +bool TimeEvent::HasOffset() const +{ + return !m_offset.IsEmpty(); +} + +TimeEvent::Event TimeEvent::GetEvent() const +{ + return m_event; +} + +void TimeEvent::SetEvent(TimeEvent::Event const event) +{ + m_event = event; +} + +HourMinutes const & TimeEvent::GetOffset() const +{ + return m_offset; +} + +void TimeEvent::SetOffset(HourMinutes const & offset) +{ + m_offset = offset; +} + +void TimeEvent::AddDurationToOffset(HourMinutes::TMinutes const duration) +{ + m_offset.AddDuration(duration); +} + +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"; + 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::Time(HourMinutes const & hm) +{ + SetHourMinutes(hm); +} + +Time::Time(TimeEvent const & te) +{ + SetEvent(te); +} + +Time::Type Time::GetType() const +{ + return m_type; } Time::THours::rep Time::GetHoursCount() const @@ -125,58 +309,58 @@ Time::TMinutes::rep Time::GetMinutesCount() const Time::THours Time::GetHours() const { if (IsEvent()) - return GetEventTime().GetHours(); - else if (IsEventOffset()) - return (GetEventTime() - *this).GetHours(); - return std::chrono::duration_cast(m_duration); + return GetEvent().GetEventTime().GetHours(); + return GetHourMinutes().GetHours(); } Time::TMinutes Time::GetMinutes() const { if (IsEvent()) - return GetEventTime().GetMinutes(); - else if (IsEventOffset()) - return (GetEventTime() - *this).GetMinutes(); - return std::chrono::duration_cast(m_duration) - GetHours(); + return GetEvent().GetEventTime().GetMinutes(); + return GetHourMinutes().GetMinutes(); } -void Time::SetHours(THours const hours) +void Time::AddDuration(TMinutes const duration) { - m_state |= HaveHours | HaveMinutes; - m_duration = hours; + if (IsEvent()) + { + m_event.AddDurationToOffset(duration); + } + else if (IsHoursMinutes()) + { + m_hourMinutes.AddDuration(duration); + } + else + { + // Undefined behaviour. + } } -void Time::SetMinutes(TMinutes const minutes) +TimeEvent const & Time::GetEvent() const { - m_state |= HaveMinutes; - m_duration = minutes; - if (m_duration > 1_h || m_duration < -1_h) - m_state |= HaveHours; + return m_event; } -void Time::SetEvent(Event const event) +void Time::SetEvent(TimeEvent const & event) { + m_type = Type::Event; m_event = event; } -bool Time::IsEvent() const +HourMinutes const & Time::GetHourMinutes() const { - return GetEvent() != Event::NotEvent; + return m_hourMinutes; } -bool Time::IsEventOffset() const +void Time::SetHourMinutes(HourMinutes const & hm) { - return IsEvent() && m_state != IsNotTime; + m_type = Type::HourMinutes; + m_hourMinutes = hm; } -bool Time::IsHoursMinutes() const +bool Time::IsEmpty() const { - return !IsEvent() && ((m_state & HaveHours) && (m_state & HaveMinutes)); -} - -bool Time::IsMinutes() const -{ - return !IsEvent() && ((m_state & HaveMinutes) && !(m_state & HaveHours)); + return GetType() == Type::None; } bool Time::IsTime() const @@ -184,96 +368,87 @@ bool Time::IsTime() const return IsHoursMinutes() || IsEvent(); } -bool Time::HasValue() const +bool Time::IsEvent() const { - return IsEvent() || IsTime() || IsMinutes(); + return GetType() == Type::Event; } -Time Time::operator+(Time const & t) +bool Time::IsHoursMinutes() const { - Time result = *this; - result.SetMinutes(m_duration + t.m_duration); - return result; -} - -Time Time::operator-(Time const & t) -{ - Time result = *this; - result.SetMinutes(m_duration - t.m_duration); - return result; -} - -Time & Time::operator-() -{ - m_duration = -m_duration; - return *this; -} - -Time Time::GetEventTime() const {return {};}; // TODO(mgsergio): get real time - -std::ostream & operator<<(std::ostream & ost, Time::Event const event) -{ - switch (event) - { - case Time::Event::NotEvent: - ost << "NotEvent"; - break; - case Time::Event::Sunrise: - ost << "sunrise"; - break; - case Time::Event::Sunset: - ost << "sunset"; - break; - case Time::Event::Dawn: - ost << "dawn"; - break; - case Time::Event::Dusk: - ost << "dusk"; - break; - } - return ost; + return GetType() == Type::HourMinutes; } std::ostream & operator<<(std::ostream & ost, Time const & time) { - if (!time.HasValue()) + if (time.IsEmpty()) { ost << "hh:mm"; return ost; } - auto const minutes = time.GetMinutesCount(); - auto const hours = time.GetHoursCount(); if (time.IsEvent()) - { - if (time.IsEventOffset()) - { - ost << '(' << time.GetEvent(); - if (hours < 0) - ost << '-'; - else - ost << '+'; - PrintPaddedNumber(ost, std::abs(hours), 2); - ost << ':'; - PrintPaddedNumber(ost, std::abs(minutes), 2); - ost << ')'; - } - else - ost << time.GetEvent(); - } - else if (time.IsMinutes()) - PrintPaddedNumber(ost, std::abs(minutes), 2); + ost << time.GetEvent(); else - { - PrintPaddedNumber(ost, std::abs(hours), 2); - ost << ':'; - PrintPaddedNumber(ost, std::abs(minutes), 2); - } + ost << time.GetHourMinutes(); return ost; } +// TimespanPrion ----------------------------------------------------------------------------------- +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) +{ +} + +bool TimespanPeriod::IsEmpty() const +{ + return m_type == Type::None; +} + +bool TimespanPeriod::IsHoursMinutes() const +{ + return m_type == Type::HourMinutes; +} + +bool TimespanPeriod::IsMinutes() const +{ + return m_type == Type::Minutes; +} + +HourMinutes const & TimespanPeriod::GetHourMinutes() const +{ + return m_hourMinutes; +} + +HourMinutes::TMinutes TimespanPeriod::GetMinutes() const +{ + return m_minutes; +} + +HourMinutes::TMinutes::rep TimespanPeriod::GetMinutesCount() const +{ + return GetMinutes().count(); +} + +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; +} + +// Timespan ---------------------------------------------------------------------------------------- bool Timespan::IsEmpty() const { return !HasStart() && !HasEnd(); @@ -286,12 +461,12 @@ bool Timespan::IsOpen() const bool Timespan::HasStart() const { - return GetStart().HasValue(); + return !GetStart().IsEmpty(); } bool Timespan::HasEnd() const { - return GetEnd().HasValue(); + return !GetEnd().IsEmpty(); } bool Timespan::HasPlus() const @@ -301,7 +476,7 @@ bool Timespan::HasPlus() const bool Timespan::HasPeriod() const { - return m_period.HasValue(); + return !m_period.IsEmpty(); } Time const & Timespan::GetStart() const @@ -314,7 +489,7 @@ Time const & Timespan::GetEnd() const return m_end; } -Time const & Timespan::GetPeriod() const +TimespanPeriod const & Timespan::GetPeriod() const { return m_period; } @@ -329,7 +504,7 @@ void Timespan::SetEnd(Time const & end) m_end = end; } -void Timespan::SetPeriod(Time const & period) +void Timespan::SetPeriod(TimespanPeriod const & period) { m_period = period; } @@ -341,7 +516,9 @@ void Timespan::SetPlus(bool const plus) bool Timespan::IsValid() const { - return false; // TODO(mgsergio): implement validator + // TODO(mgsergio): implement validator. + // See https://trello.com/c/e4pbOhDC/24-opening-hours + return false; } std::ostream & operator<<(std::ostream & ost, Timespan const & span) @@ -364,7 +541,7 @@ std::ostream & operator<<(std::ostream & ost, osmoh::TTimespans const & timespan return ost; } - +// NthWeekdayOfTheMonthEntry ----------------------------------------------------------------------- bool NthWeekdayOfTheMonthEntry::IsEmpty() const { return !HasStart() && !HasEnd(); @@ -409,6 +586,7 @@ std::ostream & operator<<(std::ostream & ost, NthWeekdayOfTheMonthEntry const en return ost; } +// WeekdayRange ------------------------------------------------------------------------------------ bool WeekdayRange::HasWday(Weekday const & wday) const { if (IsEmpty() || wday == Weekday::None) @@ -458,13 +636,6 @@ Weekday WeekdayRange::GetEnd() const return m_end; } -size_t WeekdayRange::GetDaysCount() const -{ - if (IsEmpty()) - return 0; - return static_cast(m_start) - static_cast(m_end) + 1; -} - void WeekdayRange::SetStart(Weekday const & wday) { m_start = wday; @@ -526,7 +697,7 @@ std::ostream & operator<<(std::ostream & ost, Weekday const wday) ost << "Sa"; break; case Weekday::None: - ost << "not-a-day"; + ost << "None"; } return ost; } @@ -535,7 +706,9 @@ std::ostream & operator<<(std::ostream & ost, WeekdayRange const & range) { ost << range.GetStart(); if (range.HasEnd()) + { ost << '-' << range.GetEnd(); + } else { if (range.HasNth()) @@ -555,7 +728,7 @@ std::ostream & operator<<(std::ostream & ost, TWeekdayRanges const & ranges) return ost; } - +// Holiday ----------------------------------------------------------------------------------------- bool Holiday::IsPlural() const { return m_plural; @@ -579,7 +752,9 @@ void Holiday::SetOffset(int32_t const offset) std::ostream & operator<<(std::ostream & ost, Holiday const & holiday) { if (holiday.IsPlural()) + { ost << "PH"; + } else { ost << "SH"; @@ -594,7 +769,7 @@ std::ostream & operator<<(std::ostream & ost, THolidays const & holidays) return ost; } - +// Weekdays ---------------------------------------------------------------------------------------- bool Weekdays::IsEmpty() const { return GetWeekdayRanges().empty() && GetHolidays().empty(); @@ -649,7 +824,7 @@ std::ostream & operator<<(std::ostream & ost, Weekdays const & weekday) return ost; } - +// DateOffset -------------------------------------------------------------------------------------- bool DateOffset::IsEmpty() const { return !HasOffset() && !HasWDayOffset(); @@ -657,7 +832,7 @@ bool DateOffset::IsEmpty() const bool DateOffset::HasWDayOffset() const { - return m_wday_offset != Weekday::None; + return m_wdayOffest != Weekday::None; } bool DateOffset::HasOffset() const @@ -672,7 +847,7 @@ bool DateOffset::IsWDayOffsetPositive() const Weekday DateOffset::GetWDayOffset() const { - return m_wday_offset; + return m_wdayOffest; } int32_t DateOffset::GetOffset() const @@ -682,7 +857,7 @@ int32_t DateOffset::GetOffset() const void DateOffset::SetWDayOffset(Weekday const wday) { - m_wday_offset = wday; + m_wdayOffest = wday; } void DateOffset::SetOffset(int32_t const offset) @@ -698,13 +873,15 @@ void DateOffset::SetWDayOffsetPositive(bool const on) 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; } - +// MonthDay ---------------------------------------------------------------------------------------- bool MonthDay::IsEmpty() const { return !HasYear() && !HasMonth() && !HasDayNum() && !IsVariable(); @@ -880,11 +1057,13 @@ std::ostream & operator<<(std::ostream & ost, MonthDay const md) } } if (md.HasOffset()) + { ost << ' ' << md.GetOffset(); + } return ost; } - +// MonthdayRange ----------------------------------------------------------------------------------- bool MonthdayRange::IsEmpty() const { return !HasStart() && !HasEnd(); @@ -966,7 +1145,7 @@ std::ostream & operator<<(std::ostream & ost, TMonthdayRanges const & ranges) return ost; } - +// YearRange --------------------------------------------------------------------------------------- bool YearRange::IsEmpty() const { return !HasStart() && !HasEnd(); @@ -1045,7 +1224,10 @@ std::ostream & operator<<(std::ostream & ost, YearRange const range) ost << '/' << range.GetPeriod(); } else if (range.HasPlus()) + { ost << '+'; + } + return ost; } @@ -1055,7 +1237,7 @@ std::ostream & operator<<(std::ostream & ost, TYearRanges const ranges) return ost; } - +// WeekRange --------------------------------------------------------------------------------------- bool WeekRange::IsEmpty() const { return !HasStart() && !HasEnd(); @@ -1134,7 +1316,7 @@ std::ostream & operator<<(std::ostream & ost, TWeekRanges const ranges) return ost; } - +// RuleSequence ------------------------------------------------------------------------------------ bool RuleSequence::IsEmpty() const { return (!HasYears() && !HasMonths() && @@ -1381,4 +1563,36 @@ std::ostream & operator<<(std::ostream & ost, TRuleSequences const & s) }); 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); +} + +bool OpeningHours::IsValid() const +{ + return m_valid; +} } // namespace osmoh diff --git a/3party/opening_hours/opening_hours.hpp b/3party/opening_hours/opening_hours.hpp index dc64049d70..786773ee86 100644 --- a/3party/opening_hours/opening_hours.hpp +++ b/3party/opening_hours/opening_hours.hpp @@ -24,45 +24,102 @@ #pragma once -#include -#include -#include #include +#include +#include #include +#include namespace osmoh { -class Time +class HourMinutes { - enum State - { - IsNotTime = 0, - HaveHours = 1, - HaveMinutes = 2, - }; - - using TStateRep = std::underlying_type::type; - public: - enum class Event - { - NotEvent, - Dawn, - Sunrise, - Sunset, - Dusk - }; - using THours = std::chrono::hours; using TMinutes = std::chrono::minutes; - Time() = default; - Time(Time const &) = default; - Time(THours const hours); - Time(TMinutes const minutes); + HourMinutes() = default; + explicit HourMinutes(THours const duration); + explicit HourMinutes(TMinutes const duration); - Time & operator=(Time const &) = default; + bool IsEmpty() const; + + THours GetHours() const; + TMinutes GetMinutes() const; + TMinutes GetDuration() const; + + THours::rep GetHoursCount() const; + TMinutes::rep GetMinutesCount() const; + TMinutes::rep GetDurationCount() const; + + void SetHours(THours const hours); + void SetMinutes(TMinutes const minutes); + void SetDuration(TMinutes const duration); + + void AddDuration(TMinutes const 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); + +class Time; + +class TimeEvent +{ +public: + enum class Event + { + None, + Sunrise, + Sunset + }; + + TimeEvent() = default; + TimeEvent(Event const event); + + bool IsEmpty() const; + bool HasOffset() const; + + Event GetEvent() const; + void SetEvent(Event const event); + + HourMinutes const & GetOffset() const; + void SetOffset(HourMinutes const & offset); + void AddDurationToOffset(HourMinutes::TMinutes const 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); + Time(TimeEvent const & te); + + Type GetType() const; THours::rep GetHoursCount() const; TMinutes::rep GetMinutesCount() const; @@ -70,29 +127,24 @@ public: THours GetHours() const; TMinutes GetMinutes() const; - void SetHours(THours const hours); - void SetMinutes(TMinutes const minutes); + void AddDuration(TMinutes const duration); - Event GetEvent() const {return m_event;} - void SetEvent(Event const event); + TimeEvent const & GetEvent() const; + void SetEvent(TimeEvent const & event); - bool IsEvent() const; - bool IsEventOffset() const; - bool IsHoursMinutes() const; - bool IsMinutes() const; + HourMinutes const & GetHourMinutes() const; + void SetHourMinutes(HourMinutes const & hm); + + bool IsEmpty() const; bool IsTime() const; - bool HasValue() const; + bool IsEvent() const; + bool IsHoursMinutes() const; - Time operator+(Time const & t); - Time operator-(Time const & t); - Time & operator-(); + private: + HourMinutes m_hourMinutes; + TimeEvent m_event; -private: - Time GetEventTime() const; - - Event m_event{Event::NotEvent}; - TMinutes m_duration{TMinutes::zero()}; - TStateRep m_state{State::IsNotTime}; + Type m_type = Type::None; }; inline constexpr Time::THours operator ""_h(unsigned long long int h) @@ -105,18 +157,42 @@ inline constexpr Time::TMinutes operator ""_min(unsigned long long int m) return Time::TMinutes(m); } -std::ostream & operator<<(std::ostream & ost, Time::Event const event); std::ostream & operator<<(std::ostream & ost, Time const & time); +class TimespanPeriod +{ +public: + enum class Type + { + None, + Minutes, + HourMinutes + }; + + TimespanPeriod() = default; + TimespanPeriod(HourMinutes const & hm); + TimespanPeriod(HourMinutes::TMinutes const minutes); + + bool IsEmpty() const; + bool IsHoursMinutes() const; + bool IsMinutes() const; + + HourMinutes const & GetHourMinutes() const; + HourMinutes::TMinutes GetMinutes() const; + HourMinutes::TMinutes::rep GetMinutesCount() const; + +private: + HourMinutes::TMinutes m_minutes; + HourMinutes m_hourMinutes; + + Type m_type = Type::None; +}; + +std::ostream & operator<<(std::ostream & ost, TimespanPeriod const p); + class Timespan { public: - Timespan() = default; - Timespan(Timespan const &) = default; - Timespan(Time const & start, bool plus = false); - Timespan(Time const & start, Time const & end, bool plus = false); - Timespan(Time const & start, Time const & end, Time const & period); - bool IsEmpty() const; bool IsOpen() const; bool HasStart() const; @@ -126,11 +202,11 @@ public: Time const & GetStart() const; Time const & GetEnd() const; - Time const & GetPeriod() const; + TimespanPeriod const & GetPeriod() const; void SetStart(Time const & start); void SetEnd(Time const & end); - void SetPeriod(Time const & period); + void SetPeriod(TimespanPeriod const & period); void SetPlus(bool const plus); bool IsValid() const; @@ -138,8 +214,8 @@ public: private: Time m_start; Time m_end; - Time m_period; - bool m_plus{false}; + TimespanPeriod m_period; + bool m_plus = false; }; using TTimespans = std::vector; @@ -171,8 +247,8 @@ public: void SetEnd(NthDayOfTheMonth const e); private: - NthDayOfTheMonth m_start{}; - NthDayOfTheMonth m_end{}; + NthDayOfTheMonth m_start = NthDayOfTheMonth::None; + NthDayOfTheMonth m_end = NthDayOfTheMonth::None; }; std::ostream & operator<<(std::ostream & ost, NthWeekdayOfTheMonthEntry const entry); @@ -227,7 +303,6 @@ public: Weekday GetStart() const; Weekday GetEnd() const; - size_t GetDaysCount() const; void SetStart(Weekday const & wday); void SetEnd(Weekday const & wday); @@ -241,9 +316,9 @@ public: void AddNth(NthWeekdayOfTheMonthEntry const & entry); private: - Weekday m_start{}; - Weekday m_end{}; - int32_t m_offset{}; + Weekday m_start = Weekday::None; + Weekday m_end = Weekday::None; + int32_t m_offset = 0; TNths m_nths; }; @@ -262,8 +337,8 @@ public: void SetOffset(int32_t const offset); private: - bool m_plural{false}; - int32_t m_offset{}; + bool m_plural = false; + int32_t m_offset = 0; }; using THolidays = std::vector; @@ -312,9 +387,9 @@ public: void SetWDayOffsetPositive(bool const on); private: - Weekday m_wday_offset{Weekday::None}; - bool m_positive{true}; - int32_t m_offset{}; + Weekday m_wdayOffest = Weekday::None; + bool m_positive = true; + int32_t m_offset = 0; }; std::ostream & operator<<(std::ostream & ost, DateOffset const & offset); @@ -369,11 +444,11 @@ public: void SetVariableDate(VariableDate const date); private: - TYear m_year{}; - Month m_month{Month::None}; - TDayNum m_daynum{}; - VariableDate m_variable_date{VariableDate::None}; - DateOffset m_offset{}; + 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 const month) @@ -415,8 +490,8 @@ public: private: MonthDay m_start; MonthDay m_end; - uint32_t m_period{}; - bool m_plus{false}; + uint32_t m_period = 0; + bool m_plus = false; }; using TMonthdayRanges = std::vector; @@ -446,10 +521,10 @@ public: void SetPeriod(uint32_t const period); private: - TYear m_start{}; - TYear m_end{}; - bool m_plus{false}; - uint32_t m_period{0}; + TYear m_start = 0; + TYear m_end = 0; + bool m_plus = false; + uint32_t m_period = 0; }; using TYearRanges = std::vector; @@ -477,9 +552,9 @@ public: void SetPeriod(uint32_t const period); private: - TWeek m_start{}; - TWeek m_end{}; - uint32_t m_period{0}; + TWeek m_start = 0; + TWeek m_end = 0; + uint32_t m_period = 0; }; using TWeekRanges = std::vector; @@ -539,8 +614,6 @@ public: void SetModifier(Modifier const modifier); private: - void dump() const; - bool m_twentyFourHours{false}; TYearRanges m_years; @@ -551,10 +624,10 @@ private: TTimespans m_times; std::string m_comment; - std::string m_anySeparator{";"}; - bool m_separatorForReadability{false}; + std::string m_anySeparator = ";"; + bool m_separatorForReadability = false; - Modifier m_modifier{Modifier::DefaultOpen}; + Modifier m_modifier = Modifier::DefaultOpen; std::string m_modifierComment; }; @@ -563,4 +636,21 @@ using TRuleSequences = std::vector; 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); + +class OpeningHours +{ +public: + 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; + + bool IsValid() const; + +private: + TRuleSequences m_rule; + bool const m_valid; +}; } // namespace osmoh diff --git a/3party/opening_hours/opening_hours.pro b/3party/opening_hours/opening_hours.pro index aa2b5c01fd..698657919c 100644 --- a/3party/opening_hours/opening_hours.pro +++ b/3party/opening_hours/opening_hours.pro @@ -13,12 +13,12 @@ ROOT_DIR = ../.. include($$ROOT_DIR/common.pri) -HEADERS += rules_evaluation.hpp \ - rules_evaluation_private.hpp \ +HEADERS += opening_hours.hpp \ opening_hours_parsers.hpp \ opening_hours_parsers_terminals.hpp \ - opening_hours.hpp \ - parse_opening_hours.hpp + parse_opening_hours.hpp \ + rules_evaluation_private.hpp \ + rules_evaluation.hpp SOURCES += rules_evaluation.cpp \ opening_hours.cpp \ diff --git a/3party/opening_hours/opening_hours_integration_tests/opening_hours_integration_tests.cpp b/3party/opening_hours/opening_hours_integration_tests/opening_hours_integration_tests.cpp index 3b363e0c2d..f76d6483e1 100644 --- a/3party/opening_hours/opening_hours_integration_tests/opening_hours_integration_tests.cpp +++ b/3party/opening_hours/opening_hours_integration_tests/opening_hours_integration_tests.cpp @@ -35,14 +35,19 @@ bool HasPlus(std::vector const & v) return std::any_of(begin(v), end(v), hasPlus); } -bool HasEHours(osmoh::TTimespans const & spans) +bool HasExtendedHours(osmoh::TTimespans const & spans) { - auto const hasEHours = [](osmoh::Timespan const & s) -> bool { + auto const hasExtendedHours = [](osmoh::Timespan const & s) -> bool + { if (!s.HasEnd()) return false; - return s.GetEnd().GetMinutes() + s.GetEnd().GetHours() > 24 * std::chrono::minutes(60); + + 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), hasEHours); + return std::any_of(begin(spans), end(spans), hasExtendedHours); } bool HasOffset(osmoh::TMonthdayRanges const & mr) @@ -64,40 +69,41 @@ bool HasOffset(osmoh::Weekdays const & wd) template bool CompareNormalized(std::string const & original, ParserResult const & pretendent) { - auto original_copy = original; - auto pretendent_copy = ToString(pretendent); + auto originalCopy = original; + auto pretendentCopy = ToString(pretendent); - boost::to_lower(original_copy); - boost::to_lower(pretendent_copy); + boost::to_lower(originalCopy); + boost::to_lower(pretendentCopy); - boost::replace_all(original_copy, "off", "closed"); + boost::replace_all(originalCopy, "off", "closed"); - boost::replace_all(original_copy, " ", ""); - boost::replace_all(pretendent_copy, " ", ""); + boost::replace_all(originalCopy, " ", ""); + boost::replace_all(pretendentCopy, " ", ""); - return pretendent_copy == original_copy; + return pretendentCopy == originalCopy; } enum { Parsed, - Unparsed, + Serialised, Period, Plus, - Ehours, - Offset + // 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_ }; -using TRuleFeatures = std::array; +using TRuleFeatures = std::array; std::ostream & operator<<(std::ostream & ost, TRuleFeatures const & f) { - ost << f[Parsed] << '\t' - << f[Unparsed] << '\t' - << f[Period] << '\t' - << f[Plus] << '\t' - << f[Ehours] << '\t' - << f[Offset] << '\t'; - return ost; + std::copy(begin(f), end(f), std::ostream_iterator(ost, "\t")); + return ost; } TRuleFeatures DescribeRule(osmoh::TRuleSequences const & rule) @@ -117,7 +123,7 @@ TRuleFeatures DescribeRule(osmoh::TRuleSequences const & rule) features[Offset] |= HasOffset(r.GetMonths()); features[Offset] |= HasOffset(r.GetWeekdays()); - features[Ehours] |= HasEHours(r.GetTimes()); + features[ExtendedHours] |= HasExtendedHours(r.GetTimes()); } return features; @@ -156,8 +162,8 @@ BOOST_AUTO_TEST_CASE(OpeningHours_CountFailed) } else { - count = std::stol(line.substr(0,d)); - datastr = line.substr(d+1); + count = std::stol(line.substr(0, d)); + datastr = line.substr(d + 1); } line_num++; @@ -169,21 +175,21 @@ BOOST_AUTO_TEST_CASE(OpeningHours_CountFailed) if (isParsed) features = DescribeRule(rule); features[Parsed] = true; - features[Unparsed] = true; + features[Serialised] = true; if (!isParsed) { num_failed += count; ++hist[count]; features[Parsed] = false; - features[Unparsed] = false; + features[Serialised] = false; BOOST_TEST_MESSAGE("-- " << count << " :[" << datastr << "]"); } else if (!CompareNormalized(datastr, rule)) { num_failed += count; ++hist[count]; - features[Unparsed] = false; + features[Serialised] = false; BOOST_TEST_MESSAGE("- " << count << " :[" << datastr << "]"); BOOST_TEST_MESSAGE("+ " << count << " :[" << ToString(rule) << "]"); } @@ -206,7 +212,7 @@ BOOST_AUTO_TEST_CASE(OpeningHours_CountFailed) } { std::stringstream message; - message << "Parsed\tUnparsed\tPeriod\tPlus\tEhours\tOffset\tCount\n"; + message << "Parsed\tSerialised\tPeriod\tPlus\tExtendedHours\tOffset\tCount" << std::endl; for (auto const & e : featuresDistrib) message << e.first << '\t' << e.second << std::endl; diff --git a/3party/opening_hours/opening_hours_parsers.hpp b/3party/opening_hours/opening_hours_parsers.hpp index 53378afd43..d1e0618ead 100644 --- a/3party/opening_hours/opening_hours_parsers.hpp +++ b/3party/opening_hours/opening_hours_parsers.hpp @@ -4,13 +4,13 @@ // #define BOOST_SPIRIT_DEBUG #define BOOST_SPIRIT_USE_PHOENIX_V3 -#include -#include -#include -#include -#include #include +#include #include +#include +#include +#include +#include #if defined(__clang__) #pragma clang diagnostic push @@ -29,20 +29,6 @@ namespace osmoh { namespace phx = boost::phoenix; -class test_impl -{ -public: - template - struct result { typedef void type; }; - - template - void operator() (const Arg & a) const - { - std::cout << a << " \t(" << typeid(a).name() << ")" << std::endl; - } -}; -phx::function const test = test_impl(); - namespace parsing { namespace qi = boost::spirit::qi; @@ -309,9 +295,9 @@ template class time_selector : public qi::grammar { protected: - qi::rule hour_minutes; - qi::rule extended_hour_minutes; - qi::rule variable_time; + qi::rule hour_minutes; + qi::rule extended_hour_minutes; + qi::rule variable_time; qi::rule extended_time; qi::rule time; qi::rule timespan; @@ -326,37 +312,42 @@ public: using qi::_3; using qi::_a; using qi::_val; - using qi::eps; using qi::lit; using charset::char_; using boost::phoenix::bind; using boost::phoenix::construct; + using osmoh::HourMinutes; + using osmoh::TimeEvent; using osmoh::Time; using osmoh::Timespan; hour_minutes = - (hours >> lit(':') >> minutes) [bind(&Time::SetHours, _val, _1), - _val = _val + _2] + (hours >> lit(':') >> minutes) [bind(&HourMinutes::AddDuration, _val, _1), + bind(&HourMinutes::AddDuration, _val, _2)] ; extended_hour_minutes = - (exthours >> lit(':') >> minutes)[bind(&Time::SetHours, _val, _1), - _val = _val + _2] + (exthours >> lit(':') >> minutes)[bind(&HourMinutes::AddDuration, _val, _1), + bind(&HourMinutes::AddDuration, _val, _2)] ; - variable_time = eps [phx::bind(&Time::SetHours, _val, 0_h)] >> - (lit('(') - >> charset::no_case[event][bind(&Time::SetEvent, _val, _1)] - >> ( (lit('+') >> hour_minutes) [_val = _val + _1] - | (lit('-') >> hour_minutes) [_val = _val - _1] ) - >> lit(')') - ) - | charset::no_case[event][bind(&Time::SetEvent, _val, _1)] + 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 | variable_time; + extended_time = extended_hour_minutes [bind(&Time::SetHourMinutes, _val, _1)] + | variable_time [bind(&Time::SetEvent, _val, _1)] + ; - time %= hour_minutes | variable_time; + time = hour_minutes [bind(&Time::SetHourMinutes, _val, _1)] + | variable_time [bind(&Time::SetEvent, _val, _1)] + ; timespan = (time >> dash >> extended_time >> '/' >> hour_minutes) @@ -494,3 +485,4 @@ public: }; } // namespace parsing } // namespace osmoh +#undef BOOST_SPIRIT_USE_PHOENIX_V3 diff --git a/3party/opening_hours/opening_hours_parsers_terminals.hpp b/3party/opening_hours/opening_hours_parsers_terminals.hpp index 8c5326ebb8..dfeea50d5a 100644 --- a/3party/opening_hours/opening_hours_parsers_terminals.hpp +++ b/3party/opening_hours/opening_hours_parsers_terminals.hpp @@ -1,4 +1,8 @@ +// This header file should be used only with opening_hours_parsers.hpp. +// It's only purpose is to avoid polution opening_hours_parsers.hpp with +// it's content. + namespace osmoh { namespace parsing @@ -17,15 +21,15 @@ struct dash_ : public qi::symbols } } dash; -struct event_ : public qi::symbols +struct event_ : public qi::symbols { event_() { add - ("dawn", osmoh::Time::Event::Sunrise) - ("sunrise", osmoh::Time::Event::Sunrise) - ("sunset", osmoh::Time::Event::Sunset) - ("dusk", osmoh::Time::Event::Sunset) + ("dawn", osmoh::TimeEvent::Event::Sunrise) + ("sunrise", osmoh::TimeEvent::Event::Sunrise) + ("sunset", osmoh::TimeEvent::Event::Sunset) + ("dusk", osmoh::TimeEvent::Event::Sunset) ; } } event; diff --git a/3party/opening_hours/opening_hours_supported_features_tests/opening_hours_supported_features_tests.cpp b/3party/opening_hours/opening_hours_supported_features_tests/opening_hours_supported_features_tests.cpp index 453051c63e..9187338737 100644 --- a/3party/opening_hours/opening_hours_supported_features_tests/opening_hours_supported_features_tests.cpp +++ b/3party/opening_hours/opening_hours_supported_features_tests/opening_hours_supported_features_tests.cpp @@ -48,15 +48,15 @@ void TestRanges(std::string const & name, std::initializer_list con if (!checkOpen) std::cout << "Checking unknown " << input << std::endl; if (std::get<0>(lRange) - 60 >= std::get<0>(ltr) && - !GetState(rule, std::get<0>(lRange) - 60).IsClosed()) + !IsClosed(rule, std::get<0>(lRange) - 60)) { failed = true; BOOST_CHECK_MESSAGE(false, name << " [" << input << "] not closed before " << range[0]); } else if (std::get<0>(lRange) + 60 <= std::get<1>(ltr) && (checkOpen - ? !GetState(rule, std::get<0>(lRange) + 60).IsOpen() - : !GetState(rule, std::get<0>(lRange) + 60).IsUnknown())) + ? !IsOpen(rule, std::get<0>(lRange) + 60) + : !IsUnknown(rule, std::get<0>(lRange) + 60))) { failed = true; BOOST_CHECK_MESSAGE(false, name << " [" << input << "] not " << (checkOpen ? "open" : "unknown") << " after " << range[0]); @@ -72,14 +72,14 @@ void TestRanges(std::string const & name, std::initializer_list con // } else if (std::get<1>(lRange) - 60 >= std::get<0>(ltr) && (checkOpen - ? !GetState(rule, std::get<1>(lRange) - 60).IsOpen() - : !GetState(rule, std::get<1>(lRange) - 60).IsUnknown())) + ? !IsOpen(rule, std::get<1>(lRange) - 60) + : !IsUnknown(rule, std::get<1>(lRange) - 60))) { failed = true; BOOST_CHECK_MESSAGE(false, name << " [" << input << "] not " << (checkOpen ? "open" : "unknown") << " before " << range[1]); } else if (std::get<1>(lRange) + 60 <= std::get<1>(ltr) && - !GetState(rule, std::get<1>(lRange) + 60).IsClosed()) + !IsClosed(rule, std::get<1>(lRange) + 60)) { failed = true; BOOST_CHECK_MESSAGE(false, name << " [" << input << "] not closed after " << range[1]); diff --git a/3party/opening_hours/opening_hours_tests/opening_hours_tests.cpp b/3party/opening_hours/opening_hours_tests/opening_hours_tests.cpp index 091d2e8033..28605ba789 100644 --- a/3party/opening_hours/opening_hours_tests/opening_hours_tests.cpp +++ b/3party/opening_hours/opening_hours_tests/opening_hours_tests.cpp @@ -87,7 +87,7 @@ bool GetTimeTuple(std::string const & strTime, std::string const & fmt, std::tm struct GetTimeError: std::exception { - GetTimeError(std::string const & message): m_message(message) { } + GetTimeError(std::string const & message): m_message(message) {} char const * what() const noexcept override { return m_message.data(); @@ -98,7 +98,7 @@ struct GetTimeError: std::exception osmoh::RuleState GetRulesState(osmoh::TRuleSequences const & rules, std::string const & dateTime) { static auto const & fmt = "%Y-%m-%d %H:%M"; - std::tm time{}; + std::tm time = {}; if (!GetTimeTuple(dateTime, fmt, time)) throw GetTimeError{"Can't parse " + dateTime + " against " + fmt}; @@ -112,114 +112,136 @@ osmoh::RuleState GetRulesState(osmoh::TRuleSequences const & rules, std::string bool IsOpen(osmoh::TRuleSequences const & rules, std::string const & dateTime) { - return GetRulesState(rules, dateTime).IsOpen(); + return GetRulesState(rules, dateTime) == osmoh::RuleState::Open; } bool IsClosed(osmoh::TRuleSequences const & rules, std::string const & dateTime) { - return GetRulesState(rules, dateTime).IsClosed(); + return GetRulesState(rules, dateTime) == osmoh::RuleState::Closed; } bool IsUnknown(osmoh::TRuleSequences const & rules, std::string const & dateTime) { - return GetRulesState(rules, dateTime).IsUnknown(); + return GetRulesState(rules, dateTime) == osmoh::RuleState::Unknown; } } // namespace +BOOST_AUTO_TEST_CASE(OpeningHours_TestHourMinutes) +{ + using namespace osmoh; + + { + BOOST_CHECK(HourMinutes().IsEmpty()); + BOOST_CHECK_EQUAL(ToString(HourMinutes()), "hh:mm"); + } + { + HourMinutes hm(10_min); + BOOST_CHECK(!hm.IsEmpty()); + BOOST_CHECK_EQUAL(ToString(hm), "00:10"); + } + { + HourMinutes hm{100_min}; + BOOST_CHECK(!hm.IsEmpty()); + BOOST_CHECK_EQUAL(hm.GetHoursCount(), 1); + BOOST_CHECK_EQUAL(hm.GetMinutesCount(), 40); + + BOOST_CHECK_EQUAL(ToString(hm), "01:40"); + } + { + HourMinutes hm{}; + hm.SetHours(22_h); + hm.SetMinutes(15_min); + BOOST_CHECK(!hm.IsEmpty()); + + BOOST_CHECK_EQUAL(hm.GetHoursCount(), 22); + BOOST_CHECK_EQUAL(hm.GetMinutesCount(), 15); + + BOOST_CHECK_EQUAL(ToString(hm), "22:15"); + } +} + +BOOST_AUTO_TEST_CASE(OpeningHours_TestTimeEvent) +{ + using namespace osmoh; + + { + BOOST_CHECK(TimeEvent().IsEmpty()); + BOOST_CHECK(!TimeEvent().HasOffset()); + } + { + TimeEvent te(TimeEvent::Event::Sunrise); + BOOST_CHECK(!te.IsEmpty()); + BOOST_CHECK(!te.HasOffset()); + + BOOST_CHECK_EQUAL(ToString(te), "sunrise"); + } + { + TimeEvent te(TimeEvent::Event::Sunset); + BOOST_CHECK(!te.IsEmpty()); + BOOST_CHECK(!te.HasOffset()); + + BOOST_CHECK_EQUAL(ToString(te), "sunset"); + } + { + TimeEvent te(TimeEvent::Event::Sunrise); + te.SetOffset(-HourMinutes(100_min)); + BOOST_CHECK(!te.IsEmpty()); + BOOST_CHECK(te.HasOffset()); + + BOOST_CHECK_EQUAL(ToString(te), "(sunrise-01:40)"); + + te.SetOffset(HourMinutes(100_min)); + BOOST_CHECK_EQUAL(ToString(te), "(sunrise+01:40)"); + } +} + BOOST_AUTO_TEST_CASE(OpeningHours_TestTime) { using namespace osmoh; { - BOOST_CHECK(!Time{}.HasValue()); - BOOST_CHECK_EQUAL(ToString(Time{}), "hh:mm"); + BOOST_CHECK(Time().IsEmpty()); + BOOST_CHECK(!Time().IsHoursMinutes()); + BOOST_CHECK(!Time().IsTime()); + BOOST_CHECK(!Time().IsEvent()); } { - Time time{10_min}; - BOOST_CHECK(time.HasValue()); - BOOST_CHECK(!time.IsHoursMinutes()); - BOOST_CHECK(!time.IsTime()); - BOOST_CHECK(time.IsMinutes()); - BOOST_CHECK(!time.IsEvent()); - BOOST_CHECK(!time.IsEventOffset()); - - BOOST_CHECK_EQUAL(ToString(time), "10"); - } - { - Time time{100_min}; - BOOST_CHECK(time.HasValue()); - BOOST_CHECK(time.IsHoursMinutes()); - BOOST_CHECK(time.IsTime()); - BOOST_CHECK(!time.IsMinutes()); - BOOST_CHECK(!time.IsEvent()); - BOOST_CHECK(!time.IsEventOffset()); - - BOOST_CHECK_EQUAL(time.GetHoursCount(), 1); - BOOST_CHECK_EQUAL(time.GetMinutesCount(), 40); - - BOOST_CHECK_EQUAL(ToString(time), "01:40"); - } - { - Time time{}; - time.SetHours(22_h); - time = time + 15_min; - BOOST_CHECK(time.HasValue()); - BOOST_CHECK(time.IsHoursMinutes()); - BOOST_CHECK(time.IsTime()); - BOOST_CHECK(!time.IsMinutes()); - BOOST_CHECK(!time.IsEvent()); - BOOST_CHECK(!time.IsEventOffset()); - - BOOST_CHECK_EQUAL(time.GetHoursCount(), 22); - BOOST_CHECK_EQUAL(time.GetMinutesCount(), 15); - - BOOST_CHECK_EQUAL(ToString(time), "22:15"); - } - { - Time time{}; - time.SetEvent(Time::Event::Sunrise); - BOOST_CHECK(time.HasValue()); + Time time; + time.SetEvent(TimeEvent::Event::Sunrise); + BOOST_CHECK(!time.IsEmpty()); BOOST_CHECK(!time.IsHoursMinutes()); BOOST_CHECK(time.IsTime()); - BOOST_CHECK(!time.IsMinutes()); BOOST_CHECK(time.IsEvent()); - BOOST_CHECK(!time.IsEventOffset()); BOOST_CHECK_EQUAL(ToString(time), "sunrise"); - time = time - 90_min; - BOOST_CHECK(time.IsEventOffset()); + time.AddDuration(-90_min); BOOST_CHECK_EQUAL(ToString(time), "(sunrise-01:30)"); } { Time time{}; - time.SetEvent(Time::Event::Sunrise); - time.SetHours(22_h); - time.SetMinutes(15_min); - BOOST_CHECK(time.HasValue()); - BOOST_CHECK(!time.IsHoursMinutes()); + time.SetHourMinutes(HourMinutes(22_h + 5_min)); + BOOST_CHECK(!time.IsEmpty()); + BOOST_CHECK(time.IsHoursMinutes()); BOOST_CHECK(time.IsTime()); - BOOST_CHECK(!time.IsMinutes()); - BOOST_CHECK(time.IsEvent()); - BOOST_CHECK(time.IsEventOffset()); + BOOST_CHECK(!time.IsEvent()); + + BOOST_CHECK_EQUAL(ToString(time), "22:05"); + + time.AddDuration(10_min); + BOOST_CHECK_EQUAL(ToString(time), "22:15"); } { - Time time{10_min}; - BOOST_CHECK_EQUAL((-time).GetMinutesCount(), -10); - BOOST_CHECK_EQUAL(ToString(-time), "10"); + Time time{}; + time.SetHourMinutes(HourMinutes(22_h + 5_min)); + + time.SetEvent(TimeEvent::Event::Sunset); + BOOST_CHECK_EQUAL(ToString(time), "sunset"); } { - Time t1{2_h}; - Time t2{100_min}; - Time t3 = t1 - t2; - BOOST_CHECK_EQUAL(t3.GetHoursCount(), 0); - BOOST_CHECK_EQUAL(t3.GetMinutesCount(), 20); - } - { - Time time {27_h + 30_min}; + Time time(HourMinutes(27_h + 30_min)); BOOST_CHECK_EQUAL(ToString(time), "27:30"); } - // TODO(mgsergio): more tests with event and get hours/minutes } BOOST_AUTO_TEST_CASE(OpeningHours_TestTimespan) @@ -233,12 +255,12 @@ BOOST_AUTO_TEST_CASE(OpeningHours_TestTimespan) BOOST_CHECK(!span.HasEnd()); BOOST_CHECK_EQUAL(ToString(span), "hh:mm-hh:mm"); - span.SetStart(10_h); + span.SetStart(HourMinutes(10_h)); BOOST_CHECK(span.HasStart()); BOOST_CHECK(span.IsOpen()); BOOST_CHECK_EQUAL(ToString(span), "10:00"); - span.SetEnd(12_h); + span.SetEnd(HourMinutes(12_h)); BOOST_CHECK(span.HasEnd()); BOOST_CHECK(!span.IsOpen()); BOOST_CHECK_EQUAL(ToString(span), "10:00-12:00"); @@ -622,26 +644,32 @@ BOOST_AUTO_TEST_CASE(OpeningHoursTimerange_TestParseUnparse) { auto const rule = "dusk"; auto const parsedUnparsed = ParseAndUnparse(rule); + BOOST_CHECK_EQUAL(parsedUnparsed, "sunset"); } { auto const rule = "dawn+"; auto const parsedUnparsed = ParseAndUnparse(rule); + BOOST_CHECK_EQUAL(parsedUnparsed, "sunrise+"); } { auto const rule = "sunrise-sunset"; auto const parsedUnparsed = ParseAndUnparse(rule); + BOOST_CHECK_EQUAL(parsedUnparsed, rule); } { - auto const rule = "(dusk-12:12)"; + auto const rule = "(sunset-12:12)"; auto const parsedUnparsed = ParseAndUnparse(rule); + BOOST_CHECK_EQUAL(parsedUnparsed, rule); } { auto const rule = "(dusk-12:12)+"; auto const parsedUnparsed = ParseAndUnparse(rule); + BOOST_CHECK_EQUAL(parsedUnparsed, "(sunset-12:12)+"); } { - auto const rule = "(dusk-12:12)-sunset"; + auto const rule = "(sunrise-12:12)-sunset"; auto const parsedUnparsed = ParseAndUnparse(rule); + BOOST_CHECK_EQUAL(parsedUnparsed, rule); } } @@ -1332,3 +1360,29 @@ BOOST_AUTO_TEST_CASE(OpeningHours_TestIsOpen) BOOST_CHECK(!IsClosed(rules, "2015-11-06 18:40")); } } + + +BOOST_AUTO_TEST_CASE(OpeningHours_TestOpeningHours) +{ + // OpeningHours is just a wrapper. So a couple of tests is + // enough to check if it works. + + static auto const & fmt = "%Y-%m-%d %H:%M"; + + using namespace osmoh; + + { + OpeningHours oh("Su 11:00-17:00; \"Wochentags auf Anfrage\"; Tu off"); + BOOST_CHECK(oh.IsValid()); + + std::tm time = {}; + BOOST_CHECK(GetTimeTuple("2015-11-08 12:30", fmt, time)); + BOOST_CHECK(oh.IsOpen(mktime(&time))); + + BOOST_CHECK(GetTimeTuple("2015-11-09 12:30", fmt, time)); + BOOST_CHECK(oh.IsUnknown(mktime(&time))); + + BOOST_CHECK(GetTimeTuple("2015-11-10 12:30", fmt, time)); + BOOST_CHECK(oh.IsClosed(mktime(&time))); + } +} diff --git a/3party/opening_hours/parse_opening_hours.cpp b/3party/opening_hours/parse_opening_hours.cpp index ed51efcece..fd93d3d0f4 100644 --- a/3party/opening_hours/parse_opening_hours.cpp +++ b/3party/opening_hours/parse_opening_hours.cpp @@ -53,12 +53,8 @@ bool ParseImp(std::string const & str, Context & context) auto first = begin(str); auto const last = end(str); - auto parsed = phrase_parse( - first, - last, - parser, - space, - context); + auto parsed = phrase_parse(first, last, parser, + space, context); if (!parsed || first != last) return false; diff --git a/3party/opening_hours/rules_evaluation.cpp b/3party/opening_hours/rules_evaluation.cpp index d741639418..4b05caabfa 100644 --- a/3party/opening_hours/rules_evaluation.cpp +++ b/3party/opening_hours/rules_evaluation.cpp @@ -12,7 +12,7 @@ using THourMinutes = std::tuple; constexpr osmoh::MonthDay::TYear kTMYearOrigin = 1900; -bool ToHourMinutes(osmoh::Time const & t, THourMinutes & hm) +inline bool ToHourMinutes(osmoh::Time const & t, THourMinutes & hm) { if (!t.IsHoursMinutes()) return false; @@ -20,50 +20,52 @@ bool ToHourMinutes(osmoh::Time const & t, THourMinutes & hm) return true; } -bool ToHourMinutes(std::tm const & t, THourMinutes & hm) +inline bool ToHourMinutes(std::tm const & t, THourMinutes & hm) { hm = THourMinutes{t.tm_hour, t.tm_min}; return true; } -int CompareMonthDayAndTimeTumple(osmoh::MonthDay const & monthDay, std::tm const & date) +inline int CompareMonthDayTimeTuple(osmoh::MonthDay const & monthDay, std::tm const & date) { - if (monthDay.IsVariable()) - // TODO(mgsergio): Not implemented yet - return false; - if (monthDay.HasYear()) + { if (monthDay.GetYear() != date.tm_year + kTMYearOrigin) return monthDay.GetYear() != date.tm_year + kTMYearOrigin; + } if (monthDay.HasMonth()) + { if (monthDay.GetMonth() != osmoh::ToMonth(date.tm_mon + 1)) return static_cast(monthDay.GetMonth()) - (date.tm_mon + 1); + } if (monthDay.HasDayNum()) + { if (monthDay.GetDayNum() != date.tm_mday) return monthDay.GetDayNum() - date.tm_mday; + } return 0; } -bool operator<=(osmoh::MonthDay const & monthDay, std::tm const & date) +inline bool operator<=(osmoh::MonthDay const & monthDay, std::tm const & date) { - return CompareMonthDayAndTimeTumple(monthDay, date) < 1; + return CompareMonthDayTimeTuple(monthDay, date) < 1; } -bool operator<=(std::tm const & date, osmoh::MonthDay const & monthDay) +inline bool operator<=(std::tm const & date, osmoh::MonthDay const & monthDay) { - return CompareMonthDayAndTimeTumple(monthDay, date) > -1; + return CompareMonthDayTimeTuple(monthDay, date) > -1; } -bool operator==(osmoh::MonthDay const & monthDay, std::tm const & date) +inline bool operator==(osmoh::MonthDay const & monthDay, std::tm const & date) { - return CompareMonthDayAndTimeTumple(monthDay, date) == 0; + return CompareMonthDayTimeTuple(monthDay, date) == 0; } // Fill result with fields that present in start and missing in end. -osmoh::MonthDay NormalizeEnd(osmoh::MonthDay const & start, osmoh::MonthDay const & end) +inline osmoh::MonthDay NormalizeEnd(osmoh::MonthDay const & start, osmoh::MonthDay const & end) { osmoh::MonthDay result = start; if (end.HasYear()) @@ -75,7 +77,7 @@ osmoh::MonthDay NormalizeEnd(osmoh::MonthDay const & start, osmoh::MonthDay cons return result; } -uint8_t GetWeekNumber(std::tm const & date) +inline uint8_t GetWeekNumber(std::tm const & date) { char buff[4]{}; if (strftime(&buff[0], sizeof(buff), "%V", &date) == 0) @@ -87,14 +89,33 @@ uint8_t GetWeekNumber(std::tm const & date) return weekNumber; } -bool IsBetweenLooped(osmoh::Weekday const start, - osmoh::Weekday const end, - osmoh::Weekday const p) +inline 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) +{ + 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; + } +} } // namespace namespace osmoh @@ -119,7 +140,8 @@ bool IsActive(Timespan const & span, std::tm const & time) // TODO(mgsergio): We don't handle extended hours yet. // Extended hours handling could be implemented through // splitting rule with extended hours into separated rules. - if (end <= start || end > THourMinutes{24,00}) + // 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' @@ -128,6 +150,11 @@ bool IsActive(Timespan const & span, std::tm const & time) return start <= toBeChecked && toBeChecked <= end; } + else if (span.HasStart() && span.HasPlus()) + { + // TODO(mgsergio): Not implemented yet + return false; + } return false; } @@ -209,8 +236,10 @@ template bool IsActiveAny(std::vector const & selectors, std::tm const & date) { for (auto const & selector : selectors) + { if (IsActive(selector, date)) return true; + } return selectors.empty(); } @@ -238,21 +267,21 @@ RuleState GetState(TRuleSequences const & rules, std::tm const & date) if (it->IsEmpty() && emptyRuleIt == rules.rend()) emptyRuleIt = it; else - return it->GetModifier(); + return ModifierToRuleState(it->GetModifier()); } } if (emptyRuleIt != rules.rend()) { if (emptyRuleIt->HasComment()) - return RuleSequence::Modifier::Unknown; + return RuleState::Unknown; else - return emptyRuleIt->GetModifier(); + return ModifierToRuleState(emptyRuleIt->GetModifier()); } return (rules.empty() - ? RuleSequence::Modifier::Unknown - : RuleSequence::Modifier::Closed); + ? RuleState::Unknown + : RuleState::Closed); } RuleState GetState(TRuleSequences const & rules, time_t const dateTime) diff --git a/3party/opening_hours/rules_evaluation.hpp b/3party/opening_hours/rules_evaluation.hpp index e4fdba4345..e8d4072024 100644 --- a/3party/opening_hours/rules_evaluation.hpp +++ b/3party/opening_hours/rules_evaluation.hpp @@ -1,46 +1,32 @@ #pragma once #include "opening_hours.hpp" + #include namespace osmoh { -class RuleState +enum class RuleState { -public: - RuleState(RuleSequence::Modifier const & modifier): - m_modifier(modifier) - { - } - - operator bool() const - { - return IsOpen(); - } - - bool IsOpen() const - { - return - m_modifier == RuleSequence::Modifier::DefaultOpen || - m_modifier == RuleSequence::Modifier::Open; - } - - bool IsClosed() const - { - return m_modifier == RuleSequence::Modifier::Closed; - } - - bool IsUnknown() const - { - return - m_modifier == RuleSequence::Modifier::Unknown || - m_modifier == RuleSequence::Modifier::Comment; - } - -private: - RuleSequence::Modifier m_modifier; + Open, + 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) +{ + return GetState(rules, dateTime) == RuleState::Open; +} + +inline bool IsClosed(TRuleSequences const & rules, time_t const dateTime) +{ + return GetState(rules, dateTime) == RuleState::Closed; +} + +inline bool IsUnknown(TRuleSequences const & rules, time_t const dateTime) +{ + return GetState(rules, dateTime) == RuleState::Unknown; +} } // namespace osmoh diff --git a/map/osm_opening_hours.hpp b/map/osm_opening_hours.hpp index 8b228b525d..ea725fdea0 100644 --- a/map/osm_opening_hours.hpp +++ b/map/osm_opening_hours.hpp @@ -1,7 +1,6 @@ #pragma once -#include "3party/opening_hours/parse_opening_hours.hpp" -#include "3party/opening_hours/rules_evaluation.hpp" +#include "3party/opening_hours/opening_hours.hpp" #include "std/chrono.hpp" @@ -32,14 +31,23 @@ inline string DebugPrint(EPlaceState state) inline EPlaceState PlaceStateCheck(string const & openingHours, time_t timestamp) { - osmoh::TRuleSequences rules; - Parse(openingHours, rules); + osmoh::OpeningHours oh(openingHours); auto future = system_clock::from_time_t(timestamp); future += minutes(15); - size_t nowState = GetState(rules, timestamp).IsOpen() ? 0 : 1; - size_t futureState = GetState(rules, system_clock::to_time_t(future)).IsOpen() ? 0 : 1; + enum {OPEN = 0, CLOSED = 1}; + + size_t nowState = OPEN; + size_t futureState = OPEN; + + // TODO(mgsergio): Switch to three-stated model instead of two-staed + // I.e. set unknown if we can't parse or can't answer whether it's open. + if (oh.IsValid()) + { + nowState = oh.IsOpen(timestamp) ? OPEN : CLOSED; + futureState = oh.IsOpen(system_clock::to_time_t(future)) ? OPEN : CLOSED; + } EPlaceState state[2][2] = {{EPlaceState::Open, EPlaceState::CloseSoon}, {EPlaceState::OpenSoon, EPlaceState::Closed}}; diff --git a/search/intermediate_result.cpp b/search/intermediate_result.cpp index 01478ee19f..9737bba17e 100644 --- a/search/intermediate_result.cpp +++ b/search/intermediate_result.cpp @@ -16,11 +16,7 @@ #include "base/string_utils.hpp" #include "base/logging.hpp" -#ifndef OMIM_OS_LINUX -// Lib opening_hours is not built for Linux since stdlib doesn't have required functions. -#include "3party/opening_hours/parse_opening_hours.hpp" -#include "3party/opening_hours/rules_evaluation.hpp" -#endif +#include "3party/opening_hours/opening_hours.hpp" namespace search @@ -41,10 +37,14 @@ void ProcessMetadata(FeatureType const & ft, Result::Metadata & meta) string const openHours = src.Get(feature::Metadata::FMD_OPEN_HOURS); if (!openHours.empty()) { - osmoh::TRuleSequences rules; - Parse(openHours, rules); - // TODO(mgsergio): Is there a way to report that openHours was not parsed? - meta.m_isClosed = GetState(rules, time(nullptr)).IsClosed(); + osmoh::OpeningHours oh(openHours); + + // TODO(mgsergio): Switch to three-stated model instead of two-staed + // I.e. set unknown if we can't parse or can't answer whether it's open. + if (oh.IsValid()) + meta.m_isClosed = oh.IsClosed(time(nullptr)); + else + meta.m_isClosed = false; } meta.m_stars = 0; diff --git a/xcode/opening_hours/opening_hours.xcodeproj/project.pbxproj b/xcode/opening_hours/opening_hours.xcodeproj/project.pbxproj index 13815dd680..e5e43ae404 100644 --- a/xcode/opening_hours/opening_hours.xcodeproj/project.pbxproj +++ b/xcode/opening_hours/opening_hours.xcodeproj/project.pbxproj @@ -83,7 +83,6 @@ E91738CF1BECD36E00717F6E /* opening_hours_tests_ios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = opening_hours_tests_ios.app; sourceTree = BUILT_PRODUCTS_DIR; }; E91738E21BECD36E00717F6E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; E91739081BECD60000717F6E /* opening_hours_integration_tests_ios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = opening_hours_integration_tests_ios.app; sourceTree = BUILT_PRODUCTS_DIR; }; - E917391B1BECD60000717F6E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -146,7 +145,6 @@ E91738A51BECCEB000717F6E /* opening_hours_tests */, 670C61E91AC3511700C38A8C /* opening_hours */, E91738D01BECD36E00717F6E /* opening_hours_tests_ios */, - E91739091BECD60000717F6E /* opening_hours_integration_tests_ios */, 670C61E81AC3511700C38A8C /* Products */, ); sourceTree = ""; @@ -217,14 +215,6 @@ path = opening_hours_tests_ios; sourceTree = ""; }; - E91739091BECD60000717F6E /* opening_hours_integration_tests_ios */ = { - isa = PBXGroup; - children = ( - E917391B1BECD60000717F6E /* Info.plist */, - ); - path = opening_hours_integration_tests_ios; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -753,6 +743,7 @@ E91738B91BECD02B00717F6E /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; E91738C41BECD08500717F6E /* Build configuration list for PBXNativeTarget "opening_hours_integration_tests" */ = { isa = XCConfigurationList; @@ -761,6 +752,7 @@ E91738C61BECD08500717F6E /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; E91738F91BECD36F00717F6E /* Build configuration list for PBXNativeTarget "opening_hours_tests_ios" */ = { isa = XCConfigurationList; @@ -769,6 +761,7 @@ E91738FB1BECD36F00717F6E /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; E917391C1BECD60000717F6E /* Build configuration list for PBXNativeTarget "opening_hours_integration_tests_ios" */ = { isa = XCConfigurationList; @@ -777,6 +770,7 @@ E917391E1BECD60000717F6E /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/xcode/opening_hours/opening_hours_tests_ios/Info.plist b/xcode/opening_hours/opening_hours_tests_ios/Info.plist new file mode 100644 index 0000000000..40c6215d90 --- /dev/null +++ b/xcode/opening_hours/opening_hours_tests_ios/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + +