aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-24 16:18:02 +0200
committerDanilo M. <danix@danix.xyz>2026-09-24 16:18:02 +0200
commit5c6a11e72755cc500c77b5fa92dae43ab990f21e (patch)
tree3c88c2a4b4c3064b2c32f2d6495d6bfb70618c4c
parent53bef55d4d608cd15a30727f6aef7626a1de2120 (diff)
downloadqtmaildir-5c6a11e72755cc500c77b5fa92dae43ab990f21e.tar.gz
qtmaildir-5c6a11e72755cc500c77b5fa92dae43ab990f21e.zip
fix: expand a long dense series completely and flag overrides' own all-day
-rw-r--r--src/calendarstore.cpp10
-rw-r--r--tests/test_calendarstore.cpp33
2 files changed, 39 insertions, 4 deletions
diff --git a/src/calendarstore.cpp b/src/calendarstore.cpp
index 93f21d4..a9efe8f 100644
--- a/src/calendarstore.cpp
+++ b/src/calendarstore.cpp
@@ -123,9 +123,11 @@ QDateTime endOf(icalcomponent *c, const QDateTime &start, bool allDay, bool *unk
}
/// ponytail: iterates from DTSTART rather than icalrecur_iterator_set_start,
-/// which is unsupported with COUNT. Capped per series; raise the cap only if
-/// a real rule is measured hitting it.
-constexpr int kMaxIterations = 10000;
+/// which is unsupported with COUNT. The cap guards against a pathological rule
+/// (FREQ=SECONDLY over a wide window), not against age: a daily series from
+/// 1970 to a 22nd-century window is under 50k steps, well inside 100000. 100k
+/// iterator steps is still microseconds, so the cap is not a performance knob.
+constexpr int kMaxIterations = 100000;
bool overlaps(const QDateTime &start, const QDateTime &end,
const QDateTime &from, const QDateTime &to)
@@ -336,7 +338,7 @@ QList<Occurrence> occurrences(const QList<CalEvent> &events,
for (int o = 0; o < e.overrides.size(); ++o) {
const CalOverride &ov = e.overrides[o];
if (!ov.cancelled && overlaps(ov.start, ov.end, from, to))
- result.append({ i, ov.start, ov.end, e.allDay, ov.recurrenceId, true, o });
+ result.append({ i, ov.start, ov.end, ov.allDay, ov.recurrenceId, true, o });
}
}
std::sort(result.begin(), result.end(),
diff --git a/tests/test_calendarstore.cpp b/tests/test_calendarstore.cpp
index c7e4836..b27c6ec 100644
--- a/tests/test_calendarstore.cpp
+++ b/tests/test_calendarstore.cpp
@@ -101,6 +101,8 @@ private slots:
void honoursCountAndUntil();
void expandsMonthlyLastFridayAndYearlyByMonth();
void readsTheRepeatRuleExdatesAndOverrides();
+ void expandsADenseOldSeriesToTheWindow();
+ void anAllDayOverrideKeepsItsOwnAllDayFlag();
};
void TestCalendarStore::parsesAUtcEvent()
@@ -282,6 +284,24 @@ void TestCalendarStore::anOverrideReplacesItsSlotAndMovesFreely()
QCOMPARE(occ[0].recurrenceId, utc(2026, 9, 22, 8));
}
+void TestCalendarStore::anAllDayOverrideKeepsItsOwnAllDayFlag()
+{
+ // The master is timed, the override is an all-day "day off". The override
+ // occurrence must carry its OWN all-day state, not the master's.
+ const QString body = vevent(QStringLiteral(
+ "UID:adov@example.org\r\nDTSTART:20260921T080000Z\r\nDTEND:20260921T090000Z\r\n"
+ "RRULE:FREQ=DAILY\r\n"))
+ + vevent(QStringLiteral(
+ "UID:adov@example.org\r\nRECURRENCE-ID:20260922T080000Z\r\n"
+ "DTSTART;VALUE=DATE:20260922\r\nDTEND;VALUE=DATE:20260923\r\nSUMMARY:Day off\r\n"));
+ const QDateTime day0(QDate(2026, 9, 22), QTime(0, 0));
+ const QList<Occurrence> occ = expand(body, day0, day0.addDays(1));
+ QCOMPARE(occ.size(), 1);
+ QVERIFY(occ[0].isOverride);
+ QVERIFY(occ[0].allDay);
+ QCOMPARE(occ[0].recurrenceId, utc(2026, 9, 22, 8));
+}
+
void TestCalendarStore::anInfiniteSeriesYieldsOnlyTheWindow()
{
const QList<Occurrence> occ = expand(vevent(QStringLiteral(
@@ -291,6 +311,19 @@ void TestCalendarStore::anInfiniteSeriesYieldsOnlyTheWindow()
QCOMPARE(occ.first().start, utc(2026, 9, 1, 8));
}
+void TestCalendarStore::expandsADenseOldSeriesToTheWindow()
+{
+ // A daily series from 1970 needs ~20700 iterator steps to reach a 2026
+ // window. At a 10000 cap the walk dies around 1997 and returns nothing.
+ const QList<Occurrence> occ = expand(vevent(QStringLiteral(
+ "UID:dense@example.org\r\nDTSTART:19700101T080000Z\r\n"
+ "DTEND:19700101T090000Z\r\nRRULE:FREQ=DAILY\r\n")),
+ utc(2026, 9, 21), utc(2026, 9, 28));
+ QCOMPARE(occ.size(), 7);
+ QCOMPARE(occ.first().start, utc(2026, 9, 21, 8));
+ QCOMPARE(occ.last().start, utc(2026, 9, 27, 8));
+}
+
void TestCalendarStore::honoursCountAndUntil()
{
QCOMPARE(expand(vevent(QStringLiteral(