Commit b4cbddb8 authored by David Faure's avatar David Faure
Browse files

Turn nthMask into a vector of entries, to preserve intervals and ordering

BUG: 445963
parent 9818afb3
Pipeline #117379 passed with stage
in 1 minute and 10 seconds
......@@ -95,6 +95,12 @@ private Q_SLOTS:
T("Mar Su[1]-Oct Su[1]: 11:00-20:00; PH 11:00-20:00");
T2("Mo 20:00-26:00", "Mo 20:00-26:00"); // https://github.com/osm-fr/osmose-backend/issues/1344
// https://bugs.kde.org/show_bug.cgi?id=445963
T("Th[1-2] 09:30-11:45");
T("Th[1,2] 09:30-11:45");
T("Mo[1,3,-1]"); // order as desired, -1 means last
T("Mo[1,3,2]"); // currently not normalized
// from https://wiki.openstreetmap.org/wiki/Key:opening_hours#Simple_examples
T("Mo-Fr 08:00-17:30");
T("Mo-Fr 08:00-12:00,13:00-17:30");
......
/*
SPDX-FileCopyrightText: 2020 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KOPENINGHOURS_CONSECUTIVEACCUMULATOR_P_H
#define KOPENINGHOURS_CONSECUTIVEACCUMULATOR_P_H
#include <functional>
#include <QByteArray>
// Input: 1,2,4,5,6,9
// Output: 1-2,4-6,9
class ConsecutiveAccumulator
{
public:
// The std::function is usually QByteArray::number
// but it could be also 1->Mo, 2->Tu etc. if we need that one day.
explicit ConsecutiveAccumulator(std::function<QByteArray(int)> &&f)
: func(std::move(f)) {}
void add(int value)
{
if (!first && value == cur+1) {
++cur;
} else {
flush();
cur = value;
start = value;
first = false;
}
}
QByteArray result()
{
// Finalize
flush();
if (!expr.isEmpty()) {
expr.chop(1);
}
return expr;
}
private:
void flush()
{
if (!first) {
if (start < cur) {
if (cur >= 0) {
expr += func(start) + '-' + func(cur) + ',';
} else { // don't generate -2--1
for (int i = start; i <= cur; ++i) {
expr += func(i) + ',';
}
}
} else {
expr += func(cur) + ',';
}
}
}
bool first = true;
int cur = 0;
int start = 0;
QByteArray expr;
std::function<QByteArray(int)> func;
};
#endif
......@@ -135,24 +135,27 @@ SelectorResult WeekdayRange::nextIntervalLocal(const Interval &interval, const Q
switch (holiday) {
case NoHoliday:
{
if (nthMask > 0) {
for (int i = 1; i <= 10; ++i) {
if ((nthMask & (1 << i)) == 0) {
continue;
if (nthSequence) {
qint64 smallestOffset = INT_MAX;
for (const NthEntry &entry : nthSequence->sequence) {
Q_ASSERT(entry.begin <= entry.end);
for (int n = entry.begin; n <= entry.end; ++n) {
const auto d = nthWeekdayInMonth(dt.date().addDays(-offset), beginDay, n);
if (!d.isValid() || d.addDays(offset) < dt.date()) {
continue;
}
if (d.addDays(offset) == dt.date()) {
auto i = interval;
i.setBegin(QDateTime(d.addDays(offset), {0, 0}));
i.setEnd(QDateTime(d.addDays(offset + 1), {0, 0}));
return i;
}
// d > dt.date()
smallestOffset = qMin(smallestOffset, dt.secsTo(QDateTime(d.addDays(offset), {0, 0})));
}
const auto n = (i % 2) ? (-5 + (i /2)) : (i / 2);
const auto d = nthWeekdayInMonth(dt.date().addDays(-offset), beginDay, n);
if (!d.isValid() || d.addDays(offset) < dt.date()) {
continue;
}
if (d.addDays(offset) == dt.date()) {
auto i = interval;
i.setBegin(QDateTime(d.addDays(offset), {0, 0}));
i.setEnd(QDateTime(d.addDays(offset + 1), {0, 0}));
return i;
}
// d > dt.date()
return dt.secsTo(QDateTime(d.addDays(offset), {0, 0}));
}
if (smallestOffset < INT_MAX) {
return smallestOffset;
}
// skip to next month
......
......@@ -12,7 +12,6 @@
#include "interval.h"
#include "rule_p.h"
#include "logging.h"
#include "consecutiveaccumulator_p.h"
#include <QDateTime>
#include <QJsonArray>
......
......@@ -5,8 +5,8 @@
*/
#include "openinghours_p.h"
#include "openinghoursparser_p.h"
#include "openinghoursscanner_p.h"
#include "openinghoursparser_p.h" // generated
#include "openinghoursscanner_p.h" // generated
#include "logging.h"
using namespace KOpeningHours;
......@@ -113,6 +113,8 @@ typedef void* yyscan_t;
Time time;
Selectors selectors;
Timespan *timespan;
NthEntry nthEntry;
NthSequence *nthSequence;
WeekdayRange *weekdayRange;
Week *week;
Date date;
......@@ -189,8 +191,8 @@ typedef void* yyscan_t;
%type <weekdayRange> WeekdayRange
%type <weekdayRange> HolidaySequence
%type <weekdayRange> Holiday
%type <num> NthSequence
%type <num> NthEntry
%type <nthSequence> NthSequence
%type <nthEntry> NthEntry
%type <num> DayOffset
%type <dateOffset> DateOffset
%type <week> Week
......@@ -212,6 +214,7 @@ typedef void* yyscan_t;
delete $$.yearSelector;
} <selectors>
%destructor { delete $$; } <timespan>
%destructor { delete $$; } <nthSequence>
%destructor { delete $$; } <weekdayRange>
%destructor { delete $$; } <week>
%destructor { delete $$; } <monthdayRange>
......@@ -502,12 +505,12 @@ WeekdayRange:
| T_WEEKDAY[D] T_LBRACKET NthSequence[N] T_RBRACKET {
$$ = new WeekdayRange;
$$->beginDay = $$->endDay = $D;
$$->nthMask = $N;
$$->nthSequence.reset($N);
}
| T_WEEKDAY[D] T_LBRACKET NthSequence[N] T_RBRACKET DayOffset[O] {
$$ = new WeekdayRange;
$$->beginDay = $$->endDay = $D;
$$->nthMask = $N;
$$->nthSequence.reset($N);
$$->offset = $O;
}
;
......@@ -534,24 +537,27 @@ Holiday:
;
NthSequence:
NthEntry[N] { $$ = $N; }
| NthSequence[N1] T_COMMA NthEntry[N2] { $$ = $N1 | $N2; }
NthEntry[N] {
$$ = new NthSequence;
$$->add($N);
}
| NthSequence[N1] T_COMMA NthEntry[N2] {
$N1->add($N2);
$$ = $N1;
}
NthEntry:
T_INTEGER[N] {
if ($N < 1 || $N > 5) { YYABORT; }
$$ = (1 << (2 * $N));
$$ = {$N,$N};
}
| T_INTEGER[N1] T_MINUS T_INTEGER[N2] {
if ($N1 < 1 || $N1 > 5 || $N2 < 1 || $N2 > 5 || $N2 <= $N1) { YYABORT; }
$$ = 0;
for (int i = $N1; i <= $N2; ++i) {
$$ |= (1 << (2 * i));
}
$$ = {$N1,$N2};
}
| T_MINUS T_INTEGER[N] {
if ($N < 1 || $N > 5) { YYABORT; }
$$ = (1 << ((2 * (6 - $N)) - 1));
$$ = {-$N,-$N};
}
;
......
......@@ -7,7 +7,6 @@
#include "selectors_p.h"
#include "logging.h"
#include "openinghours_p.h"
#include "consecutiveaccumulator_p.h"
#include <cstdlib>
#include <cassert>
......@@ -153,13 +152,13 @@ bool Timespan::operator==(Timespan &other) const
int WeekdayRange::requiredCapabilities() const
{
// only ranges or nthMask are allowed, not both at the same time, enforced by parser
assert(beginDay == endDay || nthMask == 0);
// only ranges or nthSequence are allowed, not both at the same time, enforced by parser
assert(beginDay == endDay || !nthSequence);
int c = Capability::None;
switch (holiday) {
case NoHoliday:
if ((offset > 0 && nthMask == 0)) {
if ((offset > 0 && !nthSequence)) {
c |= Capability::NotImplemented;
}
break;
......@@ -201,21 +200,8 @@ QByteArray WeekdayRange::toExpression() const
expr = "SH";
break;
}
if (nthMask > 0) {
ConsecutiveAccumulator accu([](int i) { return QByteArray::number(i); });
// Negative numbers
for (int i = 1; i <= 10; i += 2) {
if ((nthMask & (1 << i)) != 0) {
accu.add(-5 + (i / 2));
}
}
// Positive numbers
for (int i = 2; i <= 10; i += 2) {
if ((nthMask & (1 << i)) != 0) {
accu.add(i / 2);
}
}
expr += '[' + accu.result() + ']';
if (nthSequence) {
expr += '[' + nthSequence->toExpression() + ']';
}
if (offset > 0) {
expr += " +" + QByteArray::number(offset) + ' ' + (offset > 1 ? "days" : "day");
......@@ -237,7 +223,7 @@ void WeekdayRange::simplify()
std::fill(std::begin(seenDays), std::end(seenDays), false);
for (WeekdayRange *selector = this; selector; selector = selector->next.get()) {
// Ensure it's all just week days, no other features
if (selector->nthMask || selector->lhsAndSelector || selector->holiday != NoHoliday || selector->offset) {
if (selector->nthSequence || selector->lhsAndSelector || selector->holiday != NoHoliday || selector->offset) {
return;
}
const bool wrap = selector->beginDay > selector->endDay;
......@@ -495,3 +481,26 @@ QByteArray YearRange::toExpression() const
}
return expr;
}
void NthSequence::add(NthEntry range)
{
sequence.push_back(std::move(range));
}
QByteArray NthSequence::toExpression() const
{
QByteArray ret;
for (const NthEntry &entry : sequence) {
if (!ret.isEmpty())
ret += ',';
ret += entry.toExpression();
}
return ret;
}
QByteArray NthEntry::toExpression() const
{
if (begin == end)
return QByteArray::number(begin);
return QByteArray::number(begin) + '-' + QByteArray::number(end);
}
......@@ -135,6 +135,21 @@ public:
std::unique_ptr<Timespan> next;
};
struct NthEntry {
int begin;
int end;
QByteArray toExpression() const;
};
/** Nth week days, like 1-2,4,6-8 */
class NthSequence
{
public:
void add(NthEntry range);
QByteArray toExpression() const;
std::vector<NthEntry> sequence;
};
/** Weekday range. */
class WeekdayRange
{
......@@ -147,7 +162,7 @@ public:
uint8_t beginDay = 0; // Mo=1, Tu=2, ..., Su=7
uint8_t endDay = 0;
uint16_t nthMask = 0;
std::unique_ptr<NthSequence> nthSequence;
int16_t offset = 0;
enum Holiday : uint8_t {
NoHoliday = 0,
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment