diff -pruN 5:5.94.0-1/autotests/testicalformat.cpp 5:5.96.0-1/autotests/testicalformat.cpp
--- 5:5.94.0-1/autotests/testicalformat.cpp	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/autotests/testicalformat.cpp	2022-07-02 14:29:36.000000000 +0000
@@ -503,3 +503,41 @@ void ICalFormatTest::testUidGenerationUn
     QVERIFY(event1->uid() != event3->uid());
     QVERIFY(event2->uid() != event3->uid());
 }
+
+void ICalFormatTest::testIcalFormat()
+{
+    ICalFormat format;
+    auto duration = format.durationFromString(QStringLiteral("PT2H"));
+    QVERIFY(!duration.isNull());
+    QCOMPARE(duration.asSeconds(), 7200);
+    QCOMPARE(format.toString(duration), QLatin1String("PT2H"));
+}
+
+void ICalFormatTest::testNonTextCustomProperties()
+{
+    const auto input = QLatin1String(
+        "BEGIN:VCALENDAR\n"
+        "VERSION:2.0\n"
+        "BEGIN:VEVENT\n"
+        "X-APPLE-TRAVEL-START;ROUTING=CAR;VALUE=URI;X-ADDRESS=Bingerdenallee 1\\n\n"
+        " 6921 JN Duiven\\nNederland;X-TITLE=Home:\n"
+        "X-APPLE-TRAVEL-DURATION;VALUE=DURATION:PT45M\n"
+        "X-APPLE-STRUCTURED-LOCATION;VALUE=URI;X-ADDRESS=Olympus 1\\n3524 WB Utre\n"
+        " cht\\nThe Netherlands;X-APPLE-RADIUS=49.91307222863458;X-TITLE=Olympus 1\n"
+        " :geo:52.063921,5.128511\n"
+        "BEGIN:VALARM\n"
+        "TRIGGER;X-APPLE-RELATED-TRAVEL=-PT30M:-PT1H15M\n"
+        "END:VALARM\n"
+        "END:VEVENT\n"
+        "END:VCALENDAR\n");
+    ICalFormat format;
+    MemoryCalendar::Ptr cal(new MemoryCalendar(QTimeZone::utc()));
+    QVERIFY(format.fromString(cal, input));
+    const auto events = cal->events();
+    QCOMPARE(events.size(), 1);
+
+    const auto event = events[0];
+    QCOMPARE(event->nonKDECustomProperty("X-APPLE-TRAVEL-DURATION"), QLatin1String("PT45M"));
+    QCOMPARE(event->nonKDECustomProperty("X-APPLE-TRAVEL-START"), QString());
+    QCOMPARE(event->nonKDECustomProperty("X-APPLE-STRUCTURED-LOCATION"), QLatin1String("geo:52.063921,5.128511"));
+}
diff -pruN 5:5.94.0-1/autotests/testicalformat.h 5:5.96.0-1/autotests/testicalformat.h
--- 5:5.94.0-1/autotests/testicalformat.h	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/autotests/testicalformat.h	2022-07-02 14:29:36.000000000 +0000
@@ -30,6 +30,8 @@ private Q_SLOTS:
     void testUidGeneration();
     void testUidGenerationStability();
     void testUidGenerationUniqueness();
+    void testIcalFormat();
+    void testNonTextCustomProperties();
 };
 
 #endif
diff -pruN 5:5.94.0-1/autotests/testmemorycalendar.cpp 5:5.96.0-1/autotests/testmemorycalendar.cpp
--- 5:5.94.0-1/autotests/testmemorycalendar.cpp	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/autotests/testmemorycalendar.cpp	2022-07-02 14:29:36.000000000 +0000
@@ -518,7 +518,7 @@ void MemoryCalendarTest::testUpdateIncid
 
     // Adding event to cal, makes cal an observer of event.
     QVERIFY(cal->addIncidence(event));
-    QCOMPARE(cal->rawEventsForDate(dt).count(), 1);
+    QCOMPARE(cal->rawEventsForDate(dt.date(), dt.timeZone()).count(), 1);
 
     QVERIFY(cal->updateLastModifiedOnChange());
 
@@ -535,12 +535,12 @@ void MemoryCalendarTest::testUpdateIncid
     // Any modification within a startUpdates()/endUpdates() should not touch
     // lastModified field, before the changes are completed.
     event->startUpdates();
-    QVERIFY(cal->rawEventsForDate(dt).isEmpty());
+    QVERIFY(cal->rawEventsForDate(dt.date(), dt.timeZone()).isEmpty());
     event->setSummary(QString::fromLatin1("test again"));
     QCOMPARE(event->lastModified(), dt);
     event->endUpdates();
     QVERIFY(event->lastModified().secsTo(now) < 5);
-    QCOMPARE(cal->rawEventsForDate(dt).count(), 1);
+    QCOMPARE(cal->rawEventsForDate(dt.date(), dt.timeZone()).count(), 1);
 
     // Reset lastModified field.
     event->setLastModified(dt);
diff -pruN 5:5.94.0-1/CMakeLists.txt 5:5.96.0-1/CMakeLists.txt
--- 5:5.94.0-1/CMakeLists.txt	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/CMakeLists.txt	2022-07-02 14:29:36.000000000 +0000
@@ -1,11 +1,11 @@
 cmake_minimum_required(VERSION 3.16)
-set(KF_VERSION "5.94.0") # handled by release scripts
+set(KF_VERSION "5.96.0") # handled by release scripts
 
 project(KCalendarCore VERSION ${KF_VERSION})
 
 # ECM setup
 include(FeatureSummary)
-find_package(ECM 5.93.0  NO_MODULE)
+find_package(ECM 5.96.0  NO_MODULE)
 set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
 feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
 
@@ -22,6 +22,7 @@ include(ECMGenerateExportHeader)
 include(ECMGenerateHeaders)
 include(ECMGeneratePriFile)
 include(ECMGeneratePkgConfigFile)
+include(ECMDeprecationSettings)
 
 include(ECMSetupVersion)
 include(ECMQtDeclareLoggingCategory)
@@ -70,9 +71,9 @@ configure_package_config_file(
   INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}
 )
 
-
-add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050f02)
-
+ecm_set_disabled_deprecation_versions(
+    QT 5.15.2
+)
 
 ########### Targets ###########
 add_subdirectory(src)
diff -pruN 5:5.94.0-1/debian/changelog 5:5.96.0-1/debian/changelog
--- 5:5.94.0-1/debian/changelog	2022-05-19 21:59:01.000000000 +0000
+++ 5:5.96.0-1/debian/changelog	2022-07-31 11:32:55.000000000 +0000
@@ -1,3 +1,13 @@
+kcalcore (5:5.96.0-1) unstable; urgency=medium
+
+  [ Aurélien COUDERC ]
+  * New upstream release (5.96.0).
+  * Bump Standards-Version to 4.6.1, no change required.
+  * Update symbols from build for 5.96.0.
+  * Refresh lintian overrides.
+
+ -- Aurélien COUDERC <coucouf@debian.org>  Sun, 31 Jul 2022 13:32:55 +0200
+
 kcalcore (5:5.94.0-1) unstable; urgency=medium
 
   [ Aurélien COUDERC ]
diff -pruN 5:5.94.0-1/debian/control 5:5.96.0-1/debian/control
--- 5:5.94.0-1/debian/control	2022-04-29 21:00:56.000000000 +0000
+++ 5:5.96.0-1/debian/control	2022-07-31 11:32:55.000000000 +0000
@@ -9,13 +9,13 @@ Build-Depends: bison,
                cmake (>= 3.16~),
                debhelper-compat (= 13),
                doxygen,
-               extra-cmake-modules (>= 5.93.0~),
+               extra-cmake-modules (>= 5.96.0~),
                libical-dev (>= 3.0~),
                pkg-kde-tools (>> 0.15.15),
                qhelpgenerator-qt5,
                qtbase5-dev (>= 5.15.2~),
                qttools5-dev,
-Standards-Version: 4.6.0
+Standards-Version: 4.6.1
 Rules-Requires-Root: no
 Homepage: https://invent.kde.org/frameworks/kcalendarcore
 Vcs-Browser: https://salsa.debian.org/qt-kde-team/kde/kcalcore
diff -pruN 5:5.94.0-1/debian/libkf5calendarcore5abi2.symbols 5:5.96.0-1/debian/libkf5calendarcore5abi2.symbols
--- 5:5.94.0-1/debian/libkf5calendarcore5abi2.symbols	2022-04-29 21:45:52.000000000 +0000
+++ 5:5.96.0-1/debian/libkf5calendarcore5abi2.symbols	2022-07-31 11:32:55.000000000 +0000
@@ -339,6 +339,7 @@ libKF5CalendarCore.so.5abi2 libkf5calend
  _ZN13KCalendarCore16CustomPropertiesD1Ev@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore16CustomPropertiesD2Ev@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore16CustomPropertiesaSERKS0_@ABI_5_2 5:5.67.0
+ _ZN13KCalendarCore16staticMetaObjectE@ABI_5_2 5:5.96.0
  _ZN13KCalendarCore17ICalTimeZoneCache6insertERK10QByteArrayRKNS_12ICalTimeZoneE@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore17ICalTimeZoneCacheC1Ev@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore17ICalTimeZoneCacheC2Ev@ABI_5_2 5:5.67.0
@@ -536,6 +537,7 @@ libKF5CalendarCore.so.5abi2 libkf5calend
  _ZN13KCalendarCore8AttendeeaSERKS0_@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore8Calendar10duplicatesERK14QSharedPointerINS_9IncidenceEE@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore8Calendar10shiftTimesERK9QTimeZoneS3_@ABI_5_2 5:5.67.0
+ _ZN13KCalendarCore8Calendar10sortEventsEO7QVectorI14QSharedPointerINS_5EventEEENS_14EventSortFieldENS_13SortDirectionE@ABI_5_2 5:5.96.0
  _ZN13KCalendarCore8Calendar10sortEventsERK7QVectorI14QSharedPointerINS_5EventEEENS_14EventSortFieldENS_13SortDirectionE@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore8Calendar11addNotebookERK7QStringb@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore8Calendar11beginChangeERK14QSharedPointerINS_9IncidenceEE@ABI_5_2 5:5.67.0
@@ -548,7 +550,9 @@ libKF5CalendarCore.so.5abi2 libkf5calend
  _ZN13KCalendarCore8Calendar11setTimeZoneERK9QTimeZone@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore8Calendar12addIncidenceERK14QSharedPointerINS_9IncidenceEE@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore8Calendar12ownerChangedEv@ABI_5_2 5:5.85.0
+ _ZN13KCalendarCore8Calendar12setIsLoadingEb@ABI_5_2 5:5.96.0
  _ZN13KCalendarCore8Calendar12setProductIdERK7QString@ABI_5_2 5:5.67.0
+ _ZN13KCalendarCore8Calendar12sortJournalsEO7QVectorI14QSharedPointerINS_7JournalEEENS_16JournalSortFieldENS_13SortDirectionE@ABI_5_2 5:5.96.0
  _ZN13KCalendarCore8Calendar12sortJournalsERK7QVectorI14QSharedPointerINS_7JournalEEENS_16JournalSortFieldENS_13SortDirectionE@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore8Calendar12virtual_hookEiPv@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore8Calendar13doSetTimeZoneERK9QTimeZone@ABI_5_2 5:5.67.0
@@ -572,6 +576,7 @@ libKF5CalendarCore.so.5abi2 libkf5calend
  _ZN13KCalendarCore8Calendar16CalendarObserverD1Ev@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore8Calendar16CalendarObserverD2Ev@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore8Calendar16incidenceUpdatedERK7QStringRK9QDateTime@ABI_5_2 5:5.67.0
+ _ZN13KCalendarCore8Calendar16isLoadingChangedEv@ABI_5_2 5:5.96.0
  _ZN13KCalendarCore8Calendar16registerObserverEPNS0_16CalendarObserverE@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore8Calendar16startBatchAddingEv@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore8Calendar16staticMetaObjectE@ABI_5_2 5:5.67.0
@@ -597,6 +602,7 @@ libKF5CalendarCore.so.5abi2 libkf5calend
  _ZN13KCalendarCore8Calendar9endChangeERK14QSharedPointerINS_9IncidenceEE@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore8Calendar9idChangedEv@ABI_5_2 5:5.85.0
  _ZN13KCalendarCore8Calendar9setFilterEPNS_9CalFilterE@ABI_5_2 5:5.67.0
+ _ZN13KCalendarCore8Calendar9sortTodosEO7QVectorI14QSharedPointerINS_4TodoEEENS_13TodoSortFieldENS_13SortDirectionE@ABI_5_2 5:5.96.0
  _ZN13KCalendarCore8Calendar9sortTodosERK7QVectorI14QSharedPointerINS_4TodoEEENS_13TodoSortFieldENS_13SortDirectionE@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore8CalendarC1ERK10QByteArray@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore8CalendarC1ERK9QTimeZone@ABI_5_2 5:5.67.0
@@ -676,7 +682,9 @@ libKF5CalendarCore.so.5abi2 libkf5calend
  _ZN13KCalendarCore9CalFormat15loadedProductIdEv@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore9CalFormat18setLoadedProductIdERK7QString@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore9CalFormat9productIdEv@ABI_5_2 5:5.67.0
+ _ZN13KCalendarCore9CalFormatC1EPNS_16CalFormatPrivateE@ABI_5_2 5:5.96.0
  _ZN13KCalendarCore9CalFormatC1Ev@ABI_5_2 5:5.67.0
+ _ZN13KCalendarCore9CalFormatC2EPNS_16CalFormatPrivateE@ABI_5_2 5:5.96.0
  _ZN13KCalendarCore9CalFormatC2Ev@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore9CalFormatD0Ev@ABI_5_2 5:5.67.0
  _ZN13KCalendarCore9CalFormatD1Ev@ABI_5_2 5:5.67.0
@@ -798,7 +806,9 @@ libKF5CalendarCore.so.5abi2 libkf5calend
  _ZNK13KCalendarCore10ConferenceeqERKS0_@ABI_5_2 5:5.77.0
  _ZNK13KCalendarCore10ConferenceneERKS0_@ABI_5_2 5:5.77.0
  _ZNK13KCalendarCore10ICalFormat10timeZoneIdEv@ABI_5_2 5:5.67.0
+ _ZNK13KCalendarCore10ICalFormat18durationFromStringERK7QString@ABI_5_2 5:5.96.0
  _ZNK13KCalendarCore10ICalFormat8timeZoneEv@ABI_5_2 5:5.67.0
+ _ZNK13KCalendarCore10ICalFormat8toStringERKNS_8DurationE@ABI_5_2 5:5.96.0
  _ZNK13KCalendarCore10Recurrence10durationToERK5QDate@ABI_5_2 5:5.67.0
  _ZNK13KCalendarCore10Recurrence10durationToERK9QDateTime@ABI_5_2 5:5.67.0
  _ZNK13KCalendarCore10Recurrence10rDateTimesEv@ABI_5_2 5:5.67.0
@@ -1089,6 +1099,7 @@ libKF5CalendarCore.so.5abi2 libkf5calend
  _ZNK13KCalendarCore8Calendar8timeZoneEv@ABI_5_2 5:5.67.0
  _ZNK13KCalendarCore8Calendar9incidenceERK7QStringRK9QDateTime@ABI_5_2 5:5.67.0
  _ZNK13KCalendarCore8Calendar9instancesERK14QSharedPointerINS_9IncidenceEE@ABI_5_2 5:5.67.0
+ _ZNK13KCalendarCore8Calendar9isLoadingEv@ABI_5_2 5:5.96.0
  _ZNK13KCalendarCore8Calendar9isVisibleERK14QSharedPointerINS_9IncidenceEE@ABI_5_2 5:5.67.0
  _ZNK13KCalendarCore8Calendar9isVisibleERK7QString@ABI_5_2 5:5.69.0
  _ZNK13KCalendarCore8Calendar9notebooksEv@ABI_5_2 5:5.67.0
diff -pruN 5:5.94.0-1/debian/libkf5calendarcore-dev.lintian-overrides 5:5.96.0-1/debian/libkf5calendarcore-dev.lintian-overrides
--- 5:5.94.0-1/debian/libkf5calendarcore-dev.lintian-overrides	2022-02-02 11:32:37.000000000 +0000
+++ 5:5.96.0-1/debian/libkf5calendarcore-dev.lintian-overrides	2022-07-31 11:32:55.000000000 +0000
@@ -1,4 +1,2 @@
-# header file for "to do" items
-libkf5calendarcore-dev: package-contains-documentation-outside-usr-share-doc usr/include/KF5/KCalendarCore/KCal*Core/Todo
 # shipped like this by upstream
 libkf5calendarcore-dev: repeated-path-segment
diff -pruN 5:5.94.0-1/src/attendee.cpp 5:5.96.0-1/src/attendee.cpp
--- 5:5.94.0-1/src/attendee.cpp	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/attendee.cpp	2022-07-02 14:29:36.000000000 +0000
@@ -40,8 +40,8 @@ public:
     QString cuTypeStr() const;
 
     bool mRSVP = false;
-    Role mRole;
-    PartStat mStatus;
+    Role mRole = Attendee::ReqParticipant;
+    PartStat mStatus = Attendee::NeedsAction;
     mutable QString mUid;
     QString mDelegate;
     QString mDelegator;
@@ -51,7 +51,7 @@ public:
 
 private:
     QString sCuType;
-    CuType mCuType;
+    CuType mCuType = Attendee::Individual;
 };
 //@endcond
 
diff -pruN 5:5.94.0-1/src/calendar.cpp 5:5.96.0-1/src/calendar.cpp
--- 5:5.94.0-1/src/calendar.cpp	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/calendar.cpp	2022-07-02 14:29:36.000000000 +0000
@@ -479,48 +479,47 @@ Incidence::List Calendar::incidences(con
     }
 }
 
+#if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 95)
 /** static */
 Event::List Calendar::sortEvents(const Event::List &eventList, EventSortField sortField, SortDirection sortDirection)
 {
-    if (eventList.isEmpty()) {
-        return Event::List();
-    }
-
-    Event::List eventListSorted;
+    Event::List eventListSorted(eventList);
+    return sortEvents(std::move(eventListSorted), sortField, sortDirection);
+}
+#endif
 
-    // Notice we alphabetically presort Summaries first.
-    // We do this so comparison "ties" stay in a nice order.
-    eventListSorted = eventList;
+Event::List Calendar::sortEvents(Event::List &&eventList, EventSortField sortField, SortDirection sortDirection)
+{
     switch (sortField) {
     case EventSortUnsorted:
         break;
 
     case EventSortStartDate:
         if (sortDirection == SortDirectionAscending) {
-            std::sort(eventListSorted.begin(), eventListSorted.end(), Events::startDateLessThan);
+            std::sort(eventList.begin(), eventList.end(), Events::startDateLessThan);
         } else {
-            std::sort(eventListSorted.begin(), eventListSorted.end(), Events::startDateMoreThan);
+            std::sort(eventList.begin(), eventList.end(), Events::startDateMoreThan);
         }
         break;
 
     case EventSortEndDate:
         if (sortDirection == SortDirectionAscending) {
-            std::sort(eventListSorted.begin(), eventListSorted.end(), Events::endDateLessThan);
+            std::sort(eventList.begin(), eventList.end(), Events::endDateLessThan);
         } else {
-            std::sort(eventListSorted.begin(), eventListSorted.end(), Events::endDateMoreThan);
+            std::sort(eventList.begin(), eventList.end(), Events::endDateMoreThan);
         }
         break;
 
     case EventSortSummary:
         if (sortDirection == SortDirectionAscending) {
-            std::sort(eventListSorted.begin(), eventListSorted.end(), Events::summaryLessThan);
+            std::sort(eventList.begin(), eventList.end(), Events::summaryLessThan);
         } else {
-            std::sort(eventListSorted.begin(), eventListSorted.end(), Events::summaryMoreThan);
+            std::sort(eventList.begin(), eventList.end(), Events::summaryMoreThan);
         }
         break;
     }
 
-    return eventListSorted;
+    return eventList;
 }
 
 Event::List Calendar::events(const QDate &date, const QTimeZone &timeZone, EventSortField sortField, SortDirection sortDirection) const
@@ -532,7 +531,7 @@ Event::List Calendar::events(const QDate
 
 Event::List Calendar::events(const QDateTime &dt) const
 {
-    Event::List el = rawEventsForDate(dt);
+    Event::List el = rawEventsForDate(dt.date(), dt.timeZone());
     d->mFilter->apply(&el);
     return el;
 }
@@ -665,83 +664,80 @@ Incidence::Ptr Calendar::incidenceFromSc
     return it != itEnd ? *it : Incidence::Ptr();
 }
 
+#if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 95)
 /** static */
 Todo::List Calendar::sortTodos(const Todo::List &todoList, TodoSortField sortField, SortDirection sortDirection)
 {
-    if (todoList.isEmpty()) {
-        return Todo::List();
-    }
-
-    Todo::List todoListSorted;
-
-    // Notice we alphabetically presort Summaries first.
-    // We do this so comparison "ties" stay in a nice order.
+    Todo::List todoListSorted(todoList);
+    return sortTodos(std::move(todoListSorted), sortField, sortDirection);
+}
+#endif
 
+Todo::List Calendar::sortTodos(Todo::List &&todoList, TodoSortField sortField, SortDirection sortDirection)
+{
     // Note that To-dos may not have Start DateTimes nor due DateTimes.
-
-    todoListSorted = todoList;
     switch (sortField) {
     case TodoSortUnsorted:
         break;
 
     case TodoSortStartDate:
         if (sortDirection == SortDirectionAscending) {
-            std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::startDateLessThan);
+            std::sort(todoList.begin(), todoList.end(), Todos::startDateLessThan);
         } else {
-            std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::startDateMoreThan);
+            std::sort(todoList.begin(), todoList.end(), Todos::startDateMoreThan);
         }
         break;
 
     case TodoSortDueDate:
         if (sortDirection == SortDirectionAscending) {
-            std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::dueDateLessThan);
+            std::sort(todoList.begin(), todoList.end(), Todos::dueDateLessThan);
         } else {
-            std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::dueDateMoreThan);
+            std::sort(todoList.begin(), todoList.end(), Todos::dueDateMoreThan);
         }
         break;
 
     case TodoSortPriority:
         if (sortDirection == SortDirectionAscending) {
-            std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::priorityLessThan);
+            std::sort(todoList.begin(), todoList.end(), Todos::priorityLessThan);
         } else {
-            std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::priorityMoreThan);
+            std::sort(todoList.begin(), todoList.end(), Todos::priorityMoreThan);
         }
         break;
 
     case TodoSortPercentComplete:
         if (sortDirection == SortDirectionAscending) {
-            std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::percentLessThan);
+            std::sort(todoList.begin(), todoList.end(), Todos::percentLessThan);
         } else {
-            std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::percentMoreThan);
+            std::sort(todoList.begin(), todoList.end(), Todos::percentMoreThan);
         }
         break;
 
     case TodoSortSummary:
         if (sortDirection == SortDirectionAscending) {
-            std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::summaryLessThan);
+            std::sort(todoList.begin(), todoList.end(), Todos::summaryLessThan);
         } else {
-            std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::summaryMoreThan);
+            std::sort(todoList.begin(), todoList.end(), Todos::summaryMoreThan);
         }
         break;
 
     case TodoSortCreated:
         if (sortDirection == SortDirectionAscending) {
-            std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::createdLessThan);
+            std::sort(todoList.begin(), todoList.end(), Todos::createdLessThan);
         } else {
-            std::sort(todoListSorted.begin(), todoListSorted.end(), Todos::createdMoreThan);
+            std::sort(todoList.begin(), todoList.end(), Todos::createdMoreThan);
         }
         break;
 
     case TodoSortCategories:
         if (sortDirection == SortDirectionAscending) {
-            std::sort(todoListSorted.begin(), todoListSorted.end(), Incidences::categoriesLessThan);
+            std::sort(todoList.begin(), todoList.end(), Incidences::categoriesLessThan);
         } else {
-            std::sort(todoListSorted.begin(), todoListSorted.end(), Incidences::categoriesMoreThan);
+            std::sort(todoList.begin(), todoList.end(), Incidences::categoriesMoreThan);
         }
         break;
     }
 
-    return todoListSorted;
+    return todoList;
 }
 
 Todo::List Calendar::todos(TodoSortField sortField, SortDirection sortDirection) const
@@ -765,37 +761,39 @@ Todo::List Calendar::todos(const QDate &
     return tl;
 }
 
+#if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 95)
 /** static */
 Journal::List Calendar::sortJournals(const Journal::List &journalList, JournalSortField sortField, SortDirection sortDirection)
 {
-    if (journalList.isEmpty()) {
-        return Journal::List();
-    }
-
     Journal::List journalListSorted = journalList;
+    return sortJournals(std::move(journalListSorted), sortField, sortDirection);
+}
+#endif
 
+Journal::List Calendar::sortJournals(Journal::List &&journalList, JournalSortField sortField, SortDirection sortDirection)
+{
     switch (sortField) {
     case JournalSortUnsorted:
         break;
 
     case JournalSortDate:
         if (sortDirection == SortDirectionAscending) {
-            std::sort(journalListSorted.begin(), journalListSorted.end(), Journals::dateLessThan);
+            std::sort(journalList.begin(), journalList.end(), Journals::dateLessThan);
         } else {
-            std::sort(journalListSorted.begin(), journalListSorted.end(), Journals::dateMoreThan);
+            std::sort(journalList.begin(), journalList.end(), Journals::dateMoreThan);
         }
         break;
 
     case JournalSortSummary:
         if (sortDirection == SortDirectionAscending) {
-            std::sort(journalListSorted.begin(), journalListSorted.end(), Journals::summaryLessThan);
+            std::sort(journalList.begin(), journalList.end(), Journals::summaryLessThan);
         } else {
-            std::sort(journalListSorted.begin(), journalListSorted.end(), Journals::summaryMoreThan);
+            std::sort(journalList.begin(), journalList.end(), Journals::summaryMoreThan);
         }
         break;
     }
 
-    return journalListSorted;
+    return journalList;
 }
 
 Journal::List Calendar::journals(JournalSortField sortField, SortDirection sortDirection) const
@@ -1403,3 +1401,18 @@ void Calendar::setAccessMode(const Acces
         Q_EMIT accessModeChanged();
     }
 }
+
+bool Calendar::isLoading() const
+{
+    return d->mIsLoading;
+}
+
+void Calendar::setIsLoading(bool isLoading)
+{
+    if (d->mIsLoading == isLoading) {
+        return;
+    }
+
+    d->mIsLoading = isLoading;
+    Q_EMIT isLoadingChanged();
+}
diff -pruN 5:5.94.0-1/src/calendar.h 5:5.96.0-1/src/calendar.h
--- 5:5.94.0-1/src/calendar.h	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/calendar.h	2022-07-02 14:29:36.000000000 +0000
@@ -50,6 +50,8 @@ This API needs serious cleaning up:
 /** Namespace for all KCalendarCore types. */
 namespace KCalendarCore
 {
+Q_NAMESPACE_EXPORT(KCALENDARCORE_EXPORT)
+
 class CalFilter;
 class Person;
 class ICalFormat;
@@ -103,6 +105,7 @@ enum AccessMode {
     ReadOnly,
     ReadWrite,
 };
+Q_ENUM_NS(AccessMode)
 
 /**
   @brief
@@ -134,7 +137,8 @@ class KCALENDARCORE_EXPORT Calendar : pu
     Q_PROPERTY(QString id READ id WRITE setId NOTIFY idChanged)
     Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
     Q_PROPERTY(QIcon icon READ icon WRITE setIcon NOTIFY iconChanged)
-    Q_PROPERTY(AccessMode accessMode READ accessMode WRITE setAccessMode NOTIFY accessModeChanged)
+    Q_PROPERTY(KCalendarCore::AccessMode accessMode READ accessMode WRITE setAccessMode NOTIFY accessModeChanged)
+    Q_PROPERTY(bool isLoading READ isLoading NOTIFY isLoadingChanged)
 
 public:
     /**
@@ -335,6 +339,13 @@ public:
     void setAccessMode(const AccessMode mode);
 
     /**
+     * Returns @c true if the calendar is still loading its data and thus
+     * read access will not return complete (or even any) results.
+     * @since 5.96
+     */
+    bool isLoading() const;
+
+    /**
       Clears out the current calendar, freeing all used memory etc.
     */
     virtual void close() = 0;
@@ -694,6 +705,7 @@ public:
     */
     virtual bool deleteEventInstances(const Event::Ptr &event) = 0;
 
+#if KCALENDARCORE_ENABLE_DEPRECATED_SINCE(5, 95)
     /**
       Sort a list of Events.
 
@@ -702,8 +714,30 @@ public:
       @param sortDirection specifies the SortDirection.
 
       @return a list of Events sorted as specified.
-    */
+
+      @deprecated since 5.95 Use the sortEvents(Event::List &&eventList, EventSortField sortField, SortDirection sortDirection)
+      overload instead. In the common case that you are sorting a list in-place, wrapping the @p eventList
+      argument with std::move will be all that's needed. In the less common case you actually want a copy,
+      create that explicitly first.
+    */
+    KCALENDARCORE_DEPRECATED_VERSION(
+        5,
+        95,
+        "Use sortEvents(Event::List &&eventList, EventSortField sortField, SortDirection sortDirection); see API docs for details.")
     static Event::List sortEvents(const Event::List &eventList, EventSortField sortField, SortDirection sortDirection);
+#endif
+    /**
+      Sort a list of Events.
+
+      @param eventList the list of events that should be sorted. The list is sorted in place and returned.
+      @param sortField specifies the EventSortField.
+      @param sortDirection specifies the SortDirection.
+
+      @return a list of Events sorted as specified.
+      @since 5.95
+    */
+    static Event::List sortEvents(Event::List &&eventList, EventSortField sortField, SortDirection sortDirection);
+
     /**
       Returns a sorted, filtered list of all Events for this Calendar.
 
@@ -766,6 +800,7 @@ public:
     */
     virtual Event::List rawEvents(EventSortField sortField = EventSortUnsorted, SortDirection sortDirection = SortDirectionAscending) const = 0;
 
+#if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 95)
     /**
       Returns an unfiltered list of all Events which occur on the given
       timestamp.
@@ -774,8 +809,12 @@ public:
 
       @return the list of unfiltered Events occurring on the specified
       timestamp.
+
+      @deprecated since 5.95 use rawEventsForDate(dt.date(), dt.timeZone()) overload instead.
     */
+    KCALENDARCORE_DEPRECATED_VERSION(5, 95, "Use rawEventsForDate(dt.date(), dt.timeZone()) insead")
     virtual Event::List rawEventsForDate(const QDateTime &dt) const = 0;
+#endif
 
     /**
       Returns an unfiltered list of all Events occurring within a date range.
@@ -891,6 +930,7 @@ public:
     */
     virtual bool deleteTodoInstances(const Todo::Ptr &todo) = 0;
 
+#if KCALENDARCORE_ENABLE_DEPRECATED_SINCE(5, 95)
     /**
       Sort a list of Todos.
 
@@ -899,8 +939,29 @@ public:
       @param sortDirection specifies the SortDirection.
 
       @return a list of Todos sorted as specified.
-    */
+
+      @deprecated since 5.95 Use the sortTodos(Todo::List &&todoList, TodoSortField sortField, SortDirection sortDirection)
+      overload instead. In the common case that you are sorting a list in-place, wrapping the @p todoList
+      argument with std::move will be all that's needed. In the less common case you actually want a copy,
+      create that explicitly first.
+    */
+    KCALENDARCORE_DEPRECATED_VERSION(5,
+                                     95,
+                                     "Use sortTodos(Todo::List &&todoList, TodoSortField sortField, SortDirection sortDirection); see API docs for details.")
     static Todo::List sortTodos(const Todo::List &todoList, TodoSortField sortField, SortDirection sortDirection);
+#endif
+    /**
+      Sort a list of Todos.
+
+      @param todoList the list of todos that should be sorted. The list is sorted in place and returned.
+      @param sortField specifies the TodoSortField.
+      @param sortDirection specifies the SortDirection.
+
+      @return a list of Todos sorted as specified.
+
+      @since 5.95
+    */
+    static Todo::List sortTodos(Todo::List &&todoList, TodoSortField sortField, SortDirection sortDirection);
 
     /**
       Returns a sorted, filtered list of all Todos for this Calendar.
@@ -1052,6 +1113,7 @@ public:
     */
     virtual bool deleteJournalInstances(const Journal::Ptr &journal) = 0;
 
+#if KCALENDARCORE_ENABLE_DEPRECATED_SINCE(5, 95)
     /**
       Sort a list of Journals.
 
@@ -1060,8 +1122,30 @@ public:
       @param sortDirection specifies the SortDirection.
 
       @return a list of Journals sorted as specified.
-    */
+
+      @deprecated since 5.95 Use the sortJournals(Journal::List &&journalList, JournalSortField sortField, SortDirection sortDirection)
+      overload instead. In the common case that you are sorting a list in-place, wrapping the @p journalList
+      argument with std::move will be all that's needed. In the less common case you actually want a copy,
+      create that explicitly first.
+    */
+    KCALENDARCORE_DEPRECATED_VERSION(
+        5,
+        95,
+        "Use sortJournals(Journal::List &&journalList, JournalSortField sortField, SortDirection sortDirection); see API docs for details.")
     static Journal::List sortJournals(const Journal::List &journalList, JournalSortField sortField, SortDirection sortDirection);
+#endif
+    /**
+      Sort a list of Journals.
+
+      @param journalList the list of journals that should be sorted. The list is sorted in place and returned.
+      @param sortField specifies the JournalSortField.
+      @param sortDirection specifies the SortDirection.
+
+      @return a list of Journals sorted as specified.
+      @since 5.95
+    */
+    static Journal::List sortJournals(Journal::List &&journalList, JournalSortField sortField, SortDirection sortDirection);
+
     /**
       Returns a sorted, filtered list of all Journals for this Calendar.
 
@@ -1406,6 +1490,15 @@ protected:
     bool deletionTracking() const;
 
     /**
+     * Sets the loading state of this calendar.
+     * This is false by default and only needs to be called for calendars
+     * that implement asynchronous loading.
+     * @since 5.96
+     * @see isLoading()
+     */
+    void setIsLoading(bool isLoading);
+
+    /**
       @copydoc
       IncidenceBase::virtual_hook()
     */
@@ -1453,6 +1546,13 @@ Q_SIGNALS:
      */
     void ownerChanged();
 
+    /**
+     * Emitted when the loading state changed.
+     * @since 5.96
+     * @see isLoading()
+     */
+    void isLoadingChanged();
+
 private:
     friend class ICalFormat;
 
diff -pruN 5:5.94.0-1/src/calendar_p.h 5:5.96.0-1/src/calendar_p.h
--- 5:5.94.0-1/src/calendar_p.h	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/calendar_p.h	2022-07-02 14:29:36.000000000 +0000
@@ -75,6 +75,7 @@ public:
     QMap<QString, Incidence::List> mIncidenceRelations;
     bool batchAddingInProgress = false;
     bool mDeletionTracking = false;
+    bool mIsLoading = false;
     QString mId;
     QString mName;
     QIcon mIcon;
diff -pruN 5:5.94.0-1/src/calformat.cpp 5:5.96.0-1/src/calformat.cpp
--- 5:5.94.0-1/src/calformat.cpp	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/calformat.cpp	2022-07-02 14:29:36.000000000 +0000
@@ -17,89 +17,74 @@
 */
 
 #include "calformat.h"
+#include "calformat_p.h"
 #include "exceptions.h"
 
 #include <QUuid>
 
 using namespace KCalendarCore;
 
-/**
-  Private class that helps to provide binary compatibility between releases.
-  @internal
-*/
-//@cond PRIVATE
-class Q_DECL_HIDDEN KCalendarCore::CalFormat::Private
-{
-public:
-    Private()
-    {
-    }
-    ~Private()
-    {
-        delete mException;
-    }
-    static QString mApplication; // Name of application, for creating unique ID strings
-    static QString mProductId; // PRODID string to write to calendar files
-    QString mLoadedProductId; // PRODID string loaded from calendar file
-    Exception *mException = nullptr;
-};
-
-QString CalFormat::Private::mApplication = QStringLiteral("libkcal");
-QString CalFormat::Private::mProductId = QStringLiteral("-//K Desktop Environment//NONSGML libkcal 4.3//EN");
-//@endcond
+CalFormatPrivate::~CalFormatPrivate() = default;
+
+QString CalFormatPrivate::mApplication = QStringLiteral("libkcal");
+QString CalFormatPrivate::mProductId = QStringLiteral("-//K Desktop Environment//NONSGML libkcal 4.3//EN");
 
+#if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 96)
 CalFormat::CalFormat()
-    : d(new KCalendarCore::CalFormat::Private)
+    : d_ptr(new KCalendarCore::CalFormatPrivate)
+{
+}
+#endif
+
+CalFormat::CalFormat(CalFormatPrivate *dd)
+    : d_ptr(dd)
 {
 }
 
 CalFormat::~CalFormat()
 {
     clearException();
-    delete d;
 }
 
 void CalFormat::clearException()
 {
-    delete d->mException;
-    d->mException = nullptr;
+    d_ptr->mException.reset();
 }
 
 void CalFormat::setException(Exception *exception)
 {
-    delete d->mException;
-    d->mException = exception;
+    d_ptr->mException.reset(exception);
 }
 
 Exception *CalFormat::exception() const
 {
-    return d->mException;
+    return d_ptr->mException.get();
 }
 
 void CalFormat::setApplication(const QString &application, const QString &productID)
 {
-    Private::mApplication = application;
-    Private::mProductId = productID;
+    CalFormatPrivate::mApplication = application;
+    CalFormatPrivate::mProductId = productID;
 }
 
 const QString &CalFormat::application()
 {
-    return Private::mApplication;
+    return CalFormatPrivate::mApplication;
 }
 
 const QString &CalFormat::productId()
 {
-    return Private::mProductId;
+    return CalFormatPrivate::mProductId;
 }
 
 QString CalFormat::loadedProductId()
 {
-    return d->mLoadedProductId;
+    return d_ptr->mLoadedProductId;
 }
 
 void CalFormat::setLoadedProductId(const QString &id)
 {
-    d->mLoadedProductId = id;
+    d_ptr->mLoadedProductId = id;
 }
 
 QString CalFormat::createUniqueId()
@@ -107,9 +92,11 @@ QString CalFormat::createUniqueId()
     return QUuid::createUuid().toString().mid(1, 36);
 }
 
+#if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 96)
 void CalFormat::virtual_hook(int id, void *data)
 {
     Q_UNUSED(id);
     Q_UNUSED(data);
     Q_ASSERT(false);
 }
+#endif
diff -pruN 5:5.94.0-1/src/calformat.h 5:5.96.0-1/src/calformat.h
--- 5:5.94.0-1/src/calformat.h	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/calformat.h	2022-07-02 14:29:36.000000000 +0000
@@ -21,8 +21,11 @@
 
 #include <QString>
 
+#include <memory>
+
 namespace KCalendarCore
 {
+class CalFormatPrivate;
 class Exception;
 
 /**
@@ -35,10 +38,14 @@ class Exception;
 class KCALENDARCORE_EXPORT CalFormat
 {
 public:
+#if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 96)
     /**
       Constructs a new Calendar Format object.
+      @deprecated since 5.96, unused with the move to hierarchical dptrs.
     */
+    KCALENDARCORE_DEPRECATED_VERSION(5, 96, "unused, see API docs")
     CalFormat();
+#endif
 
     /**
       Destructor.
@@ -165,17 +172,26 @@ protected:
     */
     void setLoadedProductId(const QString &id);
 
+#if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 96)
     /**
       @copydoc
       IncidenceBase::virtual_hook()
+      @deprecated since 5.96 unused, hierarchical dptrs provide the same ABI compatible extension vector
+      as this class is not intended to be inherited externally.
     */
+    KCALENDARCORE_DEPRECATED_VERSION(5, 96, "unused, see API docs")
     virtual void virtual_hook(int id, void *data);
+#endif
+
+    //@cond PRIVATE
+    CalFormat(CalFormatPrivate *dd);
+    std::unique_ptr<CalFormatPrivate> d_ptr;
+    //@endcond
 
 private:
     //@cond PRIVATE
     Q_DISABLE_COPY(CalFormat)
-    class Private;
-    Private *const d;
+    Q_DECLARE_PRIVATE(CalFormat)
     //@endcond
 };
 
diff -pruN 5:5.94.0-1/src/calformat_p.h 5:5.96.0-1/src/calformat_p.h
--- 5:5.94.0-1/src/calformat_p.h	1970-01-01 00:00:00.000000000 +0000
+++ 5:5.96.0-1/src/calformat_p.h	2022-07-02 14:29:36.000000000 +0000
@@ -0,0 +1,25 @@
+/*
+  SPDX-FileCopyrightText: 2001 Cornelius Schumacher <schumacher@kde.org>
+  SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef KCALCORE_CALFORMAT_P_H
+#define KCALCORE_CALFORMAT_P_H
+
+#include "exceptions.h"
+
+namespace KCalendarCore
+{
+class CalFormatPrivate
+{
+public:
+    virtual ~CalFormatPrivate();
+
+    static QString mApplication; // Name of application, for creating unique ID strings
+    static QString mProductId; // PRODID string to write to calendar files
+    QString mLoadedProductId; // PRODID string loaded from calendar file
+    std::unique_ptr<Exception> mException;
+};
+}
+
+#endif
diff -pruN 5:5.94.0-1/src/CMakeLists.txt 5:5.96.0-1/src/CMakeLists.txt
--- 5:5.94.0-1/src/CMakeLists.txt	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/CMakeLists.txt	2022-07-02 14:29:36.000000000 +0000
@@ -101,7 +101,7 @@ ecm_generate_export_header(KF5CalendarCo
     GROUP_BASE_NAME KF
     VERSION ${KF_VERSION}
     DEPRECATED_BASE_VERSION 0
-    DEPRECATION_VERSIONS 5.64 5.89 5.91
+    DEPRECATION_VERSIONS 5.64 5.89 5.91 5.95 5.96
     EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
 )
 
diff -pruN 5:5.94.0-1/src/compat.cpp 5:5.96.0-1/src/compat.cpp
--- 5:5.94.0-1/src/compat.cpp	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/compat.cpp	2022-07-02 14:29:36.000000000 +0000
@@ -82,14 +82,7 @@ Compat *CompatFactory::createCompat(cons
     return compat;
 }
 
-Compat::Compat()
-    : d(nullptr)
-{
-}
-
-Compat::~Compat()
-{
-}
+Compat::~Compat() = default;
 
 void Compat::fixEmptySummary(const Incidence::Ptr &incidence)
 {
@@ -141,66 +134,46 @@ void Compat::setCreatedToDtStamp(const I
     Q_UNUSED(dtstamp);
 }
 
-class Q_DECL_HIDDEN CompatDecorator::Private
-{
-public:
-    Compat *compat;
-};
-
 CompatDecorator::CompatDecorator(Compat *compat)
-    : d(new CompatDecorator::Private)
+    : m_compat(compat)
 {
-    d->compat = compat;
 }
 
-CompatDecorator::~CompatDecorator()
-{
-    delete d->compat;
-    delete d;
-}
+CompatDecorator::~CompatDecorator() = default;
 
 void CompatDecorator::fixEmptySummary(const Incidence::Ptr &incidence)
 {
-    d->compat->fixEmptySummary(incidence);
+    m_compat->fixEmptySummary(incidence);
 }
 
 void CompatDecorator::fixAlarms(const Incidence::Ptr &incidence)
 {
-    d->compat->fixAlarms(incidence);
+    m_compat->fixAlarms(incidence);
 }
 
 void CompatDecorator::fixFloatingEnd(QDate &date)
 {
-    d->compat->fixFloatingEnd(date);
+    m_compat->fixFloatingEnd(date);
 }
 
 void CompatDecorator::fixRecurrence(const Incidence::Ptr &incidence)
 {
-    d->compat->fixRecurrence(incidence);
+    m_compat->fixRecurrence(incidence);
 }
 
 int CompatDecorator::fixPriority(int priority)
 {
-    return d->compat->fixPriority(priority);
+    return m_compat->fixPriority(priority);
 }
 
 bool CompatDecorator::useTimeZoneShift() const
 {
-    return d->compat->useTimeZoneShift();
+    return m_compat->useTimeZoneShift();
 }
 
 void CompatDecorator::setCreatedToDtStamp(const Incidence::Ptr &incidence, const QDateTime &dtstamp)
 {
-    d->compat->setCreatedToDtStamp(incidence, dtstamp);
-}
-
-CompatPre35::CompatPre35()
-    : d(nullptr)
-{
-}
-
-CompatPre35::~CompatPre35()
-{
+    m_compat->setCreatedToDtStamp(incidence, dtstamp);
 }
 
 void CompatPre35::fixRecurrence(const Incidence::Ptr &incidence)
@@ -219,15 +192,6 @@ void CompatPre35::fixRecurrence(const In
     Compat::fixRecurrence(incidence);
 }
 
-CompatPre34::CompatPre34()
-    : d(nullptr)
-{
-}
-
-CompatPre34::~CompatPre34()
-{
-}
-
 int CompatPre34::fixPriority(int priority)
 {
     if (0 < priority && priority < 6) {
@@ -238,15 +202,6 @@ int CompatPre34::fixPriority(int priorit
     }
 }
 
-CompatPre32::CompatPre32()
-    : d(nullptr)
-{
-}
-
-CompatPre32::~CompatPre32()
-{
-}
-
 void CompatPre32::fixRecurrence(const Incidence::Ptr &incidence)
 {
     Recurrence *recurrence = incidence->recurrence();
@@ -257,15 +212,6 @@ void CompatPre32::fixRecurrence(const In
     CompatPre35::fixRecurrence(incidence);
 }
 
-CompatPre31::CompatPre31()
-    : d(nullptr)
-{
-}
-
-CompatPre31::~CompatPre31()
-{
-}
-
 void CompatPre31::fixFloatingEnd(QDate &endDate)
 {
     endDate = endDate.addDays(1);
@@ -336,15 +282,6 @@ void CompatPre31::fixRecurrence(const In
     }
 }
 
-CompatOutlook9::CompatOutlook9()
-    : d(nullptr)
-{
-}
-
-CompatOutlook9::~CompatOutlook9()
-{
-}
-
 void CompatOutlook9::fixAlarms(const Incidence::Ptr &incidence)
 {
     if (!incidence) {
@@ -364,15 +301,6 @@ void CompatOutlook9::fixAlarms(const Inc
     }
 }
 
-Compat32PrereleaseVersions::Compat32PrereleaseVersions()
-    : d(nullptr)
-{
-}
-
-Compat32PrereleaseVersions::~Compat32PrereleaseVersions()
-{
-}
-
 bool Compat32PrereleaseVersions::useTimeZoneShift() const
 {
     return false;
@@ -380,11 +308,6 @@ bool Compat32PrereleaseVersions::useTime
 
 CompatPre410::CompatPre410(Compat *decoratedCompat)
     : CompatDecorator(decoratedCompat)
-    , d(nullptr)
-{
-}
-
-CompatPre410::~CompatPre410()
 {
 }
 
diff -pruN 5:5.94.0-1/src/compat_p.h 5:5.96.0-1/src/compat_p.h
--- 5:5.94.0-1/src/compat_p.h	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/compat_p.h	2022-07-02 14:29:36.000000000 +0000
@@ -23,6 +23,8 @@
 
 #include <QtGlobal> // for Q_DISABLE_COPY()
 
+#include <memory>
+
 class QDate;
 class QString;
 
@@ -59,11 +61,6 @@ class Compat
 {
 public:
     /**
-      Constructor.
-    */
-    Compat();
-
-    /**
       Destructor.
     */
     virtual ~Compat();
@@ -111,13 +108,6 @@ public:
       Sets the created and dtstamp.
     */
     virtual void setCreatedToDtStamp(const Incidence::Ptr &incidence, const QDateTime &dtstamp);
-
-private:
-    //@cond PRIVATE
-    Q_DISABLE_COPY(Compat)
-    class Private;
-    Private *const d;
-    //@endcond
 };
 
 /**
@@ -173,11 +163,8 @@ public:
     void setCreatedToDtStamp(const Incidence::Ptr &incidence, const QDateTime &dtstamp) override;
 
 private:
-    //@cond PRIVATE
     Q_DISABLE_COPY(CompatDecorator)
-    class Private;
-    Private *const d;
-    //@endcond
+    std::unique_ptr<Compat> m_compat;
 };
 
 /**
@@ -193,20 +180,11 @@ private:
 class CompatPre35 : public Compat
 {
 public:
-    CompatPre35();
-    ~CompatPre35() override;
-
     /**
       @copydoc
       Compat::fixRecurrence()
     */
     void fixRecurrence(const Incidence::Ptr &incidence) override;
-
-private:
-    //@cond PRIVATE
-    class Private;
-    Private *const d;
-    //@endcond
 };
 
 /**
@@ -216,20 +194,11 @@ private:
 class CompatPre34 : public CompatPre35
 {
 public:
-    CompatPre34();
-    ~CompatPre34() override;
-
     /**
       @copydoc
       Compat::fixPriority()
     */
     int fixPriority(int priority) override;
-
-private:
-    //@cond PRIVATE
-    class Private;
-    Private *const d;
-    //@endcond
 };
 
 /**
@@ -244,21 +213,11 @@ private:
 class CompatPre32 : public CompatPre34
 {
 public:
-    CompatPre32();
-    ~CompatPre32() override;
-
     /**
       @copydoc
       Compat::fixRecurrence()
     */
     void fixRecurrence(const Incidence::Ptr &incidence) override;
-
-private:
-    //@cond PRIVATE
-
-    class Private;
-    Private *const d;
-    //@endcond
 };
 
 /**
@@ -279,9 +238,6 @@ private:
 class CompatPre31 : public CompatPre32
 {
 public:
-    CompatPre31();
-    ~CompatPre31() override;
-
     /**
       @copydoc
       Compat::fixFloatingEnd()
@@ -293,12 +249,6 @@ public:
       Compat::fixRecurrence()
     */
     void fixRecurrence(const Incidence::Ptr &incidence) override;
-
-private:
-    //@cond PRIVATE
-    class Private;
-    Private *const d;
-    //@endcond
 };
 
 /**
@@ -308,20 +258,11 @@ private:
 class Compat32PrereleaseVersions : public Compat
 {
 public:
-    Compat32PrereleaseVersions();
-    ~Compat32PrereleaseVersions() override;
-
     /**
       @copydoc
       Compat::useTimeZoneShift()
     */
     bool useTimeZoneShift() const override;
-
-private:
-    //@cond PRIVATE
-    class Private;
-    Private *const d;
-    //@endcond
 };
 
 /**
@@ -335,20 +276,11 @@ private:
 class CompatOutlook9 : public Compat
 {
 public:
-    CompatOutlook9();
-    ~CompatOutlook9() override;
-
     /**
       @copydoc
       Compat::fixAlarms()
     */
     void fixAlarms(const Incidence::Ptr &incidence) override;
-
-private:
-    //@cond PRIVATE
-    class Private;
-    Private *const d;
-    //@endcond
 };
 
 /**
@@ -359,18 +291,11 @@ class CompatPre410 : public CompatDecora
 {
 public:
     explicit CompatPre410(Compat *decoratedCompat);
-    ~CompatPre410() override;
     /**
       @copydoc
       Compat::setCreatedToDtStamp()
     */
     void setCreatedToDtStamp(const Incidence::Ptr &incidence, const QDateTime &dtstamp) override;
-
-private:
-    //@cond PRIVATE
-    class Private;
-    Private *const d;
-    //@endcond
 };
 
 }
diff -pruN 5:5.94.0-1/src/icalformat.cpp 5:5.96.0-1/src/icalformat.cpp
--- 5:5.94.0-1/src/icalformat.cpp	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/icalformat.cpp	2022-07-02 14:29:36.000000000 +0000
@@ -17,6 +17,7 @@
 */
 #include "icalformat.h"
 #include "calendar_p.h"
+#include "calformat_p.h"
 #include "icalformat_p.h"
 #include "icaltimezones_p.h"
 #include "kcalendarcore_debug.h"
@@ -37,32 +38,27 @@ extern "C" {
 using namespace KCalendarCore;
 
 //@cond PRIVATE
-class Q_DECL_HIDDEN KCalendarCore::ICalFormat::Private
+class KCalendarCore::ICalFormatPrivate : public KCalendarCore::CalFormatPrivate
 {
 public:
-    Private(ICalFormat *parent)
-        : mImpl(new ICalFormatImpl(parent))
+    ICalFormatPrivate(ICalFormat *parent)
+        : mImpl(parent)
         , mTimeZone(QTimeZone::utc())
     {
     }
-    ~Private()
-    {
-        delete mImpl;
-    }
-    ICalFormatImpl *mImpl = nullptr;
+    ICalFormatImpl mImpl;
     QTimeZone mTimeZone;
 };
 //@endcond
 
 ICalFormat::ICalFormat()
-    : d(new Private(this))
+    : CalFormat(new ICalFormatPrivate(this))
 {
 }
 
 ICalFormat::~ICalFormat()
 {
     icalmemory_free_ring();
-    delete d;
 }
 
 bool ICalFormat::load(const Calendar::Ptr &calendar, const QString &fileName)
@@ -148,6 +144,8 @@ bool ICalFormat::fromString(const Calend
 
 Incidence::Ptr ICalFormat::readIncidence(const QByteArray &string)
 {
+    Q_D(ICalFormat);
+
     // Let's defend const correctness until the very gates of hell^Wlibical
     icalcomponent *calendar = icalcomponent_new_from_string(const_cast<char *>(string.constData()));
     if (!calendar) {
@@ -162,11 +160,11 @@ Incidence::Ptr ICalFormat::readIncidence
 
     Incidence::Ptr incidence;
     if (icalcomponent_isa(calendar) == ICAL_VCALENDAR_COMPONENT) {
-        incidence = d->mImpl->readOneIncidence(calendar, &tzCache);
+        incidence = d->mImpl.readOneIncidence(calendar, &tzCache);
     } else if (icalcomponent_isa(calendar) == ICAL_XROOT_COMPONENT) {
         icalcomponent *comp = icalcomponent_get_first_component(calendar, ICAL_VCALENDAR_COMPONENT);
         if (comp) {
-            incidence = d->mImpl->readOneIncidence(comp, &tzCache);
+            incidence = d->mImpl.readOneIncidence(comp, &tzCache);
         }
     }
 
@@ -183,6 +181,8 @@ Incidence::Ptr ICalFormat::readIncidence
 
 bool ICalFormat::fromRawString(const Calendar::Ptr &cal, const QByteArray &string, bool deleted, const QString &notebook)
 {
+    Q_D(ICalFormat);
+
     // Get first VCALENDAR component.
     // TODO: Handle more than one VCALENDAR or non-VCALENDAR top components
     icalcomponent *calendar;
@@ -202,14 +202,14 @@ bool ICalFormat::fromRawString(const Cal
         for (comp = icalcomponent_get_first_component(calendar, ICAL_VCALENDAR_COMPONENT); comp;
              comp = icalcomponent_get_next_component(calendar, ICAL_VCALENDAR_COMPONENT)) {
             // put all objects into their proper places
-            if (!d->mImpl->populate(cal, comp, deleted)) {
+            if (!d->mImpl.populate(cal, comp, deleted)) {
                 qCritical() << "Could not populate calendar";
                 if (!exception()) {
                     setException(new Exception(Exception::ParseErrorKcal));
                 }
                 success = false;
             } else {
-                setLoadedProductId(d->mImpl->loadedProductId());
+                setLoadedProductId(d->mImpl.loadedProductId());
             }
         }
     } else if (icalcomponent_isa(calendar) != ICAL_VCALENDAR_COMPONENT) {
@@ -218,14 +218,14 @@ bool ICalFormat::fromRawString(const Cal
         success = false;
     } else {
         // put all objects into their proper places
-        if (!d->mImpl->populate(cal, calendar, deleted, notebook)) {
+        if (!d->mImpl.populate(cal, calendar, deleted, notebook)) {
             qCDebug(KCALCORE_LOG) << "Could not populate calendar";
             if (!exception()) {
                 setException(new Exception(Exception::ParseErrorKcal));
             }
             success = false;
         } else {
-            setLoadedProductId(d->mImpl->loadedProductId());
+            setLoadedProductId(d->mImpl.loadedProductId());
         }
     }
 
@@ -237,6 +237,8 @@ bool ICalFormat::fromRawString(const Cal
 
 Incidence::Ptr ICalFormat::fromString(const QString &string)
 {
+    Q_D(ICalFormat);
+
     MemoryCalendar::Ptr cal(new MemoryCalendar(d->mTimeZone));
     fromString(cal, string);
 
@@ -246,7 +248,9 @@ Incidence::Ptr ICalFormat::fromString(co
 
 QString ICalFormat::toString(const Calendar::Ptr &cal, const QString &notebook, bool deleted)
 {
-    icalcomponent *calendar = d->mImpl->createCalendarComponent(cal);
+    Q_D(ICalFormat);
+
+    icalcomponent *calendar = d->mImpl.createCalendarComponent(cal);
     icalcomponent *component;
 
     QVector<QTimeZone> tzUsedList;
@@ -258,7 +262,7 @@ QString ICalFormat::toString(const Calen
         if (!deleted || !cal->todo((*it)->uid(), (*it)->recurrenceId())) {
             // use existing ones, or really deleted ones
             if (notebook.isEmpty() || (!cal->notebook(*it).isEmpty() && notebook.endsWith(cal->notebook(*it)))) {
-                component = d->mImpl->writeTodo(*it, &tzUsedList);
+                component = d->mImpl.writeTodo(*it, &tzUsedList);
                 icalcomponent_add_component(calendar, component);
                 ICalTimeZoneParser::updateTzEarliestDate((*it), &earliestTz);
             }
@@ -270,7 +274,7 @@ QString ICalFormat::toString(const Calen
         if (!deleted || !cal->event((*it)->uid(), (*it)->recurrenceId())) {
             // use existing ones, or really deleted ones
             if (notebook.isEmpty() || (!cal->notebook(*it).isEmpty() && notebook.endsWith(cal->notebook(*it)))) {
-                component = d->mImpl->writeEvent(*it, &tzUsedList);
+                component = d->mImpl.writeEvent(*it, &tzUsedList);
                 icalcomponent_add_component(calendar, component);
                 ICalTimeZoneParser::updateTzEarliestDate((*it), &earliestTz);
             }
@@ -283,7 +287,7 @@ QString ICalFormat::toString(const Calen
         if (!deleted || !cal->journal((*it)->uid(), (*it)->recurrenceId())) {
             // use existing ones, or really deleted ones
             if (notebook.isEmpty() || (!cal->notebook(*it).isEmpty() && notebook.endsWith(cal->notebook(*it)))) {
-                component = d->mImpl->writeJournal(*it, &tzUsedList);
+                component = d->mImpl.writeJournal(*it, &tzUsedList);
                 icalcomponent_add_component(calendar, component);
                 ICalTimeZoneParser::updateTzEarliestDate((*it), &earliestTz);
             }
@@ -325,6 +329,8 @@ QString ICalFormat::toString(const Calen
 
 QString ICalFormat::toICalString(const Incidence::Ptr &incidence)
 {
+    Q_D(ICalFormat);
+
     MemoryCalendar::Ptr cal(new MemoryCalendar(d->mTimeZone));
     cal->addIncidence(Incidence::Ptr(incidence->clone()));
     return toString(cal.staticCast<Calendar>());
@@ -337,9 +343,10 @@ QString ICalFormat::toString(const Incid
 
 QByteArray ICalFormat::toRawString(const Incidence::Ptr &incidence)
 {
+    Q_D(ICalFormat);
     TimeZoneList tzUsedList;
 
-    icalcomponent *component = d->mImpl->writeIncidence(incidence, iTIPRequest, &tzUsedList);
+    icalcomponent *component = d->mImpl.writeIncidence(incidence, iTIPRequest, &tzUsedList);
 
     QByteArray text = icalcomponent_as_ical_string(component);
 
@@ -368,14 +375,25 @@ QByteArray ICalFormat::toRawString(const
 
 QString ICalFormat::toString(RecurrenceRule *recurrence)
 {
-    icalproperty *property = icalproperty_new_rrule(d->mImpl->writeRecurrenceRule(recurrence));
+    Q_D(ICalFormat);
+    icalproperty *property = icalproperty_new_rrule(d->mImpl.writeRecurrenceRule(recurrence));
     QString text = QString::fromUtf8(icalproperty_as_ical_string(property));
     icalproperty_free(property);
     return text;
 }
 
+QString KCalendarCore::ICalFormat::toString(const KCalendarCore::Duration &duration) const
+{
+    Q_D(const ICalFormat);
+    const auto icalDuration = d->mImpl.writeICalDuration(duration);
+    // contrary to the libical API docs, the returned string is actually freed by icalmemory_free_ring,
+    // freeing it here explicitly causes a double deletion failure
+    return QString::fromUtf8(icaldurationtype_as_ical_string(icalDuration));
+}
+
 bool ICalFormat::fromString(RecurrenceRule *recurrence, const QString &rrule)
 {
+    Q_D(ICalFormat);
     if (!recurrence) {
         return false;
     }
@@ -388,14 +406,27 @@ bool ICalFormat::fromString(RecurrenceRu
     }
 
     if (success) {
-        d->mImpl->readRecurrence(recur, recurrence);
+        d->mImpl.readRecurrence(recur, recurrence);
     }
 
     return success;
 }
 
+Duration ICalFormat::durationFromString(const QString &duration) const
+{
+    Q_D(const ICalFormat);
+    icalerror_clear_errno();
+    const auto icalDuration = icaldurationtype_from_string(duration.toUtf8().constData());
+    if (icalerrno != ICAL_NO_ERROR) {
+        qCDebug(KCALCORE_LOG) << "Duration parsing error:" << icalerror_strerror(icalerrno);
+        return {};
+    }
+    return d->mImpl.readICalDuration(icalDuration);
+}
+
 QString ICalFormat::createScheduleMessage(const IncidenceBase::Ptr &incidence, iTIPMethod method)
 {
+    Q_D(ICalFormat);
     icalcomponent *message = nullptr;
 
     if (incidence->type() == Incidence::TypeEvent || incidence->type() == Incidence::TypeTodo) {
@@ -425,12 +456,12 @@ QString ICalFormat::createScheduleMessag
             }
 
             // Build the message with the cloned incidence
-            message = d->mImpl->createScheduleComponent(i, method);
+            message = d->mImpl.createScheduleComponent(i, method);
         }
     }
 
     if (message == nullptr) {
-        message = d->mImpl->createScheduleComponent(incidence, method);
+        message = d->mImpl.createScheduleComponent(incidence, method);
     }
 
     QString messageText = QString::fromUtf8(icalcomponent_as_ical_string(message));
@@ -441,6 +472,7 @@ QString ICalFormat::createScheduleMessag
 
 FreeBusy::Ptr ICalFormat::parseFreeBusy(const QString &str)
 {
+    Q_D(ICalFormat);
     clearException();
 
     icalcomponent *message = icalparser_parse_string(str.toUtf8().constData());
@@ -454,7 +486,7 @@ FreeBusy::Ptr ICalFormat::parseFreeBusy(
     icalcomponent *c = nullptr;
     for (c = icalcomponent_get_first_component(message, ICAL_VFREEBUSY_COMPONENT); c != nullptr;
          c = icalcomponent_get_next_component(message, ICAL_VFREEBUSY_COMPONENT)) {
-        FreeBusy::Ptr fb = d->mImpl->readFreeBusy(c);
+        FreeBusy::Ptr fb = d->mImpl.readFreeBusy(c);
 
         if (freeBusy) {
             freeBusy->merge(fb);
@@ -474,6 +506,7 @@ FreeBusy::Ptr ICalFormat::parseFreeBusy(
 
 ScheduleMessage::Ptr ICalFormat::parseScheduleMessage(const Calendar::Ptr &cal, const QString &messageText)
 {
+    Q_D(ICalFormat);
     setTimeZone(cal->timeZone());
     clearException();
 
@@ -505,27 +538,27 @@ ScheduleMessage::Ptr ICalFormat::parseSc
     IncidenceBase::Ptr incidence;
     icalcomponent *c = icalcomponent_get_first_component(message, ICAL_VEVENT_COMPONENT);
     if (c) {
-        incidence = d->mImpl->readEvent(c, &tzlist).staticCast<IncidenceBase>();
+        incidence = d->mImpl.readEvent(c, &tzlist).staticCast<IncidenceBase>();
     }
 
     if (!incidence) {
         c = icalcomponent_get_first_component(message, ICAL_VTODO_COMPONENT);
         if (c) {
-            incidence = d->mImpl->readTodo(c, &tzlist).staticCast<IncidenceBase>();
+            incidence = d->mImpl.readTodo(c, &tzlist).staticCast<IncidenceBase>();
         }
     }
 
     if (!incidence) {
         c = icalcomponent_get_first_component(message, ICAL_VJOURNAL_COMPONENT);
         if (c) {
-            incidence = d->mImpl->readJournal(c, &tzlist).staticCast<IncidenceBase>();
+            incidence = d->mImpl.readJournal(c, &tzlist).staticCast<IncidenceBase>();
         }
     }
 
     if (!incidence) {
         c = icalcomponent_get_first_component(message, ICAL_VFREEBUSY_COMPONENT);
         if (c) {
-            incidence = d->mImpl->readFreeBusy(c).staticCast<IncidenceBase>();
+            incidence = d->mImpl.readFreeBusy(c).staticCast<IncidenceBase>();
         }
     }
 
@@ -572,24 +605,24 @@ ScheduleMessage::Ptr ICalFormat::parseSc
 
     if (!icalrestriction_check(message)) {
         qCWarning(KCALCORE_LOG) << "\nkcalcore library reported a problem while parsing:";
-        qCWarning(KCALCORE_LOG) << ScheduleMessage::methodName(method) << ":" << d->mImpl->extractErrorProperty(c);
+        qCWarning(KCALCORE_LOG) << ScheduleMessage::methodName(method) << ":" << d->mImpl.extractErrorProperty(c);
     }
 
     Incidence::Ptr existingIncidence = cal->incidence(incidence->uid());
 
     icalcomponent *calendarComponent = nullptr;
     if (existingIncidence) {
-        calendarComponent = d->mImpl->createCalendarComponent(cal);
+        calendarComponent = d->mImpl.createCalendarComponent(cal);
 
         // TODO: check, if cast is required, or if it can be done by virtual funcs.
         // TODO: Use a visitor for this!
         if (existingIncidence->type() == Incidence::TypeTodo) {
             Todo::Ptr todo = existingIncidence.staticCast<Todo>();
-            icalcomponent_add_component(calendarComponent, d->mImpl->writeTodo(todo));
+            icalcomponent_add_component(calendarComponent, d->mImpl.writeTodo(todo));
         }
         if (existingIncidence->type() == Incidence::TypeEvent) {
             Event::Ptr event = existingIncidence.staticCast<Event>();
-            icalcomponent_add_component(calendarComponent, d->mImpl->writeEvent(event));
+            icalcomponent_add_component(calendarComponent, d->mImpl.writeEvent(event));
         }
     } else {
         icalcomponent_free(message);
@@ -630,22 +663,27 @@ ScheduleMessage::Ptr ICalFormat::parseSc
 
 void ICalFormat::setTimeZone(const QTimeZone &timeZone)
 {
+    Q_D(ICalFormat);
     d->mTimeZone = timeZone;
 }
 
 QTimeZone ICalFormat::timeZone() const
 {
+    Q_D(const ICalFormat);
     return d->mTimeZone;
 }
 
 QByteArray ICalFormat::timeZoneId() const
 {
+    Q_D(const ICalFormat);
     return d->mTimeZone.id();
 }
 
+#if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 96)
 void ICalFormat::virtual_hook(int id, void *data)
 {
     Q_UNUSED(id);
     Q_UNUSED(data);
     Q_ASSERT(false);
 }
+#endif
diff -pruN 5:5.94.0-1/src/icalformat.h 5:5.96.0-1/src/icalformat.h
--- 5:5.94.0-1/src/icalformat.h	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/icalformat.h	2022-07-02 14:29:36.000000000 +0000
@@ -24,6 +24,7 @@
 namespace KCalendarCore
 {
 class FreeBusy;
+class ICalFormatPrivate;
 class Incidence;
 class IncidenceBase;
 class RecurrenceRule;
@@ -104,6 +105,14 @@ public:
     Q_REQUIRED_RESULT bool fromString(RecurrenceRule *rule, const QString &string);
 
     /**
+      Parses a string representation of a duration.
+
+      @param duration iCal representation of a duration.
+      @since 5.95
+    */
+    Q_REQUIRED_RESULT Duration durationFromString(const QString &duration) const;
+
+    /**
       @copydoc
       CalFormat::fromRawString()
     */
@@ -145,6 +154,14 @@ public:
     Q_REQUIRED_RESULT QString toString(RecurrenceRule *rule);
 
     /**
+      Converts a Duration to an iCal string.
+      @param duration a Duration object.
+      @return iCal formatted duration
+      @since 5.95
+    */
+    Q_REQUIRED_RESULT QString toString(const Duration &duration) const;
+
+    /**
       Converts an Incidence to iCalendar formatted text.
 
       @param incidence is a pointer to an Incidence object to be converted
@@ -205,17 +222,21 @@ public:
     Q_REQUIRED_RESULT QByteArray timeZoneId() const;
 
 protected:
+#if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 96)
     /**
       @copydoc
       IncidenceBase::virtual_hook()
     */
     void virtual_hook(int id, void *data) override;
+#endif
 
 private:
     //@cond PRIVATE
     Q_DISABLE_COPY(ICalFormat)
-    class Private;
-    Private *const d;
+    Q_DECLARE_PRIVATE(ICalFormat)
+#if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 96)
+    void *unused; // former dptr, just kept for ABI compatibility
+#endif
     //@endcond
 };
 
diff -pruN 5:5.94.0-1/src/icalformat_p.cpp 5:5.96.0-1/src/icalformat_p.cpp
--- 5:5.94.0-1/src/icalformat_p.cpp	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/icalformat_p.cpp	2022-07-02 14:29:36.000000000 +0000
@@ -123,32 +123,6 @@ private:
 ToComponentVisitor::~ToComponentVisitor()
 {
 }
-
-class Q_DECL_HIDDEN ICalFormatImpl::Private
-{
-public:
-    Private(ICalFormatImpl *impl, ICalFormat *parent)
-        : mImpl(impl)
-        , mParent(parent)
-        , mCompat(new Compat)
-    {
-    }
-    ~Private()
-    {
-        delete mCompat;
-    }
-    void writeIncidenceBase(icalcomponent *parent, const IncidenceBase::Ptr &);
-    void readIncidenceBase(icalcomponent *parent, const IncidenceBase::Ptr &);
-    void writeCustomProperties(icalcomponent *parent, CustomProperties *);
-    void readCustomProperties(icalcomponent *parent, CustomProperties *);
-
-    ICalFormatImpl *mImpl = nullptr;
-    ICalFormat *mParent = nullptr;
-    QString mLoadedProductId; // PRODID string loaded from calendar file
-    Event::List mEventsRelate; // events with relations
-    Todo::List mTodosRelate; // todos with relations
-    Compat *mCompat = nullptr;
-};
 //@endcond
 
 inline icaltimetype ICalFormatImpl::writeICalUtcDateTime(const QDateTime &dt, bool dayOnly)
@@ -157,18 +131,16 @@ inline icaltimetype ICalFormatImpl::writ
 }
 
 ICalFormatImpl::ICalFormatImpl(ICalFormat *parent)
-    : d(new Private(this, parent))
+    : mParent(parent)
+    , mCompat(new Compat)
 {
 }
 
-ICalFormatImpl::~ICalFormatImpl()
-{
-    delete d;
-}
+ICalFormatImpl::~ICalFormatImpl() = default;
 
 QString ICalFormatImpl::loadedProductId() const
 {
-    return d->mLoadedProductId;
+    return mLoadedProductId;
 }
 
 icalcomponent *ICalFormatImpl::writeIncidence(const IncidenceBase::Ptr &incidence, iTIPMethod method, TimeZoneList *tzUsedList)
@@ -308,7 +280,7 @@ icalcomponent *ICalFormatImpl::writeFree
 {
     icalcomponent *vfreebusy = icalcomponent_new(ICAL_VFREEBUSY_COMPONENT);
 
-    d->writeIncidenceBase(vfreebusy, freebusy.staticCast<IncidenceBase>());
+    writeIncidenceBase(vfreebusy, freebusy.staticCast<IncidenceBase>());
 
     icalcomponent_add_property(vfreebusy, icalproperty_new_dtstart(writeICalUtcDateTime(freebusy->dtStart())));
 
@@ -404,7 +376,7 @@ void ICalFormatImpl::writeIncidence(ical
         incidence->removeCustomProperty("LIBKCAL", "ID");
     }
 
-    d->writeIncidenceBase(parent, incidence.staticCast<IncidenceBase>());
+    writeIncidenceBase(parent, incidence.staticCast<IncidenceBase>());
 
     // creation date in storage
     icalcomponent_add_property(parent, writeICalDateTimeProperty(ICAL_CREATED_PROPERTY, incidence->created()));
@@ -608,11 +580,11 @@ void ICalFormatImpl::writeIncidence(ical
 }
 
 //@cond PRIVATE
-void ICalFormatImpl::Private::writeIncidenceBase(icalcomponent *parent, const IncidenceBase::Ptr &incidenceBase)
+void ICalFormatImpl::writeIncidenceBase(icalcomponent *parent, const IncidenceBase::Ptr &incidenceBase)
 {
     // organizer stuff
     if (!incidenceBase->organizer().isEmpty()) {
-        icalproperty *p = mImpl->writeOrganizer(incidenceBase->organizer());
+        icalproperty *p = writeOrganizer(incidenceBase->organizer());
         if (p) {
             icalcomponent_add_property(parent, p);
         }
@@ -624,7 +596,7 @@ void ICalFormatImpl::Private::writeIncid
     if (incidenceBase->attendeeCount() > 0) {
         auto attendees = incidenceBase->attendees();
         for (auto it = attendees.constBegin(); it != attendees.constEnd(); ++it) {
-            icalproperty *p = mImpl->writeAttendee(*it);
+            icalproperty *p = writeAttendee(*it);
             if (p) {
                 icalcomponent_add_property(parent, p);
             }
@@ -653,7 +625,7 @@ void ICalFormatImpl::Private::writeIncid
     writeCustomProperties(parent, incidenceBase.data());
 }
 
-void ICalFormatImpl::Private::writeCustomProperties(icalcomponent *parent, CustomProperties *properties)
+void ICalFormatImpl::writeCustomProperties(icalcomponent *parent, CustomProperties *properties)
 {
     const QMap<QByteArray, QString> custom = properties->customProperties();
     for (auto c = custom.cbegin(); c != custom.cend(); ++c) {
@@ -1138,7 +1110,7 @@ Todo::Ptr ICalFormatImpl::readTodo(icalc
 
         case ICAL_RELATEDTO_PROPERTY: // related todo (parent)
             todo->setRelatedTo(QString::fromUtf8(icalproperty_get_relatedto(p)));
-            d->mTodosRelate.append(todo);
+            mTodosRelate.append(todo);
             break;
 
         case ICAL_DTSTART_PROPERTY:
@@ -1166,8 +1138,8 @@ Todo::Ptr ICalFormatImpl::readTodo(icalc
         p = icalcomponent_get_next_property(vtodo, ICAL_ANY_PROPERTY);
     }
 
-    if (d->mCompat) {
-        d->mCompat->fixEmptySummary(todo);
+    if (mCompat) {
+        mCompat->fixEmptySummary(todo);
     }
 
     todo->resetDirtyFields();
@@ -1194,8 +1166,8 @@ Event::Ptr ICalFormatImpl::readEvent(ica
             if (allDay) {
                 // End date is non-inclusive
                 QDate endDate = kdt.date().addDays(-1);
-                if (d->mCompat) {
-                    d->mCompat->fixFloatingEnd(endDate);
+                if (mCompat) {
+                    mCompat->fixFloatingEnd(endDate);
                 }
                 if (endDate < event->dtStart().date()) {
                     endDate = event->dtStart().date();
@@ -1211,7 +1183,7 @@ Event::Ptr ICalFormatImpl::readEvent(ica
         }
         case ICAL_RELATEDTO_PROPERTY: // related event (parent)
             event->setRelatedTo(QString::fromUtf8(icalproperty_get_relatedto(p)));
-            d->mEventsRelate.append(event);
+            mEventsRelate.append(event);
             break;
 
         case ICAL_TRANSP_PROPERTY: { // Transparency
@@ -1244,8 +1216,8 @@ Event::Ptr ICalFormatImpl::readEvent(ica
         event->setAllDay(allDay);
     }
 
-    if (d->mCompat) {
-        d->mCompat->fixEmptySummary(event);
+    if (mCompat) {
+        mCompat->fixEmptySummary(event);
     }
 
     event->resetDirtyFields();
@@ -1256,7 +1228,7 @@ FreeBusy::Ptr ICalFormatImpl::readFreeBu
 {
     FreeBusy::Ptr freebusy(new FreeBusy);
 
-    d->readIncidenceBase(vfreebusy, freebusy);
+    readIncidenceBase(vfreebusy, freebusy);
 
     icalproperty *p = icalcomponent_get_first_property(vfreebusy, ICAL_ANY_PROPERTY);
 
@@ -1588,7 +1560,7 @@ Attachment ICalFormatImpl::readAttachmen
 
 void ICalFormatImpl::readIncidence(icalcomponent *parent, const Incidence::Ptr &incidence, const ICalTimeZoneCache *tzlist)
 {
-    d->readIncidenceBase(parent, incidence);
+    readIncidenceBase(parent, incidence);
 
     icalproperty *p = icalcomponent_get_first_property(parent, ICAL_ANY_PROPERTY);
 
@@ -1730,8 +1702,8 @@ void ICalFormatImpl::readIncidence(icalc
 
         case ICAL_PRIORITY_PROPERTY: // priority
             intvalue = icalproperty_get_priority(p);
-            if (d->mCompat) {
-                intvalue = d->mCompat->fixPriority(intvalue);
+            if (mCompat) {
+                intvalue = mCompat->fixPriority(intvalue);
             }
             incidence->setPriority(intvalue);
             break;
@@ -1853,8 +1825,8 @@ void ICalFormatImpl::readIncidence(icalc
 
     // Now that recurrence and exception stuff is completely set up,
     // do any backwards compatibility adjustments.
-    if (incidence->recurs() && d->mCompat) {
-        d->mCompat->fixRecurrence(incidence);
+    if (incidence->recurs() && mCompat) {
+        mCompat->fixRecurrence(incidence);
     }
 
     // add categories
@@ -1874,15 +1846,15 @@ void ICalFormatImpl::readIncidence(icalc
     }
     incidence->setConferences(conferences);
 
-    if (d->mCompat) {
+    if (mCompat) {
         // Fix incorrect alarm settings by other applications (like outloook 9)
-        d->mCompat->fixAlarms(incidence);
-        d->mCompat->setCreatedToDtStamp(incidence, dtstamp);
+        mCompat->fixAlarms(incidence);
+        mCompat->setCreatedToDtStamp(incidence, dtstamp);
     }
 }
 
 //@cond PRIVATE
-void ICalFormatImpl::Private::readIncidenceBase(icalcomponent *parent, const IncidenceBase::Ptr &incidenceBase)
+void ICalFormatImpl::readIncidenceBase(icalcomponent *parent, const IncidenceBase::Ptr &incidenceBase)
 {
     icalproperty *p = icalcomponent_get_first_property(parent, ICAL_ANY_PROPERTY);
     bool uidProcessed = false;
@@ -1895,11 +1867,11 @@ void ICalFormatImpl::Private::readIncide
             break;
 
         case ICAL_ORGANIZER_PROPERTY: // organizer
-            incidenceBase->setOrganizer(mImpl->readOrganizer(p));
+            incidenceBase->setOrganizer(readOrganizer(p));
             break;
 
         case ICAL_ATTENDEE_PROPERTY: // attendee
-            incidenceBase->addAttendee(mImpl->readAttendee(p));
+            incidenceBase->addAttendee(readAttendee(p));
             break;
 
         case ICAL_COMMENT_PROPERTY:
@@ -1950,7 +1922,7 @@ void ICalFormatImpl::Private::readIncide
     readCustomProperties(parent, incidenceBase.data());
 }
 
-void ICalFormatImpl::Private::readCustomProperties(icalcomponent *parent, CustomProperties *properties)
+void ICalFormatImpl::readCustomProperties(icalcomponent *parent, CustomProperties *properties)
 {
     QByteArray property;
     QString value;
@@ -1966,8 +1938,7 @@ void ICalFormatImpl::Private::readCustom
                 // Calling icalvalue_get_text( value ) on a datetime value crashes.
                 nvalue = QString::fromUtf8(icalvalue_get_text(value));
             } else {
-                p = icalcomponent_get_next_property(parent, ICAL_X_PROPERTY);
-                continue;
+                nvalue = QString::fromUtf8(icalproperty_get_value_as_string(p));
             }
         }
         const char *name = icalproperty_get_x_name(p);
@@ -2262,7 +2233,7 @@ void ICalFormatImpl::readAlarm(icalcompo
     }
 
     // custom properties
-    d->readCustomProperties(alarm, ialarm.data());
+    readCustomProperties(alarm, ialarm.data());
 
     QString locationRadius = ialarm->nonKDECustomProperty("X-LOCATION-RADIUS");
     if (!locationRadius.isEmpty()) {
@@ -2644,7 +2615,7 @@ icalcomponent *ICalFormatImpl::createCal
     */
     // Custom properties
     if (cal != nullptr) {
-        d->writeCustomProperties(calendar, cal.data());
+        writeCustomProperties(calendar, cal.data());
     }
 
     return calendar;
@@ -2713,34 +2684,33 @@ bool ICalFormatImpl::populate(const Cale
     p = icalcomponent_get_first_property(calendar, ICAL_PRODID_PROPERTY);
     if (!p) {
         qCDebug(KCALCORE_LOG) << "No PRODID property found";
-        d->mLoadedProductId.clear();
+        mLoadedProductId.clear();
     } else {
-        d->mLoadedProductId = QString::fromUtf8(icalproperty_get_prodid(p));
+        mLoadedProductId = QString::fromUtf8(icalproperty_get_prodid(p));
 
-        delete d->mCompat;
-        d->mCompat = CompatFactory::createCompat(d->mLoadedProductId, implementationVersion);
+        mCompat.reset(CompatFactory::createCompat(mLoadedProductId, implementationVersion));
     }
 
     p = icalcomponent_get_first_property(calendar, ICAL_VERSION_PROPERTY);
     if (!p) {
         qCDebug(KCALCORE_LOG) << "No VERSION property found";
-        d->mParent->setException(new Exception(Exception::CalVersionUnknown));
+        mParent->setException(new Exception(Exception::CalVersionUnknown));
         return false;
     } else {
         const char *version = icalproperty_get_version(p);
         if (!version) {
             qCDebug(KCALCORE_LOG) << "No VERSION property found";
-            d->mParent->setException(new Exception(Exception::VersionPropertyMissing));
+            mParent->setException(new Exception(Exception::VersionPropertyMissing));
 
             return false;
         }
         if (strcmp(version, "1.0") == 0) {
             qCDebug(KCALCORE_LOG) << "Expected iCalendar, got vCalendar";
-            d->mParent->setException(new Exception(Exception::CalVersion1));
+            mParent->setException(new Exception(Exception::CalVersion1));
             return false;
         } else if (strcmp(version, "2.0") != 0) {
             qCDebug(KCALCORE_LOG) << "Expected iCalendar, got unknown format";
-            d->mParent->setException(new Exception(Exception::CalVersionUnknown));
+            mParent->setException(new Exception(Exception::CalVersionUnknown));
             return false;
         }
     }
@@ -2751,11 +2721,11 @@ bool ICalFormatImpl::populate(const Cale
     parser.parse(calendar);
 
     // custom properties
-    d->readCustomProperties(calendar, cal.data());
+    readCustomProperties(calendar, cal.data());
 
     // Store all events with a relatedTo property in a list for post-processing
-    d->mEventsRelate.clear();
-    d->mTodosRelate.clear();
+    mEventsRelate.clear();
+    mTodosRelate.clear();
     // TODO: make sure that only actually added events go to this lists.
 
     icalcomponent *c = icalcomponent_get_first_component(calendar, ICAL_VTODO_COMPONENT);
@@ -2774,11 +2744,11 @@ bool ICalFormatImpl::populate(const Cale
                 if (deleted) {
                     // qCDebug(KCALCORE_LOG) << "Todo " << todo->uid() << " already deleted";
                     cal->deleteTodo(old); // move old to deleted
-                    removeAllICal(d->mTodosRelate, old);
+                    removeAllICal(mTodosRelate, old);
                 } else if (todo->revision() > old->revision()) {
                     // qCDebug(KCALCORE_LOG) << "Replacing old todo " << old.data() << " with this one " << todo.data();
                     cal->deleteTodo(old); // move old to deleted
-                    removeAllICal(d->mTodosRelate, old);
+                    removeAllICal(mTodosRelate, old);
                     cal->addTodo(todo); // and replace it with this one
                 }
             } else if (deleted) {
@@ -2816,12 +2786,12 @@ bool ICalFormatImpl::populate(const Cale
                 if (deleted) {
                     // qCDebug(KCALCORE_LOG) << "Event " << event->uid() << " already deleted";
                     cal->deleteEvent(old); // move old to deleted
-                    removeAllICal(d->mEventsRelate, old);
+                    removeAllICal(mEventsRelate, old);
                 } else if (event->revision() > old->revision()) {
                     // qCDebug(KCALCORE_LOG) << "Replacing old event " << old.data()
                     //                       << " with this one " << event.data();
                     cal->deleteEvent(old); // move old to deleted
-                    removeAllICal(d->mEventsRelate, old);
+                    removeAllICal(mEventsRelate, old);
                     cal->addEvent(event); // and replace it with this one
                 }
             } else if (deleted) {
diff -pruN 5:5.94.0-1/src/icalformat_p.h 5:5.96.0-1/src/icalformat_p.h
--- 5:5.94.0-1/src/icalformat_p.h	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/icalformat_p.h	2022-07-02 14:29:36.000000000 +0000
@@ -31,6 +31,8 @@
 
 #include <libical/ical.h>
 
+#include <memory>
+
 class QDate;
 
 namespace KCalendarCore
@@ -38,6 +40,7 @@ namespace KCalendarCore
 class Alarm;
 class Attachment;
 class Attendee;
+class Compat;
 class Conference;
 class Duration;
 class Event;
@@ -86,7 +89,7 @@ public:
     /**
       Destructor.
     */
-    virtual ~ICalFormatImpl();
+    ~ICalFormatImpl();
 
     /**
       Updates a calendar with data from a raw iCalendar. Incidences already
@@ -222,10 +225,16 @@ protected:
     // void dumpIcalRecurrence( const icalrecurrencetype &r );
 
 private:
-    //@cond PRIVATE
-    class Private;
-    Private *const d;
-    //@endcond
+    void writeIncidenceBase(icalcomponent *parent, const IncidenceBase::Ptr &);
+    void readIncidenceBase(icalcomponent *parent, const IncidenceBase::Ptr &);
+    void writeCustomProperties(icalcomponent *parent, CustomProperties *);
+    void readCustomProperties(icalcomponent *parent, CustomProperties *);
+
+    ICalFormat *mParent = nullptr;
+    QString mLoadedProductId; // PRODID string loaded from calendar file
+    Event::List mEventsRelate; // events with relations
+    Todo::List mTodosRelate; // todos with relations
+    std::unique_ptr<Compat> mCompat;
 };
 
 }
diff -pruN 5:5.94.0-1/src/icaltimezones.cpp 5:5.96.0-1/src/icaltimezones.cpp
--- 5:5.94.0-1/src/icaltimezones.cpp	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/icaltimezones.cpp	2022-07-02 14:29:36.000000000 +0000
@@ -486,7 +486,7 @@ ICalTimeZone ICalTimeZoneParser::parseTi
 
         // If the VTIMEZONE is a known IANA time zone don't bother parsing the rest
         // of the VTIMEZONE, get QTimeZone directly from Qt
-        if (QTimeZone::isTimeZoneIdAvailable(icalTz.id)) {
+        if (QTimeZone::isTimeZoneIdAvailable(icalTz.id) || icalTz.id.startsWith("UTC")) {
             icalTz.qZone = QTimeZone(icalTz.id);
             return icalTz;
         } else {
diff -pruN 5:5.94.0-1/src/incidence.h 5:5.96.0-1/src/incidence.h
--- 5:5.94.0-1/src/incidence.h	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/incidence.h	2022-07-02 14:29:36.000000000 +0000
@@ -64,6 +64,8 @@ class KCALENDARCORE_EXPORT Incidence : p
     Q_PROPERTY(QString location READ location WRITE setLocation)
 #if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 89)
     Q_PROPERTY(bool hasGeo READ hasGeo WRITE setHasGeo)
+#else
+    Q_PROPERTY(bool hasGeo READ hasGeo)
 #endif
     Q_PROPERTY(float geoLatitude READ geoLatitude WRITE setGeoLatitude)
     Q_PROPERTY(float geoLongitude READ geoLongitude WRITE setGeoLongitude)
diff -pruN 5:5.94.0-1/src/memorycalendar.cpp 5:5.96.0-1/src/memorycalendar.cpp
--- 5:5.94.0-1/src/memorycalendar.cpp	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/memorycalendar.cpp	2022-07-02 14:29:36.000000000 +0000
@@ -586,7 +586,7 @@ Event::List MemoryCalendar::rawEventsFor
     if (timeZone.isValid() && timeZone != this->timeZone()) {
         // We cannot use the hash table on date, since time zone is different.
         eventList = rawEvents(date, date, timeZone, false);
-        return Calendar::sortEvents(eventList, sortField, sortDirection);
+        return Calendar::sortEvents(std::move(eventList), sortField, sortDirection);
     }
 
     // Iterate over all non-recurring, single-day events that start on this date
@@ -621,7 +621,7 @@ Event::List MemoryCalendar::rawEventsFor
         }
     }
 
-    return Calendar::sortEvents(eventList, sortField, sortDirection);
+    return Calendar::sortEvents(std::move(eventList), sortField, sortDirection);
 }
 
 Event::List MemoryCalendar::rawEvents(const QDate &start, const QDate &end, const QTimeZone &timeZone, bool inclusive) const
@@ -679,10 +679,12 @@ Event::List MemoryCalendar::rawEvents(co
     return eventList;
 }
 
+#if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 95)
 Event::List MemoryCalendar::rawEventsForDate(const QDateTime &kdt) const
 {
     return rawEventsForDate(kdt.date(), kdt.timeZone());
 }
+#endif
 
 Event::List MemoryCalendar::rawEvents(EventSortField sortField, SortDirection sortDirection) const
 {
diff -pruN 5:5.94.0-1/src/memorycalendar.h 5:5.96.0-1/src/memorycalendar.h
--- 5:5.94.0-1/src/memorycalendar.h	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/memorycalendar.h	2022-07-02 14:29:36.000000000 +0000
@@ -120,10 +120,12 @@ public:
                                                    EventSortField sortField = EventSortUnsorted,
                                                    SortDirection sortDirection = SortDirectionAscending) const override;
 
+#if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 95)
     /**
       @copydoc Calendar::rawEventsForDate(const QDateTime &)const
     */
     Q_REQUIRED_RESULT Event::List rawEventsForDate(const QDateTime &dt) const override;
+#endif
 
     /**
      * Returns an incidence by identifier.
diff -pruN 5:5.94.0-1/src/vcalformat.cpp 5:5.96.0-1/src/vcalformat.cpp
--- 5:5.94.0-1/src/vcalformat.cpp	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/vcalformat.cpp	2022-07-02 14:29:36.000000000 +0000
@@ -23,6 +23,7 @@
 */
 #include "vcalformat.h"
 #include "calendar.h"
+#include "calformat_p.h"
 #include "exceptions.h"
 
 #include "kcalendarcore_debug.h"
@@ -61,7 +62,7 @@ void removeAllVCal(QVector<QSharedPointe
     c.remove(c.indexOf(x));
 }
 
-class Q_DECL_HIDDEN KCalendarCore::VCalFormat::Private
+class KCalendarCore::VCalFormatPrivate : public CalFormatPrivate
 {
 public:
     Calendar::Ptr mCalendar;
@@ -72,13 +73,12 @@ public:
 //@endcond
 
 VCalFormat::VCalFormat()
-    : d(new KCalendarCore::VCalFormat::Private)
+    : CalFormat(new KCalendarCore::VCalFormatPrivate)
 {
 }
 
 VCalFormat::~VCalFormat()
 {
-    delete d;
 }
 
 static void mimeErrorHandler(char *e)
@@ -88,6 +88,7 @@ static void mimeErrorHandler(char *e)
 
 bool VCalFormat::load(const Calendar::Ptr &calendar, const QString &fileName)
 {
+    Q_D(VCalFormat);
     d->mCalendar = calendar;
 
     clearException();
@@ -132,6 +133,7 @@ bool VCalFormat::fromString(const Calend
 
 bool VCalFormat::fromRawString(const Calendar::Ptr &calendar, const QByteArray &string, bool deleted, const QString &notebook)
 {
+    Q_D(VCalFormat);
     d->mCalendar = calendar;
 
     if (!string.size()) {
@@ -170,6 +172,7 @@ QString VCalFormat::toString(const Calen
 
 Todo::Ptr VCalFormat::VTodoToEvent(VObject *vtodo)
 {
+    Q_D(VCalFormat);
     VObject *vo = nullptr;
     VObjectIterator voi;
     char *s = nullptr;
@@ -636,6 +639,7 @@ Todo::Ptr VCalFormat::VTodoToEvent(VObje
 
 Event::Ptr VCalFormat::VEventToEvent(VObject *vevent)
 {
+    Q_D(VCalFormat);
     VObject *vo = nullptr;
     VObjectIterator voi;
     char *s = nullptr;
@@ -1210,6 +1214,7 @@ QString VCalFormat::qDateToISO(const QDa
 
 QString VCalFormat::qDateTimeToISO(const QDateTime &dt, bool zulu)
 {
+    Q_D(VCalFormat);
     if (!dt.isValid()) {
         return QString();
     }
@@ -1235,6 +1240,7 @@ QString VCalFormat::qDateTimeToISO(const
 
 QDateTime VCalFormat::ISOToQDateTime(const QString &dtStr)
 {
+    Q_D(VCalFormat);
 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
     auto noAllocString = QStringView{dtStr};
 #else
@@ -1352,6 +1358,7 @@ bool VCalFormat::parseTZOffsetISO8601(co
 // that is used internally in the VCalFormat.
 void VCalFormat::populate(VObject *vcal, bool deleted, const QString &notebook)
 {
+    Q_D(VCalFormat);
     Q_UNUSED(notebook);
     // this function will populate the caldict dictionary and other event
     // lists. It turns vevents into Events and then inserts them.
@@ -1699,6 +1706,7 @@ void VCalFormat::readCustomProperties(VO
 
 void VCalFormat::writeCustomProperties(VObject *o, const Incidence::Ptr &i)
 {
+    Q_D(VCalFormat);
     const QMap<QByteArray, QString> custom = i->customProperties();
     for (auto cIt = custom.cbegin(); cIt != custom.cend(); ++cIt) {
         const QByteArray property = cIt.key();
@@ -1710,9 +1718,11 @@ void VCalFormat::writeCustomProperties(V
     }
 }
 
+#if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 96)
 void VCalFormat::virtual_hook(int id, void *data)
 {
     Q_UNUSED(id);
     Q_UNUSED(data);
     Q_ASSERT(false);
 }
+#endif
diff -pruN 5:5.94.0-1/src/vcalformat.h 5:5.96.0-1/src/vcalformat.h
--- 5:5.94.0-1/src/vcalformat.h	2022-04-11 22:21:08.000000000 +0000
+++ 5:5.96.0-1/src/vcalformat.h	2022-07-02 14:29:36.000000000 +0000
@@ -47,6 +47,7 @@ namespace KCalendarCore
 {
 class Event;
 class Todo;
+class VCalFormatPrivate;
 
 /**
   @brief
@@ -196,17 +197,21 @@ protected:
     void writeCustomProperties(VObject *o, const Incidence::Ptr &i);
 
 protected:
+#if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 96)
     /**
       @copydoc
       IncidenceBase::virtual_hook()
     */
     void virtual_hook(int id, void *data) override;
+#endif
 
 private:
     //@cond PRIVATE
     Q_DISABLE_COPY(VCalFormat)
-    class Private;
-    Private *const d;
+    Q_DECLARE_PRIVATE(VCalFormat)
+#if KCALENDARCORE_BUILD_DEPRECATED_SINCE(5, 96)
+    void *unused; // former dptr, just kept for ABI compatibility
+#endif
     //@endcond
 };
 
