7#include <KHolidays/SunRiseSet>
8#include "selectors_p.h"
10#include "openinghours_p.h"
13#include "holidaycache_p.h"
20static int daysInMonth(
int month)
25static QDateTime resolveTime(Time t,
QDate date, OpeningHoursPrivate *context)
30 return QDateTime(date, {t.hour % 24, t.minute});
45 dt = dt.
addSecs(t.hour * 3600 + t.minute * 60);
49bool Timespan::isMultiDay(
QDate date, OpeningHoursPrivate *context)
const
51 const auto beginDt = resolveTime(begin, date, context);
52 const auto realEnd = adjustedEnd();
53 auto endDt = resolveTime(realEnd, date, context);
54 if (endDt < beginDt || (realEnd.hour >= 24 &&
begin.hour < 24)) {
58 return next ?
next->isMultiDay(date, context) :
false;
61SelectorResult Timespan::nextInterval(
const Interval &interval,
const QDateTime &dt, OpeningHoursPrivate *context)
const
63 const auto beginDt = resolveTime(begin, dt.
date(), context);
64 const auto realEnd = adjustedEnd();
65 auto endDt = resolveTime(realEnd, dt.
date(), context);
66 if (endDt < beginDt || (realEnd.hour >= 24 &&
begin.hour < 24)) {
67 endDt = endDt.addDays(1);
70 if ((dt >= beginDt && dt < endDt) || (beginDt == endDt && beginDt == dt)) {
74 i.setOpenEndTime(openEnd);
78 return dt < beginDt ? dt.
secsTo(beginDt) : dt.
secsTo(beginDt.addDays(1));
84 const auto delta = (7 + weekDay -
start.dayOfWeek()) % 7;
85 return start.addDays(7 * (n - 1) + (delta == 0 ? 7 : delta));
87 const auto delta = (7 +
start.dayOfWeek() - weekDay) % 7;
88 return start.addDays(7 * (n + 1) - (delta == 0 ? 7 : delta));
92static QDate nthWeekdayInMonth(
QDate month,
int weekDay,
int n)
96 const auto delta = (7 + weekDay - firstOfMonth.dayOfWeek()) % 7;
97 const auto day = firstOfMonth.
addDays(7 * (n - 1) + delta);
101 const auto delta = (7 + lastOfMonth.dayOfWeek() - weekDay) % 7;
102 const auto day = lastOfMonth.
addDays(7 * (n + 1) - delta);
107SelectorResult WeekdayRange::nextInterval(
const Interval &interval,
const QDateTime &dt, OpeningHoursPrivate *context)
const
110 for (
auto s =
this; s; s = s->next.get()) {
111 r = std::min(r, s->nextIntervalLocal(interval, dt, context));
116SelectorResult WeekdayRange::nextIntervalLocal(
const Interval &interval,
const QDateTime &dt, OpeningHoursPrivate *context)
const
118 if (lhsAndSelector && rhsAndSelector) {
119 const auto r1 = lhsAndSelector->nextInterval(interval, dt, context);
120 if (r1.matchOffset() > 0 || !r1.canMatch()) {
124 const auto r2 = rhsAndSelector->nextInterval(interval, dt, context);
125 if (r2.matchOffset() > 0 || !r2.canMatch()) {
129 auto i = r1.interval();
130 i.setBegin(std::max(i.begin(), r2.interval().begin()));
131 i.setEnd(std::min(i.end(), r2.interval().end()));
139 qint64 smallestOffset = INT_MAX;
140 for (
const NthEntry &entry : nthSequence->sequence) {
141 Q_ASSERT(entry.begin <= entry.end);
142 for (
int n = entry.begin; n <= entry.end; ++n) {
143 const auto d = nthWeekdayInMonth(dt.
date().
addDays(-offset), beginDay, n);
144 if (!d.isValid() || d.addDays(offset) < dt.
date()) {
147 if (d.addDays(offset) == dt.
date()) {
149 i.setBegin(
QDateTime(d.addDays(offset), {0, 0}));
150 i.setEnd(
QDateTime(d.addDays(offset + 1), {0, 0}));
154 smallestOffset = qMin(smallestOffset, dt.
secsTo(
QDateTime(d.addDays(offset), {0, 0})));
157 if (smallestOffset < INT_MAX) {
158 return smallestOffset;
165 if (beginDay <= endDay) {
184 i.setEnd(
QDateTime(i.begin().date().addDays(1 + (beginDay <= endDay ? endDay - beginDay : 7 - (beginDay - endDay))), {0, 0}));
189 const auto h = HolidayCache::nextHoliday(context->m_region, dt.
date().
addDays(-offset));
190 if (h.name().isEmpty()) {
193 if (dt.
date() < h.observedStartDate().
addDays(offset)) {
194 return dt.
secsTo(
QDateTime(h.observedStartDate().addDays(offset), {0, 0}));
198 i.setBegin(
QDateTime(h.observedStartDate().addDays(offset), {0, 0}));
199 i.setEnd(
QDateTime(h.observedEndDate().addDays(1).addDays(offset), {0, 0}));
200 if (i.comment().isEmpty() && offset == 0) {
201 i.setComment(h.name());
211SelectorResult Week::nextInterval(
const Interval &interval,
const QDateTime &dt, OpeningHoursPrivate *context)
const
215 const auto days = (7 - dt.
date().dayOfWeek()) + 7 * (beginWeek - dt.
date().weekNumber() - 1) + 1;
221 while (d.date().weekNumber() != 1) {
227 if (this->interval > 1) {
228 const int wd = (dt.
date().weekNumber() - beginWeek) % this->interval;
230 const auto days = (7 - dt.
date().dayOfWeek()) + 7 * (this->interval - wd - 1) + 1;
236 if (this->interval > 1) {
238 i.setEnd(
QDateTime(i.begin().date().addDays(7), {0, 0}));
241 i.setEnd(
QDateTime(i.begin().date().addDays((1 + endWeek - beginWeek) * 7), {0, 0}));
246static QDate resolveDate(Date d,
int year)
249 switch (d.variableDate) {
250 case Date::FixedDate:
251 date = {d.year ? d.year : year, d.month ? d.month : 1, d.day ? d.day : 1};
254 date = Easter::easterDate(d.year ? d.year : year);
258 if (d.offset.weekday && d.offset.nthWeekday) {
259 if (d.variableDate == Date::Easter) {
260 date = nthWeekdayFromDate(date, d.offset.weekday, d.offset.nthWeekday);
262 date = nthWeekdayInMonth(date, d.offset.weekday, d.offset.nthWeekday);
265 date = date.
addDays(d.offset.dayOffset);
270static QDate resolveDateEnd(Date d,
int year)
272 auto date = resolveDate(d, year);
273 if (d.variableDate == Date::FixedDate) {
274 if (!d.day && !d.month) {
277 return date.
addDays(daysInMonth(d.month));
283SelectorResult MonthdayRange::nextInterval(
const Interval &interval,
const QDateTime &dt, OpeningHoursPrivate *context)
const
286 auto beginDt = resolveDate(begin, dt.
date().
year());
287 auto endDt = resolveDateEnd(end, dt.
date().
year());
291 if (endDt < beginDt || (endDt <= beginDt && begin != end)) {
293 endDt = resolveDateEnd(end, dt.
date().
year() + 1);
296 if (
end.year && dt.
date() >= endDt) {
302 auto lookbackBeginDt = resolveDate(begin, dt.
date().
year() - 1);
303 auto lookbackEndDt = resolveDateEnd(end, dt.
date().
year() - 1);
304 if (lookbackEndDt < lookbackEndDt || (lookbackEndDt <= lookbackBeginDt && begin != end)) {
305 lookbackEndDt = resolveDateEnd(end, dt.
date().
year());
307 if (lookbackEndDt >= dt.
date()) {
308 beginDt = lookbackBeginDt;
309 endDt = lookbackEndDt;
313 if (dt.
date() >= endDt) {
314 beginDt = resolveDate(begin, dt.
date().
year() + 1);
315 endDt = resolveDateEnd(end, dt.
date().
year() + 1);
318 if (dt.
date() < beginDt) {
328SelectorResult YearRange::nextInterval(
const Interval &interval,
const QDateTime &dt, OpeningHoursPrivate *context)
const
335 if (end > 0 && end < y) {
339 if (this->interval > 1) {
340 const int yd = (y -
begin) % this->interval;
342 return dt.
secsTo(
QDateTime({y + (this->interval - yd), 1, 1}, {0, 0}));
347 if (this->interval > 1) {
348 i.setBegin(
QDateTime({y, 1, 1}, {0, 0}));
349 i.setEnd(
QDateTime({y + 1, 1, 1}, {0, 0}));
357RuleResult Rule::nextInterval(
const QDateTime &dt, OpeningHoursPrivate *context)
const
363 if (m_timeSelector && m_timeSelector->isMultiDay(dt.
date(), context)) {
364 const auto res = nextInterval(dt.
addDays(-1), context, RecursionLimit);
365 if (res.interval.contains(dt)) {
370 return nextInterval(dt, context, RecursionLimit);
373RuleResult Rule::nextInterval(
const QDateTime &dt, OpeningHoursPrivate *context,
int recursionBudget)
const
375 const auto resultMode = (recursionBudget == Rule::RecursionLimit && m_ruleType == NormalRule && state() != Interval::Closed) ? RuleResult::Override : RuleResult::Merge;
377 if (recursionBudget == 0) {
379 qCWarning(
Log) <<
"Recursion limited reached!";
380 return {{}, resultMode};
385 i.setComment(m_comment);
386 if (!m_timeSelector && !m_weekdaySelector && !m_monthdaySelector && !m_weekSelector && !m_yearSelector) {
388 return {i, resultMode};
391 if (m_yearSelector) {
393 for (
auto s = m_yearSelector.get(); s; s = s->next.get()) {
394 r = std::min(r, s->nextInterval(i, dt, context));
397 return {{}, resultMode};
399 if (r.matchOffset() > 0) {
400 return nextInterval(dt.
addSecs(r.matchOffset()), context, --recursionBudget);
405 if (m_monthdaySelector) {
407 for (
auto s = m_monthdaySelector.get(); s; s = s->next.get()) {
408 r = std::min(r, s->nextInterval(i, dt, context));
411 return {{}, resultMode};
413 if (r.matchOffset() > 0) {
414 return nextInterval(dt.
addSecs(r.matchOffset()), context, --recursionBudget);
419 if (m_weekSelector) {
421 for (
auto s = m_weekSelector.get(); s; s = s->next.get()) {
422 r = std::min(r, s->nextInterval(i, dt, context));
425 return {{}, resultMode};
427 if (r.matchOffset() > 0) {
428 return nextInterval(dt.
addSecs(r.matchOffset()), context, --recursionBudget);
433 if (m_weekdaySelector) {
434 SelectorResult r = m_weekdaySelector->nextInterval(i, dt, context);
436 return {{}, resultMode};
438 if (r.matchOffset() > 0) {
439 return nextInterval(dt.
addSecs(r.matchOffset()), context, --recursionBudget);
444 if (m_timeSelector) {
446 for (
auto s = m_timeSelector.get(); s; s = s->next.get()) {
447 r = std::min(r, s->nextInterval(i, dt, context));
450 return {{}, resultMode};
452 if (r.matchOffset() > 0) {
453 return {nextInterval(dt.
addSecs(r.matchOffset()), context, --recursionBudget).interval, resultMode};
458 return {i, resultMode};
A time interval for which an opening hours expression has been evaluated.
@ EvaluationError
runtime error during evaluating the expression
Q_SCRIPTABLE Q_NOREPLY void start()
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
KHOLIDAYS_EXPORT QTime utcSunset(const QDate &date, double latitude, double longitude)
KHOLIDAYS_EXPORT QTime utcSunrise(const QDate &date, double latitude, double longitude)
KHOLIDAYS_EXPORT QTime utcDawn(const QDate &date, double latitude, double longitude)
KHOLIDAYS_EXPORT QTime utcDusk(const QDate &date, double latitude, double longitude)
OSM opening hours parsing and evaluation.
QAction * next(const QObject *recvr, const char *slot, QObject *parent)
const QList< QKeySequence > & begin()
int daysInMonth(int month, int year) const const
QDate addDays(qint64 ndays) const const
QDate addYears(int nyears) const const
int dayOfWeek() const const
qint64 daysTo(QDate d) const const
int weekNumber(int *yearNumber) const const
void setTimeSpec(Qt::TimeSpec spec)
QDateTime addDays(qint64 ndays) const const
QDateTime addSecs(qint64 s) const const
qint64 secsTo(const QDateTime &other) const const
QDateTime toTimeZone(const QTimeZone &timeZone) const const