7#include "selectors_p.h"
9#include "openinghours_p.h"
25bool Time::isValid(Time t)
27 return t.hour >= 0 && t.hour <= 48 && t.minute >= 0 && t.minute < 60;
30void Time::convertFromAm(Time &t)
37void Time::convertFromPm(Time &t)
44Time Time::parse(
const char *begin,
const char *end)
46 Time t{ Time::NoEvent, 0, 0 };
49 t.hour = std::strtol(begin, &it, 10);
51 for (
const auto sep : {
':',
'h',
'H'}) {
58 t.minute = std::strtol(it,
nullptr, 10);
68 if (hour % 24 == 0 && minute == 0 && end)
70 return twoDigits(hour) +
':' + twoDigits(minute);
84 const int minutes = hour * 60 + minute;
86 const QByteArray hhmm = twoDigits(qAbs(hour)) +
':' + twoDigits(qAbs(minute));
87 expr =
'(' + expr + (minutes > 0 ?
'+' :
'-') + hhmm +
')';
92int Timespan::requiredCapabilities()
const
94 int c = Capability::None;
95 if ((interval > 0 || pointInTime) && !openEnd) {
96 c |= Capability::PointInTime;
98 c |= Capability::Interval;
100 if (
begin.event != Time::NoEvent ||
end.
event != Time::NoEvent) {
101 c |= Capability::Location;
103 return next ? (
next->requiredCapabilities() | c) : c;
106static QByteArray intervalToExpression(
int minutes)
109 return twoDigits(minutes);
111 const int hours = minutes / 60;
112 minutes -= hours * 60;
113 return twoDigits(hours) +
':' + twoDigits(minutes);
121 expr +=
'-' +
end.toExpression(
true);
127 expr +=
'/' + intervalToExpression(interval);
130 expr +=
',' +
next->toExpression();
135Time Timespan::adjustedEnd()
const
137 if (begin == end && !pointInTime) {
143bool Timespan::operator==(Timespan &other)
const
145 return begin == other.begin &&
147 openEnd == other.openEnd &&
148 interval == other.interval &&
149 bool(next) == (bool)other.next &&
150 (!next || *next == *other.next);
153int WeekdayRange::requiredCapabilities()
const
156 assert(beginDay == endDay || !nthSequence);
158 int c = Capability::None;
161 if ((offset > 0 && !nthSequence)) {
162 c |= Capability::NotImplemented;
166 c |= Capability::PublicHoliday;
169 c |= Capability::SchoolHoliday;
173 c |= lhsAndSelector ? lhsAndSelector->requiredCapabilities() : Capability::None;
174 c |= rhsAndSelector ? rhsAndSelector->requiredCapabilities() : Capability::None;
175 c |=
next ?
next->requiredCapabilities() : Capability::None;
179static constexpr const char* s_weekDays[] = {
"ERROR",
"Mo",
"Tu",
"We",
"Th",
"Fr",
"Sa",
"Su"};
184 if (lhsAndSelector && rhsAndSelector) {
185 expr = lhsAndSelector->toExpression() +
' ' + rhsAndSelector->toExpression();
189 expr = s_weekDays[beginDay];
190 if (endDay != beginDay) {
192 expr += s_weekDays[endDay];
204 expr +=
'[' + nthSequence->toExpression() +
']';
208 }
else if (offset < 0) {
213 expr +=
',' +
next->toExpression();
218void WeekdayRange::simplify()
222 const int endIdx =
sizeof(seenDays);
223 std::fill(std::begin(seenDays), std::end(seenDays),
false);
224 for (WeekdayRange *selector =
this; selector; selector = selector->next.get()) {
226 if (selector->nthSequence || selector->lhsAndSelector || selector->holiday != NoHoliday || selector->offset) {
229 const bool wrap = selector->beginDay > selector->endDay;
230 for (
int day = selector->beginDay; day <= selector->endDay + (wrap ? 7 : 0); ++day) {
231 seenDays[(day - 1) % 7 + 1] =
true;
233 endToSelectorMap.
insert(selector->endDay, selector);
237 for (
int idx = 1; idx < endIdx; ++idx) {
247 auto prevIdx = [&](
int idx) {
248 Q_ASSERT(idx > 0 && idx < 8);
249 return idx == 1 ? 7 : (idx - 1);
251 auto nextIdx = [&](
int idx) {
252 Q_ASSERT(idx > 0 && idx < 8);
257 auto find = [&](
int idx,
bool value) {
259 if (seenDays[idx] == value)
262 }
while(idx != startIdx);
265 auto findPrev = [&](
int idx,
bool value) {
266 for (; idx > 0; --idx) {
267 if (seenDays[idx] == value)
273 WeekdayRange *prev =
nullptr;
274 WeekdayRange *selector =
this;
276 auto addRange = [&](
int from,
int to) {
278 selector =
new WeekdayRange;
279 prev->next.reset(selector);
281 selector->beginDay = from;
282 selector->endDay = to;
300 Q_ASSERT(startIdx > 0);
303 const int finishIdx =
find(idx,
false);
305 if (finishIdx == nextIdx(nextIdx(idx))) {
307 const int n = nextIdx(idx);
310 addRange(idx, prevIdx(finishIdx));
312 idx =
find(finishIdx,
true);
313 }
while (idx != startIdx);
316int Week::requiredCapabilities()
const
318 if (endWeek < beginWeek) {
319 return Capability::NotImplemented;
321 return next ?
next->requiredCapabilities() : Capability::None;
327 if (endWeek != beginWeek) {
329 expr += twoDigits(endWeek);
336 expr +=
',' +
next->toExpression();
341QByteArray Date::toExpression(
const Date &refDate,
const MonthdayRange &prev)
const
344 auto maybeSpace = [&]() {
349 switch (variableDate) {
351 const bool needYear = year && (year != refDate.year || (day && month && month != refDate.month));
356 const bool combineWithPrev = prev.begin.month == prev.end.month && month == prev.begin.month;
357 const bool implicitMonth = month == refDate.month || (refDate.month == 0 && combineWithPrev);
358 if (needYear || !implicitMonth || hasOffset()) {
359 static const char* s_monthName[] = {
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec" };
361 expr += s_monthName[month-1];
364 if (day && *
this != refDate) {
366 expr += twoDigits(day);
378 if (offset.nthWeekday) {
380 expr += s_weekDays[offset.weekday];
384 if (offset.dayOffset > 0) {
385 expr +=
" +" +
QByteArray::number(offset.dayOffset) +
' ' + (offset.dayOffset > 1 ?
"days" :
"day");
386 }
else if (offset.dayOffset < 0) {
387 expr +=
" -" +
QByteArray::number(-offset.dayOffset) +
' ' + (offset.dayOffset < -1 ?
"days" :
"day");
392bool DateOffset::operator==(DateOffset other)
const
394 return weekday == other.weekday && nthWeekday == other.nthWeekday && dayOffset == other.dayOffset;
397DateOffset &DateOffset::operator+=(DateOffset other)
400 dayOffset += other.dayOffset;
402 Q_ASSERT(weekday == 0);
403 Q_ASSERT(nthWeekday == 0);
404 weekday = other.weekday;
405 nthWeekday = other.nthWeekday;
409bool Date::operator==(Date other)
const
411 if (variableDate != other.variableDate)
413 if (variableDate == FixedDate && other.variableDate == FixedDate) {
414 if (!(year == other.year && month == other.month && day == other.day)) {
418 return offset == other.offset;
421bool Date::hasOffset()
const
423 return offset.dayOffset || offset.weekday;
426int MonthdayRange::requiredCapabilities()
const
428 return Capability::None;
431QByteArray MonthdayRange::toExpression(
const MonthdayRange &prev)
const
435 expr +=
'-' +
end.toExpression(begin, prev);
438 expr +=
',' +
next->toExpression(*
this);
443void MonthdayRange::simplify()
446 if (
begin.variableDate == Date::FixedDate &&
447 end.variableDate == Date::FixedDate &&
454 if (
begin.day == 1 &&
end.day == lastDay) {
461int YearRange::requiredCapabilities()
const
463 return Capability::None;
469 if (end == 0 && interval == 1) {
471 }
else if (end != begin && end != 0) {
480 expr +=
',' +
next->toExpression();
485void NthSequence::add(NthEntry range)
493 for (
const NthEntry &entry : sequence) {
496 ret += entry.toExpression();
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
OSM opening hours parsing and evaluation.
QAction * findPrev(const QObject *recvr, const char *slot, QObject *parent)
QAction * next(const QObject *recvr, const char *slot, QObject *parent)
const QList< QKeySequence > & begin()
virtual bool event(QEvent *e) override
bool isEmpty() const const
QByteArray number(double n, char format, int precision)
QByteArray & prepend(QByteArrayView ba)
void push_back(QByteArrayView str)
qsizetype size() const const
int daysInMonth() const const
iterator insert(const Key &key, const T &value)