7 #include "openinghours.h"
8 #include "openinghours_p.h"
9 #include "openinghoursparser_p.h"
10 #include "openinghoursscanner_p.h"
11 #include "holidaycache_p.h"
18 #include <QJsonObject>
25 static bool isWiderThan(Rule *lhs, Rule *rhs)
27 if ((lhs->m_yearSelector && !rhs->m_yearSelector)) {
30 if (lhs->m_monthdaySelector && rhs->m_monthdaySelector) {
31 if (lhs->m_monthdaySelector->begin.year > 0 && rhs->m_monthdaySelector->end.year == 0) {
40 void OpeningHoursPrivate::autocorrect()
51 for (
auto it = std::next(m_rules.begin()); it != m_rules.end(); ++it) {
52 auto rule = (*it).get();
53 auto prevRule = (*(std::prev(it))).get();
55 if (
rule->hasComment() || prevRule->hasComment() || !prevRule->hasImplicitState()) {
58 const auto prevRuleSingleSelector = prevRule->selectorCount() == 1;
59 const auto curRuleSingleSelector =
rule->selectorCount() == 1;
61 if (
rule->m_ruleType == Rule::AdditionalRule) {
64 if (!prevRule->m_timeSelector && prevRule->m_weekdaySelector &&
rule->m_weekdaySelector && !
rule->hasWideRangeSelector()) {
65 auto tmp = std::move(
rule->m_weekdaySelector);
66 rule->m_weekdaySelector = std::move(prevRule->m_weekdaySelector);
67 rule->m_weekSelector = std::move(prevRule->m_weekSelector);
68 rule->m_monthdaySelector = std::move(prevRule->m_monthdaySelector);
69 rule->m_yearSelector = std::move(prevRule->m_yearSelector);
70 rule->m_colonAfterWideRangeSelector = prevRule->m_colonAfterWideRangeSelector;
71 auto *selector =
rule->m_weekdaySelector.get();
72 while (selector->rhsAndSelector)
73 selector = selector->rhsAndSelector.get();
74 appendSelector(selector, std::move(tmp));
75 rule->m_ruleType = prevRule->m_ruleType;
76 std::swap(*it, *std::prev(it));
77 it = std::prev(m_rules.erase(it));
81 else if (curRuleSingleSelector &&
rule->m_timeSelector && prevRule->m_timeSelector) {
82 appendSelector(prevRule->m_timeSelector.get(), std::move(
rule->m_timeSelector));
83 prevRule->copyStateFrom(*
rule);
84 it = std::prev(m_rules.erase(it));
88 else if (curRuleSingleSelector && prevRuleSingleSelector &&
rule->m_timeSelector && prevRule->m_weekdaySelector) {
89 prevRule->m_timeSelector = std::move(
rule->m_timeSelector);
90 it = std::prev(m_rules.erase(it));
94 else if (
rule->m_monthdaySelector && prevRuleSingleSelector && prevRule->m_monthdaySelector && !isWiderThan(prevRule,
rule)) {
95 auto tmp = std::move(
rule->m_monthdaySelector);
96 rule->m_monthdaySelector = std::move(prevRule->m_monthdaySelector);
97 appendSelector(
rule->m_monthdaySelector.get(), std::move(tmp));
98 rule->m_ruleType = prevRule->m_ruleType;
99 std::swap(*it, *std::prev(it));
100 it = std::prev(m_rules.erase(it));
105 else if (
rule->selectorCount() == 0 &&
rule->m_seen_24_7 && !prevRule->m_timeSelector) {
106 prevRule->m_timeSelector.reset(
new Timespan);
107 prevRule->m_timeSelector->begin = { Time::NoEvent, 0, 0 };
108 prevRule->m_timeSelector->end = { Time::NoEvent, 24, 0 };
109 it = std::prev(m_rules.erase(it));
111 }
else if (
rule->m_ruleType == Rule::NormalRule) {
115 if (curRuleSingleSelector &&
rule->m_timeSelector
116 && prevRule->selectorCount() > 1 && prevRule->m_timeSelector
117 &&
rule->state() == prevRule->state()) {
118 appendSelector(prevRule->m_timeSelector.get(), std::move(
rule->m_timeSelector));
119 it = std::prev(m_rules.erase(it));
126 else if (
rule->selectorCount() == prevRule->selectorCount()
127 &&
rule->m_timeSelector && prevRule->m_timeSelector
128 && !
rule->hasComment() && !prevRule->hasComment()
129 &&
rule->selectorCount() == 2 &&
rule->m_weekdaySelector && prevRule->m_weekdaySelector
131 &&
rule->m_weekdaySelector->toExpression() == prevRule->m_weekdaySelector->toExpression()
132 &&
rule->state() == prevRule->state()
134 appendSelector(prevRule->m_timeSelector.get(), std::move(
rule->m_timeSelector));
135 it = std::prev(m_rules.erase(it));
141 void OpeningHoursPrivate::simplify()
147 for (
auto it = std::next(m_rules.begin()); it != m_rules.end(); ++it) {
148 auto rule = (*it).get();
149 auto prevRule = (*(std::prev(it))).get();
151 if (
rule->m_ruleType == Rule::AdditionalRule ||
rule->m_ruleType == Rule::NormalRule) {
153 auto hasNoHoliday = [](WeekdayRange *selector) {
154 return selector->holiday == WeekdayRange::NoHoliday
155 && !selector->lhsAndSelector;
159 if (
rule->selectorCount() == prevRule->selectorCount()
160 &&
rule->m_timeSelector && prevRule->m_timeSelector
161 &&
rule->selectorCount() == 2 &&
rule->m_weekdaySelector && prevRule->m_weekdaySelector
162 && hasNoHoliday(
rule->m_weekdaySelector.get())
163 && hasNoHoliday(prevRule->m_weekdaySelector.get())
164 && *
rule->m_timeSelector == *prevRule->m_timeSelector
167 appendSelector(prevRule->m_weekdaySelector.get(), std::move(
rule->m_weekdaySelector));
168 it = std::prev(m_rules.erase(it));
173 if (
rule->m_ruleType == Rule::AdditionalRule) {
177 if (
rule->selectorCount() == prevRule->selectorCount()
178 &&
rule->m_timeSelector && prevRule->m_timeSelector
179 && !
rule->hasComment() && !prevRule->hasComment()
180 &&
rule->selectorCount() == 2 &&
rule->m_weekdaySelector && prevRule->m_weekdaySelector
182 &&
rule->m_weekdaySelector->toExpression() == prevRule->m_weekdaySelector->toExpression()
184 appendSelector(prevRule->m_timeSelector.get(), std::move(
rule->m_timeSelector));
185 it = std::prev(m_rules.erase(it));
191 for (
auto it = m_rules.begin(); it != m_rules.end(); ++it) {
192 auto rule = (*it).get();
193 if (
rule->m_weekdaySelector) {
194 rule->m_weekdaySelector->simplify();
196 if (
rule->m_monthdaySelector) {
197 rule->m_monthdaySelector->simplify();
202 void OpeningHoursPrivate::validate()
207 if (m_rules.empty()) {
212 int c = Capability::None;
213 for (
const auto &
rule : m_rules) {
214 c |=
rule->requiredCapabilities();
217 if ((c & Capability::Location) && (std::isnan(m_latitude) || std::isnan(m_longitude))) {
221 #ifndef KOPENINGHOURS_VALIDATOR_ONLY
222 if (c & Capability::PublicHoliday && !m_region.isValid()) {
232 if (c & (Capability::SchoolHoliday | Capability::NotImplemented | Capability::PointInTime)) {
240 void OpeningHoursPrivate::addRule(Rule *parsedRule)
242 std::unique_ptr<Rule>
rule(parsedRule);
245 if (
rule->isEmpty()) {
249 if (m_initialRuleType != Rule::NormalRule &&
rule->m_ruleType == Rule::NormalRule) {
250 rule->m_ruleType = m_initialRuleType;
251 m_initialRuleType = Rule::NormalRule;
256 if (m_ruleSeparatorRecovery && !m_rules.empty()) {
257 if (
rule->selectorCount() <= 1) {
259 if (m_rules.back()->m_timeSelector &&
rule->m_timeSelector && m_rules.back()->state() ==
rule->state()) {
260 appendSelector(m_rules.back()->m_timeSelector.get(), std::move(
rule->m_timeSelector));
271 if (m_rules.back()->hasWideRangeSelector() &&
rule->hasWideRangeSelector()
272 && !m_rules.back()->hasSmallRangeSelector() &&
rule->hasSmallRangeSelector()
273 && isWiderThan(
rule.get(), m_rules.back().get()))
282 if (m_rules.back()->hasWideRangeSelector() && !
rule->hasWideRangeSelector()) {
287 m_ruleSeparatorRecovery =
false;
288 m_rules.push_back(std::move(
rule));
291 void OpeningHoursPrivate::restartFrom(
int pos, Rule::Type nextRuleType)
293 m_restartPosition = pos;
294 if (nextRuleType == Rule::GuessRuleType) {
295 if (m_rules.empty()) {
296 m_recoveryRuleType = Rule::NormalRule;
299 const auto &prev = m_rules.back();
300 const auto couldBeMerged = prev->selectorCount() == 1 && !prev->hasComment() && prev->hasImplicitState();
301 m_recoveryRuleType = couldBeMerged ? Rule::AdditionalRule : Rule::NormalRule;
304 m_recoveryRuleType = nextRuleType;
308 bool OpeningHoursPrivate::isRecovering()
const
310 return m_restartPosition > 0;
315 : d(new OpeningHoursPrivate)
321 : d(new OpeningHoursPrivate)
327 : d(new OpeningHoursPrivate)
335 OpeningHours::~OpeningHours() =
default;
351 d->m_initialRuleType = Rule::NormalRule;
352 d->m_recoveryRuleType = Rule::NormalRule;
353 d->m_ruleSeparatorRecovery =
false;
358 while (size > 0 && std::isspace(
static_cast<unsigned char>(openingHours[size - 1]))) {
365 d->m_restartPosition = 0;
369 if (yylex_init(&scanner)) {
370 qCWarning(
Log) <<
"Failed to initialize scanner?!";
374 const std::unique_ptr<void, decltype(&yylex_destroy)> lexerCleanup(scanner, &yylex_destroy);
376 YY_BUFFER_STATE state;
377 state = yy_scan_bytes(openingHours + offset, size - offset, scanner);
378 if (yyparse(d.
data(), scanner)) {
379 if (d->m_restartPosition > 1 && d->m_restartPosition + offset < (
int)size) {
380 offset += d->m_restartPosition - 1;
381 d->m_initialRuleType = d->m_recoveryRuleType;
382 d->m_recoveryRuleType = Rule::NormalRule;
383 d->m_restartPosition = 0;
396 yy_delete_buffer(state, scanner);
397 }
while (offset > 0);
403 QByteArray OpeningHours::normalizedExpression()
const
410 for (
const auto &
rule : d->m_rules) {
412 switch (
rule->m_ruleType) {
413 case Rule::NormalRule:
416 case Rule::AdditionalRule:
419 case Rule::FallbackRule:
422 case Rule::GuessRuleType:
427 ret +=
rule->toExpression();
436 return copy.normalizedExpression();
439 QString OpeningHours::normalizedExpressionString()
const
446 d->m_latitude = latitude;
447 d->m_longitude = longitude;
451 float OpeningHours::latitude()
const
453 return d->m_latitude;
456 void OpeningHours::setLatitude(
float latitude)
458 d->m_latitude = latitude;
462 float OpeningHours::longitude()
const
464 return d->m_longitude;
467 void OpeningHours::setLongitude(
float longitude)
469 d->m_longitude = longitude;
473 #ifndef KOPENINGHOURS_VALIDATOR_ONLY
474 QString OpeningHours::region()
const
476 return d->m_region.regionCode();
481 d->m_region = HolidayCache::resolveRegion(region);
488 return d->m_timezone;
496 QString OpeningHours::timeZoneId()
const
501 void OpeningHours::setTimeZoneId(
const QString &tzId)
511 #ifndef KOPENINGHOURS_VALIDATOR_ONLY
518 const auto alignedTime =
QDateTime(dt.
date(), {dt.time().hour(), dt.time().minute()});
521 for (
const auto &
rule : d->m_rules) {
522 if (
rule->state() == Interval::Closed) {
528 auto res =
rule->nextInterval(alignedTime, d.
data());
529 if (!res.interval.isValid()) {
532 if (i.
isValid() && res.mode == RuleResult::Override) {
533 if (res.interval.begin().isValid() && res.interval.begin().date() > alignedTime.date()) {
535 i.setBegin(alignedTime);
536 i.setEnd({alignedTime.date().addDays(1), {0, 0}});
537 i.setState(Interval::Closed),
547 if (
rule->m_ruleType == Rule::FallbackRule) {
548 res.interval.setEnd(res.interval.hasOpenEnd() ? i.begin() : std::min(res.interval.end(), i.begin()));
550 i = i.
isValid() ? std::min(i, res.interval) : res.interval;
555 QDateTime closeEnd = i.begin(), closeBegin = i.end();
557 for (
const auto &
rule : d->m_rules) {
558 if (
rule->state() != Interval::Closed) {
561 const auto j =
rule->nextInterval(i.begin().
isValid() ? i.begin() : alignedTime, d.
data()).interval;
566 if (j.contains(alignedTime)) {
567 if (closedInterval.
isValid()) {
569 closedInterval.setBegin(std::min(closedInterval.begin(), j.begin()));
570 closedInterval.setEnd(std::max(closedInterval.end(), j.end()));
574 }
else if (alignedTime < j.begin()) {
575 closeBegin = std::min(j.begin(), closeBegin);
576 }
else if (j.end() <= alignedTime) {
577 closeEnd = std::max(closeEnd, j.end());
580 if (closedInterval.
isValid()) {
583 i.setBegin(closeEnd);
584 i.setEnd(closeBegin);
593 i2.setState(Interval::Closed);
595 i2.setEnd(i.begin());
618 static Rule* openingHoursSpecToRule(
const QJsonObject &obj)
627 if (!opens.isValid() || !closes.isValid()) {
632 r->setState(State::Open);
635 r->m_timeSelector.reset(
new Timespan);
636 r->m_timeSelector->begin = { Time::NoEvent, opens.hour(), opens.minute() };
637 r->m_timeSelector->end = { Time::NoEvent, closes.hour(), closes.minute() };
641 if (validFrom.isValid() || validTo.isValid()) {
642 r->m_monthdaySelector.reset(
new MonthdayRange);
643 r->m_monthdaySelector->begin = { validFrom.year(), validFrom.month(), validFrom.day(), Date::FixedDate, { 0, 0, 0 } };
644 r->m_monthdaySelector->end = { validTo.year(), validTo.month(), validTo.day(), Date::FixedDate, { 0, 0, 0 } };
648 if (!weekday.isEmpty()) {
649 r->m_weekdaySelector.reset(
new WeekdayRange);
651 for (
const auto &d : {
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"}) {
653 r->m_weekdaySelector->beginDay = r->m_weekdaySelector->endDay = i;
670 }
else if (oh.isArray()) {
671 const auto ohA = oh.toArray();
673 for (
const auto &exprV : ohA) {
674 const auto exprS = exprV.toString();
675 if (exprS.isEmpty()) {
678 expr += (expr.
isEmpty() ?
"" :
"; ") + exprS.toUtf8();
683 std::vector<std::unique_ptr<Rule>> rules;
685 for (
const auto &ohsV : ohs) {
686 const auto r = openingHoursSpecToRule(ohsV.toObject());
688 rules.push_back(std::unique_ptr<Rule>(r));
692 for (
const auto &ohsV : sohs) {
693 const auto r = openingHoursSpecToRule(ohsV.toObject());
695 rules.push_back(std::unique_ptr<Rule>(r));
698 for (
auto &r : rules) {
699 result.d->m_rules.push_back(std::move(r));
702 result.d->validate();