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"