7#include "openinghours.h" 
    8#include "openinghours_p.h" 
    9#include "openinghoursparser_p.h" 
   10#include "openinghoursscanner_p.h" 
   11#include "holidaycache_p.h" 
   25static 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) {
 
   40void 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));
 
  141void 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();
 
  202void 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)) {
 
  240void 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));
 
  291void 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;
 
  308bool OpeningHoursPrivate::isRecovering()
 const 
  310    return m_restartPosition > 0;
 
  315    : d(new OpeningHoursPrivate)
 
 
  321    : d(new OpeningHoursPrivate)
 
 
  327    : d(new OpeningHoursPrivate)
 
 
  335OpeningHours::~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);
 
 
  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();
 
 
  439QString OpeningHours::normalizedExpressionString()
 const 
  446    d->m_latitude = latitude;
 
  447    d->m_longitude = longitude;
 
 
  451float OpeningHours::latitude()
 const 
  453    return d->m_latitude;
 
  456void OpeningHours::setLatitude(
float latitude)
 
  458    d->m_latitude = latitude;
 
  462float OpeningHours::longitude()
 const 
  464    return d->m_longitude;
 
  467void OpeningHours::setLongitude(
float longitude)
 
  469    d->m_longitude = longitude;
 
  473#ifndef KOPENINGHOURS_VALIDATOR_ONLY 
  474QString OpeningHours::region()
 const 
  476    return d->m_region.regionCode();
 
  481    d->m_region = HolidayCache::resolveRegion(region);
 
 
  488    return d->m_timezone;
 
  496QString OpeningHours::timeZoneId()
 const 
  501void 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) {
 
  525        if (i.
isValid() && i.
contains(dt) && rule->m_ruleType == Rule::FallbackRule) {
 
  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());
 
 
  606            endDt = endDt.addSecs(3600);
 
 
  618static 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 } };
 
  647    const auto weekday = obj.
value(QLatin1String(
"dayOfWeek")).
toString();
 
  648    if (!weekday.isEmpty()) {
 
  649        r->m_weekdaySelector.reset(
new WeekdayRange);
 
  651        for (
const auto &d : { 
"Monday", 
"Tuesday", 
"Wednesday", 
"Thursday", 
"Friday", 
"Saturday", 
"Sunday"}) {
 
  652            if (weekday.endsWith(QLatin1String(d))) {
 
  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();
 
 
  706#include "moc_openinghours.cpp" 
A time interval for which an opening hours expression has been evaluated.
 
bool isValid() const
Default constructed empty/invalid interval.
 
bool contains(const QDateTime &dt) const
Check if this interval contains dt.
 
bool intersects(const Interval &other) const
Checks whether this interval overlaps with other.
 
An OSM opening hours specification.
 
QByteArray simplifiedExpression() const
Returns a simplified OSM opening hours expression reconstructed from this object.
 
void setExpression(const QByteArray &openingHours, Modes modes=IntervalMode)
Parse OSM opening hours expression openingHours.
 
Q_INVOKABLE KOpeningHours::Interval interval(const QDateTime &dt) const
Returns the interval containing dt.
 
@ PointInTimeMode
Expressions that describe points in time.
 
@ IntervalMode
Expressions that describe time ranges.
 
static OpeningHours fromJsonLd(const QJsonObject &obj)
Convert opening hours in schema.org JSON-LD format.
 
void setRegion(QStringView region)
ISO 3166-2 region or ISO 316-1 country in which this expression should be evaluated.
 
void setTimeZone(const QTimeZone &tz)
Timezone in which this expression should be evaluated.
 
OpeningHours()
Create an empty/invalid instance.
 
Q_INVOKABLE KOpeningHours::Interval nextInterval(const KOpeningHours::Interval &interval) const
Returns the interval immediately following interval.
 
Q_INVOKABLE void setLocation(float latitude, float longitude)
Geographic coordinate at which this expression should be evaluated.
 
@ MissingLocation
evaluation requires location information and those aren't set
 
@ UnsupportedFeature
expression uses a feature that isn't implemented/supported (yet)
 
@ IncompatibleMode
expression mode doesn't match the expected mode
 
@ Null
no expression is set
 
@ SyntaxError
syntax error in the opening hours expression
 
@ MissingRegion
expression refers to public or school holidays, but that information is not available
 
@ NoError
there is no error, the expression is valid and can be used
 
char * toString(const EngineQuery &query)
 
KIOCORE_EXPORT TransferJob * get(const QUrl &url, LoadType reload=NoReload, JobFlags flags=DefaultFlags)
 
OSM opening hours parsing and evaluation.
 
const char * constData() const const
 
bool isEmpty() const const
 
qsizetype size() const const
 
QDate fromString(QStringView string, QStringView format, QCalendar cal)
 
bool isValid() const const
 
QJsonValue value(QLatin1StringView key) const const
 
QJsonArray toArray() const const
 
QString toString() const const
 
QString fromUtf8(QByteArrayView str)
 
QByteArray toUtf8() const const
 
QTime fromString(QStringView string, QStringView format)