#include "editor/ui2oh.hpp" #include "base/assert.hpp" #include "std/algorithm.hpp" #include "std/array.hpp" #include "std/string.hpp" #include "3party/opening_hours/opening_hours.hpp" namespace { editor::ui::TOpeningDays MakeOpeningDays(osmoh::Weekdays const & wds) { set openingDays; for (auto const & wd : wds.GetWeekdayRanges()) { if (wd.HasSunday()) openingDays.insert(osmoh::Weekday::Sunday); if (wd.HasMonday()) openingDays.insert(osmoh::Weekday::Monday); if (wd.HasTuesday()) openingDays.insert(osmoh::Weekday::Tuesday); if (wd.HasWednesday()) openingDays.insert(osmoh::Weekday::Wednesday); if (wd.HasThursday()) openingDays.insert(osmoh::Weekday::Thursday); if (wd.HasFriday()) openingDays.insert(osmoh::Weekday::Friday); if (wd.HasSaturday()) openingDays.insert(osmoh::Weekday::Saturday); } return openingDays; } void SetUpWeekdays(osmoh::Weekdays const & wds, editor::ui::TimeTable & tt) { tt.SetOpeningDays(MakeOpeningDays(wds)); } void SetUpTimeTable(osmoh::TTimespans spans, editor::ui::TimeTable & tt) { using namespace osmoh; // Expand plus: 13:15+ -> 13:15-24:00. for (auto & span : spans) span.ExpandPlus(); sort(begin(spans), end(spans), [](Timespan const & a, Timespan const & b) { auto const start1 = a.GetStart().GetHourMinutes().GetDuration(); auto const start2 = b.GetStart().GetHourMinutes().GetDuration(); return start1 < start2; }); // Take first start and last end as opening time span. tt.SetOpeningTime({spans.front().GetStart(), spans.back().GetEnd()}); // Add an end of a span of index i and start of following span // as exclude time. for (auto i = 0; i + 1 < spans.size(); ++i) tt.AddExcludeTime({spans[i].GetEnd(), spans[i + 1].GetStart()}); } int32_t WeekdayNumber(osmoh::Weekday const wd) { return static_cast(wd); } constexpr uint32_t kDaysInWeek = 7; // Shifts values from 1 to 7 like this: 1 2 3 4 5 6 7 -> 2 3 4 5 6 7 1 int32_t NextWeekdayNumber(osmoh::Weekday const wd) { auto dayNumber = WeekdayNumber(wd); // If first element of the gourp would be evaluated to 0 // the resulting formula whould be (dayNumber + 1) % kDaysInWeek. // Since the first one evaluates to 1 // the formula is: return dayNumber % kDaysInWeek + 1; } // Returns a vector of Weekdays with no gaps and with Sunday as the last day. // Exampls: // su, mo, we -> mo, we, su; // su, mo, fr, sa -> fr, sa, su, mo. vector RemoveInversion(editor::ui::TOpeningDays const & days) { vector result(begin(days), end(days)); if ((NextWeekdayNumber(result.back()) != WeekdayNumber(result.front()) && result.back() != osmoh::Weekday::Sunday) || result.size() < 2) return result; auto inversion = adjacent_find(begin(result), end(result), [](osmoh::Weekday const a, osmoh::Weekday const b) { return NextWeekdayNumber(a) != WeekdayNumber(b); }); if (inversion != end(result)) rotate(begin(result), ++inversion, end(result)); if (result.front() == osmoh::Weekday::Sunday) rotate(begin(result), begin(result) + 1, end(result)); return result; } using TWeekdays = vector; vector SplitIntoIntervals(editor::ui::TOpeningDays const & days) { ASSERT_GREATER(days.size(), 0, ("At least one day must present.")); vector result; auto const & noInversionDays = RemoveInversion(days); ASSERT(!noInversionDays.empty(), ()); auto previous = *begin(noInversionDays); result.push_back({previous}); for (auto it = next(begin(noInversionDays)); it != end(noInversionDays); ++it) { if (NextWeekdayNumber(previous) != WeekdayNumber(*it)) result.push_back({}); result.back().push_back(*it); previous = *it; } return result; } osmoh::Weekdays MakeWeekdays(editor::ui::TimeTable const & tt) { osmoh::Weekdays wds; for (auto const & daysInterval : SplitIntoIntervals(tt.GetOpeningDays())) { osmoh::WeekdayRange wdr; wdr.SetStart(*begin(daysInterval)); if (daysInterval.size() > 1) wdr.SetEnd(*(prev(end(daysInterval)))); wds.AddWeekdayRange(wdr); } return wds; } osmoh::TTimespans MakeTimespans(editor::ui::TimeTable const & tt) { if (tt.IsTwentyFourHours()) return {}; auto const & excludeTime = tt.GetExcludeTime(); if (excludeTime.empty()) return {tt.GetOpeningTime()}; osmoh::TTimespans spans{{tt.GetOpeningTime().GetStart(), excludeTime[0].GetStart()}}; for (auto i = 0; i + 1 < excludeTime.size(); ++i) spans.emplace_back(excludeTime[i].GetEnd(), excludeTime[i + 1].GetStart()); spans.emplace_back(excludeTime.back().GetEnd(), tt.GetOpeningTime().GetEnd()); return spans; } editor::ui::TOpeningDays const kWholeWeek = { osmoh::Weekday::Monday, osmoh::Weekday::Tuesday, osmoh::Weekday::Wednesday, osmoh::Weekday::Thursday, osmoh::Weekday::Friday, osmoh::Weekday::Saturday, osmoh::Weekday::Sunday }; editor::ui::TOpeningDays GetCommonDays(editor::ui::TOpeningDays const & a, editor::ui::TOpeningDays const & b) { editor::ui::TOpeningDays result; set_intersection(begin(a), end(a), begin(b), end(b), inserter(result, begin(result))); return result; } osmoh::HourMinutes::TMinutes::rep GetDuration(osmoh::Time const & time) { return time.GetHourMinutes().GetDurationCount(); } bool Includes(osmoh::Timespan const & a, osmoh::Timespan const & b) { return GetDuration(a.GetStart()) <= GetDuration(b.GetStart()) && GetDuration(b.GetEnd()) <= GetDuration(a.GetEnd()); } bool ExcludeRulePart(osmoh::RuleSequence const & rulePart, editor::ui::TimeTableSet & tts) { auto const ttsInitialSize = tts.Size(); for (size_t i = 0; i < ttsInitialSize; ++i) { auto tt = tts.Get(i); auto const ttOpeningDays = tt.GetOpeningDays(); auto const commonDays = GetCommonDays(ttOpeningDays, MakeOpeningDays(rulePart.GetWeekdays())); auto const removeCommonDays = [&commonDays](editor::ui::TTimeTableProxy & tt) { for (auto const day : commonDays) VERIFY(tt.RemoveWorkingDay(day), ("Can't remove working day")); VERIFY(tt.Commit(), ("Can't commit changes")); }; auto const twentyFourHoursGuard = [](editor::ui::TimeTable & tt) { using osmoh::operator ""_h; if (tt.IsTwentyFourHours()) { tt.SetTwentyFourHours(false); // TODO(mgsergio): Consider TimeTable refactoring: // get rid of separation of TwentyFourHours and OpeningTime. tt.SetOpeningTime({0_h, 24_h}); } }; auto const & excludeTime = rulePart.GetTimes(); // The whole rule matches to the tt. if (commonDays.size() == ttOpeningDays.size()) { // rulePart applies to commonDays in a whole. if (excludeTime.empty()) return tts.Remove(i); twentyFourHoursGuard(tt); for (auto const & time : excludeTime) { // Whatever it is, it's already closed at a time out of opening time. if (!Includes(tt.GetOpeningTime(), time)) continue; // The whole opening time interval should be switched off if (!tt.AddExcludeTime(time)) return tts.Remove(i); } VERIFY(tt.Commit(), ("Can't update time table")); return true; } // A rule is applied to a subset of a time table. We should // subtract common parts from tt and add a new time table if needed. if (commonDays.size() != 0) { // rulePart applies to commonDays in a whole. if (excludeTime.empty()) { removeCommonDays(tt); continue; } twentyFourHoursGuard(tt); editor::ui::TimeTable copy = tt; VERIFY(copy.SetOpeningDays(commonDays), ("Can't set opening days")); auto doAppendRest = true; for (auto const & time : excludeTime) { // Whatever it is, it's already closed at a time out of opening time. if (!Includes(copy.GetOpeningTime(), time)) continue; // The whole opening time interval should be switched off if (!copy.AddExcludeTime(time)) { doAppendRest = false; break; } } removeCommonDays(tt); if (doAppendRest) VERIFY(tts.Append(copy), ("Can't add new time table")); } } return true; } } // namespace namespace editor { osmoh::OpeningHours MakeOpeningHours(ui::TimeTableSet const & tts) { ASSERT_GREATER(tts.Size(), 0, ("At least one time table must present.")); if (tts.IsTwentyFourPerSeven()) { osmoh::RuleSequence rulePart; rulePart.SetTwentyFourHours(true); return osmoh::OpeningHours({rulePart}); } osmoh::TRuleSequences rule; for (auto const & tt : tts) { osmoh::RuleSequence rulePart; rulePart.SetWeekdays(MakeWeekdays(tt)); rulePart.SetTimes(MakeTimespans(tt)); rule.push_back(rulePart); } return rule; } bool MakeTimeTableSet(osmoh::OpeningHours const & oh, ui::TimeTableSet & tts) { if (!oh.IsValid()) return false; if (oh.HasYearSelector() || oh.HasWeekSelector() || oh.HasMonthSelector()) return false; tts = ui::TimeTableSet(); if (oh.IsTwentyFourHours()) return true; bool first = true; for (auto const & rulePart : oh.GetRule()) { ui::TimeTable tt = ui::TimeTable::GetUninitializedTimeTable(); tt.SetOpeningTime(tt.GetPredefinedOpeningTime()); // Comments and unknown rules belong to advanced mode. if (rulePart.GetModifier() == osmoh::RuleSequence::Modifier::Unknown || rulePart.GetModifier() == osmoh::RuleSequence::Modifier::Comment) return false; if (rulePart.GetModifier() == osmoh::RuleSequence::Modifier::Closed) { // off modifier in the first part in oh is useless. if (first == true) return false; if (!ExcludeRulePart(rulePart, tts)) return false; continue; } if (rulePart.HasWeekdays()) SetUpWeekdays(rulePart.GetWeekdays(), tt); else tt.SetOpeningDays(kWholeWeek); if (rulePart.HasTimes()) { tt.SetTwentyFourHours(false); SetUpTimeTable(rulePart.GetTimes(), tt); } else { tt.SetTwentyFourHours(true); } // Check size as well since ExcludeRulePart can add new time tables. bool const appended = first && tts.Size() == 1 ? tts.Replace(tt, 0) : tts.Append(tt); first = false; if (!appended) return false; } return true; } } // namespace editor