File indexing completed on 2024-04-21 03:52:42
0001 /* 0002 This file is part of the kcalcore library. 0003 0004 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> 0005 SPDX-FileContributor: Sergio Martins <sergio.martins@kdab.com> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 #include "testtimesininterval.h" 0010 #include "event.h" 0011 0012 #include <QDebug> 0013 0014 #include <QTest> 0015 QTEST_MAIN(TimesInIntervalTest) 0016 0017 using namespace KCalendarCore; 0018 0019 void TimesInIntervalTest::test() 0020 { 0021 const QDateTime currentDate(QDate::currentDate(), {}); 0022 Event event; 0023 event.setDtStart(currentDate); 0024 event.setDtEnd(currentDate.addDays(1)); 0025 event.setAllDay(true); 0026 event.setSummary(QStringLiteral("Event1 Summary")); 0027 0028 event.recurrence()->setDaily(1); 0029 0030 //------------------------------------------------------------------------------------------------ 0031 // Just to warm up 0032 QVERIFY(event.recurs()); 0033 QVERIFY(event.recursAt(currentDate)); 0034 0035 //------------------------------------------------------------------------------------------------ 0036 // Daily recurrence that never stops. 0037 // Should return numDaysInInterval+1 occurrences 0038 const int numDaysInInterval = 7; 0039 QDateTime start(currentDate); 0040 QDateTime end(start.addDays(numDaysInInterval)); 0041 0042 start.setTime(QTime(0, 0, 0)); 0043 end.setTime(QTime(23, 59, 59)); 0044 auto dateList = event.recurrence()->timesInInterval(start, end); 0045 QVERIFY(dateList.count() == numDaysInInterval + 1); 0046 0047 //------------------------------------------------------------------------------------------------ 0048 // start == end == first day of the recurrence, should only return 1 occurrence 0049 end = start; 0050 end.setTime(QTime(23, 59, 59)); 0051 dateList = event.recurrence()->timesInInterval(start, end); 0052 QVERIFY(dateList.count() == 1); 0053 0054 //------------------------------------------------------------------------------------------------ 0055 // Test daily recurrence that only lasts X days 0056 const int recurrenceDuration = 3; 0057 event.recurrence()->setDuration(recurrenceDuration); 0058 end = start.addDays(100); 0059 dateList = event.recurrence()->timesInInterval(start, end); 0060 QVERIFY(dateList.count() == recurrenceDuration); 0061 //------------------------------------------------------------------------------------------------ 0062 // Test daily recurrence that only lasts X days, and give start == end == last day of 0063 // recurrence. Previous versions of kcal had a bug and didn't return an occurrence 0064 start = start.addDays(recurrenceDuration - 1); 0065 end = start; 0066 start.setTime(QTime(0, 0, 0)); 0067 end.setTime(QTime(23, 59, 59)); 0068 0069 dateList = event.recurrence()->timesInInterval(start, end); 0070 QVERIFY(dateList.count() == 1); 0071 0072 //------------------------------------------------------------------------------------------------ 0073 } 0074 0075 // Test that interval start and end are inclusive 0076 void TimesInIntervalTest::testSubDailyRecurrenceIntervalInclusive() 0077 { 0078 const QDateTime start(QDate(2013, 03, 10), QTime(10, 0, 0), QTimeZone::UTC); 0079 const QDateTime end(QDate(2013, 03, 10), QTime(11, 0, 0), QTimeZone::UTC); 0080 0081 KCalendarCore::Event::Ptr event(new KCalendarCore::Event()); 0082 event->setUid(QStringLiteral("event")); 0083 event->setDtStart(start); 0084 event->recurrence()->setHourly(1); 0085 event->recurrence()->setDuration(2); 0086 0087 QList<QDateTime> expectedEventOccurrences; 0088 expectedEventOccurrences << start << start.addSecs(60 * 60); 0089 0090 const auto timesInInterval = event->recurrence()->timesInInterval(start, end); 0091 for (const auto &dt : timesInInterval) { 0092 QCOMPARE(expectedEventOccurrences.removeAll(dt), 1); 0093 } 0094 QCOMPARE(expectedEventOccurrences.size(), 0); 0095 } 0096 0097 // Test that the recurrence dtStart is used for calculation and not the interval start date 0098 void TimesInIntervalTest::testSubDailyRecurrence2() 0099 { 0100 const QDateTime start(QDate(2013, 03, 10), QTime(10, 2, 3), QTimeZone::UTC); 0101 const QDateTime end(QDate(2013, 03, 10), QTime(13, 4, 5), QTimeZone::UTC); 0102 0103 KCalendarCore::Event::Ptr event(new KCalendarCore::Event()); 0104 event->setUid(QStringLiteral("event")); 0105 event->setDtStart(start); 0106 event->recurrence()->setHourly(1); 0107 event->recurrence()->setDuration(2); 0108 0109 QList<QDateTime> expectedEventOccurrences; 0110 expectedEventOccurrences << start << start.addSecs(60 * 60); 0111 0112 const auto timesInInterval = event->recurrence()->timesInInterval(start.addSecs(-20), end.addSecs(20)); 0113 // qDebug() << "timesInInterval " << timesInInterval; 0114 for (const auto &dt : timesInInterval) { 0115 // qDebug() << dt; 0116 QCOMPARE(expectedEventOccurrences.removeAll(dt), 1); 0117 } 0118 QCOMPARE(expectedEventOccurrences.size(), 0); 0119 } 0120 0121 void TimesInIntervalTest::testSubDailyRecurrenceIntervalLimits() 0122 { 0123 const QDateTime start(QDate(2013, 03, 10), QTime(10, 2, 3), QTimeZone::UTC); 0124 const QDateTime end(QDate(2013, 03, 10), QTime(12, 2, 3), QTimeZone::UTC); 0125 0126 KCalendarCore::Event::Ptr event(new KCalendarCore::Event()); 0127 event->setUid(QStringLiteral("event")); 0128 event->setDtStart(start); 0129 event->recurrence()->setHourly(1); 0130 event->recurrence()->setDuration(3); 0131 0132 QList<QDateTime> expectedEventOccurrences; 0133 expectedEventOccurrences << start.addSecs(60 * 60); 0134 0135 const auto timesInInterval = event->recurrence()->timesInInterval(start.addSecs(1), end.addSecs(-1)); 0136 for (const auto &dt : timesInInterval) { 0137 QCOMPARE(expectedEventOccurrences.removeAll(dt), 1); 0138 } 0139 QCOMPARE(expectedEventOccurrences.size(), 0); 0140 } 0141 0142 void TimesInIntervalTest::testLocalTimeHandlingNonAllDay() 0143 { 0144 // Create an event which occurs every weekday of every week, 0145 // starting from Friday the 11th of October, from 12 pm until 1 pm, clock time, 0146 // and lasts for two weeks, with three exception datetimes, 0147 // (only two of which will apply). 0148 const QDateTime startDt(QDate(2019, 10, 11), QTime(12, 0), QTimeZone::LocalTime); 0149 QTimeZone anotherZone("America/Toronto"); 0150 if (anotherZone.offsetFromUtc(startDt) == QTimeZone::systemTimeZone().offsetFromUtc(startDt)) { 0151 anotherZone = QTimeZone(QByteArray("Pacific/Midway")); 0152 } 0153 Event event; 0154 event.setAllDay(false); 0155 event.setDtStart(startDt); 0156 0157 RecurrenceRule *const rule = new RecurrenceRule(); 0158 rule->setRecurrenceType(RecurrenceRule::rDaily); 0159 rule->setStartDt(event.dtStart()); 0160 rule->setFrequency(1); 0161 rule->setDuration(14); 0162 rule->setByDays(QList<RecurrenceRule::WDayPos>() << RecurrenceRule::WDayPos(0, 1) // Monday 0163 << RecurrenceRule::WDayPos(0, 2) // Tuesday 0164 << RecurrenceRule::WDayPos(0, 3) // Wednesday 0165 << RecurrenceRule::WDayPos(0, 4) // Thursday 0166 << RecurrenceRule::WDayPos(0, 5)); // Friday 0167 0168 Recurrence *recurrence = event.recurrence(); 0169 recurrence->addRRule(rule); 0170 // 12 o'clock in local time, will apply. 0171 recurrence->addExDateTime(QDateTime(QDate(2019, 10, 15), QTime(12, 0), QTimeZone::LocalTime)); 0172 // 12 o'clock in another time zone, will not apply. 0173 recurrence->addExDateTime(QDateTime(QDate(2019, 10, 17), QTime(12, 0), anotherZone)); 0174 // The time in another time zone, corresponding to 12 o'clock in the system time zone, will apply. 0175 recurrence->addExDateTime(QDateTime(QDate(2019, 10, 24), QTime(12, 00), QTimeZone::systemTimeZone()).toTimeZone(anotherZone)); 0176 0177 // Expand the events and within a wide interval 0178 const DateTimeList timesInInterval = 0179 recurrence->timesInInterval(QDateTime(QDate(2019, 10, 05), QTime(0, 0)), QDateTime(QDate(2019, 10, 25), QTime(23, 59))); 0180 0181 // ensure that the expansion does not include weekend days, 0182 // nor either of the exception date times. 0183 const QList<int> expectedDays{11, 14, 16, 17, 18, 21, 22, 23, 25}; 0184 for (int day : expectedDays) { 0185 QVERIFY(timesInInterval.contains(QDateTime(QDate(2019, 10, day), QTime(12, 0), QTimeZone::LocalTime))); 0186 } 0187 QCOMPARE(timesInInterval.size(), expectedDays.size()); 0188 } 0189 0190 void TimesInIntervalTest::testLocalTimeHandlingAllDay() 0191 { 0192 // Create an event which occurs every weekday of every week, 0193 // starting from Friday the 11th of October, and lasts for two weeks, 0194 // with four exception datetimes (only three of which will apply). 0195 const QDateTime startDt(QDate(2019, 10, 11).startOfDay()); 0196 QTimeZone anotherZone("America/Toronto"); 0197 if (anotherZone.offsetFromUtc(startDt) == QTimeZone::systemTimeZone().offsetFromUtc(startDt)) { 0198 anotherZone = QTimeZone(QByteArray("Pacific/Midway")); 0199 } 0200 Event event; 0201 event.setAllDay(true); 0202 event.setDtStart(startDt); 0203 0204 RecurrenceRule *const rule = new RecurrenceRule(); 0205 rule->setRecurrenceType(RecurrenceRule::rDaily); 0206 rule->setStartDt(event.dtStart()); 0207 rule->setFrequency(1); 0208 rule->setDuration(14); 0209 rule->setByDays(QList<RecurrenceRule::WDayPos>() << RecurrenceRule::WDayPos(0, 1) // Monday 0210 << RecurrenceRule::WDayPos(0, 2) // Tuesday 0211 << RecurrenceRule::WDayPos(0, 3) // Wednesday 0212 << RecurrenceRule::WDayPos(0, 4) // Thursday 0213 << RecurrenceRule::WDayPos(0, 5)); // Friday 0214 0215 Recurrence *recurrence = event.recurrence(); 0216 recurrence->addRRule(rule); 0217 // A simple date, will apply. 0218 recurrence->addExDate(QDate(2019, 10, 14)); 0219 // A date only local time, will apply. 0220 recurrence->addExDateTime(QDate(2019, 10, 15).startOfDay()); 0221 // A date time starting at 00:00 in another zone, will not apply. 0222 recurrence->addExDateTime(QDateTime(QDate(2019, 10, 17), QTime(), anotherZone)); 0223 // A date time starting at 00:00 in the system time zone, will apply. 0224 recurrence->addExDateTime(QDateTime(QDate(2019, 10, 24), QTime(), QTimeZone::systemTimeZone())); 0225 0226 // Expand the events and within a wide interval 0227 const DateTimeList timesInInterval = 0228 recurrence->timesInInterval(QDateTime(QDate(2019, 10, 05), QTime(0, 0)), QDateTime(QDate(2019, 10, 25), QTime(23, 59))); 0229 0230 // ensure that the expansion does not include weekend days, 0231 // nor either of the exception date times. 0232 const QList<int> expectedDays{11, 16, 17, 18, 21, 22, 23, 25}; 0233 for (int day : expectedDays) { 0234 QVERIFY(timesInInterval.contains(QDate(2019, 10, day).startOfDay())); 0235 } 0236 QCOMPARE(timesInInterval.size(), expectedDays.size()); 0237 } 0238 0239 // Test that the recurrence dtStart is used for calculation and not the interval start date 0240 void TimesInIntervalTest::testByDayRecurrence() 0241 { 0242 const int days = 7; 0243 const QDateTime start(QDate(2020, 11, 6), QTime(2, 0, 0), QTimeZone::UTC); 0244 const QDateTime intervalEnd = start.addDays(days); 0245 const QDateTime intervalStart = start.addDays(-days); 0246 0247 Event::Ptr event(new Event()); 0248 event->setDtStart(start); 0249 event->setDtEnd(start.addSecs(3600)); 0250 0251 RecurrenceRule *const rule = new RecurrenceRule(); 0252 rule->setRecurrenceType(RecurrenceRule::rWeekly); 0253 rule->setStartDt(event->dtStart()); // the start day is a Friday 0254 rule->setFrequency(1); 0255 rule->setByDays(QList<RecurrenceRule::WDayPos>() << RecurrenceRule::WDayPos(0, 2) // Tuesday 0256 << RecurrenceRule::WDayPos(0, 3) // Wednesday 0257 << RecurrenceRule::WDayPos(0, 4) // Thursday 0258 << RecurrenceRule::WDayPos(0, 5)); // Friday 0259 event->recurrence()->addRRule(rule); 0260 0261 QList<QDateTime> expectedEventOccurrences; 0262 for (int i = 0; i <= days; ++i) { 0263 const QDateTime dt = start.addDays(i); 0264 if (dt.date().dayOfWeek() < 6 && dt.date().dayOfWeek() > 1) { 0265 expectedEventOccurrences << dt; 0266 } 0267 } 0268 0269 QCOMPARE(event->recurrence()->getNextDateTime(intervalStart), start); 0270 QCOMPARE(event->recurrence()->getNextDateTime(start.addDays(1)), start.addDays(4)); 0271 0272 const QList<QDateTime> timesInInterval = event->recurrence()->timesInInterval(intervalStart, intervalEnd); 0273 for (const QDateTime &dt : timesInInterval) { 0274 QCOMPARE(expectedEventOccurrences.removeAll(dt), 1); 0275 } 0276 0277 QCOMPARE(expectedEventOccurrences.size(), 0); 0278 } 0279 0280 void TimesInIntervalTest::testRDatePeriod() 0281 { 0282 const QDateTime start(QDate(2021, 8, 30), QTime(11, 14)); 0283 const QDateTime end(QDate(2021, 8, 30), QTime(11, 42)); 0284 const QDateTime other(QDate(2021, 8, 30), QTime(12, 00)); 0285 const QDateTime later(QDate(2021, 8, 30), QTime(15, 00)); 0286 Recurrence recur; 0287 0288 QVERIFY(!recur.recurs()); 0289 QVERIFY(!recur.rDateTimePeriod(start).isValid()); 0290 0291 recur.addRDateTimePeriod(Period(start, end)); 0292 QVERIFY(recur.recurs()); 0293 QCOMPARE(recur.rDateTimePeriod(start), Period(start, end)); 0294 0295 const QList<QDateTime> timesInInterval = recur.timesInInterval(start.addDays(-1), 0296 start.addDays(+1)); 0297 QCOMPARE(timesInInterval, QList<QDateTime>() << start); 0298 0299 recur.addRDateTime(other); 0300 QCOMPARE(recur.rDateTimes(), QList<QDateTime>() << start << other); 0301 QCOMPARE(recur.rDateTimePeriod(start), Period(start, end)); 0302 0303 recur.setRDateTimes(QList<QDateTime>() << other << later); 0304 QCOMPARE(recur.rDateTimes(), QList<QDateTime>() << other << later); 0305 QVERIFY(!recur.rDateTimePeriod(start).isValid()); 0306 } 0307 0308 #include "moc_testtimesininterval.cpp"