File indexing completed on 2024-05-05 16:05:25

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 
0010 #include "testicalformat.h"
0011 #include "event.h"
0012 #include "icalformat.h"
0013 #include "memorycalendar.h"
0014 #include "occurrenceiterator.h"
0015 
0016 #include <QTest>
0017 #include <QTimeZone>
0018 
0019 QTEST_MAIN(ICalFormatTest)
0020 
0021 using namespace KCalendarCore;
0022 
0023 void ICalFormatTest::testDeserializeSerialize()
0024 {
0025     ICalFormat format;
0026 
0027     const QString serializedCalendar = QLatin1String(
0028         "BEGIN:VCALENDAR\n"
0029         "PRODID:-//IDN nextcloud.com//Calendar app 2.0.4//EN\n"
0030         "VERSION:2.0\n"
0031         "BEGIN:VEVENT\n"
0032         "CREATED:20201103T161248Z\n"
0033         "DTSTAMP:20201103T161340Z\n"
0034         "LAST-MODIFIED:20201103T161340Z\n"
0035         "SEQUENCE:2\n"
0036         "UID:bd1d299d-3b03-4514-be69-e680ad2ff884\n"
0037         "DTSTART;TZID=Europe/Paris:20201103T100000\n"
0038         "DTEND;TZID=Europe/Paris:20201103T110000\n"
0039         "SUMMARY:test recur\n"
0040         "RRULE:FREQ=DAILY;COUNT=4\n"
0041         "END:VEVENT\n"
0042         "BEGIN:VEVENT\n"
0043         "CREATED:20201103T161823Z\n"
0044         "DTSTAMP:20201103T161823Z\n"
0045         "LAST-MODIFIED:20201103T161823Z\n"
0046         "SEQUENCE:1\n"
0047         "UID:bd1d299d-3b03-4514-be69-e680ad2ff884\n"
0048         "DTSTART;TZID=Europe/Paris:20201104T111500\n"
0049         "DTEND;TZID=Europe/Paris:20201104T121500\n"
0050         "SUMMARY:test recur\n"
0051         "COLOR:khaki\n"
0052         "RECURRENCE-ID;TZID=Europe/Paris:20201104T100000\n"
0053         "END:VEVENT\n"
0054         "END:VCALENDAR");
0055     MemoryCalendar::Ptr calendar = MemoryCalendar::Ptr(new MemoryCalendar(QTimeZone::utc()));
0056     QVERIFY(format.fromString(calendar, serializedCalendar));
0057     const QString uid = QString::fromLatin1("bd1d299d-3b03-4514-be69-e680ad2ff884");
0058     Incidence::Ptr parent = calendar->incidence(uid);
0059     QVERIFY(parent);
0060     const QDateTime start(QDate(2020, 11, 3), QTime(9, 0), QTimeZone::utc());
0061     QCOMPARE(parent->dtStart(), start);
0062     QCOMPARE(parent.staticCast<Event>()->dtEnd(), start.addSecs(3600));
0063     QCOMPARE(parent->summary(), QString::fromLatin1("test recur"));
0064     QCOMPARE(parent->revision(), 2);
0065     Recurrence *recur = parent->recurrence();
0066     QVERIFY(recur->recurs());
0067     QCOMPARE(recur->duration(), 4);
0068     QCOMPARE(recur->recurrenceType(), static_cast<ushort>(Recurrence::rDaily));
0069 
0070     Incidence::Ptr occurrence = calendar->incidence(uid, start.addDays(1));
0071     QVERIFY(occurrence);
0072     const QDateTime startOcc(QDate(2020, 11, 4), QTime(10, 15), QTimeZone::utc());
0073     QCOMPARE(occurrence->dtStart(), startOcc);
0074     QCOMPARE(occurrence.staticCast<Event>()->dtEnd(), startOcc.addSecs(3600));
0075     QCOMPARE(occurrence->color(), QString::fromLatin1("khaki"));
0076     QCOMPARE(occurrence->summary(), QString::fromLatin1("test recur"));
0077     QCOMPARE(occurrence->revision(), 1);
0078     QVERIFY(occurrence->hasRecurrenceId());
0079     QCOMPARE(occurrence->recurrenceId(), start.addDays(1));
0080 
0081     const QString serialization = format.toString(calendar, QString());
0082     QVERIFY(!serialization.isEmpty());
0083     MemoryCalendar::Ptr check = MemoryCalendar::Ptr(new MemoryCalendar(QTimeZone::utc()));
0084     QVERIFY(format.fromString(check, serialization));
0085     Incidence::Ptr reparent = check->incidence(uid);
0086     QVERIFY(reparent);
0087     QCOMPARE(*parent, *reparent);
0088     Incidence::Ptr reoccurence = check->incidence(uid, start.addDays(1));
0089     QVERIFY(reoccurence);
0090     QCOMPARE(*occurrence, *reoccurence);
0091 }
0092 
0093 void ICalFormatTest::testCharsets()
0094 {
0095     ICalFormat format;
0096     const QDate currentDate = QDate::currentDate();
0097     Event::Ptr event = Event::Ptr(new Event());
0098     event->setUid(QStringLiteral("12345"));
0099     event->setDtStart(QDateTime(currentDate, {}));
0100     event->setDtEnd(QDateTime(currentDate.addDays(1), {}));
0101     event->setAllDay(true);
0102 
0103     // ü
0104     const QChar latin1_umlaut[] = {(QChar)0xFC, QLatin1Char('\0')};
0105     event->setSummary(QString(latin1_umlaut));
0106 
0107     // Test if toString( Incidence ) didn't mess charsets
0108     const QString serialized = format.toString(event.staticCast<Incidence>());
0109     const QChar utf_umlaut[] = {(QChar)0xC3, (QChar)0XBC, QLatin1Char('\0')};
0110     QVERIFY(serialized.toUtf8().contains(QString(utf_umlaut).toLatin1().constData()));
0111     QVERIFY(!serialized.toUtf8().contains(QString(latin1_umlaut).toLatin1().constData()));
0112     QVERIFY(serialized.toLatin1().contains(QString(latin1_umlaut).toLatin1().constData()));
0113     QVERIFY(!serialized.toLatin1().contains(QString(utf_umlaut).toLatin1().constData()));
0114 
0115     // test fromString( QString )
0116     const QString serializedCalendar = QLatin1String("BEGIN:VCALENDAR\nPRODID:-//K Desktop Environment//NONSGML libkcal 3.2//EN\nVERSION:2.0\n") + serialized
0117         + QLatin1String("\nEND:VCALENDAR");
0118 
0119     Incidence::Ptr event2 = format.fromString(serializedCalendar);
0120     QVERIFY(event->summary() == event2->summary());
0121     QVERIFY(event2->summary().toUtf8() == QByteArray(QString(utf_umlaut).toLatin1().constData()));
0122 
0123     // test save()
0124     MemoryCalendar::Ptr calendar(new MemoryCalendar(QTimeZone::utc()));
0125     calendar->addIncidence(event);
0126     QVERIFY(format.save(calendar, QLatin1String("hommer.ics")));
0127 
0128     // Make sure hommer.ics is in UTF-8
0129     QFile file(QStringLiteral("hommer.ics"));
0130     QVERIFY(file.open(QIODevice::ReadOnly | QIODevice::Text));
0131 
0132     const QByteArray bytesFromFile = file.readAll();
0133     QVERIFY(bytesFromFile.contains(QString(utf_umlaut).toLatin1().constData()));
0134     QVERIFY(!bytesFromFile.contains(QString(latin1_umlaut).toLatin1().constData()));
0135     file.close();
0136 
0137     // Test load:
0138     MemoryCalendar::Ptr calendar2(new MemoryCalendar(QTimeZone::utc()));
0139     QVERIFY(format.load(calendar2, QLatin1String("hommer.ics")));
0140     QVERIFY(calendar2->incidences().count() == 1);
0141 
0142     Event::Ptr loadedEvent = calendar2->incidences().at(0).staticCast<Event>();
0143     QVERIFY(loadedEvent->summary().toUtf8() == QByteArray(QString(utf_umlaut).toLatin1().constData()));
0144     QVERIFY(*loadedEvent == *event);
0145 
0146     // Test fromRawString()
0147     MemoryCalendar::Ptr calendar3(new MemoryCalendar(QTimeZone::utc()));
0148     QVERIFY(format.fromRawString(calendar3, bytesFromFile));
0149     QVERIFY(calendar3->incidences().count() == 1);
0150     QVERIFY(*calendar3->incidences().at(0) == *event);
0151 
0152     QFile::remove(QStringLiteral("hommer.ics"));
0153 }
0154 
0155 void ICalFormatTest::testVolatileProperties()
0156 {
0157     // Volatile properties are not written to the serialized data
0158     ICalFormat format;
0159     const QDate currentDate = QDate::currentDate();
0160     Event::Ptr event = Event::Ptr(new Event());
0161     event->setUid(QStringLiteral("12345"));
0162     event->setDtStart(QDateTime(currentDate, {}));
0163     event->setDtEnd(QDateTime(currentDate.addDays(1), {}));
0164     event->setAllDay(true);
0165     event->setCustomProperty("VOLATILE", "FOO", QStringLiteral("BAR"));
0166     QString string = format.toICalString(event);
0167     Incidence::Ptr incidence = format.fromString(string);
0168 
0169     QCOMPARE(incidence->uid(), QStringLiteral("12345"));
0170     QVERIFY(incidence->customProperties().isEmpty());
0171 }
0172 
0173 void ICalFormatTest::testCuType()
0174 {
0175     ICalFormat format;
0176     const QDate currentDate = QDate::currentDate();
0177     Event::Ptr event(new Event());
0178     event->setUid(QStringLiteral("12345"));
0179     event->setDtStart(QDateTime(currentDate, {}));
0180     event->setDtEnd(QDateTime(currentDate.addDays(1), {}));
0181     event->setAllDay(true);
0182 
0183     Attendee attendee(QStringLiteral("fred"), QStringLiteral("fred@flintstone.com"));
0184     attendee.setCuType(Attendee::Resource);
0185 
0186     event->addAttendee(attendee);
0187 
0188     const QString serialized = format.toString(event.staticCast<Incidence>());
0189 
0190     // test fromString(QString)
0191     const QString serializedCalendar = QLatin1String("BEGIN:VCALENDAR\nPRODID:-//K Desktop Environment//NONSGML libkcal 3.2//EN\nVERSION:2.0\n") + serialized
0192         + QLatin1String("\nEND:VCALENDAR");
0193 
0194     Incidence::Ptr event2 = format.fromString(serializedCalendar);
0195     QVERIFY(event2->attendeeCount() == 1);
0196     Attendee attendee2 = event2->attendees()[0];
0197     QVERIFY(attendee2.cuType() == attendee.cuType());
0198     QVERIFY(attendee2.name() == attendee.name());
0199     QVERIFY(attendee2.email() == attendee.email());
0200 }
0201 
0202 void ICalFormatTest::testAlarm()
0203 {
0204     ICalFormat format;
0205 
0206     Event::Ptr event(new Event);
0207     event->setDtStart(QDate(2017, 03, 24).startOfDay());
0208     Alarm::Ptr alarm = event->newAlarm();
0209     alarm->setType(Alarm::Display);
0210     alarm->setStartOffset(Duration(0));
0211 
0212     const QString serialized = QLatin1String("BEGIN:VCALENDAR\nPRODID:-//K Desktop Environment//NONSGML libkcal 3.2//EN\nVERSION:2.0\n")
0213         + format.toString(event.staticCast<Incidence>()) + QLatin1String("\nEND:VCALENDAR");
0214 
0215     Incidence::Ptr event2 = format.fromString(serialized);
0216     Alarm::Ptr alarm2 = event2->alarms()[0];
0217     QCOMPARE(*alarm, *alarm2);
0218 }
0219 
0220 void ICalFormatTest::testDateTimeSerialization_data()
0221 {
0222     QTest::addColumn<QDateTime>("dtStart");
0223     QTest::addColumn<QByteArray>("dtStartData");
0224 
0225     QTest::newRow("UTC time spec")
0226         << QDateTime(QDate(2021, 4, 9), QTime(12, 00), Qt::UTC)
0227         << QByteArray("DTSTART:20210409T120000Z");
0228     QTest::newRow("UTC time zone")
0229         << QDateTime(QDate(2021, 4, 9), QTime(12, 00), QTimeZone::utc())
0230         << QByteArray("DTSTART:20210409T120000Z");
0231     QTest::newRow("named time zone")
0232         << QDateTime(QDate(2021, 4, 9), QTime(14, 00), QTimeZone("Europe/Paris"))
0233         << QByteArray("DTSTART;TZID=Europe/Paris:20210409T140000");
0234 }
0235 
0236 void ICalFormatTest::testDateTimeSerialization()
0237 {
0238     QFETCH(QDateTime, dtStart);
0239     QFETCH(QByteArray, dtStartData);
0240 
0241     Incidence::Ptr event(new Event);
0242     QVERIFY(event);
0243     event->setDtStart(dtStart);
0244     QCOMPARE(event->dtStart(), dtStart);
0245 
0246     ICalFormat format;
0247     const QByteArray output = format.toRawString(event);
0248     const QList<QByteArray> lines = output.split('\n');
0249     for (const QByteArray &line: lines) {
0250         if (line.startsWith(QByteArray("DTSTART"))) {
0251             QCOMPARE(line.chopped(1), dtStartData);
0252             break;
0253         }
0254     }
0255 }
0256 
0257 void ICalFormatTest::testRDate()
0258 {
0259     ICalFormat format;
0260 
0261     const QString serializedCalendar = QLatin1String(
0262         "BEGIN:VCALENDAR\n"
0263         "VERSION:2.0\n"
0264         "PRODID:-//Lotus Development Corporation//NONSGML Notes 9.0.1//EN_C\n"
0265         "METHOD:PUBLISH\n"
0266         "BEGIN:VEVENT\n"
0267         "DTSTART:20210630T100000Z\n"
0268         "DTEND:20210630T110000Z\n"
0269         "TRANSP:OPAQUE\n"
0270         "RDATE;VALUE=PERIOD:20210630T100000Z/20210630T110000Z\n"
0271         " ,20210825T100000Z/20210825T110000Z,20211027T100000Z/20211027T110000Z\n"
0272         " ,20211215T110000Z/PT2H\n"
0273         "LAST-MODIFIED:20210601T094627Z\n"
0274         "DTSTAMP:20210601T092939Z\n"
0275         "UID:5FC21473F5CC80CCC12586E70033ED9C-Lotus_Notes_Generated\n"
0276         "END:VEVENT\n"
0277         "END:VCALENDAR\n");
0278     MemoryCalendar::Ptr calendar(new MemoryCalendar(QTimeZone::utc()));
0279     QVERIFY(format.fromString(calendar, serializedCalendar));
0280     const QString uid = QString::fromLatin1("5FC21473F5CC80CCC12586E70033ED9C-Lotus_Notes_Generated");
0281     Incidence::Ptr event = calendar->incidence(uid);
0282     QVERIFY(event);
0283     QVERIFY(event->recurs());
0284 
0285     const QDateTime ev1(QDate(2021, 6, 30), QTime(10, 0), Qt::UTC);
0286     const QDateTime ev2(QDate(2021, 8, 25), QTime(10, 0), Qt::UTC);
0287     const QDateTime ev3(QDate(2021, 10, 27), QTime(10, 0), Qt::UTC);
0288     const QDateTime ev4(QDate(2021, 12, 15), QTime(11, 0), Qt::UTC);
0289     QCOMPARE(event->recurrence()->rDateTimes(),
0290              QList<QDateTime>() << ev1 << ev2 << ev3 << ev4);
0291 
0292     OccurrenceIterator it(*calendar, QDateTime(QDate(2021, 6, 1), QTime(0, 0)),
0293                           QDateTime(QDate(2021, 12, 31), QTime(0, 0)));
0294     QVERIFY(it.hasNext());
0295     it.next();
0296     QCOMPARE(it.occurrenceStartDate(), ev1);
0297     QCOMPARE(it.occurrenceEndDate(), ev1.addSecs(3600));
0298     QVERIFY(it.hasNext());
0299     it.next();
0300     QCOMPARE(it.occurrenceStartDate(), ev2);
0301     QCOMPARE(it.occurrenceEndDate(), ev2.addSecs(3600));
0302     QVERIFY(it.hasNext());
0303     it.next();
0304     QCOMPARE(it.occurrenceStartDate(), ev3);
0305     QCOMPARE(it.occurrenceEndDate(), ev3.addSecs(3600));
0306     QVERIFY(it.hasNext());
0307     it.next();
0308     QCOMPARE(it.occurrenceStartDate(), ev4);
0309     QCOMPARE(it.occurrenceEndDate(), ev4.addSecs(7200));
0310 
0311     const QStringList output = format.toString(calendar, QString()).split(QString::fromLatin1("\r\n"));
0312     QVERIFY(output.contains(QString::fromLatin1("RDATE;VALUE=PERIOD:20210630T100000Z/20210630T110000Z")));
0313     QVERIFY(output.contains(QString::fromLatin1("RDATE;VALUE=PERIOD:20210825T100000Z/20210825T110000Z")));
0314     QVERIFY(output.contains(QString::fromLatin1("RDATE;VALUE=PERIOD:20211027T100000Z/20211027T110000Z")));
0315     QVERIFY(output.contains(QString::fromLatin1("RDATE;VALUE=PERIOD:20211215T110000Z/PT2H")));
0316 }
0317 
0318 void ICalFormatTest::testDateTime_data()
0319 {
0320     QTest::addColumn<QByteArray>("dtStartData");
0321     QTest::addColumn<QDateTime>("dtStart");
0322 
0323     QTest::newRow("clock time")
0324         << QByteArray("DTSTART:20191113T130000")
0325         << QDateTime(QDate(2019, 11, 13), QTime(13, 00), Qt::LocalTime);
0326     QTest::newRow("date")
0327         << QByteArray("DTSTART;VALUE=DATE:20191113")
0328         << QDate(2019, 11, 13).startOfDay();
0329     QTest::newRow("UTC time")
0330         << QByteArray("DTSTART:20191113T130000Z")
0331         << QDateTime(QDate(2019, 11, 13), QTime(13, 00), Qt::UTC);
0332     QTest::newRow("time zone time")
0333         << QByteArray("DTSTART;TZID=Europe/Paris:20191113T130000")
0334         << QDateTime(QDate(2019, 11, 13), QTime(13, 00), QTimeZone("Europe/Paris"));
0335 }
0336 
0337 void ICalFormatTest::testDateTime()
0338 {
0339     QFETCH(QByteArray, dtStartData);
0340     QFETCH(QDateTime, dtStart);
0341 
0342     // test fromString(QString)
0343     const QByteArray serializedCalendar
0344         = "BEGIN:VCALENDAR\nPRODID:-//K Desktop Environment//NONSGML libkcal 3.2//EN\nVERSION:2.0\nBEGIN:VEVENT\nUID:12345\n"
0345           + dtStartData
0346           + "\nEND:VEVENT\nEND:VCALENDAR";
0347 
0348     ICalFormat format;
0349     Incidence::Ptr event = format.fromString(QString::fromUtf8(serializedCalendar));
0350     QVERIFY(event);
0351     QCOMPARE(dtStart, event->dtStart());
0352 }
0353 
0354 void ICalFormatTest::testNotebook()
0355 {
0356     Event::Ptr event(new Event);
0357     event->setDtStart(QDateTime(QDate(2022, 3, 21), QTime(8, 49), Qt::UTC));
0358     Todo::Ptr todo(new Todo);
0359     todo->setDtStart(QDateTime(QDate(2022, 3, 21), QTime(8, 49), Qt::UTC));
0360     Journal::Ptr journal(new Journal);
0361     journal->setDtStart(QDateTime(QDate(2022, 3, 21), QTime(8, 49), Qt::UTC));
0362     MemoryCalendar::Ptr calendar(new MemoryCalendar(QTimeZone::utc()));
0363     QVERIFY(calendar->addEvent(event));
0364     QVERIFY(calendar->addTodo(todo));
0365     QVERIFY(calendar->addJournal(journal));
0366 
0367     ICalFormat format;
0368     const QString data = format.toString(calendar, QString());
0369     QVERIFY(!format.exception());
0370 
0371     calendar->close();
0372     QVERIFY(!calendar->event(event->uid(), event->recurrenceId()));
0373     QVERIFY(!calendar->todo(todo->uid(), todo->recurrenceId()));
0374     QVERIFY(!calendar->journal(journal->uid(), journal->recurrenceId()));
0375 
0376     const QString notebook(QString::fromLatin1("my-imported-notebook"));
0377     QVERIFY(calendar->addNotebook(notebook, true));
0378     QVERIFY(format.fromString(calendar, data, notebook));
0379 
0380     Event::Ptr reloadedEvent = calendar->event(event->uid(), event->recurrenceId());
0381     QVERIFY(reloadedEvent);
0382     Todo::Ptr reloadedTodo = calendar->todo(todo->uid(), todo->recurrenceId());
0383     QVERIFY(reloadedTodo);
0384     Journal::Ptr reloadedJournal = calendar->journal(journal->uid(), journal->recurrenceId());
0385     QVERIFY(reloadedJournal);
0386 
0387     QCOMPARE(calendar->incidences(notebook).length(), 3);
0388     QCOMPARE(calendar->notebook(reloadedEvent), notebook);
0389     QCOMPARE(calendar->notebook(reloadedTodo), notebook);
0390     QCOMPARE(calendar->notebook(reloadedJournal), notebook);
0391 }
0392 
0393 /**
0394  * If an instance does not have a UID, one will be created for it.
0395  */
0396 void ICalFormatTest::testUidGeneration()
0397 {
0398     const QString serializedCalendar = QLatin1String(
0399         "BEGIN:VCALENDAR\n"
0400         "VERSION:2.0\n"
0401         "BEGIN:VEVENT\n"
0402         "DTSTAMP:20201103T161340Z\n"
0403         "SUMMARY:test\n"
0404         "END:VEVENT\n"
0405         "END:VCALENDAR");
0406     auto calendar = MemoryCalendar::Ptr(new MemoryCalendar(QTimeZone::utc()));
0407     ICalFormat format;
0408     QVERIFY(format.fromString(calendar, serializedCalendar));
0409     const auto events = calendar->events();
0410     QCOMPARE(events.count(), 1);
0411     const auto event = events[0];
0412     QVERIFY( ! event->uid().isEmpty());
0413 }
0414 
0415 /**
0416  * Generated UIDs do not depend on the order of properties.
0417  */
0418 void ICalFormatTest::testUidGenerationStability()
0419 {
0420     ICalFormat format;
0421 
0422     const QString serializedCalendar1 = QLatin1String(
0423         "BEGIN:VCALENDAR\n"
0424         "VERSION:2.0\n"
0425         "BEGIN:VEVENT\n"
0426         "DTSTAMP:20201103T161340Z\n"
0427         "SUMMARY:test\n"
0428         "END:VEVENT\n"
0429         "END:VCALENDAR");
0430     auto calendar1 = MemoryCalendar::Ptr(new MemoryCalendar(QTimeZone::utc()));
0431     QVERIFY(format.fromString(calendar1, serializedCalendar1));
0432     const auto events1 = calendar1->events();
0433     QCOMPARE(events1.count(), 1);
0434 
0435     const QString serializedCalendar2 = QLatin1String(
0436         "BEGIN:VCALENDAR\n"
0437         "VERSION:2.0\n"
0438         "BEGIN:VEVENT\n"
0439         "SUMMARY:test\n"
0440         "DTSTAMP:20201103T161340Z\n"    // Reordered.
0441         "END:VEVENT\n"
0442         "END:VCALENDAR");
0443     auto calendar2 = MemoryCalendar::Ptr(new MemoryCalendar(QTimeZone::utc()));
0444     QVERIFY(format.fromString(calendar2, serializedCalendar2));
0445     const auto events2 = calendar2->events();
0446     QCOMPARE(events2.count(), 1);
0447 
0448     const auto event1 = events1[0];
0449     const auto event2 = events2[0];
0450     QCOMPARE(event1->uid(), event2->uid());
0451 }
0452 
0453 /**
0454  * Generated UIDs depend on property names and values.
0455  */
0456 void ICalFormatTest::testUidGenerationUniqueness()
0457 {
0458     ICalFormat format;
0459 
0460     const QString serializedCalendar1 = QLatin1String(
0461         "BEGIN:VCALENDAR\n"
0462         "VERSION:2.0\n"
0463         "BEGIN:VEVENT\n"
0464         "DTSTAMP:20201103T161340Z\n"
0465         "SUMMARY:test\n"
0466         "END:VEVENT\n"
0467         "END:VCALENDAR");
0468     auto calendar1 = MemoryCalendar::Ptr(new MemoryCalendar(QTimeZone::utc()));
0469     QVERIFY(format.fromString(calendar1, serializedCalendar1));
0470     const auto events1 = calendar1->events();
0471     QCOMPARE(events1.count(), 1);
0472 
0473     const QString serializedCalendar2 = QLatin1String(
0474         "BEGIN:VCALENDAR\n"
0475         "VERSION:2.0\n"
0476         "BEGIN:VEVENT\n"
0477         "DTSTART:20201103T161340Z\n"    // Property name change.
0478         "SUMMARY:test\n"
0479         "END:VEVENT\n"
0480         "END:VCALENDAR");
0481     auto calendar2 = MemoryCalendar::Ptr(new MemoryCalendar(QTimeZone::utc()));
0482     QVERIFY(format.fromString(calendar2, serializedCalendar2));
0483     const auto events2 = calendar2->events();
0484     QCOMPARE(events2.count(), 1);
0485 
0486     const QString serializedCalendar3 = QLatin1String(
0487         "BEGIN:VCALENDAR\n"
0488         "VERSION:2.0\n"
0489         "BEGIN:VEVENT\n"
0490         "DTSTAMP:20201103T161341Z\n"    // Property value changed.
0491         "SUMMARY:test\n"
0492         "END:VEVENT\n"
0493         "END:VCALENDAR");
0494     auto calendar3 = MemoryCalendar::Ptr(new MemoryCalendar(QTimeZone::utc()));
0495     QVERIFY(format.fromString(calendar3, serializedCalendar3));
0496     const auto events3 = calendar3->events();
0497     QCOMPARE(events3.count(), 1);
0498 
0499     const auto event1 = events1[0];
0500     const auto event2 = events2[0];
0501     const auto event3 = events3[0];
0502     QVERIFY(event1->uid() != event2->uid());
0503     QVERIFY(event1->uid() != event3->uid());
0504     QVERIFY(event2->uid() != event3->uid());
0505 }
0506 
0507 void ICalFormatTest::testIcalFormat()
0508 {
0509     ICalFormat format;
0510     auto duration = format.durationFromString(QStringLiteral("PT2H"));
0511     QVERIFY(!duration.isNull());
0512     QCOMPARE(duration.asSeconds(), 7200);
0513     QCOMPARE(format.toString(duration), QLatin1String("PT2H"));
0514 }
0515 
0516 void ICalFormatTest::testNonTextCustomProperties()
0517 {
0518     const auto input = QLatin1String(
0519         "BEGIN:VCALENDAR\n"
0520         "VERSION:2.0\n"
0521         "BEGIN:VEVENT\n"
0522         "X-APPLE-TRAVEL-START;ROUTING=CAR;VALUE=URI;X-ADDRESS=Bingerdenallee 1\\n\n"
0523         " 6921 JN Duiven\\nNederland;X-TITLE=Home:\n"
0524         "X-APPLE-TRAVEL-DURATION;VALUE=DURATION:PT45M\n"
0525         "X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-ADDRESS=Olympus 1\\n3524 WB Utre\n"
0526         " cht\\nThe Netherlands;X-APPLE-RADIUS=49.91307222863458;X-TITLE=Olympus 1\n"
0527         " :geo:52.063921,5.128511\n"
0528         "BEGIN:VALARM\n"
0529         "TRIGGER;X-APPLE-RELATED-TRAVEL=-PT30M:-PT1H15M\n"
0530         "END:VALARM\n"
0531         "END:VEVENT\n"
0532         "END:VCALENDAR\n");
0533     ICalFormat format;
0534     MemoryCalendar::Ptr cal(new MemoryCalendar(QTimeZone::utc()));
0535     QVERIFY(format.fromString(cal, input));
0536     const auto events = cal->events();
0537     QCOMPARE(events.size(), 1);
0538 
0539     const auto event = events[0];
0540     QCOMPARE(event->nonKDECustomProperty("X-APPLE-TRAVEL-DURATION"), QLatin1String("PT45M"));
0541     QCOMPARE(event->nonKDECustomProperty("X-APPLE-TRAVEL-START"), QString());
0542     QCOMPARE(event->nonKDECustomProperty("X-APPLE-STRUCTURED-LOCATION"), QLatin1String("geo:52.063921,5.128511"));
0543 }
0544 
0545 void ICalFormatTest::testAllDaySchedulingMessage()
0546 {
0547     auto event = KCalendarCore::Event::Ptr::create();
0548     event->setSummary(QStringLiteral("All Day Event"));
0549     event->setDtStart(QDateTime(QDate(2023, 10, 13), QTime(0, 0, 0), QTimeZone("Europe/Prague")));
0550     event->setDtEnd(QDateTime(QDate(2023, 10, 15), QTime(0, 0, 0), QTimeZone("Europe/Prague")));
0551     event->setOrganizer(Person(QStringLiteral("Dan"), QStringLiteral("dvratil@example.com")));
0552     event->addAttendee(Attendee(QStringLiteral("Konqi"), QStringLiteral("konqi@example.com")));
0553     event->setAllDay(true);
0554 
0555     ICalFormat format;
0556     auto calendar = MemoryCalendar::Ptr::create(QTimeZone::utc());
0557     const auto itipString = format.createScheduleMessage(event, KCalendarCore::iTIPRequest);
0558     QVERIFY(!itipString.isEmpty());
0559 
0560     auto scheduleMsg = format.parseScheduleMessage(calendar, itipString);
0561     QVERIFY(scheduleMsg->error().isEmpty());
0562 
0563     auto parsedEvent = scheduleMsg->event().staticCast<KCalendarCore::Event>();
0564     QVERIFY(parsedEvent);
0565     QCOMPARE(parsedEvent->dtStart().date(), event->dtStart().date());
0566     QCOMPARE(parsedEvent->dtEnd().date(), event->dtEnd().date());
0567 }
0568 
0569 #include "moc_testicalformat.cpp"