diff --git a/CMakeLists.txt b/CMakeLists.txt index 242894075..aa1f999cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,94 +1,94 @@ cmake_minimum_required(VERSION 3.0) set(PIM_VERSION "5.6.41") project(KCalCore VERSION ${PIM_VERSION}) # ECM setup set(KF5_VERSION "5.38.0") find_package(ECM ${KF5_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${KCalCore_SOURCE_DIR}/cmake) include(GenerateExportHeader) include(ECMGenerateHeaders) include(ECMGeneratePriFile) include(CMakePackageConfigHelpers) include(ECMSetupVersion) include(FeatureSummary) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMQtDeclareLoggingCategory) include(ECMCoverageOption) set(QT_REQUIRED_VERSION "5.8.0") set(KCALENDARCORE_LIB_VERSION ${PIM_VERSION}) ecm_setup_version(PROJECT VARIABLE_PREFIX KCALCORE VERSION_HEADER "${KCalCore_BINARY_DIR}/kcalcore_version.h" PACKAGE_VERSION_FILE "${KCalCore_BINARY_DIR}/KF5CalendarCoreConfigVersion.cmake" SOVERSION 5 ) ########### Find packages ########### find_package(KF5 ${KF5_VERSION} REQUIRED Config KDELibs4Support) set(LibIcal_MIN_VERSION "0.42") find_package(LibIcal ${LibIcal_MIN_VERSION}) set_package_properties(LibIcal PROPERTIES DESCRIPTION "The libical library" URL "http://sourceforge.net/projects/freeassociation" TYPE REQUIRED ) find_package(BISON REQUIRED) set_package_properties(BISON PROPERTIES DESCRIPTION "general-purpose parser generator" URL "http://www.gnu.org/software/bison" PURPOSE "Required for the Versit parser" TYPE REQUIRED ) ########### CMake Config Files ########### set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5CalendarCore") configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5CalendarCoreConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5CalendarCoreConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) add_definitions(-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII) add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) add_definitions(-DQT_USE_QSTRINGBUILDER) -#add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000) +add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000) ########### Targets ########### add_subdirectory(src) if(BUILD_TESTING) find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Test) add_subdirectory(autotests) endif() add_subdirectory(cmake) ########### Install Files ########### install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5CalendarCoreConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5CalendarCoreConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5CalendarCoreTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5CalendarCoreTargets.cmake NAMESPACE KF5:: ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kcalcore_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/autotests/testicaltimezones.cpp b/autotests/testicaltimezones.cpp index c06794775..b93d1df17 100644 --- a/autotests/testicaltimezones.cpp +++ b/autotests/testicaltimezones.cpp @@ -1,620 +1,620 @@ /* This file is part of the kcalcore library. Copyright (c) 2005-2007 David Jarvie This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "testicaltimezones.h" #include "icaltimezones.h" #include #include #include #include #include QTEST_MAIN(ICalTimeZonesTest) extern "C" { #include } using namespace KCalCore; static icalcomponent *loadCALENDAR(const char *vcal); static icalcomponent *loadVTIMEZONE(const char *vtz); #define QDTUtc(y,mo,d,h,mi,s) QDateTime(QDate(y,mo,d), QTime(h,mi,s), Qt::UTC) #define QDTLocal(y,mo,d,h,mi,s) QDateTime(QDate(y,mo,d), QTime(h,mi,s), Qt::LocalTime) static QDateTime start(QDate(1967, 10, 29), QTime(6, 0, 0), Qt::UTC); static QDateTime daylight87(QDate(1987, 4, 5), QTime(7, 0, 0), Qt::UTC); static QDateTime standardOct87(QDate(1987, 10, 25), QTime(6, 0, 0), Qt::UTC); static QDateTime daylight88(QDate(1988, 4, 3), QTime(7, 0, 0), Qt::UTC); static QDateTime daylight97(QDate(1997, 4, 6), QTime(7, 0, 0), Qt::UTC); static QDateTime standardOct97(QDate(1997, 10, 26), QTime(6, 0, 0), Qt::UTC); static QDateTime spring98(QDate(1998, 5, 5), QTime(7, 0, 0), Qt::UTC); static QDateTime standardOct98(QDate(1998, 10, 25), QTime(6, 0, 0), Qt::UTC); static QDateTime daylight99(QDate(1999, 4, 25), QTime(7, 0, 0), Qt::UTC); static QDateTime standardOct99(QDate(1999, 10, 31), QTime(6, 0, 0), Qt::UTC); static QDateTime daylight00(QDate(2000, 4, 30), QTime(7, 0, 0), Qt::UTC); static QDateTime spring01(QDate(2001, 5, 1), QTime(7, 0, 0), Qt::UTC); // First daylight savings time has an end date, takes a break for a year, // and is then replaced by another static const char *VTZ_Western = "BEGIN:VTIMEZONE\r\n" "TZID:Test-Dummy-Western\r\n" "LAST-MODIFIED:19870101T000000Z\r\n" "TZURL:http://tz.reference.net/dummies/western\r\n" "LOCATION:Zedland/Tryburgh\r\n" "X-LIC-LOCATION:Wyland/Tryburgh\r\n" "BEGIN:STANDARD\r\n" "DTSTART:19671029T020000\r\n" "RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\r\n" "TZOFFSETFROM:-0400\r\n" "TZOFFSETTO:-0500\r\n" "TZNAME:WST\r\n" "END:STANDARD\r\n" "BEGIN:DAYLIGHT\r\n" "DTSTART:19870405T020000\r\n" "RRULE:FREQ=YEARLY;UNTIL=19970406T070000Z;BYDAY=1SU;BYMONTH=4\r\n" "TZOFFSETFROM:-0500\r\n" "TZOFFSETTO:-0400\r\n" "TZNAME:WDT1\r\n" "END:DAYLIGHT\r\n" "BEGIN:DAYLIGHT\r\n" "DTSTART:19990425T020000\r\n" "RDATE;VALUE=DATE-TIME:20000430T020000\r\n" "TZOFFSETFROM:-0500\r\n" "TZOFFSETTO:-0400\r\n" "TZNAME:WDT2\r\n" "END:DAYLIGHT\r\n" "END:VTIMEZONE\r\n"; // Standard time only static const char *VTZ_other = "BEGIN:VTIMEZONE\r\n" "TZID:Test-Dummy-Other\r\n" "TZURL:http://tz.reference.net/dummies/other\r\n" "X-LIC-LOCATION:Wyland/Tryburgh\r\n" "BEGIN:STANDARD\r\n" "DTSTART:19500101T000000\r\n" "RDATE;VALUE=DATE-TIME:19500101T000000\r\n" "TZOFFSETFROM:+0000\r\n" "TZOFFSETTO:+0300\r\n" "TZNAME:OST\r\n" "END:STANDARD\r\n" "END:VTIMEZONE\r\n"; // CALENDAR component header and footer static const char *calendarHeader = "BEGIN:VCALENDAR\r\n" "PRODID:-//Libkcal//NONSGML ICalTimeZonesTest//EN\r\n" "VERSION:2.0\r\n"; static const char *calendarFooter = "END:CALENDAR\r\n"; /////////////////////////// // ICalTimeZoneSource tests /////////////////////////// void ICalTimeZonesTest::parse() { // Create the full CALENDAR text and write it to a temporary file QByteArray text = calendarHeader; text += VTZ_Western; text += VTZ_other; text += calendarFooter; QTemporaryFile tmpFile; tmpFile.open(); QString path = tmpFile.fileName(); QTextStream ts(&tmpFile); ts << text.data(); ts.flush(); // Parse the file, the CALENDAR text string and the individual VTIMEZONE strings, // and check that ICalTimeZone instances with the same names are created in each case. ICalTimeZoneSource src; ICalTimeZones timezones1; QVERIFY(src.parse(path, timezones1)); icalcomponent *calendar = loadCALENDAR(text.constData()); QVERIFY(calendar); ICalTimeZones timezones2; QVERIFY(src.parse(calendar, timezones2)); icaltimezone *icaltz = icaltimezone_new(); for (icalcomponent *ctz = icalcomponent_get_first_component(calendar, ICAL_VTIMEZONE_COMPONENT); ctz; ctz = icalcomponent_get_next_component(calendar, ICAL_VTIMEZONE_COMPONENT)) { ICalTimeZone tz = src.parse(ctz); QVERIFY(tz.isValid()); QVERIFY(timezones1.zone(tz.name()).isValid()); QVERIFY(timezones2.zone(tz.name()).isValid()); QVERIFY(icaltimezone_set_component(icaltz, ctz)); ICalTimeZone tz2 = src.parse(icaltz); QVERIFY(tz2.isValid()); QCOMPARE(tz2.name(), tz.name()); } icaltimezone_free(icaltz, 1); icalcomponent_free(calendar); } ///////////////////// // ICalTimeZone tests ///////////////////// void ICalTimeZonesTest::general() { icalcomponent *vtimezone = loadVTIMEZONE(VTZ_Western); QVERIFY(vtimezone); ICalTimeZoneSource src; ICalTimeZone tz = src.parse(vtimezone); QVERIFY(tz.isValid()); icaltimezone *icaltz = icaltimezone_new(); QVERIFY(icaltimezone_set_component(icaltz, vtimezone)); ICalTimeZone itz = src.parse(icaltz); QVERIFY(itz.isValid()); QCOMPARE(tz.name(), QStringLiteral("Test-Dummy-Western")); QCOMPARE(tz.url(), QByteArray("http://tz.reference.net/dummies/western")); QCOMPARE(tz.city(), QStringLiteral("Zedland/Tryburgh")); QCOMPARE(tz.lastModified(), QDateTime(QDate(1987, 1, 1), QTime(0, 0, 0), Qt::UTC)); QCOMPARE(tz.vtimezone(), QByteArray(VTZ_Western)); ICalTimeZone tz1(tz); QCOMPARE(tz1.name(), tz.name()); QCOMPARE(tz1.url(), tz.url()); QCOMPARE(tz1.city(), tz.city()); QCOMPARE(tz1.lastModified(), tz.lastModified()); QCOMPARE(tz1.vtimezone(), tz.vtimezone()); QCOMPARE(itz.name(), tz.name()); QCOMPARE(itz.url(), tz.url()); QCOMPARE(itz.city(), tz.city()); QCOMPARE(itz.lastModified(), tz.lastModified()); icaltimezone_free(icaltz, 0); vtimezone = loadVTIMEZONE(VTZ_other); QVERIFY(vtimezone); ICalTimeZone tz2 = src.parse(vtimezone); QVERIFY(tz2.isValid()); QVERIFY(icaltimezone_set_component(icaltz, vtimezone)); ICalTimeZone itz2 = src.parse(icaltz); QVERIFY(itz2.isValid()); QCOMPARE(tz2.name(), QStringLiteral("Test-Dummy-Other")); QCOMPARE(tz2.url(), QByteArray("http://tz.reference.net/dummies/other")); QCOMPARE(tz2.city(), QStringLiteral("Wyland/Tryburgh")); QVERIFY(tz2.lastModified().isNull()); QCOMPARE(tz2.vtimezone(), QByteArray(VTZ_other)); tz1 = tz2; QCOMPARE(tz1.name(), tz2.name()); QCOMPARE(tz1.url(), tz2.url()); QCOMPARE(tz1.city(), tz2.city()); QCOMPARE(tz1.lastModified(), tz2.lastModified()); QCOMPARE(tz1.vtimezone(), tz2.vtimezone()); QCOMPARE(tz1.name(), itz2.name()); QCOMPARE(tz1.url(), itz2.url()); QCOMPARE(tz1.city(), itz2.city()); QCOMPARE(tz1.lastModified(), itz2.lastModified()); icaltimezone_free(icaltz, 1); } void ICalTimeZonesTest::offsetAtUtc() { QDateTime local(QDate(2000, 6, 30), QTime(7, 0, 0), Qt::LocalTime); icalcomponent *vtimezone = loadVTIMEZONE(VTZ_Western); QVERIFY(vtimezone); ICalTimeZoneSource src; ICalTimeZone tz = src.parse(vtimezone); QVERIFY(tz.isValid()); icalcomponent_free(vtimezone); QCOMPARE(tz.data(true)->previousUtcOffset(), -4 * 3600); QCOMPARE(tz.transitions()[0].time(), start); QCOMPARE(tz.offsetAtUtc(start.addSecs(-1)), -4 * 3600); QCOMPARE(tz.offsetAtUtc(start), -5 * 3600); QCOMPARE(tz.offsetAtUtc(daylight87.addSecs(-1)), -5 * 3600); QCOMPARE(tz.offsetAtUtc(daylight87), -4 * 3600); QCOMPARE(tz.offsetAtUtc(standardOct87.addSecs(-1)), -4 * 3600); QCOMPARE(tz.offsetAtUtc(standardOct87), -5 * 3600); QCOMPARE(tz.offsetAtUtc(standardOct87.addDays(1)), -5 * 3600); QCOMPARE(tz.offsetAtUtc(daylight88.addSecs(-1)), -5 * 3600); QCOMPARE(tz.offsetAtUtc(daylight88), -4 * 3600); QCOMPARE(tz.offsetAtUtc(daylight97.addSecs(-1)), -5 * 3600); QCOMPARE(tz.offsetAtUtc(daylight97), -4 * 3600); QCOMPARE(tz.offsetAtUtc(standardOct97.addSecs(-1)), -4 * 3600); QCOMPARE(tz.offsetAtUtc(standardOct97), -5 * 3600); QCOMPARE(tz.offsetAtUtc(spring98), -5 * 3600); QCOMPARE(tz.offsetAtUtc(standardOct98.addSecs(-1)), -5 * 3600); QCOMPARE(tz.offsetAtUtc(standardOct98), -5 * 3600); QCOMPARE(tz.offsetAtUtc(daylight99.addSecs(-1)), -5 * 3600); QCOMPARE(tz.offsetAtUtc(daylight99), -4 * 3600); QCOMPARE(tz.offsetAtUtc(standardOct99.addSecs(-1)), -4 * 3600); QCOMPARE(tz.offsetAtUtc(standardOct99), -5 * 3600); QCOMPARE(tz.offsetAtUtc(daylight00.addSecs(-1)), -5 * 3600); QCOMPARE(tz.offsetAtUtc(daylight00), -4 * 3600); QCOMPARE(tz.offsetAtUtc(spring01), -5 * 3600); QCOMPARE(tz.offsetAtUtc(local), 0); // Check that copy constructor copies phases correctly ICalTimeZone tz1(tz); QCOMPARE(tz1.offsetAtUtc(start.addSecs(-1)), -4 * 3600); QCOMPARE(tz1.offsetAtUtc(start), -5 * 3600); QCOMPARE(tz1.offsetAtUtc(daylight87.addSecs(-1)), -5 * 3600); QCOMPARE(tz1.offsetAtUtc(daylight87), -4 * 3600); QCOMPARE(tz1.offsetAtUtc(standardOct87.addSecs(-1)), -4 * 3600); QCOMPARE(tz1.offsetAtUtc(standardOct87), -5 * 3600); QCOMPARE(tz1.offsetAtUtc(standardOct87.addDays(1)), -5 * 3600); QCOMPARE(tz1.offsetAtUtc(daylight88.addSecs(-1)), -5 * 3600); QCOMPARE(tz1.offsetAtUtc(daylight88), -4 * 3600); QCOMPARE(tz1.offsetAtUtc(daylight97.addSecs(-1)), -5 * 3600); QCOMPARE(tz1.offsetAtUtc(daylight97), -4 * 3600); QCOMPARE(tz1.offsetAtUtc(standardOct97.addSecs(-1)), -4 * 3600); QCOMPARE(tz1.offsetAtUtc(standardOct97), -5 * 3600); QCOMPARE(tz1.offsetAtUtc(spring98), -5 * 3600); QCOMPARE(tz1.offsetAtUtc(standardOct98.addSecs(-1)), -5 * 3600); QCOMPARE(tz1.offsetAtUtc(standardOct98), -5 * 3600); QCOMPARE(tz1.offsetAtUtc(daylight99.addSecs(-1)), -5 * 3600); QCOMPARE(tz1.offsetAtUtc(daylight99), -4 * 3600); QCOMPARE(tz1.offsetAtUtc(standardOct99.addSecs(-1)), -4 * 3600); QCOMPARE(tz1.offsetAtUtc(standardOct99), -5 * 3600); QCOMPARE(tz1.offsetAtUtc(daylight00.addSecs(-1)), -5 * 3600); QCOMPARE(tz1.offsetAtUtc(daylight00), -4 * 3600); QCOMPARE(tz1.offsetAtUtc(spring01), -5 * 3600); QCOMPARE(tz1.offsetAtUtc(local), 0); } void ICalTimeZonesTest::offset() { icalcomponent *vtimezone = loadVTIMEZONE(VTZ_Western); QVERIFY(vtimezone); ICalTimeZoneSource src; ICalTimeZone tz = src.parse(vtimezone); QVERIFY(tz.isValid()); icalcomponent_free(vtimezone); QCOMPARE(tz.offset(KTimeZone::toTime_t(start.addSecs(-1))), -4 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(start)), -5 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(daylight87.addSecs(-1))), -5 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(daylight87)), -4 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(standardOct87.addSecs(-1))), -4 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(standardOct87)), -5 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(standardOct87.addDays(1))), -5 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(daylight88.addSecs(-1))), -5 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(daylight88)), -4 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(daylight97.addSecs(-1))), -5 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(daylight97)), -4 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(standardOct97.addSecs(-1))), -4 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(standardOct97)), -5 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(spring98)), -5 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(standardOct98.addSecs(-1))), -5 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(standardOct98)), -5 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(daylight99.addSecs(-1))), -5 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(daylight99)), -4 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(standardOct99.addSecs(-1))), -4 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(standardOct99)), -5 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(daylight00.addSecs(-1))), -5 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(daylight00)), -4 * 3600); QCOMPARE(tz.offset(KTimeZone::toTime_t(spring01)), -5 * 3600); } void ICalTimeZonesTest::offsetAtZoneTime() { int offset2; icalcomponent *vtimezone = loadVTIMEZONE(VTZ_Western); QVERIFY(vtimezone); ICalTimeZoneSource src; icaltimezone *icaltz = icaltimezone_new(); QVERIFY(icaltimezone_set_component(icaltz, vtimezone)); ICalTimeZone tz = src.parse(icaltz); QVERIFY(tz.isValid()); // Standard time: start of definitions at 2:00:00 local time QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1967, 10, 29, 0, 59, 59), &offset2), -4 * 3600); QCOMPARE(offset2, -4 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1967, 10, 29, 1, 0, 0), &offset2), -4 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1967, 10, 29, 1, 59, 59), &offset2), -4 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1967, 10, 29, 2, 0, 0), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1967, 10, 29, 2, 59, 59), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1967, 10, 29, 3, 0, 0), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); // Change to daylight savings time at 2:00:00 local time // Local times 2:00:00 to 2:59:59 don't exist. QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1987, 4, 5, 1, 59, 59), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1987, 4, 5, 2, 0, 0), &offset2), KTimeZone::InvalidOffset); QCOMPARE(offset2, KTimeZone::InvalidOffset); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1987, 4, 5, 2, 59, 59), &offset2), KTimeZone::InvalidOffset); QCOMPARE(offset2, KTimeZone::InvalidOffset); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1987, 4, 5, 3, 0, 0), &offset2), -4 * 3600); QCOMPARE(offset2, -4 * 3600); // Change to standard time at 2:00:00 local time // Local times 2:00:00 to 2:59:59 occur twice. QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1987, 10, 25, 0, 59, 59), &offset2), -4 * 3600); QCOMPARE(offset2, -4 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1987, 10, 25, 1, 0, 0), &offset2), -4 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1987, 10, 25, 1, 59, 59), &offset2), -4 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1987, 10, 25, 2, 0, 0), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1987, 10, 25, 2, 59, 59), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1987, 10, 25, 3, 0, 0), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); // Change to daylight savings time at 2:00:00 local time // Local times 2:00:00 to 2:59:59 don't exist. QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1988, 4, 3, 1, 59, 59), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1988, 4, 3, 2, 0, 0), &offset2), KTimeZone::InvalidOffset); QCOMPARE(offset2, KTimeZone::InvalidOffset); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1988, 4, 3, 2, 59, 59), &offset2), KTimeZone::InvalidOffset); QCOMPARE(offset2, KTimeZone::InvalidOffset); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1988, 4, 3, 3, 0, 0), &offset2), -4 * 3600); QCOMPARE(offset2, -4 * 3600); // Change to daylight savings time at 2:00:00 local time // Local times 2:00:00 to 2:59:59 don't exist. QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1997, 4, 6, 1, 59, 59), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1997, 4, 6, 2, 0, 0), &offset2), KTimeZone::InvalidOffset); QCOMPARE(offset2, KTimeZone::InvalidOffset); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1997, 4, 6, 2, 59, 59), &offset2), KTimeZone::InvalidOffset); QCOMPARE(offset2, KTimeZone::InvalidOffset); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1997, 4, 6, 3, 0, 0), &offset2), -4 * 3600); QCOMPARE(offset2, -4 * 3600); // Change to standard time at 2:00:00 local time // Local times 2:00:00 to 2:59:59 occur twice. QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1997, 10, 26, 0, 59, 59), &offset2), -4 * 3600); QCOMPARE(offset2, -4 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1997, 10, 26, 1, 0, 0), &offset2), -4 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1997, 10, 26, 1, 59, 59), &offset2), -4 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1997, 10, 26, 2, 0, 0), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1997, 10, 26, 2, 59, 59), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1997, 10, 26, 3, 0, 0), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); // In standard time (no daylight savings this year) QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1998, 5, 5, 2, 0, 0), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); // Remain in standard time (no daylight savings this year) QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1998, 10, 25, 0, 59, 59), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1998, 10, 25, 1, 59, 59), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1998, 10, 25, 2, 0, 0), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1998, 10, 25, 2, 59, 59), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1998, 10, 25, 3, 0, 0), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); // Change to daylight savings time at 2:00:00 local time // Local times 2:00:00 to 2:59:59 don't exist. QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1999, 4, 25, 1, 59, 59), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1999, 4, 25, 2, 0, 0), &offset2), KTimeZone::InvalidOffset); QCOMPARE(offset2, KTimeZone::InvalidOffset); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1999, 4, 25, 2, 59, 59), &offset2), KTimeZone::InvalidOffset); QCOMPARE(offset2, KTimeZone::InvalidOffset); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1999, 4, 25, 3, 0, 0), &offset2), -4 * 3600); QCOMPARE(offset2, -4 * 3600); // Change to standard time at 2:00:00 local time // Local times 2:00:00 to 2:59:59 occur twice. QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1999, 10, 31, 0, 59, 59), &offset2), -4 * 3600); QCOMPARE(offset2, -4 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1999, 10, 31, 1, 0, 0), &offset2), -4 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1999, 10, 31, 1, 59, 59), &offset2), -4 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1999, 10, 31, 2, 0, 0), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1999, 10, 31, 2, 59, 59), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(1999, 10, 31, 3, 0, 0), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); // Change to daylight savings time at 2:00:00 local time // Local times 2:00:00 to 2:59:59 don't exist. QCOMPARE(tz.offsetAtZoneTime(QDTLocal(2000, 4, 30, 1, 59, 59), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(2000, 4, 30, 2, 0, 0), &offset2), KTimeZone::InvalidOffset); QCOMPARE(offset2, KTimeZone::InvalidOffset); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(2000, 4, 30, 2, 59, 59), &offset2), KTimeZone::InvalidOffset); QCOMPARE(offset2, KTimeZone::InvalidOffset); QCOMPARE(tz.offsetAtZoneTime(QDTLocal(2000, 4, 30, 3, 0, 0), &offset2), -4 * 3600); QCOMPARE(offset2, -4 * 3600); // In standard time (no daylight savings this year) QCOMPARE(tz.offsetAtZoneTime(QDTLocal(2001, 5, 1, 2, 0, 0), &offset2), -5 * 3600); QCOMPARE(offset2, -5 * 3600); // UTC time QCOMPARE(tz.offsetAtZoneTime(daylight99.addSecs(-1), &offset2), KTimeZone::InvalidOffset); QCOMPARE(offset2, KTimeZone::InvalidOffset); icaltimezone_free(icaltz, 1); } void ICalTimeZonesTest::abbreviation() { QDateTime local(QDate(2000, 6, 30), QTime(7, 0, 0), Qt::LocalTime); icalcomponent *vtimezone = loadVTIMEZONE(VTZ_Western); QVERIFY(vtimezone); ICalTimeZoneSource src; ICalTimeZone tz = src.parse(vtimezone); QVERIFY(tz.isValid()); icalcomponent_free(vtimezone); QCOMPARE(tz.abbreviation(start), QByteArray("WST")); QCOMPARE(tz.abbreviation(daylight87), QByteArray("WDT1")); QCOMPARE(tz.abbreviation(spring98), QByteArray("WST")); QCOMPARE(tz.abbreviation(daylight99), QByteArray("WDT2")); QCOMPARE(tz.abbreviation(standardOct99), QByteArray("WST")); QCOMPARE(tz.abbreviation(spring01), QByteArray("WST")); QVERIFY(tz.abbreviation(local).isEmpty()); QList abbrs = tz.abbreviations(); QCOMPARE(abbrs.count(), 3); QVERIFY(abbrs.indexOf(QByteArray("WST")) >= 0); QVERIFY(abbrs.indexOf(QByteArray("WDT1")) >= 0); QVERIFY(abbrs.indexOf(QByteArray("WDT2")) >= 0); } void ICalTimeZonesTest::isDstAtUtc() { QDateTime local(QDate(2000, 6, 30), QTime(7, 0, 0), Qt::LocalTime); icalcomponent *vtimezone = loadVTIMEZONE(VTZ_Western); QVERIFY(vtimezone); ICalTimeZoneSource src; ICalTimeZone tz = src.parse(vtimezone); QVERIFY(tz.isValid()); icalcomponent_free(vtimezone); QVERIFY(!tz.isDstAtUtc(start.addSecs(-1))); QVERIFY(!tz.isDstAtUtc(start)); QVERIFY(!tz.isDstAtUtc(daylight87.addSecs(-1))); QVERIFY(tz.isDstAtUtc(daylight87)); QVERIFY(tz.isDstAtUtc(standardOct87.addSecs(-1))); QVERIFY(!tz.isDstAtUtc(standardOct87)); QVERIFY(!tz.isDstAtUtc(standardOct87.addDays(1))); QVERIFY(!tz.isDstAtUtc(daylight88.addSecs(-1))); QVERIFY(tz.isDstAtUtc(daylight88)); QVERIFY(!tz.isDstAtUtc(daylight97.addSecs(-1))); QVERIFY(tz.isDstAtUtc(daylight97)); QVERIFY(tz.isDstAtUtc(standardOct97.addSecs(-1))); QVERIFY(!tz.isDstAtUtc(standardOct97)); QVERIFY(!tz.isDstAtUtc(spring98)); QVERIFY(!tz.isDstAtUtc(standardOct98.addSecs(-1))); QVERIFY(!tz.isDstAtUtc(standardOct98)); QVERIFY(!tz.isDstAtUtc(daylight99.addSecs(-1))); QVERIFY(tz.isDstAtUtc(daylight99)); QVERIFY(tz.isDstAtUtc(standardOct99.addSecs(-1))); QVERIFY(!tz.isDstAtUtc(standardOct99)); QVERIFY(!tz.isDstAtUtc(daylight00.addSecs(-1))); QVERIFY(tz.isDstAtUtc(daylight00)); QVERIFY(!tz.isDstAtUtc(spring01)); QVERIFY(!tz.isDstAtUtc(local)); } void ICalTimeZonesTest::isDst() { icalcomponent *vtimezone = loadVTIMEZONE(VTZ_Western); QVERIFY(vtimezone); ICalTimeZoneSource src; ICalTimeZone tz = src.parse(vtimezone); QVERIFY(tz.isValid()); icalcomponent_free(vtimezone); - QVERIFY(!tz.isDst((time_t)start.addSecs(-1).toTime_t())); - QVERIFY(!tz.isDst((time_t)start.toTime_t())); - QVERIFY(!tz.isDst((time_t)daylight87.addSecs(-1).toTime_t())); - QVERIFY(tz.isDst((time_t)daylight87.toTime_t())); - QVERIFY(tz.isDst((time_t)standardOct87.addSecs(-1).toTime_t())); - QVERIFY(!tz.isDst((time_t)standardOct87.toTime_t())); - QVERIFY(!tz.isDst((time_t)standardOct87.addDays(1).toTime_t())); - QVERIFY(!tz.isDst((time_t)daylight88.addSecs(-1).toTime_t())); - QVERIFY(tz.isDst((time_t)daylight88.toTime_t())); - QVERIFY(!tz.isDst((time_t)daylight97.addSecs(-1).toTime_t())); - QVERIFY(tz.isDst((time_t)daylight97.toTime_t())); - QVERIFY(tz.isDst((time_t)standardOct97.addSecs(-1).toTime_t())); - QVERIFY(!tz.isDst((time_t)standardOct97.toTime_t())); - QVERIFY(!tz.isDst((time_t)spring98.toTime_t())); - QVERIFY(!tz.isDst((time_t)standardOct98.addSecs(-1).toTime_t())); - QVERIFY(!tz.isDst((time_t)standardOct98.toTime_t())); - QVERIFY(!tz.isDst((time_t)daylight99.addSecs(-1).toTime_t())); - QVERIFY(tz.isDst((time_t)daylight99.toTime_t())); - QVERIFY(tz.isDst((time_t)standardOct99.addSecs(-1).toTime_t())); - QVERIFY(!tz.isDst((time_t)standardOct99.toTime_t())); - QVERIFY(!tz.isDst((time_t)daylight00.addSecs(-1).toTime_t())); - QVERIFY(tz.isDst((time_t)daylight00.toTime_t())); - QVERIFY(!tz.isDst((time_t)spring01.toTime_t())); + QVERIFY(!tz.isDst((time_t)start.addSecs(-1).toSecsSinceEpoch())); + QVERIFY(!tz.isDst((time_t)start.toSecsSinceEpoch())); + QVERIFY(!tz.isDst((time_t)daylight87.addSecs(-1).toSecsSinceEpoch())); + QVERIFY(tz.isDst((time_t)daylight87.toSecsSinceEpoch())); + QVERIFY(tz.isDst((time_t)standardOct87.addSecs(-1).toSecsSinceEpoch())); + QVERIFY(!tz.isDst((time_t)standardOct87.toSecsSinceEpoch())); + QVERIFY(!tz.isDst((time_t)standardOct87.addDays(1).toSecsSinceEpoch())); + QVERIFY(!tz.isDst((time_t)daylight88.addSecs(-1).toSecsSinceEpoch())); + QVERIFY(tz.isDst((time_t)daylight88.toSecsSinceEpoch())); + QVERIFY(!tz.isDst((time_t)daylight97.addSecs(-1).toSecsSinceEpoch())); + QVERIFY(tz.isDst((time_t)daylight97.toSecsSinceEpoch())); + QVERIFY(tz.isDst((time_t)standardOct97.addSecs(-1).toSecsSinceEpoch())); + QVERIFY(!tz.isDst((time_t)standardOct97.toSecsSinceEpoch())); + QVERIFY(!tz.isDst((time_t)spring98.toSecsSinceEpoch())); + QVERIFY(!tz.isDst((time_t)standardOct98.addSecs(-1).toSecsSinceEpoch())); + QVERIFY(!tz.isDst((time_t)standardOct98.toSecsSinceEpoch())); + QVERIFY(!tz.isDst((time_t)daylight99.addSecs(-1).toSecsSinceEpoch())); + QVERIFY(tz.isDst((time_t)daylight99.toSecsSinceEpoch())); + QVERIFY(tz.isDst((time_t)standardOct99.addSecs(-1).toSecsSinceEpoch())); + QVERIFY(!tz.isDst((time_t)standardOct99.toSecsSinceEpoch())); + QVERIFY(!tz.isDst((time_t)daylight00.addSecs(-1).toSecsSinceEpoch())); + QVERIFY(tz.isDst((time_t)daylight00.toSecsSinceEpoch())); + QVERIFY(!tz.isDst((time_t)spring01.toSecsSinceEpoch())); } void ICalTimeZonesTest::utcOffsets() { icalcomponent *vtimezone = loadVTIMEZONE(VTZ_Western); QVERIFY(vtimezone); ICalTimeZoneSource src; ICalTimeZone tz = src.parse(vtimezone); QVERIFY(tz.isValid()); icalcomponent_free(vtimezone); vtimezone = loadVTIMEZONE(VTZ_other); QVERIFY(vtimezone); ICalTimeZone tz2 = src.parse(vtimezone); QVERIFY(tz2.isValid()); icalcomponent_free(vtimezone); QList offsets = tz.utcOffsets(); QCOMPARE(offsets.count(), 2); QCOMPARE(offsets[0], -5 * 3600); QCOMPARE(offsets[1], -4 * 3600); offsets = tz2.utcOffsets(); QCOMPARE(offsets.count(), 1); QCOMPARE(offsets[0], 3 * 3600); } icalcomponent *loadCALENDAR(const char *vcal) { icalcomponent *calendar = icalcomponent_new_from_string(const_cast(vcal)); if (calendar) { if (icalcomponent_isa(calendar) == ICAL_VCALENDAR_COMPONENT) { return calendar; } icalcomponent_free(calendar); } return nullptr; } icalcomponent *loadVTIMEZONE(const char *vtz) { icalcomponent *vtimezone = icalcomponent_new_from_string(const_cast(vtz)); if (vtimezone) { if (icalcomponent_isa(vtimezone) == ICAL_VTIMEZONE_COMPONENT) { return vtimezone; } icalcomponent_free(vtimezone); } return nullptr; } diff --git a/src/icalformat_p.cpp b/src/icalformat_p.cpp index ad17e2543..e832a85f3 100644 --- a/src/icalformat_p.cpp +++ b/src/icalformat_p.cpp @@ -1,3116 +1,3116 @@ /* This file is part of the kcalcore library. Copyright (c) 2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (c) 2006 David Jarvie Copyright (C) 2012 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and defines the internal ICalFormat classes. @brief This class provides the libical dependent functions for ICalFormat. @author Cornelius Schumacher \ @author Reinhold Kainhofer \ @author David Jarvie \ */ #include #include "icalformat_p.h" #include "compat_p.h" #include "event.h" #include "freebusy.h" #include "icalformat.h" #include "icaltimezones.h" #include "incidencebase.h" #include "journal.h" #include "memorycalendar.h" #include "todo.h" #include "visitor.h" #include "utils.h" #include #include "kcalcore_debug.h" #include using namespace KCalCore; static const char APP_NAME_FOR_XPROPERTIES[] = "KCALCORE"; static const char ENABLED_ALARM_XPROPERTY[] = "ENABLED"; static const char IMPLEMENTATION_VERSION_XPROPERTY[] = "X-KDE-ICAL-IMPLEMENTATION-VERSION"; /* Static helpers */ /* static void _dumpIcaltime( const icaltimetype& t) { qCDebug(KCALCORE_LOG) << "--- Y:" << t.year << "M:" << t.month << "D:" << t.day; qCDebug(KCALCORE_LOG) << "--- H:" << t.hour << "M:" << t.minute << "S:" << t.second; qCDebug(KCALCORE_LOG) << "--- isUtc:" << icaltime_is_utc( t ); qCDebug(KCALCORE_LOG) << "--- zoneId:" << icaltimezone_get_tzid( const_cast( t.zone ) ); } */ //@cond PRIVATE template void removeAllICal(QVector< QSharedPointer > &c, const QSharedPointer &x) { if (c.count() < 1) { return; } int cnt = c.count(x); if (cnt != 1) { qCritical() << "There number of relatedTos for this incidence is " << cnt << " (there must be 1 relatedTo only)"; Q_ASSERT_X(false, "removeAllICal", "Count is not 1."); return; } c.remove(c.indexOf(x)); } static QString quoteForParam(const QString &text) { QString tmp = text; tmp.remove(QLatin1Char('"')); if (tmp.contains(QLatin1Char(';')) || tmp.contains(QLatin1Char(':')) || tmp.contains(QLatin1Char(','))) { return tmp; // libical quotes in this case already, see icalparameter_as_ical_string() } return QStringLiteral("\"") + tmp + QStringLiteral("\""); } const int gSecondsPerMinute = 60; const int gSecondsPerHour = gSecondsPerMinute * 60; const int gSecondsPerDay = gSecondsPerHour * 24; const int gSecondsPerWeek = gSecondsPerDay * 7; class ToComponentVisitor : public Visitor { public: ToComponentVisitor(ICalFormatImpl *impl, iTIPMethod m, ICalTimeZones *tzList = nullptr, ICalTimeZones *tzUsedList = nullptr) : mImpl(impl), mComponent(nullptr), mMethod(m), mTzList(tzList), mTzUsedList(tzUsedList) { } ~ToComponentVisitor(); bool visit(const Event::Ptr &e) override { mComponent = mImpl->writeEvent(e, mTzList, mTzUsedList); return true; } bool visit(const Todo::Ptr &t) override { mComponent = mImpl->writeTodo(t, mTzList, mTzUsedList); return true; } bool visit(const Journal::Ptr &j) override { mComponent = mImpl->writeJournal(j, mTzList, mTzUsedList); return true; } bool visit(const FreeBusy::Ptr &fb) override { mComponent = mImpl->writeFreeBusy(fb, mMethod); return true; } icalcomponent *component() { return mComponent; } private: ICalFormatImpl *mImpl = nullptr; icalcomponent *mComponent = nullptr; iTIPMethod mMethod; ICalTimeZones *mTzList = nullptr; ICalTimeZones *mTzUsedList = nullptr; }; 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) { return writeICalDateTime(dt.toUTC(), dayOnly); } ICalFormatImpl::ICalFormatImpl(ICalFormat *parent) : d(new Private(this, parent)) { } ICalFormatImpl::~ICalFormatImpl() { delete d; } QString ICalFormatImpl::loadedProductId() const { return d->mLoadedProductId; } icalcomponent *ICalFormatImpl::writeIncidence(const IncidenceBase::Ptr &incidence, iTIPMethod method, ICalTimeZones *tzList, ICalTimeZones *tzUsedList) { ToComponentVisitor v(this, method, tzList, tzUsedList); if (incidence->accept(v, incidence)) { return v.component(); } else { return nullptr; } } icalcomponent *ICalFormatImpl::writeTodo(const Todo::Ptr &todo, ICalTimeZones *tzlist, ICalTimeZones *tzUsedList) { icalcomponent *vtodo = icalcomponent_new(ICAL_VTODO_COMPONENT); writeIncidence(vtodo, todo.staticCast(), tzlist, tzUsedList); // due date icalproperty *prop; if (todo->hasDueDate()) { icaltimetype due; if (todo->allDay()) { due = writeICalDate(todo->dtDue(true).date()); prop = icalproperty_new_due(due); } else { prop = writeICalDateTimeProperty( ICAL_DUE_PROPERTY, todo->dtDue(true), tzlist, tzUsedList); } icalcomponent_add_property(vtodo, prop); } // start time if (todo->hasStartDate()) { icaltimetype start; if (todo->allDay()) { start = writeICalDate(todo->dtStart(true).date()); prop = icalproperty_new_dtstart(start); } else { prop = writeICalDateTimeProperty( ICAL_DTSTART_PROPERTY, todo->dtStart(true), tzlist, tzUsedList); } icalcomponent_add_property(vtodo, prop); } // completion date (UTC) if (todo->isCompleted()) { if (!todo->hasCompletedDate()) { // If the todo was created by KOrganizer<2.2 it does not have // a correct completion date. Set one now. todo->setCompleted(QDateTime::currentDateTimeUtc()); } icaltimetype completed = writeICalUtcDateTime(todo->completed()); icalcomponent_add_property( vtodo, icalproperty_new_completed(completed)); } icalcomponent_add_property( vtodo, icalproperty_new_percentcomplete(todo->percentComplete())); if (todo->isCompleted()) { if (icalcomponent_count_properties(vtodo, ICAL_STATUS_PROPERTY)) { icalproperty *p = icalcomponent_get_first_property(vtodo, ICAL_STATUS_PROPERTY); icalcomponent_remove_property(vtodo, p); icalproperty_free(p); } icalcomponent_add_property(vtodo, icalproperty_new_status(ICAL_STATUS_COMPLETED)); } if (todo->recurs() && todo->dtDue().isValid()) { // dtDue( first = true ) returns the dtRecurrence() prop = writeICalDateTimeProperty(ICAL_X_PROPERTY, todo->dtDue(), tzlist, tzUsedList); icalproperty_set_x_name(prop, "X-KDE-LIBKCAL-DTRECURRENCE"); icalcomponent_add_property(vtodo, prop); } return vtodo; } icalcomponent *ICalFormatImpl::writeEvent(const Event::Ptr &event, ICalTimeZones *tzlist, ICalTimeZones *tzUsedList) { icalcomponent *vevent = icalcomponent_new(ICAL_VEVENT_COMPONENT); writeIncidence(vevent, event.staticCast(), tzlist, tzUsedList); // start time icalproperty *prop = nullptr; icaltimetype start; QDateTime dt = event->dtStart(); if (dt.isValid()) { if (event->allDay()) { start = writeICalDate(event->dtStart().date()); prop = icalproperty_new_dtstart(start); } else { prop = writeICalDateTimeProperty( ICAL_DTSTART_PROPERTY, event->dtStart(), tzlist, tzUsedList); } icalcomponent_add_property(vevent, prop); } if (event->hasEndDate()) { // End time. // RFC2445 says that if DTEND is present, it has to be greater than DTSTART. icaltimetype end; QDateTime dt = event->dtEnd(); if (event->allDay()) { // +1 day because end date is non-inclusive. end = writeICalDate(dt.date().addDays(1)); icalcomponent_add_property(vevent, icalproperty_new_dtend(end)); } else { if (dt != event->dtStart()) { icalcomponent_add_property( vevent, writeICalDateTimeProperty( ICAL_DTEND_PROPERTY, dt, tzlist, tzUsedList)); } } } // TODO: resources #if 0 // resources QStringList tmpStrList = anEvent->resources(); QString tmpStr = tmpStrList.join(";"); if (!tmpStr.isEmpty()) { addPropValue(vevent, VCResourcesProp, tmpStr.toUtf8()); } #endif // Transparency switch (event->transparency()) { case Event::Transparent: icalcomponent_add_property( vevent, icalproperty_new_transp(ICAL_TRANSP_TRANSPARENT)); break; case Event::Opaque: icalcomponent_add_property( vevent, icalproperty_new_transp(ICAL_TRANSP_OPAQUE)); break; } return vevent; } icalcomponent *ICalFormatImpl::writeFreeBusy(const FreeBusy::Ptr &freebusy, iTIPMethod method) { icalcomponent *vfreebusy = icalcomponent_new(ICAL_VFREEBUSY_COMPONENT); d->writeIncidenceBase(vfreebusy, freebusy.staticCast()); icalcomponent_add_property( vfreebusy, icalproperty_new_dtstart(writeICalUtcDateTime(freebusy->dtStart()))); icalcomponent_add_property( vfreebusy, icalproperty_new_dtend(writeICalUtcDateTime(freebusy->dtEnd()))); #ifdef USE_ICAL_1_0 Q_UNUSED(method); icalcomponent_add_property( vfreebusy, icalproperty_new_uid(freebusy->uid().toUtf8().constData())); #else if (method == iTIPRequest) { icalcomponent_add_property( vfreebusy, icalproperty_new_uid(freebusy->uid().toUtf8().constData())); } #endif //Loops through all the periods in the freebusy object FreeBusyPeriod::List list = freebusy->fullBusyPeriods(); icalperiodtype period = icalperiodtype_null_period(); for (int i = 0, count = list.count(); i < count; ++i) { const FreeBusyPeriod fbPeriod = list[i]; period.start = writeICalUtcDateTime(fbPeriod.start()); if (fbPeriod.hasDuration()) { period.duration = writeICalDuration(fbPeriod.duration()); } else { period.end = writeICalUtcDateTime(fbPeriod.end()); } icalproperty *property = icalproperty_new_freebusy(period); icalparameter_fbtype fbType; switch (fbPeriod.type()) { case FreeBusyPeriod::Free: fbType = ICAL_FBTYPE_FREE; break; case FreeBusyPeriod::Busy: fbType = ICAL_FBTYPE_BUSY; break; case FreeBusyPeriod::BusyTentative: fbType = ICAL_FBTYPE_BUSYTENTATIVE; break; case FreeBusyPeriod::BusyUnavailable: fbType = ICAL_FBTYPE_BUSYUNAVAILABLE; break; case FreeBusyPeriod::Unknown: fbType = ICAL_FBTYPE_X; break; default: fbType = ICAL_FBTYPE_NONE; break; } icalproperty_set_parameter(property, icalparameter_new_fbtype(fbType)); if (!fbPeriod.summary().isEmpty()) { icalparameter *param = icalparameter_new_x("X-SUMMARY"); icalparameter_set_xvalue(param, KCodecs::base64Encode(fbPeriod.summary().toUtf8()).constData()); icalproperty_set_parameter(property, param); } if (!fbPeriod.location().isEmpty()) { icalparameter *param = icalparameter_new_x("X-LOCATION"); icalparameter_set_xvalue(param, KCodecs::base64Encode(fbPeriod.location().toUtf8()).constData()); icalproperty_set_parameter(property, param); } icalcomponent_add_property(vfreebusy, property); } return vfreebusy; } icalcomponent *ICalFormatImpl::writeJournal(const Journal::Ptr &journal, ICalTimeZones *tzlist, ICalTimeZones *tzUsedList) { icalcomponent *vjournal = icalcomponent_new(ICAL_VJOURNAL_COMPONENT); writeIncidence(vjournal, journal.staticCast(), tzlist, tzUsedList); // start time icalproperty *prop = nullptr; QDateTime dt = journal->dtStart(); if (dt.isValid()) { icaltimetype start; if (journal->allDay()) { start = writeICalDate(dt.date()); prop = icalproperty_new_dtstart(start); } else { prop = writeICalDateTimeProperty( ICAL_DTSTART_PROPERTY, dt, tzlist, tzUsedList); } icalcomponent_add_property(vjournal, prop); } return vjournal; } void ICalFormatImpl::writeIncidence(icalcomponent *parent, const Incidence::Ptr &incidence, ICalTimeZones *tzlist, ICalTimeZones *tzUsedList) { if (incidence->schedulingID() != incidence->uid()) { // We need to store the UID in here. The rawSchedulingID will // go into the iCal UID component incidence->setCustomProperty("LIBKCAL", "ID", incidence->uid()); } else { incidence->removeCustomProperty("LIBKCAL", "ID"); } d->writeIncidenceBase(parent, incidence.staticCast()); // creation date in storage icalcomponent_add_property( parent, writeICalDateTimeProperty( ICAL_CREATED_PROPERTY, incidence->created())); // unique id // If the scheduling ID is different from the real UID, the real // one is stored on X-REALID above if (!incidence->schedulingID().isEmpty()) { icalcomponent_add_property( parent, icalproperty_new_uid(incidence->schedulingID().toUtf8().constData())); } // revision if (incidence->revision() > 0) { // 0 is default, so don't write that out icalcomponent_add_property( parent, icalproperty_new_sequence(incidence->revision())); } // last modification date if (incidence->lastModified().isValid()) { icalcomponent_add_property( parent, writeICalDateTimeProperty( ICAL_LASTMODIFIED_PROPERTY, incidence->lastModified())); } // description if (!incidence->description().isEmpty()) { icalcomponent_add_property( parent, writeDescription( incidence->description(), incidence->descriptionIsRich())); } // summary if (!incidence->summary().isEmpty()) { icalcomponent_add_property( parent, writeSummary( incidence->summary(), incidence->summaryIsRich())); } // location if (!incidence->location().isEmpty()) { icalcomponent_add_property( parent, writeLocation( incidence->location(), incidence->locationIsRich())); } // status icalproperty_status status = ICAL_STATUS_NONE; switch (incidence->status()) { case Incidence::StatusTentative: status = ICAL_STATUS_TENTATIVE; break; case Incidence::StatusConfirmed: status = ICAL_STATUS_CONFIRMED; break; case Incidence::StatusCompleted: status = ICAL_STATUS_COMPLETED; break; case Incidence::StatusNeedsAction: status = ICAL_STATUS_NEEDSACTION; break; case Incidence::StatusCanceled: status = ICAL_STATUS_CANCELLED; break; case Incidence::StatusInProcess: status = ICAL_STATUS_INPROCESS; break; case Incidence::StatusDraft: status = ICAL_STATUS_DRAFT; break; case Incidence::StatusFinal: status = ICAL_STATUS_FINAL; break; case Incidence::StatusX: { icalproperty *p = icalproperty_new_status(ICAL_STATUS_X); icalvalue_set_x(icalproperty_get_value(p), incidence->customStatus().toUtf8().constData()); icalcomponent_add_property(parent, p); break; } case Incidence::StatusNone: default: break; } if (status != ICAL_STATUS_NONE) { icalcomponent_add_property(parent, icalproperty_new_status(status)); } // secrecy icalproperty_class secClass; switch (incidence->secrecy()) { case Incidence::SecrecyPublic: secClass = ICAL_CLASS_PUBLIC; break; case Incidence::SecrecyConfidential: secClass = ICAL_CLASS_CONFIDENTIAL; break; case Incidence::SecrecyPrivate: default: secClass = ICAL_CLASS_PRIVATE; break; } if (secClass != ICAL_CLASS_PUBLIC) { icalcomponent_add_property(parent, icalproperty_new_class(secClass)); } // geo if (incidence->hasGeo()) { icalgeotype geo; geo.lat = incidence->geoLatitude(); geo.lon = incidence->geoLongitude(); icalcomponent_add_property(parent, icalproperty_new_geo(geo)); } // priority if (incidence->priority() > 0) { // 0 is undefined priority icalcomponent_add_property( parent, icalproperty_new_priority(incidence->priority())); } // categories QString categories = incidence->categories().join(QLatin1Char(',')); if (!categories.isEmpty()) { icalcomponent_add_property( parent, icalproperty_new_categories(categories.toUtf8().constData())); } // related event if (!incidence->relatedTo().isEmpty()) { icalcomponent_add_property( parent, icalproperty_new_relatedto(incidence->relatedTo().toUtf8().constData())); } // recurrenceid if (incidence->hasRecurrenceId()) { icalproperty *p = writeICalDateTimeProperty( ICAL_RECURRENCEID_PROPERTY, incidence->recurrenceId(), tzlist, tzUsedList); if (incidence->thisAndFuture()) { icalproperty_add_parameter( p, icalparameter_new_range(ICAL_RANGE_THISANDFUTURE)); } icalcomponent_add_property(parent, p); } RecurrenceRule::List rrules(incidence->recurrence()->rRules()); RecurrenceRule::List::ConstIterator rit; for (rit = rrules.constBegin(); rit != rrules.constEnd(); ++rit) { icalcomponent_add_property( parent, icalproperty_new_rrule(writeRecurrenceRule((*rit)))); } RecurrenceRule::List exrules(incidence->recurrence()->exRules()); RecurrenceRule::List::ConstIterator exit; for (exit = exrules.constBegin(); exit != exrules.constEnd(); ++exit) { icalcomponent_add_property( parent, icalproperty_new_exrule(writeRecurrenceRule((*exit)))); } DateList dateList = incidence->recurrence()->exDates(); DateList::ConstIterator exIt; for (exIt = dateList.constBegin(); exIt != dateList.constEnd(); ++exIt) { icalcomponent_add_property( parent, icalproperty_new_exdate(writeICalDate(*exIt))); } auto dateTimeList = incidence->recurrence()->exDateTimes(); for (auto extIt = dateTimeList.constBegin(); extIt != dateTimeList.constEnd(); ++extIt) { icalcomponent_add_property( parent, writeICalDateTimeProperty(ICAL_EXDATE_PROPERTY, *extIt, tzlist, tzUsedList)); } dateList = incidence->recurrence()->rDates(); DateList::ConstIterator rdIt; for (rdIt = dateList.constBegin(); rdIt != dateList.constEnd(); ++rdIt) { icalcomponent_add_property( parent, icalproperty_new_rdate(writeICalDatePeriod(*rdIt))); } dateTimeList = incidence->recurrence()->rDateTimes(); for (auto rdtIt = dateTimeList.constBegin(); rdtIt != dateTimeList.constEnd(); ++rdtIt) { icalcomponent_add_property( parent, writeICalDateTimeProperty(ICAL_RDATE_PROPERTY, *rdtIt, tzlist, tzUsedList)); } // attachments Attachment::List attachments = incidence->attachments(); Attachment::List::ConstIterator atIt; for (atIt = attachments.constBegin(); atIt != attachments.constEnd(); ++atIt) { icalcomponent_add_property(parent, writeAttachment(*atIt)); } // alarms auto alarms = incidence->alarms(); for (auto it = alarms.cbegin(), end = alarms.cend(); it != end; ++it) { icalcomponent_add_component(parent, writeAlarm(*it)); } // duration if (incidence->hasDuration()) { icaldurationtype duration; duration = writeICalDuration(incidence->duration()); icalcomponent_add_property(parent, icalproperty_new_duration(duration)); } } //@cond PRIVATE void ICalFormatImpl::Private::writeIncidenceBase(icalcomponent *parent, const IncidenceBase::Ptr &incidenceBase) { // organizer stuff if (!incidenceBase->organizer()->isEmpty()) { icalproperty *p = mImpl->writeOrganizer(incidenceBase->organizer()); if (p) { icalcomponent_add_property(parent, p); } } icalcomponent_add_property( parent, icalproperty_new_dtstamp(writeICalUtcDateTime(incidenceBase->lastModified()))); // attendees if (incidenceBase->attendeeCount() > 0) { auto attendees = incidenceBase->attendees(); for (auto it = attendees.constBegin(); it != attendees.constEnd(); ++it) { icalproperty *p = mImpl->writeAttendee(*it); if (p) { icalcomponent_add_property(parent, p); } } } //contacts QStringList contacts = incidenceBase->contacts(); for (QStringList::const_iterator it = contacts.constBegin(); it != contacts.constEnd(); ++it) { icalcomponent_add_property(parent, icalproperty_new_contact((*it).toUtf8().constData())); } // comments QStringList comments = incidenceBase->comments(); for (QStringList::const_iterator it = comments.constBegin(); it != comments.constEnd(); ++it) { icalcomponent_add_property(parent, icalproperty_new_comment((*it).toUtf8().constData())); } // url const QUrl url = incidenceBase->url(); if (url.isValid()) { icalcomponent_add_property(parent, icalproperty_new_url(url.toString().toUtf8().constData())); } // custom properties writeCustomProperties(parent, incidenceBase.data()); } void ICalFormatImpl::Private::writeCustomProperties(icalcomponent *parent, CustomProperties *properties) { const QMap custom = properties->customProperties(); for (QMap::ConstIterator c = custom.begin(); c != custom.end(); ++c) { if (c.key().startsWith("X-KDE-VOLATILE")) { //krazy:exclude=strings // We don't write these properties to disk to disk continue; } icalproperty *p = icalproperty_new_x(c.value().toUtf8().constData()); QString parameters = properties->nonKDECustomPropertyParameters(c.key()); // Minimalist parameter handler: extract icalparameter's out of // the given input text (not really parsing as such) if (!parameters.isEmpty()) { const QStringList sl = parameters.split(QLatin1Char(';')); for (const QString ¶meter : sl) { icalparameter *param = icalparameter_new_from_string(parameter.toUtf8().constData()); if (param) { icalproperty_add_parameter(p, param); } } } icalproperty_set_x_name(p, c.key().constData()); icalcomponent_add_property(parent, p); } } //@endcond icalproperty *ICalFormatImpl::writeOrganizer(const Person::Ptr &organizer) { if (organizer->email().isEmpty()) { return nullptr; } icalproperty *p = icalproperty_new_organizer(QByteArray(QByteArray("MAILTO:") + organizer->email().toUtf8()).constData()); if (!organizer->name().isEmpty()) { icalproperty_add_parameter( p, icalparameter_new_cn(quoteForParam(organizer->name()).toUtf8().constData())); } // TODO: Write dir, sent-by and language return p; } icalproperty *ICalFormatImpl::writeDescription(const QString &description, bool isRich) { icalproperty *p = icalproperty_new_description(description.toUtf8().constData()); if (isRich) { icalproperty_add_parameter(p, icalparameter_new_from_string("X-KDE-TEXTFORMAT=HTML")); } return p; } icalproperty *ICalFormatImpl::writeSummary(const QString &summary, bool isRich) { icalproperty *p = icalproperty_new_summary(summary.toUtf8().constData()); if (isRich) { icalproperty_add_parameter(p, icalparameter_new_from_string("X-KDE-TEXTFORMAT=HTML")); } return p; } icalproperty *ICalFormatImpl::writeLocation(const QString &location, bool isRich) { icalproperty *p = icalproperty_new_location(location.toUtf8().constData()); if (isRich) { icalproperty_add_parameter(p, icalparameter_new_from_string("X-KDE-TEXTFORMAT=HTML")); } return p; } icalproperty *ICalFormatImpl::writeAttendee(const Attendee::Ptr &attendee) { if (attendee->email().isEmpty()) { return nullptr; } icalproperty *p = icalproperty_new_attendee(QByteArray(QByteArray("mailto:") + attendee->email().toUtf8()).constData()); if (!attendee->name().isEmpty()) { icalproperty_add_parameter( p, icalparameter_new_cn(quoteForParam(attendee->name()).toUtf8().constData())); } icalproperty_add_parameter( p, icalparameter_new_rsvp(attendee->RSVP() ? ICAL_RSVP_TRUE : ICAL_RSVP_FALSE)); icalparameter_partstat status = ICAL_PARTSTAT_NEEDSACTION; switch (attendee->status()) { default: case Attendee::NeedsAction: status = ICAL_PARTSTAT_NEEDSACTION; break; case Attendee::Accepted: status = ICAL_PARTSTAT_ACCEPTED; break; case Attendee::Declined: status = ICAL_PARTSTAT_DECLINED; break; case Attendee::Tentative: status = ICAL_PARTSTAT_TENTATIVE; break; case Attendee::Delegated: status = ICAL_PARTSTAT_DELEGATED; break; case Attendee::Completed: status = ICAL_PARTSTAT_COMPLETED; break; case Attendee::InProcess: status = ICAL_PARTSTAT_INPROCESS; break; } icalproperty_add_parameter(p, icalparameter_new_partstat(status)); icalparameter_role role = ICAL_ROLE_REQPARTICIPANT; switch (attendee->role()) { case Attendee::Chair: role = ICAL_ROLE_CHAIR; break; default: case Attendee::ReqParticipant: role = ICAL_ROLE_REQPARTICIPANT; break; case Attendee::OptParticipant: role = ICAL_ROLE_OPTPARTICIPANT; break; case Attendee::NonParticipant: role = ICAL_ROLE_NONPARTICIPANT; break; } icalproperty_add_parameter(p, icalparameter_new_role(role)); icalparameter_cutype cutype = ICAL_CUTYPE_INDIVIDUAL; switch (attendee->cuType()) { case Attendee::Unknown: cutype = ICAL_CUTYPE_UNKNOWN; break; default: case Attendee::Individual: cutype = ICAL_CUTYPE_INDIVIDUAL; break; case Attendee::Group: cutype = ICAL_CUTYPE_GROUP; break; case Attendee::Resource: cutype = ICAL_CUTYPE_RESOURCE; break; case Attendee::Room: cutype = ICAL_CUTYPE_ROOM; break; } icalproperty_add_parameter(p, icalparameter_new_cutype(cutype)); if (!attendee->uid().isEmpty()) { icalparameter *icalparameter_uid = icalparameter_new_x(attendee->uid().toUtf8().constData()); icalparameter_set_xname(icalparameter_uid, "X-UID"); icalproperty_add_parameter(p, icalparameter_uid); } if (!attendee->delegate().isEmpty()) { icalparameter *icalparameter_delegate = icalparameter_new_delegatedto(attendee->delegate().toUtf8().constData()); icalproperty_add_parameter(p, icalparameter_delegate); } if (!attendee->delegator().isEmpty()) { icalparameter *icalparameter_delegator = icalparameter_new_delegatedfrom(attendee->delegator().toUtf8().constData()); icalproperty_add_parameter(p, icalparameter_delegator); } return p; } icalproperty *ICalFormatImpl::writeAttachment(const Attachment::Ptr &att) { icalattach *attach; if (att->isUri()) { attach = icalattach_new_from_url(att->uri().toUtf8().data()); } else { #ifdef USE_ICAL_0_46 attach = icalattach_new_from_data((const char *)att->data().constData(), nullptr, nullptr); #else attach = icalattach_new_from_data((unsigned char *)att->data().constData(), 0, 0); #endif } icalproperty *p = icalproperty_new_attach(attach); icalattach_unref(attach); if (!att->mimeType().isEmpty()) { icalproperty_add_parameter( p, icalparameter_new_fmttype(att->mimeType().toUtf8().data())); } if (att->isBinary()) { icalproperty_add_parameter(p, icalparameter_new_value(ICAL_VALUE_BINARY)); icalproperty_add_parameter(p, icalparameter_new_encoding(ICAL_ENCODING_BASE64)); } if (att->showInline()) { icalparameter *icalparameter_inline = icalparameter_new_x("inline"); icalparameter_set_xname(icalparameter_inline, "X-CONTENT-DISPOSITION"); icalproperty_add_parameter(p, icalparameter_inline); } if (!att->label().isEmpty()) { icalparameter *icalparameter_label = icalparameter_new_x(att->label().toUtf8().constData()); icalparameter_set_xname(icalparameter_label, "X-LABEL"); icalproperty_add_parameter(p, icalparameter_label); } if (att->isLocal()) { icalparameter *icalparameter_local = icalparameter_new_x("local"); icalparameter_set_xname(icalparameter_local, "X-KONTACT-TYPE"); icalproperty_add_parameter(p, icalparameter_local); } return p; } icalrecurrencetype ICalFormatImpl::writeRecurrenceRule(RecurrenceRule *recur) { icalrecurrencetype r; icalrecurrencetype_clear(&r); switch (recur->recurrenceType()) { case RecurrenceRule::rSecondly: r.freq = ICAL_SECONDLY_RECURRENCE; break; case RecurrenceRule::rMinutely: r.freq = ICAL_MINUTELY_RECURRENCE; break; case RecurrenceRule::rHourly: r.freq = ICAL_HOURLY_RECURRENCE; break; case RecurrenceRule::rDaily: r.freq = ICAL_DAILY_RECURRENCE; break; case RecurrenceRule::rWeekly: r.freq = ICAL_WEEKLY_RECURRENCE; break; case RecurrenceRule::rMonthly: r.freq = ICAL_MONTHLY_RECURRENCE; break; case RecurrenceRule::rYearly: r.freq = ICAL_YEARLY_RECURRENCE; break; default: r.freq = ICAL_NO_RECURRENCE; qCDebug(KCALCORE_LOG) << "no recurrence"; break; } int index = 0; QList bys; QList::ConstIterator it; // Now write out the BY* parts: bys = recur->bySeconds(); index = 0; for (it = bys.constBegin(); it != bys.constEnd(); ++it) { r.by_second[index++] = *it; r.by_second[index++] = static_cast(*it); } bys = recur->byMinutes(); index = 0; for (it = bys.constBegin(); it != bys.constEnd(); ++it) { r.by_minute[index++] = *it; r.by_minute[index++] = static_cast(*it); } bys = recur->byHours(); index = 0; for (it = bys.constBegin(); it != bys.constEnd(); ++it) { r.by_hour[index++] = *it; r.by_hour[index++] = static_cast(*it); } bys = recur->byMonthDays(); index = 0; for (it = bys.constBegin(); it != bys.constEnd(); ++it) { short dShort = static_cast((*it) * 8); r.by_month_day[index++] = static_cast(icalrecurrencetype_day_position(dShort)); } bys = recur->byYearDays(); index = 0; for (it = bys.constBegin(); it != bys.constEnd(); ++it) { r.by_year_day[index++] = static_cast(*it); } bys = recur->byWeekNumbers(); index = 0; for (it = bys.constBegin(); it != bys.constEnd(); ++it) { r.by_week_no[index++] = static_cast(*it); } bys = recur->byMonths(); index = 0; for (it = bys.constBegin(); it != bys.constEnd(); ++it) { r.by_month[index++] = static_cast(*it); } bys = recur->bySetPos(); index = 0; for (it = bys.constBegin(); it != bys.constEnd(); ++it) { r.by_set_pos[index++] = static_cast(*it); } QList byd = recur->byDays(); int day; index = 0; for (QList::ConstIterator dit = byd.constBegin(); dit != byd.constEnd(); ++dit) { day = (*dit).day() % 7 + 1; // convert from Monday=1 to Sunday=1 if ((*dit).pos() < 0) { day += (-(*dit).pos()) * 8; day = -day; } else { day += (*dit).pos() * 8; } r.by_day[index++] = static_cast(day); } r.week_start = static_cast(recur->weekStart() % 7 + 1); if (recur->frequency() > 1) { // Dont' write out INTERVAL=1, because that's the default anyway r.interval = static_cast(recur->frequency()); } if (recur->duration() > 0) { r.count = recur->duration(); } else if (recur->duration() == -1) { r.count = 0; } else { if (recur->allDay()) { r.until = writeICalDate(recur->endDt().date()); } else { r.until = writeICalUtcDateTime(recur->endDt()); } } return r; } icalcomponent *ICalFormatImpl::writeAlarm(const Alarm::Ptr &alarm) { if (alarm->enabled()) { alarm->setCustomProperty(APP_NAME_FOR_XPROPERTIES, ENABLED_ALARM_XPROPERTY, QStringLiteral("TRUE")); } else { alarm->setCustomProperty(APP_NAME_FOR_XPROPERTIES, ENABLED_ALARM_XPROPERTY, QStringLiteral("FALSE")); } icalcomponent *a = icalcomponent_new(ICAL_VALARM_COMPONENT); icalproperty_action action; icalattach *attach = nullptr; switch (alarm->type()) { case Alarm::Procedure: action = ICAL_ACTION_PROCEDURE; attach = icalattach_new_from_url( QFile::encodeName(alarm->programFile()).data()); icalcomponent_add_property(a, icalproperty_new_attach(attach)); if (!alarm->programArguments().isEmpty()) { icalcomponent_add_property( a, icalproperty_new_description(alarm->programArguments().toUtf8().constData())); } break; case Alarm::Audio: action = ICAL_ACTION_AUDIO; if (!alarm->audioFile().isEmpty()) { attach = icalattach_new_from_url( QFile::encodeName(alarm->audioFile()).data()); icalcomponent_add_property(a, icalproperty_new_attach(attach)); } break; case Alarm::Email: { action = ICAL_ACTION_EMAIL; const Person::List addresses = alarm->mailAddresses(); for (Person::List::ConstIterator ad = addresses.constBegin(); ad != addresses.constEnd(); ++ad) { if (!(*ad)->email().isEmpty()) { icalproperty *p = icalproperty_new_attendee(QByteArray(QByteArray("MAILTO:") + (*ad)->email().toUtf8()).constData()); if (!(*ad)->name().isEmpty()) { icalproperty_add_parameter( p, icalparameter_new_cn(quoteForParam((*ad)->name()).toUtf8().constData())); } icalcomponent_add_property(a, p); } } icalcomponent_add_property( a, icalproperty_new_summary(alarm->mailSubject().toUtf8().constData())); icalcomponent_add_property( a, icalproperty_new_description(alarm->mailText().toUtf8().constData())); const QStringList attachments = alarm->mailAttachments(); if (!attachments.isEmpty()) { for (QStringList::const_iterator at = attachments.constBegin(), end = attachments.constEnd(); at != end; ++at) { attach = icalattach_new_from_url(QFile::encodeName(*at).data()); icalcomponent_add_property(a, icalproperty_new_attach(attach)); } } break; } case Alarm::Display: action = ICAL_ACTION_DISPLAY; icalcomponent_add_property( a, icalproperty_new_description(alarm->text().toUtf8().constData())); break; case Alarm::Invalid: default: qCDebug(KCALCORE_LOG) << "Unknown type of alarm"; action = ICAL_ACTION_NONE; break; } icalcomponent_add_property(a, icalproperty_new_action(action)); // Trigger time icaltriggertype trigger; if (alarm->hasTime()) { trigger.time = writeICalUtcDateTime(alarm->time(), false); trigger.duration = icaldurationtype_null_duration(); } else { trigger.time = icaltime_null_time(); Duration offset; if (alarm->hasStartOffset()) { offset = alarm->startOffset(); } else { offset = alarm->endOffset(); } trigger.duration = writeICalDuration(offset); } icalproperty *p = icalproperty_new_trigger(trigger); if (alarm->hasEndOffset()) { icalproperty_add_parameter(p, icalparameter_new_related(ICAL_RELATED_END)); } icalcomponent_add_property(a, p); // Repeat count and duration if (alarm->repeatCount()) { icalcomponent_add_property( a, icalproperty_new_repeat(alarm->repeatCount())); icalcomponent_add_property( a, icalproperty_new_duration(writeICalDuration(alarm->snoozeTime()))); } // Custom properties const QMap custom = alarm->customProperties(); for (QMap::ConstIterator c = custom.begin(); c != custom.end(); ++c) { icalproperty *p = icalproperty_new_x(c.value().toUtf8().constData()); icalproperty_set_x_name(p, c.key().constData()); icalcomponent_add_property(a, p); } icalattach_unref(attach); return a; } Todo::Ptr ICalFormatImpl::readTodo(icalcomponent *vtodo, ICalTimeZones *tzlist) { Todo::Ptr todo(new Todo); readIncidence(vtodo, todo, tzlist); icalproperty *p = icalcomponent_get_first_property(vtodo, ICAL_ANY_PROPERTY); while (p) { icalproperty_kind kind = icalproperty_isa(p); switch (kind) { case ICAL_DUE_PROPERTY: { // due date/time bool allDay = false; QDateTime kdt = readICalDateTimeProperty(p, tzlist, false, &allDay); todo->setDtDue(kdt, true); todo->setAllDay(false); break; } case ICAL_COMPLETED_PROPERTY: // completion date/time todo->setCompleted(readICalDateTimeProperty(p, tzlist)); break; case ICAL_PERCENTCOMPLETE_PROPERTY: // Percent completed todo->setPercentComplete(icalproperty_get_percentcomplete(p)); break; case ICAL_RELATEDTO_PROPERTY: // related todo (parent) todo->setRelatedTo(QString::fromUtf8(icalproperty_get_relatedto(p))); d->mTodosRelate.append(todo); break; case ICAL_DTSTART_PROPERTY: // Flag that todo has start date. Value is read in by readIncidence(). if (!todo->comments().filter(QStringLiteral("NoStartDate")).isEmpty()) { todo->setDtStart(QDateTime()); } break; case ICAL_X_PROPERTY: { const char *name = icalproperty_get_x_name(p); if (QLatin1String(name) == QLatin1String("X-KDE-LIBKCAL-DTRECURRENCE")) { const QDateTime dateTime = readICalDateTimeProperty(p, tzlist); if (dateTime.isValid()) { todo->setDtRecurrence(dateTime); } else { qCDebug(KCALCORE_LOG) << "Invalid dateTime"; } } } break; default: // TODO: do something about unknown properties? break; } p = icalcomponent_get_next_property(vtodo, ICAL_ANY_PROPERTY); } if (d->mCompat) { d->mCompat->fixEmptySummary(todo); } todo->resetDirtyFields(); return todo; } Event::Ptr ICalFormatImpl::readEvent(icalcomponent *vevent, ICalTimeZones *tzlist) { Event::Ptr event(new Event); readIncidence(vevent, event, tzlist); icalproperty *p = icalcomponent_get_first_property(vevent, ICAL_ANY_PROPERTY); bool dtEndProcessed = false; while (p) { icalproperty_kind kind = icalproperty_isa(p); switch (kind) { case ICAL_DTEND_PROPERTY: { // end date and time bool allDay = false; QDateTime kdt = readICalDateTimeProperty(p, tzlist, false, &allDay); if (allDay) { // End date is non-inclusive QDate endDate = kdt.date().addDays(-1); if (d->mCompat) { d->mCompat->fixFloatingEnd(endDate); } if (endDate < event->dtStart().date()) { endDate = event->dtStart().date(); } event->setDtEnd(QDateTime(endDate, {}, event->dtStart().timeZone())); event->setAllDay(true); } else { event->setDtEnd(kdt); event->setAllDay(false); } dtEndProcessed = true; break; } case ICAL_RELATEDTO_PROPERTY: // related event (parent) event->setRelatedTo(QString::fromUtf8(icalproperty_get_relatedto(p))); d->mEventsRelate.append(event); break; case ICAL_TRANSP_PROPERTY: { // Transparency icalproperty_transp transparency = icalproperty_get_transp(p); if (transparency == ICAL_TRANSP_TRANSPARENT) { event->setTransparency(Event::Transparent); } else { event->setTransparency(Event::Opaque); } break; } default: // TODO: do something about unknown properties? break; } p = icalcomponent_get_next_property(vevent, ICAL_ANY_PROPERTY); } // according to rfc2445 the dtend shouldn't be written when it equals // start date. so assign one equal to start date. if (!dtEndProcessed && !event->hasDuration()) { event->setDtEnd(event->dtStart()); } QString msade = event->nonKDECustomProperty("X-MICROSOFT-CDO-ALLDAYEVENT"); if (!msade.isEmpty()) { bool allDay = (msade == QLatin1String("TRUE")); event->setAllDay(allDay); } if (d->mCompat) { d->mCompat->fixEmptySummary(event); } event->resetDirtyFields(); return event; } FreeBusy::Ptr ICalFormatImpl::readFreeBusy(icalcomponent *vfreebusy) { FreeBusy::Ptr freebusy(new FreeBusy); d->readIncidenceBase(vfreebusy, freebusy); icalproperty *p = icalcomponent_get_first_property(vfreebusy, ICAL_ANY_PROPERTY); FreeBusyPeriod::List periods; while (p) { icalproperty_kind kind = icalproperty_isa(p); switch (kind) { case ICAL_DTSTART_PROPERTY: // start date and time (UTC) freebusy->setDtStart(readICalUtcDateTimeProperty(p, nullptr)); break; case ICAL_DTEND_PROPERTY: // end Date and Time (UTC) freebusy->setDtEnd(readICalUtcDateTimeProperty(p, nullptr)); break; case ICAL_FREEBUSY_PROPERTY: { //Any FreeBusy Times (UTC) icalperiodtype icalperiod = icalproperty_get_freebusy(p); QDateTime period_start = readICalUtcDateTime(p, icalperiod.start); FreeBusyPeriod period; if (!icaltime_is_null_time(icalperiod.end)) { QDateTime period_end = readICalUtcDateTime(p, icalperiod.end); period = FreeBusyPeriod(period_start, period_end); } else { Duration duration(readICalDuration(icalperiod.duration)); period = FreeBusyPeriod(period_start, duration); } icalparameter *param = icalproperty_get_first_parameter(p, ICAL_FBTYPE_PARAMETER); if (param) { icalparameter_fbtype fbType = icalparameter_get_fbtype(param); switch (fbType) { case ICAL_FBTYPE_FREE: period.setType(FreeBusyPeriod::Free); break; case ICAL_FBTYPE_BUSY: period.setType(FreeBusyPeriod::Busy); break; case ICAL_FBTYPE_BUSYTENTATIVE: period.setType(FreeBusyPeriod::BusyTentative); break; case ICAL_FBTYPE_BUSYUNAVAILABLE: period.setType(FreeBusyPeriod::BusyUnavailable); break; case ICAL_FBTYPE_X: period.setType(FreeBusyPeriod::Unknown); break; case ICAL_FBTYPE_NONE: period.setType(FreeBusyPeriod::Free); break; } } param = icalproperty_get_first_parameter(p, ICAL_X_PARAMETER); while (param) { if (strncmp(icalparameter_get_xname(param), "X-SUMMARY", 9) == 0) { period.setSummary(QString::fromUtf8( KCodecs::base64Decode(icalparameter_get_xvalue(param)))); } if (strncmp(icalparameter_get_xname(param), "X-LOCATION", 10) == 0) { period.setLocation(QString::fromUtf8( KCodecs::base64Decode(icalparameter_get_xvalue(param)))); } param = icalproperty_get_next_parameter(p, ICAL_X_PARAMETER); } periods.append(period); break; } default: // TODO: do something about unknown properties? break; } p = icalcomponent_get_next_property(vfreebusy, ICAL_ANY_PROPERTY); } freebusy->addPeriods(periods); freebusy->resetDirtyFields(); return freebusy; } Journal::Ptr ICalFormatImpl::readJournal(icalcomponent *vjournal, ICalTimeZones *tzlist) { Journal::Ptr journal(new Journal); readIncidence(vjournal, journal, tzlist); journal->resetDirtyFields(); return journal; } Attendee::Ptr ICalFormatImpl::readAttendee(icalproperty *attendee) { // the following is a hack to support broken calendars (like WebCalendar 1.0.x) // that include non-RFC-compliant attendees. Otherwise libical 0.42 asserts. if (!icalproperty_get_value(attendee)) { return Attendee::Ptr(); } icalparameter *p = nullptr; QString email = QString::fromUtf8(icalproperty_get_attendee(attendee)); if (email.startsWith(QStringLiteral("mailto:"), Qt::CaseInsensitive)) { email = email.mid(7); } // libical may return everything after ATTENDEE tag if the rest is // not meaningful. Verify the address to filter out these cases. if (!Person::isValidEmail(email)) { return Attendee::Ptr(); } QString name; QString uid; p = icalproperty_get_first_parameter(attendee, ICAL_CN_PARAMETER); if (p) { name = QString::fromUtf8(icalparameter_get_cn(p)); } else { } bool rsvp = false; p = icalproperty_get_first_parameter(attendee, ICAL_RSVP_PARAMETER); if (p) { icalparameter_rsvp rsvpParameter = icalparameter_get_rsvp(p); if (rsvpParameter == ICAL_RSVP_TRUE) { rsvp = true; } } Attendee::PartStat status = Attendee::NeedsAction; p = icalproperty_get_first_parameter(attendee, ICAL_PARTSTAT_PARAMETER); if (p) { icalparameter_partstat partStatParameter = icalparameter_get_partstat(p); switch (partStatParameter) { default: case ICAL_PARTSTAT_NEEDSACTION: status = Attendee::NeedsAction; break; case ICAL_PARTSTAT_ACCEPTED: status = Attendee::Accepted; break; case ICAL_PARTSTAT_DECLINED: status = Attendee::Declined; break; case ICAL_PARTSTAT_TENTATIVE: status = Attendee::Tentative; break; case ICAL_PARTSTAT_DELEGATED: status = Attendee::Delegated; break; case ICAL_PARTSTAT_COMPLETED: status = Attendee::Completed; break; case ICAL_PARTSTAT_INPROCESS: status = Attendee::InProcess; break; } } Attendee::Role role = Attendee::ReqParticipant; p = icalproperty_get_first_parameter(attendee, ICAL_ROLE_PARAMETER); if (p) { icalparameter_role roleParameter = icalparameter_get_role(p); switch (roleParameter) { case ICAL_ROLE_CHAIR: role = Attendee::Chair; break; default: case ICAL_ROLE_REQPARTICIPANT: role = Attendee::ReqParticipant; break; case ICAL_ROLE_OPTPARTICIPANT: role = Attendee::OptParticipant; break; case ICAL_ROLE_NONPARTICIPANT: role = Attendee::NonParticipant; break; } } Attendee::CuType cuType = Attendee::Individual; p = icalproperty_get_first_parameter(attendee, ICAL_CUTYPE_PARAMETER); if (p) { icalparameter_cutype cutypeParameter = icalparameter_get_cutype(p); switch (cutypeParameter) { case ICAL_CUTYPE_X: case ICAL_CUTYPE_UNKNOWN: cuType = Attendee::Unknown; break; default: case ICAL_CUTYPE_NONE: case ICAL_CUTYPE_INDIVIDUAL: cuType = Attendee::Individual; break; case ICAL_CUTYPE_GROUP: cuType = Attendee::Group; break; case ICAL_CUTYPE_RESOURCE: cuType = Attendee::Resource; break; case ICAL_CUTYPE_ROOM: cuType = Attendee::Room; break; } } p = icalproperty_get_first_parameter(attendee, ICAL_X_PARAMETER); QMap custom; while (p) { QString xname = QString::fromLatin1(icalparameter_get_xname(p)).toUpper(); QString xvalue = QString::fromUtf8(icalparameter_get_xvalue(p)); if (xname == QLatin1String("X-UID")) { uid = xvalue; } else { custom[xname.toUtf8()] = xvalue; } p = icalproperty_get_next_parameter(attendee, ICAL_X_PARAMETER); } Attendee::Ptr a(new Attendee(name, email, rsvp, status, role, uid)); a->setCuType(cuType); a->customProperties().setCustomProperties(custom); p = icalproperty_get_first_parameter(attendee, ICAL_DELEGATEDTO_PARAMETER); if (p) { a->setDelegate(QLatin1String(icalparameter_get_delegatedto(p))); } p = icalproperty_get_first_parameter(attendee, ICAL_DELEGATEDFROM_PARAMETER); if (p) { a->setDelegator(QLatin1String(icalparameter_get_delegatedfrom(p))); } return a; } Person::Ptr ICalFormatImpl::readOrganizer(icalproperty *organizer) { QString email = QString::fromUtf8(icalproperty_get_organizer(organizer)); if (email.startsWith(QStringLiteral("mailto:"), Qt::CaseInsensitive)) { email = email.mid(7); } QString cn; icalparameter *p = icalproperty_get_first_parameter(organizer, ICAL_CN_PARAMETER); if (p) { cn = QString::fromUtf8(icalparameter_get_cn(p)); } Person::Ptr org(new Person(cn, email)); // TODO: Treat sent-by, dir and language here, too return org; } Attachment::Ptr ICalFormatImpl::readAttachment(icalproperty *attach) { Attachment::Ptr attachment; QByteArray p; icalvalue *value = icalproperty_get_value(attach); switch (icalvalue_isa(value)) { case ICAL_ATTACH_VALUE: { icalattach *a = icalproperty_get_attach(attach); if (!icalattach_get_is_url(a)) { p = QByteArray(reinterpret_cast(icalattach_get_data(a))); if (!p.isEmpty()) { attachment = Attachment::Ptr(new Attachment(p)); } } else { p = icalattach_get_url(a); if (!p.isEmpty()) { attachment = Attachment::Ptr(new Attachment(QString::fromUtf8(p))); } } break; } case ICAL_BINARY_VALUE: { icalattach *a = icalproperty_get_attach(attach); p = QByteArray(reinterpret_cast(icalattach_get_data(a))); if (!p.isEmpty()) { attachment = Attachment::Ptr(new Attachment(p)); } break; } case ICAL_URI_VALUE: p = icalvalue_get_uri(value); attachment = Attachment::Ptr(new Attachment(QString::fromUtf8(p))); break; default: break; } if (attachment) { icalparameter *p = icalproperty_get_first_parameter(attach, ICAL_FMTTYPE_PARAMETER); if (p) { attachment->setMimeType(QLatin1String(icalparameter_get_fmttype(p))); } p = icalproperty_get_first_parameter(attach, ICAL_X_PARAMETER); while (p) { QString xname = QString::fromLatin1(icalparameter_get_xname(p)).toUpper(); QString xvalue = QString::fromUtf8(icalparameter_get_xvalue(p)); if (xname == QLatin1String("X-CONTENT-DISPOSITION")) { attachment->setShowInline(xvalue.toLower() == QLatin1String("inline")); } else if (xname == QLatin1String("X-LABEL")) { attachment->setLabel(xvalue); } else if (xname == QLatin1String("X-KONTACT-TYPE")) { attachment->setLocal(xvalue.toLower() == QLatin1String("local")); } p = icalproperty_get_next_parameter(attach, ICAL_X_PARAMETER); } p = icalproperty_get_first_parameter(attach, ICAL_X_PARAMETER); while (p) { if (strncmp(icalparameter_get_xname(p), "X-LABEL", 7) == 0) { attachment->setLabel(QString::fromUtf8(icalparameter_get_xvalue(p))); } p = icalproperty_get_next_parameter(attach, ICAL_X_PARAMETER); } } return attachment; } void ICalFormatImpl::readIncidence(icalcomponent *parent, const Incidence::Ptr &incidence, ICalTimeZones *tzlist) { d->readIncidenceBase(parent, incidence); icalproperty *p = icalcomponent_get_first_property(parent, ICAL_ANY_PROPERTY); const char *text; int intvalue, inttext; icaldurationtype icalduration; QDateTime kdt; QDateTime dtstamp; QStringList categories; while (p) { icalproperty_kind kind = icalproperty_isa(p); switch (kind) { case ICAL_CREATED_PROPERTY: incidence->setCreated(readICalDateTimeProperty(p, tzlist)); break; case ICAL_DTSTAMP_PROPERTY: dtstamp = readICalDateTimeProperty(p, tzlist); break; case ICAL_SEQUENCE_PROPERTY: // sequence intvalue = icalproperty_get_sequence(p); incidence->setRevision(intvalue); break; case ICAL_LASTMODIFIED_PROPERTY: // last modification UTC date/time incidence->setLastModified(readICalDateTimeProperty(p, tzlist)); break; case ICAL_DTSTART_PROPERTY: { // start date and time bool allDay = false; kdt = readICalDateTimeProperty(p, tzlist, false, &allDay); incidence->setDtStart(kdt); incidence->setAllDay(allDay); break; } case ICAL_DURATION_PROPERTY: // start date and time icalduration = icalproperty_get_duration(p); incidence->setDuration(readICalDuration(icalduration)); break; case ICAL_DESCRIPTION_PROPERTY: { // description QString textStr = QString::fromUtf8(icalproperty_get_description(p)); if (!textStr.isEmpty()) { QString valStr = QString::fromUtf8( icalproperty_get_parameter_as_string(p, "X-KDE-TEXTFORMAT")); if (!valStr.compare(QStringLiteral("HTML"), Qt::CaseInsensitive)) { incidence->setDescription(textStr, true); } else { incidence->setDescription(textStr, false); } } } break; case ICAL_SUMMARY_PROPERTY: { // summary QString textStr = QString::fromUtf8(icalproperty_get_summary(p)); if (!textStr.isEmpty()) { QString valStr = QString::fromUtf8( icalproperty_get_parameter_as_string(p, "X-KDE-TEXTFORMAT")); if (!valStr.compare(QStringLiteral("HTML"), Qt::CaseInsensitive)) { incidence->setSummary(textStr, true); } else { incidence->setSummary(textStr, false); } } } break; case ICAL_LOCATION_PROPERTY: { // location if (!icalproperty_get_value(p)) { //Fix for #191472. This is a pre-crash guard in case libical was //compiled in superstrict mode (--enable-icalerrors-are-fatal) //TODO: pre-crash guard other property getters too. break; } QString textStr = QString::fromUtf8(icalproperty_get_location(p)); if (!textStr.isEmpty()) { QString valStr = QString::fromUtf8( icalproperty_get_parameter_as_string(p, "X-KDE-TEXTFORMAT")); if (!valStr.compare(QStringLiteral("HTML"), Qt::CaseInsensitive)) { incidence->setLocation(textStr, true); } else { incidence->setLocation(textStr, false); } } } break; case ICAL_STATUS_PROPERTY: { // status Incidence::Status stat; switch (icalproperty_get_status(p)) { case ICAL_STATUS_TENTATIVE: stat = Incidence::StatusTentative; break; case ICAL_STATUS_CONFIRMED: stat = Incidence::StatusConfirmed; break; case ICAL_STATUS_COMPLETED: stat = Incidence::StatusCompleted; break; case ICAL_STATUS_NEEDSACTION: stat = Incidence::StatusNeedsAction; break; case ICAL_STATUS_CANCELLED: stat = Incidence::StatusCanceled; break; case ICAL_STATUS_INPROCESS: stat = Incidence::StatusInProcess; break; case ICAL_STATUS_DRAFT: stat = Incidence::StatusDraft; break; case ICAL_STATUS_FINAL: stat = Incidence::StatusFinal; break; case ICAL_STATUS_X: incidence->setCustomStatus( QString::fromUtf8(icalvalue_get_x(icalproperty_get_value(p)))); stat = Incidence::StatusX; break; case ICAL_STATUS_NONE: default: stat = Incidence::StatusNone; break; } if (stat != Incidence::StatusX) { incidence->setStatus(stat); } break; } case ICAL_GEO_PROPERTY: { // geo icalgeotype geo = icalproperty_get_geo(p); incidence->setGeoLatitude(geo.lat); incidence->setGeoLongitude(geo.lon); incidence->setHasGeo(true); break; } case ICAL_PRIORITY_PROPERTY: // priority intvalue = icalproperty_get_priority(p); if (d->mCompat) { intvalue = d->mCompat->fixPriority(intvalue); } incidence->setPriority(intvalue); break; case ICAL_CATEGORIES_PROPERTY: { // categories // We have always supported multiple CATEGORIES properties per component // even though the RFC seems to indicate only 1 is permitted. // We can't change that -- in order to retain backwards compatibility. text = icalproperty_get_categories(p); const QString val = QString::fromUtf8(text); const QStringList lstVal = val.split(QLatin1Char(','), QString::SkipEmptyParts); for (const QString &cat : lstVal) { // ensure no duplicates if (!categories.contains(cat)) { categories.append(cat); } } break; } case ICAL_RECURRENCEID_PROPERTY: // recurrenceId kdt = readICalDateTimeProperty(p, tzlist); if (kdt.isValid()) { incidence->setRecurrenceId(kdt); const icalparameter *param = icalproperty_get_first_parameter(p, ICAL_RANGE_PARAMETER); if (param && icalparameter_get_range(param) == ICAL_RANGE_THISANDFUTURE) { incidence->setThisAndFuture(true); } else { // A workaround for a bug in libical (https://github.com/libical/libical/issues/185) // If a recurrenceId has both tzid and range, both parameters end up in the tzid. // This results in invalid tzid's like: "Europe/Berlin;RANGE=THISANDFUTURE" const icalparameter *param = icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER); QString tzid = QString::fromLatin1(icalparameter_get_tzid(param)); const QStringList parts = tzid.toLower().split(QLatin1Char(';')); for (const QString &part : parts) { if (part == QLatin1String("range=thisandfuture")) { incidence->setThisAndFuture(true); break; } } } } break; case ICAL_RRULE_PROPERTY: readRecurrenceRule(p, incidence); break; case ICAL_RDATE_PROPERTY: { bool allDay = false; kdt = readICalDateTimeProperty(p, tzlist, false, &allDay); if (kdt.isValid()) { if (allDay) { incidence->recurrence()->addRDate(kdt.date()); } else { incidence->recurrence()->addRDateTime(kdt); } } else { // TODO: RDates as period are not yet implemented! } break; } case ICAL_EXRULE_PROPERTY: readExceptionRule(p, incidence); break; case ICAL_EXDATE_PROPERTY: { bool allDay = false; kdt = readICalDateTimeProperty(p, tzlist, false, &allDay); if (allDay) { incidence->recurrence()->addExDate(kdt.date()); } else { incidence->recurrence()->addExDateTime(kdt); } break; } case ICAL_CLASS_PROPERTY: inttext = icalproperty_get_class(p); if (inttext == ICAL_CLASS_PUBLIC) { incidence->setSecrecy(Incidence::SecrecyPublic); } else if (inttext == ICAL_CLASS_CONFIDENTIAL) { incidence->setSecrecy(Incidence::SecrecyConfidential); } else { incidence->setSecrecy(Incidence::SecrecyPrivate); } break; case ICAL_ATTACH_PROPERTY: // attachments incidence->addAttachment(readAttachment(p)); break; default: // TODO: do something about unknown properties? break; } p = icalcomponent_get_next_property(parent, ICAL_ANY_PROPERTY); } // Set the scheduling ID const QString uid = incidence->customProperty("LIBKCAL", "ID"); if (!uid.isNull()) { // The UID stored in incidencebase is actually the scheduling ID // It has to be stored in the iCal UID component for compatibility // with other iCal applications incidence->setSchedulingID(incidence->uid(), uid); } // Now that recurrence and exception stuff is completely set up, // do any backwards compatibility adjustments. if (incidence->recurs() && d->mCompat) { d->mCompat->fixRecurrence(incidence); } // add categories incidence->setCategories(categories); // iterate through all alarms for (icalcomponent *alarm = icalcomponent_get_first_component(parent, ICAL_VALARM_COMPONENT); alarm; alarm = icalcomponent_get_next_component(parent, ICAL_VALARM_COMPONENT)) { readAlarm(alarm, incidence, tzlist); } if (d->mCompat) { // Fix incorrect alarm settings by other applications (like outloook 9) d->mCompat->fixAlarms(incidence); d->mCompat->setCreatedToDtStamp(incidence, dtstamp); } } //@cond PRIVATE void ICalFormatImpl::Private::readIncidenceBase(icalcomponent *parent, const IncidenceBase::Ptr &incidenceBase) { icalproperty *p = icalcomponent_get_first_property(parent, ICAL_ANY_PROPERTY); bool uidProcessed = false; while (p) { icalproperty_kind kind = icalproperty_isa(p); switch (kind) { case ICAL_UID_PROPERTY: // unique id uidProcessed = true; incidenceBase->setUid(QString::fromUtf8(icalproperty_get_uid(p))); break; case ICAL_ORGANIZER_PROPERTY: // organizer incidenceBase->setOrganizer(mImpl->readOrganizer(p)); break; case ICAL_ATTENDEE_PROPERTY: // attendee incidenceBase->addAttendee(mImpl->readAttendee(p)); break; case ICAL_COMMENT_PROPERTY: incidenceBase->addComment( QString::fromUtf8(icalproperty_get_comment(p))); break; case ICAL_CONTACT_PROPERTY: incidenceBase->addContact( QString::fromUtf8(icalproperty_get_contact(p))); break; case ICAL_URL_PROPERTY: incidenceBase->setUrl( QUrl(QString::fromUtf8(icalproperty_get_url(p)))); break; default: break; } p = icalcomponent_get_next_property(parent, ICAL_ANY_PROPERTY); } if (!uidProcessed) { qCWarning(KCALCORE_LOG) << "The incidence didn't have any UID! Report a bug " << "to the application that generated this file." << endl; // Our in-memory incidence has a random uid generated in Event's ctor. // Make it empty so it matches what's in the file: incidenceBase->setUid(QString()); // Otherwise, next time we read the file, this function will return // an event with another random uid and we will have two events in the calendar. } // custom properties readCustomProperties(parent, incidenceBase.data()); } void ICalFormatImpl::Private::readCustomProperties(icalcomponent *parent, CustomProperties *properties) { QByteArray property; QString value, parameters; icalproperty *p = icalcomponent_get_first_property(parent, ICAL_X_PROPERTY); icalparameter *param = nullptr; while (p) { QString nvalue = QString::fromUtf8(icalproperty_get_x(p)); if (nvalue.isEmpty()) { icalvalue *value = icalproperty_get_value(p); if (icalvalue_isa(value) == ICAL_TEXT_VALUE) { // 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; } } const char *name = icalproperty_get_x_name(p); QByteArray nproperty(name); if (property != nproperty) { // New property if (!property.isEmpty()) { properties->setNonKDECustomProperty(property, value, parameters); } property = name; value = nvalue; QStringList parametervalues; for (param = icalproperty_get_first_parameter(p, ICAL_ANY_PARAMETER); param; param = icalproperty_get_next_parameter(p, ICAL_ANY_PARAMETER)) { // 'c' is owned by ical library => all we need to do is just use it const char *c = icalparameter_as_ical_string(param); parametervalues.push_back(QLatin1String(c)); } parameters = parametervalues.join(QLatin1Char(';')); } else { value = value.append(QLatin1Char(',')).append(nvalue); } p = icalcomponent_get_next_property(parent, ICAL_X_PROPERTY); } if (!property.isEmpty()) { properties->setNonKDECustomProperty(property, value, parameters); } } //@endcond void ICalFormatImpl::readRecurrenceRule(icalproperty *rrule, const Incidence::Ptr &incidence) { Recurrence *recur = incidence->recurrence(); struct icalrecurrencetype r = icalproperty_get_rrule(rrule); // dumpIcalRecurrence(r); RecurrenceRule *recurrule = new RecurrenceRule(/*incidence*/); recurrule->setStartDt(incidence->dtStart()); readRecurrence(r, recurrule); recur->addRRule(recurrule); } void ICalFormatImpl::readExceptionRule(icalproperty *rrule, const Incidence::Ptr &incidence) { struct icalrecurrencetype r = icalproperty_get_exrule(rrule); // dumpIcalRecurrence(r); RecurrenceRule *recurrule = new RecurrenceRule(/*incidence*/); recurrule->setStartDt(incidence->dtStart()); readRecurrence(r, recurrule); Recurrence *recur = incidence->recurrence(); recur->addExRule(recurrule); } void ICalFormatImpl::readRecurrence(const struct icalrecurrencetype &r, RecurrenceRule *recur) { // Generate the RRULE string recur->setRRule( QLatin1String(icalrecurrencetype_as_string(const_cast(&r)))); // Period switch (r.freq) { case ICAL_SECONDLY_RECURRENCE: recur->setRecurrenceType(RecurrenceRule::rSecondly); break; case ICAL_MINUTELY_RECURRENCE: recur->setRecurrenceType(RecurrenceRule::rMinutely); break; case ICAL_HOURLY_RECURRENCE: recur->setRecurrenceType(RecurrenceRule::rHourly); break; case ICAL_DAILY_RECURRENCE: recur->setRecurrenceType(RecurrenceRule::rDaily); break; case ICAL_WEEKLY_RECURRENCE: recur->setRecurrenceType(RecurrenceRule::rWeekly); break; case ICAL_MONTHLY_RECURRENCE: recur->setRecurrenceType(RecurrenceRule::rMonthly); break; case ICAL_YEARLY_RECURRENCE: recur->setRecurrenceType(RecurrenceRule::rYearly); break; case ICAL_NO_RECURRENCE: default: recur->setRecurrenceType(RecurrenceRule::rNone); } // Frequency recur->setFrequency(r.interval); // Duration & End Date if (!icaltime_is_null_time(r.until)) { icaltimetype t = r.until; recur->setEndDt(readICalUtcDateTime(nullptr, t)); } else { if (r.count == 0) { recur->setDuration(-1); } else { recur->setDuration(r.count); } } // Week start setting short wkst = static_cast((r.week_start + 5) % 7 + 1); recur->setWeekStart(wkst); // And now all BY* QList lst; int i; int index = 0; //@cond PRIVATE #define readSetByList( rrulecomp, setfunc ) \ index = 0; \ lst.clear(); \ while ( ( i = r.rrulecomp[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { \ lst.append( i ); \ } \ if ( !lst.isEmpty() ) { \ recur->setfunc( lst ); \ } //@endcond // BYSECOND, MINUTE and HOUR, MONTHDAY, YEARDAY, WEEKNUMBER, MONTH // and SETPOS are standard int lists, so we can treat them with the // same macro readSetByList(by_second, setBySeconds); readSetByList(by_minute, setByMinutes); readSetByList(by_hour, setByHours); readSetByList(by_month_day, setByMonthDays); readSetByList(by_year_day, setByYearDays); readSetByList(by_week_no, setByWeekNumbers); readSetByList(by_month, setByMonths); readSetByList(by_set_pos, setBySetPos); #undef readSetByList // BYDAY is a special case, since it's not an int list QList wdlst; short day; index = 0; while ((day = r.by_day[index++]) != ICAL_RECURRENCE_ARRAY_MAX) { RecurrenceRule::WDayPos pos; pos.setDay(static_cast((icalrecurrencetype_day_day_of_week(day) + 5) % 7 + 1)); pos.setPos(icalrecurrencetype_day_position(day)); wdlst.append(pos); } if (!wdlst.isEmpty()) { recur->setByDays(wdlst); } // TODO: Store all X- fields of the RRULE inside the recurrence (so they are // preserved } void ICalFormatImpl::readAlarm(icalcomponent *alarm, const Incidence::Ptr &incidence, ICalTimeZones *tzlist) { Alarm::Ptr ialarm = incidence->newAlarm(); ialarm->setRepeatCount(0); ialarm->setEnabled(true); // Determine the alarm's action type icalproperty *p = icalcomponent_get_first_property(alarm, ICAL_ACTION_PROPERTY); Alarm::Type type = Alarm::Display; icalproperty_action action = ICAL_ACTION_DISPLAY; if (!p) { qCDebug(KCALCORE_LOG) << "Unknown type of alarm, using default"; // TODO: do something about unknown alarm type? } else { action = icalproperty_get_action(p); switch (action) { case ICAL_ACTION_DISPLAY: type = Alarm::Display; break; case ICAL_ACTION_AUDIO: type = Alarm::Audio; break; case ICAL_ACTION_PROCEDURE: type = Alarm::Procedure; break; case ICAL_ACTION_EMAIL: type = Alarm::Email; break; default: break; // TODO: do something about invalid alarm type? } } ialarm->setType(type); p = icalcomponent_get_first_property(alarm, ICAL_ANY_PROPERTY); while (p) { icalproperty_kind kind = icalproperty_isa(p); switch (kind) { case ICAL_TRIGGER_PROPERTY: { icaltriggertype trigger = icalproperty_get_trigger(p); if (!icaltime_is_null_time(trigger.time)) { //set the trigger to a specific time (which is not in rfc2445, btw) ialarm->setTime(readICalUtcDateTime(p, trigger.time, tzlist)); } else { //set the trigger to an offset from the incidence start or end time. if (!icaldurationtype_is_bad_duration(trigger.duration)) { Duration duration(readICalDuration(trigger.duration)); icalparameter *param = icalproperty_get_first_parameter(p, ICAL_RELATED_PARAMETER); if (param && icalparameter_get_related(param) == ICAL_RELATED_END) { ialarm->setEndOffset(duration); } else { ialarm->setStartOffset(duration); } } else { // a bad duration was encountered, just set a 0 duration from start ialarm->setStartOffset(Duration(0)); } } break; } case ICAL_DURATION_PROPERTY: { icaldurationtype duration = icalproperty_get_duration(p); ialarm->setSnoozeTime(readICalDuration(duration)); break; } case ICAL_REPEAT_PROPERTY: ialarm->setRepeatCount(icalproperty_get_repeat(p)); break; case ICAL_DESCRIPTION_PROPERTY: { // Only in DISPLAY and EMAIL and PROCEDURE alarms QString description = QString::fromUtf8(icalproperty_get_description(p)); switch (action) { case ICAL_ACTION_DISPLAY: ialarm->setText(description); break; case ICAL_ACTION_PROCEDURE: ialarm->setProgramArguments(description); break; case ICAL_ACTION_EMAIL: ialarm->setMailText(description); break; default: break; } break; } case ICAL_SUMMARY_PROPERTY: // Only in EMAIL alarm ialarm->setMailSubject(QString::fromUtf8(icalproperty_get_summary(p))); break; case ICAL_ATTENDEE_PROPERTY: { // Only in EMAIL alarm QString email = QString::fromUtf8(icalproperty_get_attendee(p)); if (email.startsWith(QStringLiteral("mailto:"), Qt::CaseInsensitive)) { email = email.mid(7); } QString name; icalparameter *param = icalproperty_get_first_parameter(p, ICAL_CN_PARAMETER); if (param) { name = QString::fromUtf8(icalparameter_get_cn(param)); } ialarm->addMailAddress(Person::Ptr(new Person(name, email))); break; } case ICAL_ATTACH_PROPERTY: { // Only in AUDIO and EMAIL and PROCEDURE alarms Attachment::Ptr attach = readAttachment(p); if (attach && attach->isUri()) { switch (action) { case ICAL_ACTION_AUDIO: ialarm->setAudioFile(attach->uri()); break; case ICAL_ACTION_PROCEDURE: ialarm->setProgramFile(attach->uri()); break; case ICAL_ACTION_EMAIL: ialarm->addMailAttachment(attach->uri()); break; default: break; } } else { qCDebug(KCALCORE_LOG) << "Alarm attachments currently only support URIs," << "but no binary data"; } break; } default: break; } p = icalcomponent_get_next_property(alarm, ICAL_ANY_PROPERTY); } // custom properties d->readCustomProperties(alarm, ialarm.data()); QString locationRadius = ialarm->nonKDECustomProperty("X-LOCATION-RADIUS"); if (!locationRadius.isEmpty()) { ialarm->setLocationRadius(locationRadius.toInt()); ialarm->setHasLocationRadius(true); } if (ialarm->customProperty(APP_NAME_FOR_XPROPERTIES, ENABLED_ALARM_XPROPERTY) == QLatin1String("FALSE")) { ialarm->setEnabled(false); } // TODO: check for consistency of alarm properties } icaldatetimeperiodtype ICalFormatImpl::writeICalDatePeriod(const QDate &date) { icaldatetimeperiodtype t; t.time = writeICalDate(date); t.period = icalperiodtype_null_period(); return t; } icaltimetype ICalFormatImpl::writeICalDate(const QDate &date) { icaltimetype t = icaltime_null_time(); t.year = date.year(); t.month = date.month(); t.day = date.day(); t.hour = 0; t.minute = 0; t.second = 0; t.is_date = 1; t.is_utc = 0; t.zone = nullptr; return t; } icaltimetype ICalFormatImpl::writeICalDateTime(const QDateTime &datetime, bool dateOnly) { icaltimetype t = icaltime_null_time(); t.year = datetime.date().year(); t.month = datetime.date().month(); t.day = datetime.date().day(); t.is_date = dateOnly; if (!t.is_date) { t.hour = datetime.time().hour(); t.minute = datetime.time().minute(); t.second = datetime.time().second(); } t.zone = nullptr; // zone is NOT set t.is_utc = datetime.timeSpec() == Qt::UTC || - (datetime.timeSpec() == Qt::OffsetFromUTC && datetime.utcOffset() == 0); + (datetime.timeSpec() == Qt::OffsetFromUTC && datetime.offsetFromUtc() == 0); return t; } icalproperty *ICalFormatImpl::writeICalDateTimeProperty(const icalproperty_kind type, const QDateTime &dt, ICalTimeZones *tzlist, ICalTimeZones *tzUsedList) { icaltimetype t; switch (type) { case ICAL_DTSTAMP_PROPERTY: case ICAL_CREATED_PROPERTY: case ICAL_LASTMODIFIED_PROPERTY: t = writeICalDateTime(dt.toUTC()); break; default: t = writeICalDateTime(dt); break; } icalproperty *p; switch (type) { case ICAL_DTSTAMP_PROPERTY: p = icalproperty_new_dtstamp(t); break; case ICAL_CREATED_PROPERTY: p = icalproperty_new_created(t); break; case ICAL_LASTMODIFIED_PROPERTY: p = icalproperty_new_lastmodified(t); break; case ICAL_DTSTART_PROPERTY: // start date and time p = icalproperty_new_dtstart(t); break; case ICAL_DTEND_PROPERTY: // end date and time p = icalproperty_new_dtend(t); break; case ICAL_DUE_PROPERTY: p = icalproperty_new_due(t); break; case ICAL_RECURRENCEID_PROPERTY: p = icalproperty_new_recurrenceid(t); break; case ICAL_EXDATE_PROPERTY: p = icalproperty_new_exdate(t); break; case ICAL_X_PROPERTY: { p = icalproperty_new_x(""); icaltimetype timeType = writeICalDateTime(dt); icalvalue *text = icalvalue_new_datetime(timeType); icalproperty_set_value(p, text); } break; default: { icaldatetimeperiodtype tp; tp.time = t; tp.period = icalperiodtype_null_period(); switch (type) { case ICAL_RDATE_PROPERTY: p = icalproperty_new_rdate(tp); break; default: return nullptr; } } } KTimeZone ktz; if (!t.is_utc) { ktz = zoneToSpec(dt.timeZone()).timeZone(); qDebug() << dt.timeZone().id() << ktz.name(); } if (ktz.isValid()) { if (tzlist) { ICalTimeZone tz = tzlist->zone(ktz.name()); if (!tz.isValid()) { // The time zone isn't in the list of known zones for the calendar // - add it to the calendar's zone list ICalTimeZone tznew(ktz); tzlist->add(tznew); tz = tznew; } if (tzUsedList) { tzUsedList->add(tz); } } icalproperty_add_parameter( p, icalparameter_new_tzid(ktz.name().toUtf8().constData())); } return p; } QDateTime ICalFormatImpl::readICalDateTime(icalproperty *p, const icaltimetype &t, ICalTimeZones *tzlist, bool utc) { // qCDebug(KCALCORE_LOG); // _dumpIcaltime( t ); KDateTime::Spec timeSpec; if (t.is_utc || t.zone == icaltimezone_get_utc_timezone()) { timeSpec = KDateTime::UTC; // the time zone is UTC utc = false; // no need to convert to UTC } else { if (!tzlist) { utc = true; // should be UTC, but it isn't } icalparameter *param = p ? icalproperty_get_first_parameter(p, ICAL_TZID_PARAMETER) : nullptr; QByteArray tzid = param ? QByteArray(icalparameter_get_tzid(param)) : QByteArray(); // A workaround for a bug in libical (https://github.com/libical/libical/issues/185) // If a recurrenceId has both tzid and range, both parameters end up in the tzid. // This results in invalid tzid's like: "Europe/Berlin;RANGE=THISANDFUTURE" QStringList parts = QString::fromLatin1(tzid).split(QLatin1Char(';')); if (parts.count() > 1) { tzid = parts.first().toLatin1(); } if (tzid.isNull()) { timeSpec = KDateTime::ClockTime; } else { QString tzidStr = QString::fromUtf8(tzid); ICalTimeZone tz; if (tzlist) { tz = tzlist->zone(tzidStr); } if (!tz.isValid()) { // The time zone is not in the existing list for the calendar. // Try to read it from the system or libical databases. ICalTimeZoneSource tzsource; ICalTimeZone newtz = tzsource.standardZone(tzidStr); if (newtz.isValid() && tzlist) { tzlist->add(newtz); } tz = newtz; } timeSpec = tz.isValid() ? KDateTime::Spec(tz) : KDateTime::LocalZone; } } QDateTime result; if (t.is_date) { result = QDateTime(QDate(t.year, t.month, t.day), {}, specToZone(timeSpec)); } else { result = QDateTime(QDate(t.year, t.month, t.day), QTime(t.hour, t.minute, t.second), specToZone(timeSpec)); } return utc ? result.toUTC() : result; } QDate ICalFormatImpl::readICalDate(const icaltimetype &t) { return QDate(t.year, t.month, t.day); } QDateTime ICalFormatImpl::readICalDateTimeProperty(icalproperty *p, ICalTimeZones *tzlist, bool utc, bool *allDay) { icaldatetimeperiodtype tp; icalproperty_kind kind = icalproperty_isa(p); switch (kind) { case ICAL_CREATED_PROPERTY: // UTC date/time tp.time = icalproperty_get_created(p); utc = true; break; case ICAL_DTSTAMP_PROPERTY: // UTC date/time tp.time = icalproperty_get_dtstamp(p); utc = true; break; case ICAL_LASTMODIFIED_PROPERTY: // last modification UTC date/time tp.time = icalproperty_get_lastmodified(p); utc = true; break; case ICAL_DTSTART_PROPERTY: // start date and time (UTC for freebusy) tp.time = icalproperty_get_dtstart(p); break; case ICAL_DTEND_PROPERTY: // end date and time (UTC for freebusy) tp.time = icalproperty_get_dtend(p); break; case ICAL_DUE_PROPERTY: // due date/time tp.time = icalproperty_get_due(p); break; case ICAL_COMPLETED_PROPERTY: // UTC completion date/time tp.time = icalproperty_get_completed(p); utc = true; break; case ICAL_RECURRENCEID_PROPERTY: tp.time = icalproperty_get_recurrenceid(p); break; case ICAL_EXDATE_PROPERTY: tp.time = icalproperty_get_exdate(p); break; case ICAL_X_PROPERTY: { const char *name = icalproperty_get_x_name(p); if (QLatin1String(name) == QLatin1String("X-KDE-LIBKCAL-DTRECURRENCE")) { const char *value = icalvalue_as_ical_string(icalproperty_get_value(p)); icalvalue *v = icalvalue_new_from_string(ICAL_DATETIME_VALUE, value); tp.time = icalvalue_get_datetime(v); icalvalue_free(v); break; } } default: switch (kind) { case ICAL_RDATE_PROPERTY: tp = icalproperty_get_rdate(p); break; default: return QDateTime(); } if (!icaltime_is_valid_time(tp.time)) { return QDateTime(); // a time period was found (not implemented yet) } break; } if (allDay) { *allDay = tp.time.is_date; } if (tp.time.is_date) { return QDateTime(readICalDate(tp.time), QTime()); } else { return readICalDateTime(p, tp.time, tzlist, utc); } } icaldurationtype ICalFormatImpl::writeICalDuration(const Duration &duration) { // should be able to use icaldurationtype_from_int(), except we know // that some older tools do not properly support weeks. So we never // set a week duration, only days icaldurationtype d; int value = duration.value(); d.is_neg = (value < 0) ? 1 : 0; if (value < 0) { value = -value; } // RFC2445 states that an ical duration value must be // EITHER weeks OR days/time, not both. if (duration.isDaily()) { if (!(value % 7)) { d.weeks = value / 7; d.days = 0; } else { d.weeks = 0; d.days = value; } d.hours = d.minutes = d.seconds = 0; } else { if (!(value % gSecondsPerWeek)) { d.weeks = value / gSecondsPerWeek; d.days = d.hours = d.minutes = d.seconds = 0; } else { d.weeks = 0; d.days = value / gSecondsPerDay; value %= gSecondsPerDay; d.hours = value / gSecondsPerHour; value %= gSecondsPerHour; d.minutes = value / gSecondsPerMinute; value %= gSecondsPerMinute; d.seconds = value; } } return d; } Duration ICalFormatImpl::readICalDuration(const icaldurationtype &d) { int days = d.weeks * 7; days += d.days; int seconds = d.hours * gSecondsPerHour; seconds += d.minutes * gSecondsPerMinute; seconds += d.seconds; if (seconds) { seconds += days * gSecondsPerDay; if (d.is_neg) { seconds = -seconds; } return Duration(seconds, Duration::Seconds); } else { if (d.is_neg) { days = -days; } return Duration(days, Duration::Days); } } icalcomponent *ICalFormatImpl::createCalendarComponent(const Calendar::Ptr &cal) { icalcomponent *calendar; // Root component calendar = icalcomponent_new(ICAL_VCALENDAR_COMPONENT); // Product Identifier icalproperty *p = icalproperty_new_prodid(CalFormat::productId().toUtf8().constData()); icalcomponent_add_property(calendar, p); // iCalendar version (2.0) p = icalproperty_new_version(const_cast(_ICAL_VERSION)); icalcomponent_add_property(calendar, p); // Implementation Version p = icalproperty_new_x(_ICAL_IMPLEMENTATION_VERSION); icalproperty_set_x_name(p, IMPLEMENTATION_VERSION_XPROPERTY); icalcomponent_add_property(calendar, p); // Add time zone // NOTE: Commented out since relevant timezones are added by the caller. // Previously we got some timezones listed twice in the ical file. /* if ( cal && cal->timeZones() ) { const ICalTimeZones::ZoneMap zmaps = cal->timeZones()->zones(); for ( ICalTimeZones::ZoneMap::ConstIterator it=zmaps.constBegin(); it != zmaps.constEnd(); ++it ) { icaltimezone *icaltz = (*it).icalTimezone(); if ( !icaltz ) { qCritical() << "bad time zone"; } else { icalcomponent *tz = icalcomponent_new_clone( icaltimezone_get_component( icaltz ) ); icalcomponent_add_component( calendar, tz ); icaltimezone_free( icaltz, 1 ); } } } */ // Custom properties if (cal != nullptr) { d->writeCustomProperties(calendar, cal.data()); } return calendar; } Incidence::Ptr ICalFormatImpl::readOneIncidence(icalcomponent *calendar, ICalTimeZones *tzlist) { if (!calendar) { qCWarning(KCALCORE_LOG) << "Populate called with empty calendar"; return Incidence::Ptr(); } icalcomponent *c = icalcomponent_get_first_component(calendar, ICAL_VEVENT_COMPONENT); if (c) { return readEvent(c, tzlist); } c = icalcomponent_get_first_component(calendar, ICAL_VTODO_COMPONENT); if (c) { return readTodo(c, tzlist); } c = icalcomponent_get_first_component(calendar, ICAL_VJOURNAL_COMPONENT); if (c) { return readJournal(c, tzlist); } qCWarning(KCALCORE_LOG) << "Found no incidence"; return Incidence::Ptr(); } // take a raw vcalendar (i.e. from a file on disk, clipboard, etc. etc. // and break it down from its tree-like format into the dictionary format // that is used internally in the ICalFormatImpl. bool ICalFormatImpl::populate(const Calendar::Ptr &cal, icalcomponent *calendar, bool deleted, const QString ¬ebook) { Q_UNUSED(notebook); // qCDebug(KCALCORE_LOG)<<"Populate called"; // this function will populate the caldict dictionary and other event // lists. It turns vevents into Events and then inserts them. if (!calendar) { qCWarning(KCALCORE_LOG) << "Populate called with empty calendar"; return false; } // TODO: check for METHOD icalproperty *p = icalcomponent_get_first_property(calendar, ICAL_X_PROPERTY); QString implementationVersion; while (p) { const char *name = icalproperty_get_x_name(p); QByteArray nproperty(name); if (nproperty == QByteArray(IMPLEMENTATION_VERSION_XPROPERTY)) { QString nvalue = QString::fromUtf8(icalproperty_get_x(p)); if (nvalue.isEmpty()) { icalvalue *value = icalproperty_get_value(p); if (icalvalue_isa(value) == ICAL_TEXT_VALUE) { nvalue = QString::fromUtf8(icalvalue_get_text(value)); } } implementationVersion = nvalue; icalcomponent_remove_property(calendar, p); icalproperty_free(p); } p = icalcomponent_get_next_property(calendar, ICAL_X_PROPERTY); } p = icalcomponent_get_first_property(calendar, ICAL_PRODID_PROPERTY); if (!p) { qCDebug(KCALCORE_LOG) << "No PRODID property found"; d->mLoadedProductId = QStringLiteral(""); } else { d->mLoadedProductId = QString::fromUtf8(icalproperty_get_prodid(p)); delete d->mCompat; d->mCompat = CompatFactory::createCompat(d->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)); 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)); return false; } if (strcmp(version, "1.0") == 0) { qCDebug(KCALCORE_LOG) << "Expected iCalendar, got vCalendar"; d->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)); return false; } } // Populate the calendar's time zone collection with all VTIMEZONE components // FIXME: HUUUUUGE memory consumption ICalTimeZones *tzlist = cal->timeZones(); ICalTimeZoneSource tzs; tzs.parse(calendar, *tzlist); // custom properties d->readCustomProperties(calendar, cal.data()); // Store all events with a relatedTo property in a list for post-processing d->mEventsRelate.clear(); d->mTodosRelate.clear(); // TODO: make sure that only actually added events go to this lists. icalcomponent *c = icalcomponent_get_first_component(calendar, ICAL_VTODO_COMPONENT); while (c) { Todo::Ptr todo = readTodo(c, tzlist); if (todo) { // qCDebug(KCALCORE_LOG) << "todo is not zero and deleted is " << deleted; Todo::Ptr old = cal->todo(todo->uid(), todo->recurrenceId()); if (old) { if (old->uid().isEmpty()) { qCWarning(KCALCORE_LOG) << "Skipping invalid VTODO"; c = icalcomponent_get_next_component(calendar, ICAL_VTODO_COMPONENT); continue; } // qCDebug(KCALCORE_LOG) << "Found an old todo with uid " << old->uid(); if (deleted) { // qCDebug(KCALCORE_LOG) << "Todo " << todo->uid() << " already deleted"; cal->deleteTodo(old); // move old to deleted removeAllICal(d->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); cal->addTodo(todo); // and replace it with this one } } else if (deleted) { // qCDebug(KCALCORE_LOG) << "Todo " << todo->uid() << " already deleted"; old = cal->deletedTodo(todo->uid(), todo->recurrenceId()); if (!old) { cal->addTodo(todo); // add this one cal->deleteTodo(todo); // and move it to deleted } } else { // qCDebug(KCALCORE_LOG) << "Adding todo " << todo.data() << todo->uid(); cal->addTodo(todo); // just add this one } } c = icalcomponent_get_next_component(calendar, ICAL_VTODO_COMPONENT); } // Iterate through all events c = icalcomponent_get_first_component(calendar, ICAL_VEVENT_COMPONENT); while (c) { Event::Ptr event = readEvent(c, tzlist); if (event) { // qCDebug(KCALCORE_LOG) << "event is not zero and deleted is " << deleted; Event::Ptr old = cal->event(event->uid(), event->recurrenceId()); if (old) { if (old->uid().isEmpty()) { qCWarning(KCALCORE_LOG) << "Skipping invalid VEVENT"; c = icalcomponent_get_next_component(calendar, ICAL_VEVENT_COMPONENT); continue; } // qCDebug(KCALCORE_LOG) << "Found an old event with uid " << old->uid(); if (deleted) { // qCDebug(KCALCORE_LOG) << "Event " << event->uid() << " already deleted"; cal->deleteEvent(old); // move old to deleted removeAllICal(d->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); cal->addEvent(event); // and replace it with this one } } else if (deleted) { // qCDebug(KCALCORE_LOG) << "Event " << event->uid() << " already deleted"; old = cal->deletedEvent(event->uid(), event->recurrenceId()); if (!old) { cal->addEvent(event); // add this one cal->deleteEvent(event); // and move it to deleted } } else { // qCDebug(KCALCORE_LOG) << "Adding event " << event.data() << event->uid(); cal->addEvent(event); // just add this one } } c = icalcomponent_get_next_component(calendar, ICAL_VEVENT_COMPONENT); } // Iterate through all journals c = icalcomponent_get_first_component(calendar, ICAL_VJOURNAL_COMPONENT); while (c) { Journal::Ptr journal = readJournal(c, tzlist); if (journal) { Journal::Ptr old = cal->journal(journal->uid(), journal->recurrenceId()); if (old) { if (deleted) { cal->deleteJournal(old); // move old to deleted } else if (journal->revision() > old->revision()) { cal->deleteJournal(old); // move old to deleted cal->addJournal(journal); // and replace it with this one } } else if (deleted) { old = cal->deletedJournal(journal->uid(), journal->recurrenceId()); if (!old) { cal->addJournal(journal); // add this one cal->deleteJournal(journal); // and move it to deleted } } else { cal->addJournal(journal); // just add this one } } c = icalcomponent_get_next_component(calendar, ICAL_VJOURNAL_COMPONENT); } // TODO: Remove any previous time zones no longer referenced in the calendar return true; } QString ICalFormatImpl::extractErrorProperty(icalcomponent *c) { QString errorMessage; icalproperty *error = icalcomponent_get_first_property(c, ICAL_XLICERROR_PROPERTY); while (error) { errorMessage += QLatin1String(icalproperty_get_xlicerror(error)); errorMessage += QLatin1Char('\n'); error = icalcomponent_get_next_property(c, ICAL_XLICERROR_PROPERTY); } return errorMessage; } /* void ICalFormatImpl::dumpIcalRecurrence( const icalrecurrencetype &r ) { int i; qCDebug(KCALCORE_LOG) << " Freq:" << int( r.freq ); qCDebug(KCALCORE_LOG) << " Until:" << icaltime_as_ical_string( r.until ); qCDebug(KCALCORE_LOG) << " Count:" << r.count; if ( r.by_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) { int index = 0; QString out = " By Day: "; while ( ( i = r.by_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { out.append( QString::number( i ) + ' ' ); } qCDebug(KCALCORE_LOG) << out; } if ( r.by_month_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) { int index = 0; QString out = " By Month Day: "; while ( ( i = r.by_month_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { out.append( QString::number( i ) + ' ' ); } qCDebug(KCALCORE_LOG) << out; } if ( r.by_year_day[0] != ICAL_RECURRENCE_ARRAY_MAX ) { int index = 0; QString out = " By Year Day: "; while ( ( i = r.by_year_day[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { out.append( QString::number( i ) + ' ' ); } qCDebug(KCALCORE_LOG) << out; } if ( r.by_month[0] != ICAL_RECURRENCE_ARRAY_MAX ) { int index = 0; QString out = " By Month: "; while ( ( i = r.by_month[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { out.append( QString::number( i ) + ' ' ); } qCDebug(KCALCORE_LOG) << out; } if ( r.by_set_pos[0] != ICAL_RECURRENCE_ARRAY_MAX ) { int index = 0; QString out = " By Set Pos: "; while ( ( i = r.by_set_pos[index++] ) != ICAL_RECURRENCE_ARRAY_MAX ) { qCDebug(KCALCORE_LOG) << "=========" << i; out.append( QString::number( i ) + ' ' ); } qCDebug(KCALCORE_LOG) << out; } } */ icalcomponent *ICalFormatImpl::createScheduleComponent(const IncidenceBase::Ptr &incidence, iTIPMethod method) { icalcomponent *message = createCalendarComponent(); // Create VTIMEZONE components for this incidence ICalTimeZones zones; if (incidence) { const QDateTime kd1 = incidence->dateTime(IncidenceBase::RoleStartTimeZone); const QDateTime kd2 = incidence->dateTime(IncidenceBase::RoleEndTimeZone); if (kd1.isValid() && kd1.timeZone() != QTimeZone::utc()) { zones.add(ICalTimeZone(zoneToSpec(kd1.timeZone()).timeZone())); } if (kd2.isValid() && kd2.timeZone() != QTimeZone::utc()) { zones.add(ICalTimeZone(zoneToSpec(kd2.timeZone()).timeZone())); } const ICalTimeZones::ZoneMap zmaps = zones.zones(); for (ICalTimeZones::ZoneMap::ConstIterator it = zmaps.constBegin(); it != zmaps.constEnd(); ++it) { icaltimezone *icaltz = (*it).icalTimezone(); if (!icaltz) { qCritical() << "bad time zone"; } else { icalcomponent *tz = icalcomponent_new_clone(icaltimezone_get_component(icaltz)); icalcomponent_add_component(message, tz); icaltimezone_free(icaltz, 1); } } } else { qCDebug(KCALCORE_LOG) << "No incidence"; return message; } icalproperty_method icalmethod = ICAL_METHOD_NONE; switch (method) { case iTIPPublish: icalmethod = ICAL_METHOD_PUBLISH; break; case iTIPRequest: icalmethod = ICAL_METHOD_REQUEST; break; case iTIPRefresh: icalmethod = ICAL_METHOD_REFRESH; break; case iTIPCancel: icalmethod = ICAL_METHOD_CANCEL; break; case iTIPAdd: icalmethod = ICAL_METHOD_ADD; break; case iTIPReply: icalmethod = ICAL_METHOD_REPLY; break; case iTIPCounter: icalmethod = ICAL_METHOD_COUNTER; break; case iTIPDeclineCounter: icalmethod = ICAL_METHOD_DECLINECOUNTER; break; default: qCDebug(KCALCORE_LOG) << "Unknown method"; return message; } icalcomponent_add_property(message, icalproperty_new_method(icalmethod)); icalcomponent *inc = writeIncidence(incidence, method); if (method != KCalCore::iTIPNoMethod) { //Not very nice, but since dtstamp changes semantics if used in scheduling, we have to adapt icalcomponent_set_dtstamp( inc, writeICalUtcDateTime(QDateTime::currentDateTimeUtc())); } /* * RFC 2446 states in section 3.4.3 ( REPLY to a VTODO ), that * a REQUEST-STATUS property has to be present. For the other two, event and * free busy, it can be there, but is optional. Until we do more * fine grained handling, assume all is well. Note that this is the * status of the _request_, not the attendee. Just to avoid confusion. * - till */ if (icalmethod == ICAL_METHOD_REPLY) { struct icalreqstattype rst; rst.code = ICAL_2_0_SUCCESS_STATUS; rst.desc = nullptr; rst.debug = nullptr; icalcomponent_add_property(inc, icalproperty_new_requeststatus(rst)); } icalcomponent_add_component(message, inc); return message; } diff --git a/src/vcalformat.cpp b/src/vcalformat.cpp index 3dc74b72e..1b12568f0 100644 --- a/src/vcalformat.cpp +++ b/src/vcalformat.cpp @@ -1,2526 +1,2526 @@ /* This file is part of the kcalcore library. Copyright (c) 1998 Preston Brown Copyright (c) 2001 Cornelius Schumacher This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** @file This file is part of the API for handling calendar data and defines the VCalFormat base class. This class implements the vCalendar format. It provides methods for loading/saving/converting vCalendar format data into the internal representation as Calendar and Incidences. @brief vCalendar format implementation. @author Preston Brown \ @author Cornelius Schumacher \ */ #include "vcalformat.h" #include "calendar.h" #include "event.h" #include "exceptions.h" #include "icaltimezones.h" #include "todo.h" #include "utils.h" #include "versit/vcc.h" #include "versit/vobject.h" #include "utils.h" #include #include "kcalcore_debug.h" #include #include #include // for .toHtmlEscaped() and Qt::mightBeRichText() #include using namespace KCalCore; /** Private class that helps to provide binary compatibility between releases. @internal */ //@cond PRIVATE template void removeAllVCal(QVector< QSharedPointer > &c, const QSharedPointer &x) { if (c.count() < 1) { return; } int cnt = c.count(x); if (cnt != 1) { qCritical() << "There number of relatedTos for this incidence is " << cnt << " (there must be 1 relatedTo only)"; Q_ASSERT_X(false, "removeAllVCal", "Count is not 1."); return; } c.remove(c.indexOf(x)); } static QString dayFromNum(int day) { static const QStringList days = { QStringLiteral("MO "), QStringLiteral("TU "), QStringLiteral("WE "), QStringLiteral("TH "), QStringLiteral("FR "), QStringLiteral("SA "), QStringLiteral("SU ") }; return days[day]; } class Q_DECL_HIDDEN KCalCore::VCalFormat::Private { public: Calendar::Ptr mCalendar; Event::List mEventsRelate; // Events with relations Todo::List mTodosRelate; // To-dos with relations QSet mManuallyWrittenExtensionFields; // X- fields that are manually dumped }; //@endcond VCalFormat::VCalFormat() : d(new KCalCore::VCalFormat::Private) { } VCalFormat::~VCalFormat() { delete d; } bool VCalFormat::load(const Calendar::Ptr &calendar, const QString &fileName) { d->mCalendar = calendar; clearException(); VObject *vcal = nullptr; // this is not necessarily only 1 vcal. Could be many vcals, or include // a vcard... vcal = Parse_MIME_FromFileName(const_cast(QFile::encodeName(fileName).data())); if (!vcal) { setException(new Exception(Exception::CalVersionUnknown)); return false; } // any other top-level calendar stuff should be added/initialized here // put all vobjects into their proper places auto savedTimeZoneId = d->mCalendar->timeZoneId(); populate(vcal, false, fileName); d->mCalendar->setTimeZoneId(savedTimeZoneId); // clean up from vcal API stuff cleanVObjects(vcal); cleanStrTbl(); return true; } bool VCalFormat::save(const Calendar::Ptr &calendar, const QString &fileName) { d->mCalendar = calendar; ICalTimeZones *tzlist = d->mCalendar->timeZones(); VObject *vcal, *vo; vcal = newVObject(VCCalProp); // addPropValue(vcal,VCLocationProp, "0.0"); addPropValue(vcal, VCProdIdProp, productId().toLatin1().constData()); addPropValue(vcal, VCVersionProp, _VCAL_VERSION); // TODO STUFF Todo::List todoList = d->mCalendar->rawTodos(); Todo::List::ConstIterator it; for (it = todoList.constBegin(); it != todoList.constEnd(); ++it) { if ((*it)->dtStart().timeZone().id().mid(0, 4) == "VCAL") { ICalTimeZone zone = tzlist->zone(QString::fromUtf8((*it)->dtStart().timeZone().id())); if (zone.isValid()) { QByteArray timezone = zone.vtimezone(); addPropValue(vcal, VCTimeZoneProp, parseTZ(timezone).toLocal8Bit().constData()); QString dst = parseDst(timezone); while (!dst.isEmpty()) { addPropValue(vcal, VCDayLightProp, dst.toLocal8Bit().constData()); dst = parseDst(timezone); } } } vo = eventToVTodo(*it); addVObjectProp(vcal, vo); } // EVENT STUFF Event::List events = d->mCalendar->rawEvents(); Event::List::ConstIterator it2; for (it2 = events.constBegin(); it2 != events.constEnd(); ++it2) { if ((*it2)->dtStart().timeZone().id().mid(0, 4) == "VCAL") { ICalTimeZone zone = tzlist->zone(QString::fromUtf8((*it2)->dtStart().timeZone().id())); if (zone.isValid()) { QByteArray timezone = zone.vtimezone(); addPropValue(vcal, VCTimeZoneProp, parseTZ(timezone).toLocal8Bit().constData()); QString dst = parseDst(timezone); while (!dst.isEmpty()) { addPropValue(vcal, VCDayLightProp, dst.toLocal8Bit().constData()); dst = parseDst(timezone); } } } vo = eventToVEvent(*it2); addVObjectProp(vcal, vo); } writeVObjectToFile(QFile::encodeName(fileName).data(), vcal); cleanVObjects(vcal); cleanStrTbl(); if (QFile::exists(fileName)) { return true; } else { return false; // error } return false; } bool VCalFormat::fromString(const Calendar::Ptr &calendar, const QString &string, bool deleted, const QString ¬ebook) { return fromRawString(calendar, string.toUtf8(), deleted, notebook); } bool VCalFormat::fromRawString(const Calendar::Ptr &calendar, const QByteArray &string, bool deleted, const QString ¬ebook) { d->mCalendar = calendar; if (!string.size()) { return false; } VObject *vcal = Parse_MIME(string.data(), string.size()); if (!vcal) { return false; } VObjectIterator i; initPropIterator(&i, vcal); // put all vobjects into their proper places auto savedTimeZoneId = d->mCalendar->timeZoneId(); populate(vcal, deleted, notebook); d->mCalendar->setTimeZoneId(savedTimeZoneId); // clean up from vcal API stuff cleanVObjects(vcal); cleanStrTbl(); return true; } QString VCalFormat::toString(const Calendar::Ptr &calendar, const QString ¬ebook, bool deleted) { // TODO: Factor out VCalFormat::asString() d->mCalendar = calendar; ICalTimeZones *tzlist = d->mCalendar->timeZones(); VObject *vo; VObject *vcal = newVObject(VCCalProp); addPropValue(vcal, VCProdIdProp, CalFormat::productId().toLatin1().constData()); addPropValue(vcal, VCVersionProp, _VCAL_VERSION); // TODO STUFF Todo::List todoList = deleted ? d->mCalendar->deletedTodos() : d->mCalendar->rawTodos(); Todo::List::ConstIterator it; for (it = todoList.constBegin(); it != todoList.constEnd(); ++it) { if (!deleted || !d->mCalendar->todo((*it)->uid(), (*it)->recurrenceId())) { // use existing ones, or really deleted ones if (notebook.isEmpty() || (!calendar->notebook(*it).isEmpty() && notebook.endsWith(calendar->notebook(*it)))) { if ((*it)->dtStart().timeZone().id().mid(0, 4) == "VCAL") { ICalTimeZone zone = tzlist->zone(QString::fromUtf8((*it)->dtStart().timeZone().id())); if (zone.isValid()) { QByteArray timezone = zone.vtimezone(); addPropValue(vcal, VCTimeZoneProp, parseTZ(timezone).toUtf8().constData()); QString dst = parseDst(timezone); while (!dst.isEmpty()) { addPropValue(vcal, VCDayLightProp, dst.toUtf8().constData()); dst = parseDst(timezone); } } } vo = eventToVTodo(*it); addVObjectProp(vcal, vo); } } } // EVENT STUFF Event::List events = deleted ? d->mCalendar->deletedEvents() : d->mCalendar->rawEvents(); Event::List::ConstIterator it2; for (it2 = events.constBegin(); it2 != events.constEnd(); ++it2) { if (!deleted || !d->mCalendar->event((*it2)->uid(), (*it2)->recurrenceId())) { // use existing ones, or really deleted ones if (notebook.isEmpty() || (!calendar->notebook(*it2).isEmpty() && notebook.endsWith(calendar->notebook(*it2)))) { if ((*it2)->dtStart().timeZone().id().mid(0, 4) == "VCAL") { ICalTimeZone zone = tzlist->zone(QString::fromUtf8((*it2)->dtStart().timeZone().id())); if (zone.isValid()) { QByteArray timezone = zone.vtimezone(); addPropValue(vcal, VCTimeZoneProp, parseTZ(timezone).toUtf8().constData()); QString dst = parseDst(timezone); while (!dst.isEmpty()) { addPropValue(vcal, VCDayLightProp, dst.toUtf8().constData()); dst = parseDst(timezone); } } } vo = eventToVEvent(*it2); addVObjectProp(vcal, vo); } } } char *buf = writeMemVObject(nullptr, nullptr, vcal); QString result(QString::fromUtf8(buf)); deleteStr(buf); cleanVObject(vcal); return result; } VObject *VCalFormat::eventToVTodo(const Todo::Ptr &anEvent) { VObject *vtodo; QString tmpStr; vtodo = newVObject(VCTodoProp); // due date if (anEvent->hasDueDate()) { tmpStr = qDateTimeToISO(anEvent->dtDue(), !anEvent->allDay()); addPropValue(vtodo, VCDueProp, tmpStr.toUtf8().constData()); } // start date if (anEvent->hasStartDate()) { tmpStr = qDateTimeToISO(anEvent->dtStart(), !anEvent->allDay()); addPropValue(vtodo, VCDTstartProp, tmpStr.toUtf8().constData()); } // creation date tmpStr = qDateTimeToISO(anEvent->created()); addPropValue(vtodo, VCDCreatedProp, tmpStr.toUtf8().constData()); // unique id addPropValue(vtodo, VCUniqueStringProp, anEvent->uid().toUtf8().constData()); // revision tmpStr.sprintf("%i", anEvent->revision()); addPropValue(vtodo, VCSequenceProp, tmpStr.toUtf8().constData()); // last modification date tmpStr = qDateTimeToISO(anEvent->lastModified()); addPropValue(vtodo, VCLastModifiedProp, tmpStr.toUtf8().constData()); // organizer stuff // @TODO: How about the common name? if (!anEvent->organizer()->email().isEmpty()) { tmpStr = QStringLiteral("MAILTO:") + anEvent->organizer()->email(); addPropValue(vtodo, ICOrganizerProp, tmpStr.toUtf8().constData()); } // attendees if (anEvent->attendeeCount() > 0) { Attendee::Ptr curAttendee; auto attendees = anEvent->attendees(); for (auto it = attendees.constBegin(); it != attendees.constEnd(); ++it) { curAttendee = *it; if (!curAttendee->email().isEmpty() && !curAttendee->name().isEmpty()) { tmpStr = QStringLiteral("MAILTO:") + curAttendee->name() + QStringLiteral(" <") + curAttendee->email() + QLatin1Char('>'); } else if (curAttendee->name().isEmpty() && curAttendee->email().isEmpty()) { tmpStr = QStringLiteral("MAILTO: "); qCDebug(KCALCORE_LOG) << "warning! this Event has an attendee w/o name or email!"; } else if (curAttendee->name().isEmpty()) { tmpStr = QStringLiteral("MAILTO: ") + curAttendee->email(); } else { tmpStr = QStringLiteral("MAILTO: ") + curAttendee->name(); } VObject *aProp = addPropValue(vtodo, VCAttendeeProp, tmpStr.toUtf8().constData()); addPropValue(aProp, VCRSVPProp, curAttendee->RSVP() ? "TRUE" : "FALSE"); addPropValue(aProp, VCStatusProp, writeStatus(curAttendee->status()).constData()); } } // recurrence rule stuff const Recurrence *recur = anEvent->recurrence(); if (recur->recurs()) { bool validRecur = true; QString tmpStr2; switch (recur->recurrenceType()) { case Recurrence::rDaily: tmpStr.sprintf("D%i ", recur->frequency()); break; case Recurrence::rWeekly: tmpStr.sprintf("W%i ", recur->frequency()); for (int i = 0; i < 7; ++i) { QBitArray days(recur->days()); if (days.testBit(i)) { tmpStr += dayFromNum(i); } } break; case Recurrence::rMonthlyPos: { tmpStr.sprintf("MP%i ", recur->frequency()); // write out all rMonthPos's QList tmpPositions = recur->monthPositions(); for (QList::ConstIterator posit = tmpPositions.constBegin(); posit != tmpPositions.constEnd(); ++posit) { int pos = (*posit).pos(); tmpStr2.sprintf("%i", (pos > 0) ? pos : (-pos)); if (pos < 0) { tmpStr2 += QStringLiteral("- "); } else { tmpStr2 += QStringLiteral("+ "); } tmpStr += tmpStr2; tmpStr += dayFromNum((*posit).day() - 1); } break; } case Recurrence::rMonthlyDay: { tmpStr.sprintf("MD%i ", recur->frequency()); // write out all rMonthDays; const QList tmpDays = recur->monthDays(); for (QList::ConstIterator tmpDay = tmpDays.constBegin(); tmpDay != tmpDays.constEnd(); ++tmpDay) { tmpStr2.sprintf("%i ", *tmpDay); tmpStr += tmpStr2; } break; } case Recurrence::rYearlyMonth: { tmpStr.sprintf("YM%i ", recur->frequency()); // write out all the months;' // TODO: Any way to write out the day within the month??? const QList months = recur->yearMonths(); for (QList::ConstIterator mit = months.constBegin(); mit != months.constEnd(); ++mit) { tmpStr2.sprintf("%i ", *mit); tmpStr += tmpStr2; } break; } case Recurrence::rYearlyDay: { tmpStr.sprintf("YD%i ", recur->frequency()); // write out all the rYearNums; const QList tmpDays = recur->yearDays(); for (QList::ConstIterator tmpDay = tmpDays.begin(); tmpDay != tmpDays.end(); ++tmpDay) { tmpStr2.sprintf("%i ", *tmpDay); tmpStr += tmpStr2; } break; } default: // TODO: Write rYearlyPos and arbitrary rules! qCDebug(KCALCORE_LOG) << "ERROR, it should never get here in eventToVTodo!"; validRecur = false; break; } // switch if (recur->duration() > 0) { tmpStr2.sprintf("#%i", recur->duration()); tmpStr += tmpStr2; } else if (recur->duration() == -1) { tmpStr += QLatin1String("#0"); // defined as repeat forever } else { tmpStr += qDateTimeToISO(recur->endDateTime(), false); } // Only write out the rrule if we have a valid recurrence (i.e. a known // type in thee switch above) if (validRecur) { addPropValue(vtodo, VCRRuleProp, tmpStr.toUtf8().constData()); } } // event repeats // exceptions dates to recurrence DateList dateList = recur->exDates(); DateList::ConstIterator id; QString tmpStr2; for (id = dateList.constBegin(); id != dateList.constEnd(); ++id) { tmpStr = qDateToISO(*id) + QLatin1Char(';'); tmpStr2 += tmpStr; } if (!tmpStr2.isEmpty()) { tmpStr2.truncate(tmpStr2.length() - 1); addPropValue(vtodo, VCExpDateProp, tmpStr2.toUtf8().constData()); } // exceptions datetimes to recurrence auto dateTimeList = recur->exDateTimes(); tmpStr2.clear(); for (auto idt = dateTimeList.constBegin(); idt != dateTimeList.constEnd(); ++idt) { tmpStr = qDateTimeToISO(*idt) + QLatin1Char(';'); tmpStr2 += tmpStr; } if (!tmpStr2.isEmpty()) { tmpStr2.truncate(tmpStr2.length() - 1); addPropValue(vtodo, VCExpDateProp, tmpStr2.toUtf8().constData()); } // description BL: if (!anEvent->description().isEmpty()) { QByteArray in = anEvent->description().toUtf8(); QByteArray out; KCodecs::quotedPrintableEncode(in, out, true); if (out != in) { VObject *d = addPropValue(vtodo, VCDescriptionProp, out.constData()); addPropValue(d, VCEncodingProp, VCQuotedPrintableProp); addPropValue(d, VCCharSetProp, VCUtf8Prop); } else { addPropValue(vtodo, VCDescriptionProp, in.constData()); } } // summary if (!anEvent->summary().isEmpty()) { QByteArray in = anEvent->summary().toUtf8(); QByteArray out; KCodecs::quotedPrintableEncode(in, out, true); if (out != in) { VObject *d = addPropValue(vtodo, VCSummaryProp, out.constData()); addPropValue(d, VCEncodingProp, VCQuotedPrintableProp); addPropValue(d, VCCharSetProp, VCUtf8Prop); } else { addPropValue(vtodo, VCSummaryProp, in.constData()); } } // location if (!anEvent->location().isEmpty()) { QByteArray in = anEvent->location().toUtf8(); QByteArray out; KCodecs::quotedPrintableEncode(in, out, true); if (out != in) { VObject *d = addPropValue(vtodo, VCLocationProp, out.constData()); addPropValue(d, VCEncodingProp, VCQuotedPrintableProp); addPropValue(d, VCCharSetProp, VCUtf8Prop); } else { addPropValue(vtodo, VCLocationProp, in.constData()); } } // completed status // backward compatibility, KOrganizer used to interpret only these two values addPropValue(vtodo, VCStatusProp, anEvent->isCompleted() ? "COMPLETED" : "NEEDS ACTION"); // completion date if (anEvent->hasCompletedDate()) { tmpStr = qDateTimeToISO(anEvent->completed()); addPropValue(vtodo, VCCompletedProp, tmpStr.toUtf8().constData()); } // priority tmpStr.sprintf("%i", anEvent->priority()); addPropValue(vtodo, VCPriorityProp, tmpStr.toUtf8().constData()); // related event if (!anEvent->relatedTo().isEmpty()) { addPropValue(vtodo, VCRelatedToProp, anEvent->relatedTo().toUtf8().constData()); } // secrecy const char *text = nullptr; switch (anEvent->secrecy()) { case Incidence::SecrecyPublic: text = "PUBLIC"; break; case Incidence::SecrecyPrivate: text = "PRIVATE"; break; case Incidence::SecrecyConfidential: text = "CONFIDENTIAL"; break; } if (text) { addPropValue(vtodo, VCClassProp, text); } // categories const QStringList tmpStrList = anEvent->categories(); tmpStr.clear(); QString catStr; QStringList::const_iterator its; for (its = tmpStrList.constBegin(); its != tmpStrList.constEnd(); ++its) { catStr = *its; if (catStr[0] == QLatin1Char(' ')) { tmpStr += catStr.midRef(1); } else { tmpStr += catStr; } // this must be a ';' character as the vCalendar specification requires! // vcc.y has been hacked to translate the ';' to a ',' when the vcal is // read in. tmpStr += QLatin1Char(';'); } if (!tmpStr.isEmpty()) { tmpStr.truncate(tmpStr.length() - 1); addPropValue(vtodo, VCCategoriesProp, tmpStr.toUtf8().constData()); } // alarm stuff auto alarms = anEvent->alarms(); for (auto it = alarms.constBegin(); it != alarms.constEnd(); ++it) { Alarm::Ptr alarm = *it; if (alarm->enabled()) { VObject *a; if (alarm->type() == Alarm::Display) { a = addProp(vtodo, VCDAlarmProp); tmpStr = qDateTimeToISO(alarm->time()); addPropValue(a, VCRunTimeProp, tmpStr.toUtf8().constData()); addPropValue(a, VCRepeatCountProp, "1"); if (alarm->text().isNull()) { addPropValue(a, VCDisplayStringProp, "beep!"); } else { addPropValue(a, VCDisplayStringProp, alarm->text().toLatin1().data()); } } else if (alarm->type() == Alarm::Audio) { a = addProp(vtodo, VCAAlarmProp); tmpStr = qDateTimeToISO(alarm->time()); addPropValue(a, VCRunTimeProp, tmpStr.toUtf8().constData()); addPropValue(a, VCRepeatCountProp, "1"); addPropValue(a, VCAudioContentProp, QFile::encodeName(alarm->audioFile()).constData()); } else if (alarm->type() == Alarm::Procedure) { a = addProp(vtodo, VCPAlarmProp); tmpStr = qDateTimeToISO(alarm->time()); addPropValue(a, VCRunTimeProp, tmpStr.toUtf8().constData()); addPropValue(a, VCRepeatCountProp, "1"); addPropValue(a, VCProcedureNameProp, QFile::encodeName(alarm->programFile()).constData()); } } } return vtodo; } VObject *VCalFormat::eventToVEvent(const Event::Ptr &anEvent) { VObject *vevent; QString tmpStr; vevent = newVObject(VCEventProp); // start and end time tmpStr = qDateTimeToISO(anEvent->dtStart(), !anEvent->allDay()); addPropValue(vevent, VCDTstartProp, tmpStr.toUtf8().constData()); // events that have time associated but take up no time should // not have both DTSTART and DTEND. if (anEvent->dtStart() != anEvent->dtEnd()) { tmpStr = qDateTimeToISO(anEvent->dtEnd(), !anEvent->allDay()); addPropValue(vevent, VCDTendProp, tmpStr.toUtf8().constData()); } // creation date tmpStr = qDateTimeToISO(anEvent->created()); addPropValue(vevent, VCDCreatedProp, tmpStr.toUtf8().constData()); // unique id addPropValue(vevent, VCUniqueStringProp, anEvent->uid().toUtf8().constData()); // revision tmpStr.sprintf("%i", anEvent->revision()); addPropValue(vevent, VCSequenceProp, tmpStr.toUtf8().constData()); // last modification date tmpStr = qDateTimeToISO(anEvent->lastModified()); addPropValue(vevent, VCLastModifiedProp, tmpStr.toUtf8().constData()); // attendee and organizer stuff // TODO: What to do with the common name? if (!anEvent->organizer()->email().isEmpty()) { tmpStr = QStringLiteral("MAILTO:") + anEvent->organizer()->email(); addPropValue(vevent, ICOrganizerProp, tmpStr.toUtf8().constData()); } // TODO: Put this functionality into Attendee class if (anEvent->attendeeCount() > 0) { auto attendees = anEvent->attendees(); for (auto it = attendees.constBegin(); it != attendees.constEnd(); ++it) { Attendee::Ptr curAttendee = *it; if (!curAttendee->email().isEmpty() && !curAttendee->name().isEmpty()) { tmpStr = QStringLiteral("MAILTO:") + curAttendee->name() + QStringLiteral(" <") + curAttendee->email() + QLatin1Char('>'); } else if (curAttendee->name().isEmpty() && curAttendee->email().isEmpty()) { tmpStr = QStringLiteral("MAILTO: "); qCDebug(KCALCORE_LOG) << "warning! this Event has an attendee w/o name or email!"; } else if (curAttendee->name().isEmpty()) { tmpStr = QStringLiteral("MAILTO: ") + curAttendee->email(); } else { tmpStr = QStringLiteral("MAILTO: ") + curAttendee->name(); } VObject *aProp = addPropValue(vevent, VCAttendeeProp, tmpStr.toUtf8().constData()); addPropValue(aProp, VCRSVPProp, curAttendee->RSVP() ? "TRUE" : "FALSE"); addPropValue(aProp, VCStatusProp, writeStatus(curAttendee->status()).constData()); } } // recurrence rule stuff const Recurrence *recur = anEvent->recurrence(); if (recur->recurs()) { bool validRecur = true; QString tmpStr2; switch (recur->recurrenceType()) { case Recurrence::rDaily: tmpStr.sprintf("D%i ", recur->frequency()); break; case Recurrence::rWeekly: tmpStr.sprintf("W%i ", recur->frequency()); for (int i = 0; i < 7; ++i) { QBitArray days(recur->days()); if (days.testBit(i)) { tmpStr += dayFromNum(i); } } break; case Recurrence::rMonthlyPos: { tmpStr.sprintf("MP%i ", recur->frequency()); // write out all rMonthPos's QList tmpPositions = recur->monthPositions(); for (QList::ConstIterator posit = tmpPositions.constBegin(); posit != tmpPositions.constEnd(); ++posit) { int pos = (*posit).pos(); tmpStr2.sprintf("%i", (pos > 0) ? pos : (-pos)); if (pos < 0) { tmpStr2 += QLatin1String("- "); } else { tmpStr2 += QLatin1String("+ "); } tmpStr += tmpStr2; tmpStr += dayFromNum((*posit).day() - 1); } break; } case Recurrence::rMonthlyDay: { tmpStr.sprintf("MD%i ", recur->frequency()); // write out all rMonthDays; const QList tmpDays = recur->monthDays(); for (QList::ConstIterator tmpDay = tmpDays.constBegin(); tmpDay != tmpDays.constEnd(); ++tmpDay) { tmpStr2.sprintf("%i ", *tmpDay); tmpStr += tmpStr2; } break; } case Recurrence::rYearlyMonth: { tmpStr.sprintf("YM%i ", recur->frequency()); // write out all the months;' // TODO: Any way to write out the day within the month??? const QList months = recur->yearMonths(); for (QList::ConstIterator mit = months.constBegin(); mit != months.constEnd(); ++mit) { tmpStr2.sprintf("%i ", *mit); tmpStr += tmpStr2; } break; } case Recurrence::rYearlyDay: { tmpStr.sprintf("YD%i ", recur->frequency()); // write out all the rYearNums; const QList tmpDays = recur->yearDays(); for (QList::ConstIterator tmpDay = tmpDays.begin(); tmpDay != tmpDays.end(); ++tmpDay) { tmpStr2.sprintf("%i ", *tmpDay); tmpStr += tmpStr2; } break; } default: // TODO: Write rYearlyPos and arbitrary rules! qCDebug(KCALCORE_LOG) << "ERROR, it should never get here in eventToVEvent!"; validRecur = false; break; } // switch if (recur->duration() > 0) { tmpStr2.sprintf("#%i", recur->duration()); tmpStr += tmpStr2; } else if (recur->duration() == -1) { tmpStr += QLatin1String("#0"); // defined as repeat forever } else { tmpStr += qDateTimeToISO(recur->endDateTime(), false); } // Only write out the rrule if we have a valid recurrence (i.e. a known // type in thee switch above) if (validRecur) { addPropValue(vevent, VCRRuleProp, tmpStr.toUtf8().constData()); } } // event repeats // exceptions dates to recurrence DateList dateList = recur->exDates(); DateList::ConstIterator it; QString tmpStr2; for (it = dateList.constBegin(); it != dateList.constEnd(); ++it) { tmpStr = qDateToISO(*it) + QLatin1Char(';'); tmpStr2 += tmpStr; } if (!tmpStr2.isEmpty()) { tmpStr2.truncate(tmpStr2.length() - 1); addPropValue(vevent, VCExpDateProp, tmpStr2.toUtf8().constData()); } // exceptions datetimes to recurrence auto dateTimeList = recur->exDateTimes(); tmpStr2.clear(); for (auto idt = dateTimeList.constBegin(); idt != dateTimeList.constEnd(); ++idt) { tmpStr = qDateTimeToISO(*idt) + QLatin1Char(';'); tmpStr2 += tmpStr; } if (!tmpStr2.isEmpty()) { tmpStr2.truncate(tmpStr2.length() - 1); addPropValue(vevent, VCExpDateProp, tmpStr2.toUtf8().constData()); } // description if (!anEvent->description().isEmpty()) { QByteArray in = anEvent->description().toUtf8(); QByteArray out; KCodecs::quotedPrintableEncode(in, out, true); if (out != in) { VObject *d = addPropValue(vevent, VCDescriptionProp, out.constData()); addPropValue(d, VCEncodingProp, VCQuotedPrintableProp); addPropValue(d, VCCharSetProp, VCUtf8Prop); } else { addPropValue(vevent, VCDescriptionProp, in.constData()); } } // summary if (!anEvent->summary().isEmpty()) { QByteArray in = anEvent->summary().toUtf8(); QByteArray out; KCodecs::quotedPrintableEncode(in, out, true); if (out != in) { VObject *d = addPropValue(vevent, VCSummaryProp, out.constData()); addPropValue(d, VCEncodingProp, VCQuotedPrintableProp); addPropValue(d, VCCharSetProp, VCUtf8Prop); } else { addPropValue(vevent, VCSummaryProp, in.constData()); } } // location if (!anEvent->location().isEmpty()) { QByteArray in = anEvent->location().toUtf8(); QByteArray out; KCodecs::quotedPrintableEncode(in, out, true); if (out != in) { VObject *d = addPropValue(vevent, VCLocationProp, out.constData()); addPropValue(d, VCEncodingProp, VCQuotedPrintableProp); addPropValue(d, VCCharSetProp, VCUtf8Prop); } else { addPropValue(vevent, VCLocationProp, in.constData()); } } // status // TODO: define Event status // addPropValue( vevent, VCStatusProp, anEvent->statusStr().toUtf8() ); // secrecy const char *text = nullptr; switch (anEvent->secrecy()) { case Incidence::SecrecyPublic: text = "PUBLIC"; break; case Incidence::SecrecyPrivate: text = "PRIVATE"; break; case Incidence::SecrecyConfidential: text = "CONFIDENTIAL"; break; } if (text) { addPropValue(vevent, VCClassProp, text); } // categories QStringList tmpStrList = anEvent->categories(); tmpStr.clear(); QString catStr; for (QStringList::const_iterator it = tmpStrList.constBegin(); it != tmpStrList.constEnd(); ++it) { catStr = *it; if (catStr[0] == QLatin1Char(' ')) { tmpStr += catStr.midRef(1); } else { tmpStr += catStr; } // this must be a ';' character as the vCalendar specification requires! // vcc.y has been hacked to translate the ';' to a ',' when the vcal is // read in. tmpStr += QLatin1Char(';'); } if (!tmpStr.isEmpty()) { tmpStr.truncate(tmpStr.length() - 1); addPropValue(vevent, VCCategoriesProp, tmpStr.toUtf8().constData()); } // attachments // TODO: handle binary attachments! Attachment::List attachments = anEvent->attachments(); Attachment::List::ConstIterator atIt; for (atIt = attachments.constBegin(); atIt != attachments.constEnd(); ++atIt) { addPropValue(vevent, VCAttachProp, (*atIt)->uri().toUtf8().constData()); } // resources tmpStrList = anEvent->resources(); tmpStr = tmpStrList.join(QLatin1Char(';')); if (!tmpStr.isEmpty()) { addPropValue(vevent, VCResourcesProp, tmpStr.toUtf8().constData()); } // alarm stuff auto alarms = anEvent->alarms(); for (auto it2 = alarms.constBegin(); it2 != alarms.constEnd(); ++it2) { Alarm::Ptr alarm = *it2; if (alarm->enabled()) { VObject *a; if (alarm->type() == Alarm::Display) { a = addProp(vevent, VCDAlarmProp); tmpStr = qDateTimeToISO(alarm->time()); addPropValue(a, VCRunTimeProp, tmpStr.toUtf8().constData()); addPropValue(a, VCRepeatCountProp, "1"); if (alarm->text().isNull()) { addPropValue(a, VCDisplayStringProp, "beep!"); } else { addPropValue(a, VCDisplayStringProp, alarm->text().toLatin1().data()); } } else if (alarm->type() == Alarm::Audio) { a = addProp(vevent, VCAAlarmProp); tmpStr = qDateTimeToISO(alarm->time()); addPropValue(a, VCRunTimeProp, tmpStr.toUtf8().constData()); addPropValue(a, VCRepeatCountProp, "1"); addPropValue(a, VCAudioContentProp, QFile::encodeName(alarm->audioFile()).constData()); } if (alarm->type() == Alarm::Procedure) { a = addProp(vevent, VCPAlarmProp); tmpStr = qDateTimeToISO(alarm->time()); addPropValue(a, VCRunTimeProp, tmpStr.toUtf8().constData()); addPropValue(a, VCRepeatCountProp, "1"); addPropValue(a, VCProcedureNameProp, QFile::encodeName(alarm->programFile()).constData()); } } } // priority tmpStr.sprintf("%i", anEvent->priority()); addPropValue(vevent, VCPriorityProp, tmpStr.toUtf8().constData()); // transparency tmpStr.sprintf("%i", anEvent->transparency()); addPropValue(vevent, VCTranspProp, tmpStr.toUtf8().constData()); // related event if (!anEvent->relatedTo().isEmpty()) { addPropValue(vevent, VCRelatedToProp, anEvent->relatedTo().toUtf8().constData()); } return vevent; } Todo::Ptr VCalFormat::VTodoToEvent(VObject *vtodo) { VObject *vo = nullptr; VObjectIterator voi; char *s = nullptr; Todo::Ptr anEvent(new Todo); // creation date if ((vo = isAPropertyOf(vtodo, VCDCreatedProp)) != nullptr) { anEvent->setCreated(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))))); deleteStr(s); } // unique id vo = isAPropertyOf(vtodo, VCUniqueStringProp); // while the UID property is preferred, it is not required. We'll use the // default Event UID if none is given. if (vo) { anEvent->setUid(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))); deleteStr(s); } // last modification date if ((vo = isAPropertyOf(vtodo, VCLastModifiedProp)) != nullptr) { anEvent->setLastModified(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))))); deleteStr(s); } else { anEvent->setLastModified(QDateTime::currentDateTimeUtc()); } // organizer // if our extension property for the event's ORGANIZER exists, add it. if ((vo = isAPropertyOf(vtodo, ICOrganizerProp)) != nullptr) { anEvent->setOrganizer(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))); deleteStr(s); } else { if (d->mCalendar->owner()->name() != QLatin1String("Unknown Name")) { anEvent->setOrganizer(d->mCalendar->owner()); } } // attendees. initPropIterator(&voi, vtodo); while (moreIteration(&voi)) { vo = nextVObject(&voi); if (strcmp(vObjectName(vo), VCAttendeeProp) == 0) { Attendee::Ptr a; VObject *vp; s = fakeCString(vObjectUStringZValue(vo)); QString tmpStr = QString::fromUtf8(s); deleteStr(s); tmpStr = tmpStr.simplified(); int emailPos1, emailPos2; if ((emailPos1 = tmpStr.indexOf(QLatin1Char('<'))) > 0) { // both email address and name emailPos2 = tmpStr.lastIndexOf(QLatin1Char('>')); a = Attendee::Ptr(new Attendee(tmpStr.left(emailPos1 - 1), tmpStr.mid(emailPos1 + 1, emailPos2 - (emailPos1 + 1)))); } else if (tmpStr.indexOf(QLatin1Char('@')) > 0) { // just an email address a = Attendee::Ptr(new Attendee(QString(), tmpStr)); } else { // just a name // WTF??? Replacing the spaces of a name and using this as email? QString email = tmpStr.replace(QLatin1Char(' '), QLatin1Char('.')); a = Attendee::Ptr(new Attendee(tmpStr, email)); } // is there an RSVP property? if ((vp = isAPropertyOf(vo, VCRSVPProp)) != nullptr) { a->setRSVP(vObjectStringZValue(vp)); } // is there a status property? if ((vp = isAPropertyOf(vo, VCStatusProp)) != nullptr) { a->setStatus(readStatus(vObjectStringZValue(vp))); } // add the attendee anEvent->addAttendee(a); } } // description for todo if ((vo = isAPropertyOf(vtodo, VCDescriptionProp)) != nullptr) { s = fakeCString(vObjectUStringZValue(vo)); anEvent->setDescription(QString::fromUtf8(s), Qt::mightBeRichText(QString::fromUtf8(s))); deleteStr(s); } // summary if ((vo = isAPropertyOf(vtodo, VCSummaryProp))) { s = fakeCString(vObjectUStringZValue(vo)); anEvent->setSummary(QString::fromUtf8(s), Qt::mightBeRichText(QString::fromUtf8(s))); deleteStr(s); } // location if ((vo = isAPropertyOf(vtodo, VCLocationProp)) != nullptr) { s = fakeCString(vObjectUStringZValue(vo)); anEvent->setLocation(QString::fromUtf8(s), Qt::mightBeRichText(QString::fromUtf8(s))); deleteStr(s); } // completed // was: status if ((vo = isAPropertyOf(vtodo, VCStatusProp)) != nullptr) { s = fakeCString(vObjectUStringZValue(vo)); if (s && strcmp(s, "COMPLETED") == 0) { anEvent->setCompleted(true); } else { anEvent->setCompleted(false); } deleteStr(s); } else { anEvent->setCompleted(false); } // completion date if ((vo = isAPropertyOf(vtodo, VCCompletedProp)) != nullptr) { anEvent->setCompleted(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))))); deleteStr(s); } // priority if ((vo = isAPropertyOf(vtodo, VCPriorityProp))) { s = fakeCString(vObjectUStringZValue(vo)); if (s) { anEvent->setPriority(atoi(s)); deleteStr(s); } } anEvent->setAllDay(false); // due date if ((vo = isAPropertyOf(vtodo, VCDueProp)) != nullptr) { anEvent->setDtDue(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))))); deleteStr(s); if (anEvent->dtDue().time().hour() == 0 && anEvent->dtDue().time().minute() == 0 && anEvent->dtDue().time().second() == 0) { anEvent->setAllDay(true); } } else { anEvent->setDtDue(QDateTime()); } // start time if ((vo = isAPropertyOf(vtodo, VCDTstartProp)) != nullptr) { anEvent->setDtStart(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))))); deleteStr(s); if (anEvent->dtStart().time().hour() == 0 && anEvent->dtStart().time().minute() == 0 && anEvent->dtStart().time().second() == 0) { anEvent->setAllDay(true); } } else { anEvent->setDtStart(QDateTime()); } // repeat stuff if ((vo = isAPropertyOf(vtodo, VCRRuleProp)) != nullptr) { QString tmpStr = (QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))); deleteStr(s); tmpStr = tmpStr.simplified(); tmpStr = tmpStr.toUpper(); // first, read the type of the recurrence int typelen = 1; uint type = Recurrence::rNone; if (tmpStr.left(1) == QStringLiteral("D")) { type = Recurrence::rDaily; } else if (tmpStr.left(1) == QStringLiteral("W")) { type = Recurrence::rWeekly; } else { typelen = 2; if (tmpStr.left(2) == QStringLiteral("MP")) { type = Recurrence::rMonthlyPos; } else if (tmpStr.left(2) == QStringLiteral("MD")) { type = Recurrence::rMonthlyDay; } else if (tmpStr.left(2) == QStringLiteral("YM")) { type = Recurrence::rYearlyMonth; } else if (tmpStr.left(2) == QStringLiteral("YD")) { type = Recurrence::rYearlyDay; } } if (type != Recurrence::rNone) { // Immediately after the type is the frequency int index = tmpStr.indexOf(QLatin1Char(' ')); int last = tmpStr.lastIndexOf(QLatin1Char(' ')) + 1; // find last entry int rFreq = tmpStr.midRef(typelen, (index - 1)).toInt(); ++index; // advance to beginning of stuff after freq // Read the type-specific settings switch (type) { case Recurrence::rDaily: anEvent->recurrence()->setDaily(rFreq); break; case Recurrence::rWeekly: { QBitArray qba(7); QString dayStr; if (index == last) { // e.g. W1 #0 qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1); } else { // e.g. W1 SU #0 while (index < last) { dayStr = tmpStr.mid(index, 3); int dayNum = numFromDay(dayStr); if (dayNum >= 0) { qba.setBit(dayNum); } index += 3; // advance to next day, or possibly "#" } } anEvent->recurrence()->setWeekly(rFreq, qba); break; } case Recurrence::rMonthlyPos: { anEvent->recurrence()->setMonthly(rFreq); QBitArray qba(7); short tmpPos; if (index == last) { // e.g. MP1 #0 tmpPos = anEvent->dtStart().date().day() / 7 + 1; if (tmpPos == 5) { tmpPos = -1; } qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1); anEvent->recurrence()->addMonthlyPos(tmpPos, qba); } else { // e.g. MP1 1+ SU #0 while (index < last) { tmpPos = tmpStr.mid(index, 1).toShort(); index += 1; if (tmpStr.mid(index, 1) == QLatin1String("-")) { // convert tmpPos to negative tmpPos = 0 - tmpPos; } index += 2; // advance to day(s) while (numFromDay(tmpStr.mid(index, 3)) >= 0) { int dayNum = numFromDay(tmpStr.mid(index, 3)); qba.setBit(dayNum); index += 3; // advance to next day, or possibly pos or "#" } anEvent->recurrence()->addMonthlyPos(tmpPos, qba); qba.detach(); qba.fill(false); // clear out } // while != "#" } break; } case Recurrence::rMonthlyDay: anEvent->recurrence()->setMonthly(rFreq); if (index == last) { // e.g. MD1 #0 short tmpDay = anEvent->dtStart().date().day(); anEvent->recurrence()->addMonthlyDate(tmpDay); } else { // e.g. MD1 3 #0 while (index < last) { int index2 = tmpStr.indexOf(QLatin1Char(' '), index); if ((tmpStr.mid((index2 - 1), 1) == QLatin1String("-")) || (tmpStr.mid((index2 - 1), 1) == QLatin1String("+"))) { index2 = index2 - 1; } short tmpDay = tmpStr.mid(index, (index2 - index)).toShort(); index = index2; if (tmpStr.mid(index, 1) == QLatin1String("-")) { tmpDay = 0 - tmpDay; } index += 2; // advance the index; anEvent->recurrence()->addMonthlyDate(tmpDay); } // while != # } break; case Recurrence::rYearlyMonth: anEvent->recurrence()->setYearly(rFreq); if (index == last) { // e.g. YM1 #0 short tmpMonth = anEvent->dtStart().date().month(); anEvent->recurrence()->addYearlyMonth(tmpMonth); } else { // e.g. YM1 3 #0 while (index < last) { int index2 = tmpStr.indexOf(QLatin1Char(' '), index); short tmpMonth = tmpStr.mid(index, (index2 - index)).toShort(); index = index2 + 1; anEvent->recurrence()->addYearlyMonth(tmpMonth); } // while != # } break; case Recurrence::rYearlyDay: anEvent->recurrence()->setYearly(rFreq); if (index == last) { // e.g. YD1 #0 short tmpDay = anEvent->dtStart().date().dayOfYear(); anEvent->recurrence()->addYearlyDay(tmpDay); } else { // e.g. YD1 123 #0 while (index < last) { int index2 = tmpStr.indexOf(QLatin1Char(' '), index); short tmpDay = tmpStr.mid(index, (index2 - index)).toShort(); index = index2 + 1; anEvent->recurrence()->addYearlyDay(tmpDay); } // while != # } break; default: break; } // find the last field, which is either the duration or the end date index = last; if (tmpStr.mid(index, 1) == QLatin1String("#")) { // Nr of occurrences index++; int rDuration = tmpStr.midRef(index, tmpStr.length() - index).toInt(); if (rDuration > 0) { anEvent->recurrence()->setDuration(rDuration); } } else if (tmpStr.indexOf(QLatin1Char('T'), index) != -1) { QDateTime rEndDate = ISOToQDateTime(tmpStr.mid(index, tmpStr.length() - index)); anEvent->recurrence()->setEndDateTime(rEndDate); } } else { qCDebug(KCALCORE_LOG) << "we don't understand this type of recurrence!"; } // if known recurrence type } // repeats // recurrence exceptions if ((vo = isAPropertyOf(vtodo, VCExpDateProp)) != nullptr) { s = fakeCString(vObjectUStringZValue(vo)); QStringList exDates = QString::fromUtf8(s).split(QLatin1Char(',')); QStringList::ConstIterator it; for (it = exDates.constBegin(); it != exDates.constEnd(); ++it) { QDateTime exDate = ISOToQDateTime(*it); if (exDate.time().hour() == 0 && exDate.time().minute() == 0 && exDate.time().second() == 0) { anEvent->recurrence()->addExDate(ISOToQDate(*it)); } else { anEvent->recurrence()->addExDateTime(exDate); } } deleteStr(s); } // alarm stuff if ((vo = isAPropertyOf(vtodo, VCDAlarmProp))) { Alarm::Ptr alarm; VObject *a = isAPropertyOf(vo, VCRunTimeProp); VObject *b = isAPropertyOf(vo, VCDisplayStringProp); if (a || b) { alarm = anEvent->newAlarm(); if (a) { alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a))))); deleteStr(s); } alarm->setEnabled(true); if (b) { s = fakeCString(vObjectUStringZValue(b)); alarm->setDisplayAlarm(QString::fromUtf8(s)); deleteStr(s); } else { alarm->setDisplayAlarm(QString()); } } } if ((vo = isAPropertyOf(vtodo, VCAAlarmProp))) { Alarm::Ptr alarm; VObject *a; VObject *b; a = isAPropertyOf(vo, VCRunTimeProp); b = isAPropertyOf(vo, VCAudioContentProp); if (a || b) { alarm = anEvent->newAlarm(); if (a) { alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a))))); deleteStr(s); } alarm->setEnabled(true); if (b) { s = fakeCString(vObjectUStringZValue(b)); alarm->setAudioAlarm(QFile::decodeName(s)); deleteStr(s); } else { alarm->setAudioAlarm(QString()); } } } if ((vo = isAPropertyOf(vtodo, VCPAlarmProp))) { Alarm::Ptr alarm; VObject *a = isAPropertyOf(vo, VCRunTimeProp); VObject *b = isAPropertyOf(vo, VCProcedureNameProp); if (a || b) { alarm = anEvent->newAlarm(); if (a) { alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a))))); deleteStr(s); } alarm->setEnabled(true); if (b) { s = fakeCString(vObjectUStringZValue(b)); alarm->setProcedureAlarm(QFile::decodeName(s)); deleteStr(s); } else { alarm->setProcedureAlarm(QString()); } } } // related todo if ((vo = isAPropertyOf(vtodo, VCRelatedToProp)) != nullptr) { anEvent->setRelatedTo(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))); deleteStr(s); d->mTodosRelate.append(anEvent); } // secrecy Incidence::Secrecy secrecy = Incidence::SecrecyPublic; if ((vo = isAPropertyOf(vtodo, VCClassProp)) != nullptr) { s = fakeCString(vObjectUStringZValue(vo)); if (s && strcmp(s, "PRIVATE") == 0) { secrecy = Incidence::SecrecyPrivate; } else if (s && strcmp(s, "CONFIDENTIAL") == 0) { secrecy = Incidence::SecrecyConfidential; } deleteStr(s); } anEvent->setSecrecy(secrecy); // categories if ((vo = isAPropertyOf(vtodo, VCCategoriesProp)) != nullptr) { s = fakeCString(vObjectUStringZValue(vo)); QString categories = QString::fromUtf8(s); deleteStr(s); QStringList tmpStrList = categories.split(QLatin1Char(';')); anEvent->setCategories(tmpStrList); } return anEvent; } Event::Ptr VCalFormat::VEventToEvent(VObject *vevent) { VObject *vo = nullptr; VObjectIterator voi; char *s = nullptr; Event::Ptr anEvent(new Event); // creation date if ((vo = isAPropertyOf(vevent, VCDCreatedProp)) != nullptr) { anEvent->setCreated(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))))); deleteStr(s); } // unique id vo = isAPropertyOf(vevent, VCUniqueStringProp); // while the UID property is preferred, it is not required. We'll use the // default Event UID if none is given. if (vo) { anEvent->setUid(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))); deleteStr(s); } // revision // again NSCAL doesn't give us much to work with, so we improvise... anEvent->setRevision(0); if ((vo = isAPropertyOf(vevent, VCSequenceProp)) != nullptr) { s = fakeCString(vObjectUStringZValue(vo)); if (s) { anEvent->setRevision(atoi(s)); deleteStr(s); } } // last modification date if ((vo = isAPropertyOf(vevent, VCLastModifiedProp)) != nullptr) { anEvent->setLastModified(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))))); deleteStr(s); } else { anEvent->setLastModified(QDateTime::currentDateTimeUtc()); } // organizer // if our extension property for the event's ORGANIZER exists, add it. if ((vo = isAPropertyOf(vevent, ICOrganizerProp)) != nullptr) { // FIXME: Also use the full name, not just the email address anEvent->setOrganizer(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))); deleteStr(s); } else { if (d->mCalendar->owner()->name() != QStringLiteral("Unknown Name")) { anEvent->setOrganizer(d->mCalendar->owner()); } } // deal with attendees. initPropIterator(&voi, vevent); while (moreIteration(&voi)) { vo = nextVObject(&voi); if (strcmp(vObjectName(vo), VCAttendeeProp) == 0) { Attendee::Ptr a; VObject *vp = nullptr; s = fakeCString(vObjectUStringZValue(vo)); QString tmpStr = QString::fromUtf8(s); deleteStr(s); tmpStr = tmpStr.simplified(); int emailPos1, emailPos2; if ((emailPos1 = tmpStr.indexOf(QLatin1Char('<'))) > 0) { // both email address and name emailPos2 = tmpStr.lastIndexOf(QLatin1Char('>')); a = Attendee::Ptr(new Attendee(tmpStr.left(emailPos1 - 1), tmpStr.mid(emailPos1 + 1, emailPos2 - (emailPos1 + 1)))); } else if (tmpStr.indexOf(QLatin1Char('@')) > 0) { // just an email address a = Attendee::Ptr(new Attendee(QString(), tmpStr)); } else { // just a name QString email = tmpStr.replace(QLatin1Char(' '), QLatin1Char('.')); a = Attendee::Ptr(new Attendee(tmpStr, email)); } // is there an RSVP property? if ((vp = isAPropertyOf(vo, VCRSVPProp)) != nullptr) { a->setRSVP(vObjectStringZValue(vp)); } // is there a status property? if ((vp = isAPropertyOf(vo, VCStatusProp)) != nullptr) { a->setStatus(readStatus(vObjectStringZValue(vp))); } // add the attendee anEvent->addAttendee(a); } } // This isn't strictly true. An event that doesn't have a start time // or an end time isn't all-day, it has an anchor in time but it doesn't // "take up" any time. /*if ((isAPropertyOf(vevent, VCDTstartProp) == 0) || (isAPropertyOf(vevent, VCDTendProp) == 0)) { anEvent->setAllDay(true); } else { }*/ anEvent->setAllDay(false); // start time if ((vo = isAPropertyOf(vevent, VCDTstartProp)) != nullptr) { anEvent->setDtStart(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))))); deleteStr(s); if (anEvent->dtStart().time().hour() == 0 && anEvent->dtStart().time().minute() == 0 && anEvent->dtStart().time().second() == 0) { anEvent->setAllDay(true); } } // stop time if ((vo = isAPropertyOf(vevent, VCDTendProp)) != nullptr) { anEvent->setDtEnd(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))))); deleteStr(s); if (anEvent->dtEnd().time().hour() == 0 && anEvent->dtEnd().time().minute() == 0 && anEvent->dtEnd().time().second() == 0) { anEvent->setAllDay(true); } } // at this point, there should be at least a start or end time. // fix up for events that take up no time but have a time associated if (!isAPropertyOf(vevent, VCDTstartProp)) { anEvent->setDtStart(anEvent->dtEnd()); } if (! isAPropertyOf(vevent, VCDTendProp)) { anEvent->setDtEnd(anEvent->dtStart()); } /////////////////////////////////////////////////////////////////////////// // repeat stuff if ((vo = isAPropertyOf(vevent, VCRRuleProp)) != nullptr) { QString tmpStr = (QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))); deleteStr(s); tmpStr = tmpStr.simplified(); tmpStr = tmpStr.toUpper(); // first, read the type of the recurrence int typelen = 1; uint type = Recurrence::rNone; if (tmpStr.left(1) == QLatin1String("D")) { type = Recurrence::rDaily; } else if (tmpStr.left(1) == QLatin1String("W")) { type = Recurrence::rWeekly; } else { typelen = 2; if (tmpStr.left(2) == QLatin1String("MP")) { type = Recurrence::rMonthlyPos; } else if (tmpStr.left(2) == QLatin1String("MD")) { type = Recurrence::rMonthlyDay; } else if (tmpStr.left(2) == QLatin1String("YM")) { type = Recurrence::rYearlyMonth; } else if (tmpStr.left(2) == QLatin1String("YD")) { type = Recurrence::rYearlyDay; } } if (type != Recurrence::rNone) { // Immediately after the type is the frequency int index = tmpStr.indexOf(QLatin1Char(' ')); int last = tmpStr.lastIndexOf(QLatin1Char(' ')) + 1; // find last entry int rFreq = tmpStr.midRef(typelen, (index - 1)).toInt(); ++index; // advance to beginning of stuff after freq // Read the type-specific settings switch (type) { case Recurrence::rDaily: anEvent->recurrence()->setDaily(rFreq); break; case Recurrence::rWeekly: { QBitArray qba(7); QString dayStr; if (index == last) { // e.g. W1 #0 qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1); } else { // e.g. W1 SU #0 while (index < last) { dayStr = tmpStr.mid(index, 3); int dayNum = numFromDay(dayStr); if (dayNum >= 0) { qba.setBit(dayNum); } index += 3; // advance to next day, or possibly "#" } } anEvent->recurrence()->setWeekly(rFreq, qba); break; } case Recurrence::rMonthlyPos: { anEvent->recurrence()->setMonthly(rFreq); QBitArray qba(7); short tmpPos; if (index == last) { // e.g. MP1 #0 tmpPos = anEvent->dtStart().date().day() / 7 + 1; if (tmpPos == 5) { tmpPos = -1; } qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1); anEvent->recurrence()->addMonthlyPos(tmpPos, qba); } else { // e.g. MP1 1+ SU #0 while (index < last) { tmpPos = tmpStr.mid(index, 1).toShort(); index += 1; if (tmpStr.mid(index, 1) == QStringLiteral("-")) { // convert tmpPos to negative tmpPos = 0 - tmpPos; } index += 2; // advance to day(s) while (numFromDay(tmpStr.mid(index, 3)) >= 0) { int dayNum = numFromDay(tmpStr.mid(index, 3)); qba.setBit(dayNum); index += 3; // advance to next day, or possibly pos or "#" } anEvent->recurrence()->addMonthlyPos(tmpPos, qba); qba.detach(); qba.fill(false); // clear out } // while != "#" } break; } case Recurrence::rMonthlyDay: anEvent->recurrence()->setMonthly(rFreq); if (index == last) { // e.g. MD1 #0 short tmpDay = anEvent->dtStart().date().day(); anEvent->recurrence()->addMonthlyDate(tmpDay); } else { // e.g. MD1 3 #0 while (index < last) { int index2 = tmpStr.indexOf(QLatin1Char(' '), index); if ((tmpStr.mid((index2 - 1), 1) == QStringLiteral("-")) || (tmpStr.mid((index2 - 1), 1) == QStringLiteral("+"))) { index2 = index2 - 1; } short tmpDay = tmpStr.mid(index, (index2 - index)).toShort(); index = index2; if (tmpStr.mid(index, 1) == QStringLiteral("-")) { tmpDay = 0 - tmpDay; } index += 2; // advance the index; anEvent->recurrence()->addMonthlyDate(tmpDay); } // while != # } break; case Recurrence::rYearlyMonth: anEvent->recurrence()->setYearly(rFreq); if (index == last) { // e.g. YM1 #0 short tmpMonth = anEvent->dtStart().date().month(); anEvent->recurrence()->addYearlyMonth(tmpMonth); } else { // e.g. YM1 3 #0 while (index < last) { int index2 = tmpStr.indexOf(QLatin1Char(' '), index); short tmpMonth = tmpStr.mid(index, (index2 - index)).toShort(); index = index2 + 1; anEvent->recurrence()->addYearlyMonth(tmpMonth); } // while != # } break; case Recurrence::rYearlyDay: anEvent->recurrence()->setYearly(rFreq); if (index == last) { // e.g. YD1 #0 short tmpDay = anEvent->dtStart().date().dayOfYear(); anEvent->recurrence()->addYearlyDay(tmpDay); } else { // e.g. YD1 123 #0 while (index < last) { int index2 = tmpStr.indexOf(QLatin1Char(' '), index); short tmpDay = tmpStr.mid(index, (index2 - index)).toShort(); index = index2 + 1; anEvent->recurrence()->addYearlyDay(tmpDay); } // while != # } break; default: break; } // find the last field, which is either the duration or the end date index = last; if (tmpStr.mid(index, 1) == QLatin1String("#")) { // Nr of occurrences index++; int rDuration = tmpStr.midRef(index, tmpStr.length() - index).toInt(); if (rDuration > 0) { anEvent->recurrence()->setDuration(rDuration); } } else if (tmpStr.indexOf(QLatin1Char('T'), index) != -1) { QDateTime rEndDate = ISOToQDateTime(tmpStr.mid(index, tmpStr.length() - index)); anEvent->recurrence()->setEndDateTime(rEndDate); } // anEvent->recurrence()->dump(); } else { qCDebug(KCALCORE_LOG) << "we don't understand this type of recurrence!"; } // if known recurrence type } // repeats // recurrence exceptions if ((vo = isAPropertyOf(vevent, VCExpDateProp)) != nullptr) { s = fakeCString(vObjectUStringZValue(vo)); QStringList exDates = QString::fromUtf8(s).split(QLatin1Char(',')); QStringList::ConstIterator it; for (it = exDates.constBegin(); it != exDates.constEnd(); ++it) { QDateTime exDate = ISOToQDateTime(*it); if (exDate.time().hour() == 0 && exDate.time().minute() == 0 && exDate.time().second() == 0) { anEvent->recurrence()->addExDate(ISOToQDate(*it)); } else { anEvent->recurrence()->addExDateTime(exDate); } } deleteStr(s); } // summary if ((vo = isAPropertyOf(vevent, VCSummaryProp))) { s = fakeCString(vObjectUStringZValue(vo)); anEvent->setSummary(QString::fromUtf8(s), Qt::mightBeRichText(QString::fromUtf8(s))); deleteStr(s); } // description if ((vo = isAPropertyOf(vevent, VCDescriptionProp)) != nullptr) { s = fakeCString(vObjectUStringZValue(vo)); bool isRich = Qt::mightBeRichText(QString::fromUtf8(s)); if (!anEvent->description().isEmpty()) { anEvent->setDescription( anEvent->description() + QLatin1Char('\n') + QString::fromUtf8(s), isRich); } else { anEvent->setDescription(QString::fromUtf8(s), isRich); } deleteStr(s); } // location if ((vo = isAPropertyOf(vevent, VCLocationProp)) != nullptr) { s = fakeCString(vObjectUStringZValue(vo)); anEvent->setLocation(QString::fromUtf8(s), Qt::mightBeRichText(QString::fromUtf8(s))); deleteStr(s); } // some stupid vCal exporters ignore the standard and use Description // instead of Summary for the default field. Correct for this. if (anEvent->summary().isEmpty() && !(anEvent->description().isEmpty())) { QString tmpStr = anEvent->description().simplified(); anEvent->setDescription(QString()); anEvent->setSummary(tmpStr); } #if 0 // status if ((vo = isAPropertyOf(vevent, VCStatusProp)) != 0) { QString tmpStr(s = fakeCString(vObjectUStringZValue(vo))); deleteStr(s); // TODO: Define Event status // anEvent->setStatus( tmpStr ); } else { // anEvent->setStatus( "NEEDS ACTION" ); } #endif // secrecy Incidence::Secrecy secrecy = Incidence::SecrecyPublic; if ((vo = isAPropertyOf(vevent, VCClassProp)) != nullptr) { s = fakeCString(vObjectUStringZValue(vo)); if (s && strcmp(s, "PRIVATE") == 0) { secrecy = Incidence::SecrecyPrivate; } else if (s && strcmp(s, "CONFIDENTIAL") == 0) { secrecy = Incidence::SecrecyConfidential; } deleteStr(s); } anEvent->setSecrecy(secrecy); // categories if ((vo = isAPropertyOf(vevent, VCCategoriesProp)) != nullptr) { s = fakeCString(vObjectUStringZValue(vo)); QString categories = QString::fromUtf8(s); deleteStr(s); QStringList tmpStrList = categories.split(QLatin1Char(',')); anEvent->setCategories(tmpStrList); } // attachments initPropIterator(&voi, vevent); while (moreIteration(&voi)) { vo = nextVObject(&voi); if (strcmp(vObjectName(vo), VCAttachProp) == 0) { s = fakeCString(vObjectUStringZValue(vo)); anEvent->addAttachment(Attachment::Ptr(new Attachment(QString::fromUtf8(s)))); deleteStr(s); } } // resources if ((vo = isAPropertyOf(vevent, VCResourcesProp)) != nullptr) { QString resources = (QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))); deleteStr(s); QStringList tmpStrList = resources.split(QLatin1Char(';')); anEvent->setResources(tmpStrList); } // alarm stuff if ((vo = isAPropertyOf(vevent, VCDAlarmProp))) { Alarm::Ptr alarm; VObject *a = isAPropertyOf(vo, VCRunTimeProp); VObject *b = isAPropertyOf(vo, VCDisplayStringProp); if (a || b) { alarm = anEvent->newAlarm(); if (a) { alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a))))); deleteStr(s); } alarm->setEnabled(true); if (b) { s = fakeCString(vObjectUStringZValue(b)); alarm->setDisplayAlarm(QString::fromUtf8(s)); deleteStr(s); } else { alarm->setDisplayAlarm(QString()); } } } if ((vo = isAPropertyOf(vevent, VCAAlarmProp))) { Alarm::Ptr alarm; VObject *a; VObject *b; a = isAPropertyOf(vo, VCRunTimeProp); b = isAPropertyOf(vo, VCAudioContentProp); if (a || b) { alarm = anEvent->newAlarm(); if (a) { alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a))))); deleteStr(s); } alarm->setEnabled(true); if (b) { s = fakeCString(vObjectUStringZValue(b)); alarm->setAudioAlarm(QFile::decodeName(s)); deleteStr(s); } else { alarm->setAudioAlarm(QString()); } } } if ((vo = isAPropertyOf(vevent, VCPAlarmProp))) { Alarm::Ptr alarm; VObject *a; VObject *b; a = isAPropertyOf(vo, VCRunTimeProp); b = isAPropertyOf(vo, VCProcedureNameProp); if (a || b) { alarm = anEvent->newAlarm(); if (a) { alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a))))); deleteStr(s); } alarm->setEnabled(true); if (b) { s = fakeCString(vObjectUStringZValue(b)); alarm->setProcedureAlarm(QFile::decodeName(s)); deleteStr(s); } else { alarm->setProcedureAlarm(QString()); } } } // priority if ((vo = isAPropertyOf(vevent, VCPriorityProp))) { s = fakeCString(vObjectUStringZValue(vo)); if (s) { anEvent->setPriority(atoi(s)); deleteStr(s); } } // transparency if ((vo = isAPropertyOf(vevent, VCTranspProp)) != nullptr) { s = fakeCString(vObjectUStringZValue(vo)); if (s) { int i = atoi(s); anEvent->setTransparency(i == 1 ? Event::Transparent : Event::Opaque); deleteStr(s); } } // related event if ((vo = isAPropertyOf(vevent, VCRelatedToProp)) != nullptr) { anEvent->setRelatedTo(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))); deleteStr(s); d->mEventsRelate.append(anEvent); } /* Rest of the custom properties */ readCustomProperties(vevent, anEvent); return anEvent; } QString VCalFormat::parseTZ(const QByteArray &timezone) const { // qCDebug(KCALCORE_LOG) << timezone; QString pZone = QString::fromUtf8(timezone.mid(timezone.indexOf("TZID:VCAL") + 9)); return pZone.mid(0, pZone.indexOf(QLatin1Char('\n'))); } QString VCalFormat::parseDst(QByteArray &timezone) const { if (!timezone.contains("BEGIN:DAYLIGHT")) { return QString(); } timezone = timezone.mid(timezone.indexOf("BEGIN:DAYLIGHT")); timezone = timezone.mid(timezone.indexOf("TZNAME:") + 7); QString sStart = QString::fromUtf8(timezone.mid(0, (timezone.indexOf("COMMENT:")))); sStart.chop(2); timezone = timezone.mid(timezone.indexOf("TZOFFSETTO:") + 11); QString sOffset = QString::fromUtf8(timezone.mid(0, (timezone.indexOf("DTSTART:")))); sOffset.chop(2); sOffset.insert(3, QLatin1Char(':')); timezone = timezone.mid(timezone.indexOf("TZNAME:") + 7); QString sEnd = QString::fromUtf8(timezone.mid(0, (timezone.indexOf("COMMENT:")))); sEnd.chop(2); return QStringLiteral("TRUE;") + sOffset + QLatin1Char(';') + sStart + QLatin1Char(';') + sEnd + QLatin1String(";;"); } QString VCalFormat::qDateToISO(const QDate &qd) { QString tmpStr; if (!qd.isValid()) { return QString(); } tmpStr.sprintf("%.2d%.2d%.2d", qd.year(), qd.month(), qd.day()); return tmpStr; } QString VCalFormat::qDateTimeToISO(const QDateTime &dt, bool zulu) { QString tmpStr; if (!dt.isValid()) { return QString(); } QDateTime tmpDT; if (zulu) { tmpDT = dt.toUTC(); } else { tmpDT = dt.toTimeZone(d->mCalendar->timeZone()); } tmpStr.sprintf("%.2d%.2d%.2dT%.2d%.2d%.2d", tmpDT.date().year(), tmpDT.date().month(), tmpDT.date().day(), tmpDT.time().hour(), tmpDT.time().minute(), tmpDT.time().second()); if (zulu || dt.timeZone() == QTimeZone::utc()) { tmpStr += QLatin1Char('Z'); } return tmpStr; } QDateTime VCalFormat::ISOToQDateTime(const QString &dtStr) { QDate tmpDate; QTime tmpTime; QString tmpStr; int year, month, day, hour, minute, second; tmpStr = dtStr; year = tmpStr.leftRef(4).toInt(); month = tmpStr.midRef(4, 2).toInt(); day = tmpStr.midRef(6, 2).toInt(); hour = tmpStr.midRef(9, 2).toInt(); minute = tmpStr.midRef(11, 2).toInt(); second = tmpStr.midRef(13, 2).toInt(); tmpDate.setDate(year, month, day); tmpTime.setHMS(hour, minute, second); if (tmpDate.isValid() && tmpTime.isValid()) { // correct for GMT if string is in Zulu format if (dtStr.at(dtStr.length() - 1) == QLatin1Char('Z')) { return QDateTime(tmpDate, tmpTime, Qt::UTC); } else { return QDateTime(tmpDate, tmpTime, d->mCalendar->timeZone()); } } else { return QDateTime(); } } QDate VCalFormat::ISOToQDate(const QString &dateStr) { int year, month, day; year = dateStr.leftRef(4).toInt(); month = dateStr.midRef(4, 2).toInt(); day = dateStr.midRef(6, 2).toInt(); return QDate(year, month, day); } bool VCalFormat::parseTZOffsetISO8601(const QString &s, int &result) { // ISO8601 format(s): // +- hh : mm // +- hh mm // +- hh // We also accept broken one without + int mod = 1; int v = 0; QString str = s.trimmed(); int ofs = 0; result = 0; // Check for end if (str.size() <= ofs) { return false; } if (str[ofs] == QLatin1Char('-')) { mod = -1; ofs++; } else if (str[ofs] == QLatin1Char('+')) { ofs++; } if (str.size() <= ofs) { return false; } // Make sure next two values are numbers bool ok; if (str.size() < (ofs + 2)) { return false; } v = str.midRef(ofs, 2).toInt(&ok) * 60; if (!ok) { return false; } ofs += 2; if (str.size() > ofs) { if (str[ofs] == QLatin1Char(':')) { ofs++; } if (str.size() > ofs) { if (str.size() < (ofs + 2)) { return false; } v += str.midRef(ofs, 2).toInt(&ok); if (!ok) { return false; } } } result = v * mod * 60; return true; } // take a raw vcalendar (i.e. from a file on disk, clipboard, etc. etc. // and break it down from it's tree-like format into the dictionary format // that is used internally in the VCalFormat. void VCalFormat::populate(VObject *vcal, bool deleted, const QString ¬ebook) { Q_UNUSED(notebook); // this function will populate the caldict dictionary and other event // lists. It turns vevents into Events and then inserts them. VObjectIterator i; VObject *curVO; Event::Ptr anEvent; bool hasTimeZone = false; //The calendar came with a TZ and not UTC QTimeZone previousZone; //If we add a new TZ we should leave the spec as it was before if ((curVO = isAPropertyOf(vcal, ICMethodProp)) != nullptr) { char *methodType = nullptr; methodType = fakeCString(vObjectUStringZValue(curVO)); // qCDebug(KCALCORE_LOG) << "This calendar is an iTIP transaction of type '" << methodType << "'"; deleteStr(methodType); } // warn the user that we might have trouble reading non-known calendar. if ((curVO = isAPropertyOf(vcal, VCProdIdProp)) != nullptr) { char *s = fakeCString(vObjectUStringZValue(curVO)); if (!s || strcmp(productId().toUtf8().constData(), s) != 0) { qCDebug(KCALCORE_LOG) << "This vCalendar file was not created by KOrganizer or" << "any other product we support. Loading anyway..."; } setLoadedProductId(QString::fromUtf8(s)); deleteStr(s); } // warn the user we might have trouble reading this unknown version. if ((curVO = isAPropertyOf(vcal, VCVersionProp)) != nullptr) { char *s = fakeCString(vObjectUStringZValue(curVO)); if (!s || strcmp(_VCAL_VERSION, s) != 0) { qCDebug(KCALCORE_LOG) << "This vCalendar file has version" << s << "We only support" << _VCAL_VERSION; } deleteStr(s); } // set the time zone (this is a property of the view, so just discard!) if ((curVO = isAPropertyOf(vcal, VCTimeZoneProp)) != nullptr) { char *s = fakeCString(vObjectUStringZValue(curVO)); QString ts = QString::fromUtf8(s); QString name = QStringLiteral("VCAL") + ts; deleteStr(s); // TODO: While using the timezone-offset + vcal as timezone is is // most likely unique, we should REALLY actually create something // like vcal-tzoffset-daylightoffsets, or better yet, // vcal-hash QStringList tzList; QString tz; int utcOffset; int utcOffsetDst; if (parseTZOffsetISO8601(ts, utcOffset)) { // qCDebug(KCALCORE_LOG) << "got standard offset" << ts << utcOffset; // standard from tz // starting date for now 01011900 QDateTime dt = QDateTime(QDateTime(QDate(1900, 1, 1), QTime(0, 0, 0))); tz = QStringLiteral("STD;%1;false;%2").arg(QString::number(utcOffset), dt.toString()); tzList.append(tz); // go through all the daylight tags initPropIterator(&i, vcal); while (moreIteration(&i)) { curVO = nextVObject(&i); if (strcmp(vObjectName(curVO), VCDayLightProp) == 0) { char *s = fakeCString(vObjectUStringZValue(curVO)); QString dst = QLatin1String(s); QStringList argl = dst.split(QLatin1Char(',')); deleteStr(s); // Too short -> not interesting if (argl.size() < 4) { continue; } // We don't care about the non-DST periods if (argl[0] != QLatin1String("TRUE")) { continue; } if (parseTZOffsetISO8601(argl[1], utcOffsetDst)) { // qCDebug(KCALCORE_LOG) << "got DST offset" << argl[1] << utcOffsetDst; // standard QString strEndDate = argl[3]; QDateTime endDate = ISOToQDateTime(strEndDate); // daylight QString strStartDate = argl[2]; QDateTime startDate = ISOToQDateTime(strStartDate); QString strRealEndDate = strEndDate; QString strRealStartDate = strStartDate; QDateTime realEndDate = endDate; QDateTime realStartDate = startDate; // if we get dates for some reason in wrong order, earlier is used for dst if (endDate < startDate) { strRealEndDate = strStartDate; strRealStartDate = strEndDate; realEndDate = startDate; realStartDate = endDate; } tz = QStringLiteral("%1;%2;false;%3"). arg(strRealEndDate, QString::number(utcOffset), realEndDate.toString()); tzList.append(tz); tz = QStringLiteral("%1;%2;true;%3"). arg(strRealStartDate, QString::number(utcOffsetDst), realStartDate.toString()); tzList.append(tz); } else { qCDebug(KCALCORE_LOG) << "unable to parse dst" << argl[1]; } } } ICalTimeZones *tzlist = d->mCalendar->timeZones(); ICalTimeZoneSource tzs; ICalTimeZone zone = tzs.parse(name, tzList, *tzlist); if (!zone.isValid()) { qCDebug(KCALCORE_LOG) << "zone is not valid, parsing error" << tzList; } else { previousZone = d->mCalendar->timeZone(); d->mCalendar->setTimeZoneId(name.toUtf8()); hasTimeZone = true; } } else { qCDebug(KCALCORE_LOG) << "unable to parse tzoffset" << ts; } } // Store all events with a relatedTo property in a list for post-processing d->mEventsRelate.clear(); d->mTodosRelate.clear(); initPropIterator(&i, vcal); // go through all the vobjects in the vcal while (moreIteration(&i)) { curVO = nextVObject(&i); /************************************************************************/ // now, check to see that the object is an event or todo. if (strcmp(vObjectName(curVO), VCEventProp) == 0) { if (!isAPropertyOf(curVO, VCDTstartProp) && !isAPropertyOf(curVO, VCDTendProp)) { qCDebug(KCALCORE_LOG) << "found a VEvent with no DTSTART and no DTEND! Skipping..."; goto SKIP; } anEvent = VEventToEvent(curVO); if (anEvent) { if (hasTimeZone && !anEvent->allDay() && anEvent->dtStart().timeZone() == QTimeZone::utc()) { //This sounds stupid but is how others are doing it, so here //we go. If there is a TZ in the VCALENDAR even if the dtStart //and dtend are in UTC, clients interpret it using also the TZ defined //in the Calendar. I know it sounds braindead but oh well - int utcOffSet = anEvent->dtStart().utcOffset(); + int utcOffSet = anEvent->dtStart().offsetFromUtc(); QDateTime dtStart(anEvent->dtStart().addSecs(utcOffSet)); dtStart.setTimeZone(d->mCalendar->timeZone()); QDateTime dtEnd(anEvent->dtEnd().addSecs(utcOffSet)); dtEnd.setTimeZone(d->mCalendar->timeZone()); anEvent->setDtStart(dtStart); anEvent->setDtEnd(dtEnd); } Event::Ptr old = !anEvent->hasRecurrenceId() ? d->mCalendar->event(anEvent->uid()) : d->mCalendar->event(anEvent->uid(), anEvent->recurrenceId()); if (old) { if (deleted) { d->mCalendar->deleteEvent(old); // move old to deleted removeAllVCal(d->mEventsRelate, old); } else if (anEvent->revision() > old->revision()) { d->mCalendar->deleteEvent(old); // move old to deleted removeAllVCal(d->mEventsRelate, old); d->mCalendar->addEvent(anEvent); // and replace it with this one } } else if (deleted) { old = !anEvent->hasRecurrenceId() ? d->mCalendar->deletedEvent(anEvent->uid()) : d->mCalendar->deletedEvent(anEvent->uid(), anEvent->recurrenceId()); if (!old) { d->mCalendar->addEvent(anEvent); // add this one d->mCalendar->deleteEvent(anEvent); // and move it to deleted } } else { d->mCalendar->addEvent(anEvent); // just add this one } } } else if (strcmp(vObjectName(curVO), VCTodoProp) == 0) { Todo::Ptr aTodo = VTodoToEvent(curVO); if (aTodo) { if (hasTimeZone && !aTodo->allDay() && aTodo->dtStart().timeZone() == QTimeZone::utc()) { //This sounds stupid but is how others are doing it, so here //we go. If there is a TZ in the VCALENDAR even if the dtStart //and dtend are in UTC, clients interpret it usint alse the TZ defined //in the Calendar. I know it sounds braindead but oh well - int utcOffSet = aTodo->dtStart().utcOffset(); + int utcOffSet = aTodo->dtStart().offsetFromUtc(); QDateTime dtStart(aTodo->dtStart().addSecs(utcOffSet)); dtStart.setTimeZone(d->mCalendar->timeZone()); aTodo->setDtStart(dtStart); if (aTodo->hasDueDate()) { QDateTime dtDue(aTodo->dtDue().addSecs(utcOffSet)); dtDue.setTimeZone(d->mCalendar->timeZone()); aTodo->setDtDue(dtDue); } } Todo::Ptr old = !aTodo->hasRecurrenceId() ? d->mCalendar->todo(aTodo->uid()) : d->mCalendar->todo(aTodo->uid(), aTodo->recurrenceId()); if (old) { if (deleted) { d->mCalendar->deleteTodo(old); // move old to deleted removeAllVCal(d->mTodosRelate, old); } else if (aTodo->revision() > old->revision()) { d->mCalendar->deleteTodo(old); // move old to deleted removeAllVCal(d->mTodosRelate, old); d->mCalendar->addTodo(aTodo); // and replace it with this one } } else if (deleted) { old = d->mCalendar->deletedTodo(aTodo->uid(), aTodo->recurrenceId()); if (!old) { d->mCalendar->addTodo(aTodo); // add this one d->mCalendar->deleteTodo(aTodo); // and move it to deleted } } else { d->mCalendar->addTodo(aTodo); // just add this one } } } else if ((strcmp(vObjectName(curVO), VCVersionProp) == 0) || (strcmp(vObjectName(curVO), VCProdIdProp) == 0) || (strcmp(vObjectName(curVO), VCTimeZoneProp) == 0)) { // do nothing, we know these properties and we want to skip them. // we have either already processed them or are ignoring them. ; } else if (strcmp(vObjectName(curVO), VCDayLightProp) == 0) { // do nothing daylights are already processed ; } else { qCDebug(KCALCORE_LOG) << "Ignoring unknown vObject \"" << vObjectName(curVO) << "\""; } SKIP: ; } // while // Post-Process list of events with relations, put Event objects in relation Event::List::ConstIterator eIt; for (eIt = d->mEventsRelate.constBegin(); eIt != d->mEventsRelate.constEnd(); ++eIt) { (*eIt)->setRelatedTo((*eIt)->relatedTo()); } Todo::List::ConstIterator tIt; for (tIt = d->mTodosRelate.constBegin(); tIt != d->mTodosRelate.constEnd(); ++tIt) { (*tIt)->setRelatedTo((*tIt)->relatedTo()); } //Now lets put the TZ back as it was if we have changed it. if (hasTimeZone) { d->mCalendar->setTimeZone(previousZone); } } int VCalFormat::numFromDay(const QString &day) { if (day == QLatin1String("MO ")) { return 0; } if (day == QLatin1String("TU ")) { return 1; } if (day == QLatin1String("WE ")) { return 2; } if (day == QLatin1String("TH ")) { return 3; } if (day == QLatin1String("FR ")) { return 4; } if (day == QLatin1String("SA ")) { return 5; } if (day == QLatin1String("SU ")) { return 6; } return -1; // something bad happened. :) } Attendee::PartStat VCalFormat::readStatus(const char *s) const { QString statStr = QString::fromUtf8(s); statStr = statStr.toUpper(); Attendee::PartStat status; if (statStr == QLatin1String("X-ACTION")) { status = Attendee::NeedsAction; } else if (statStr == QLatin1String("NEEDS ACTION")) { status = Attendee::NeedsAction; } else if (statStr == QLatin1String("ACCEPTED")) { status = Attendee::Accepted; } else if (statStr == QLatin1String("SENT")) { status = Attendee::NeedsAction; } else if (statStr == QLatin1String("TENTATIVE")) { status = Attendee::Tentative; } else if (statStr == QLatin1String("CONFIRMED")) { status = Attendee::Accepted; } else if (statStr == QLatin1String("DECLINED")) { status = Attendee::Declined; } else if (statStr == QLatin1String("COMPLETED")) { status = Attendee::Completed; } else if (statStr == QLatin1String("DELEGATED")) { status = Attendee::Delegated; } else { qCDebug(KCALCORE_LOG) << "error setting attendee mStatus, unknown mStatus!"; status = Attendee::NeedsAction; } return status; } QByteArray VCalFormat::writeStatus(Attendee::PartStat status) const { switch (status) { default: case Attendee::NeedsAction: return "NEEDS ACTION"; break; case Attendee::Accepted: return "ACCEPTED"; break; case Attendee::Declined: return "DECLINED"; break; case Attendee::Tentative: return "TENTATIVE"; break; case Attendee::Delegated: return "DELEGATED"; break; case Attendee::Completed: return "COMPLETED"; break; case Attendee::InProcess: return "NEEDS ACTION"; break; } } void VCalFormat::readCustomProperties(VObject *o, const Incidence::Ptr &i) { VObjectIterator iter; VObject *cur; const char *curname; char *s; initPropIterator(&iter, o); while (moreIteration(&iter)) { cur = nextVObject(&iter); curname = vObjectName(cur); Q_ASSERT(curname); if ((curname[0] == 'X' && curname[1] == '-') && strcmp(curname, ICOrganizerProp) != 0) { // TODO - for the time being, we ignore the parameters part // and just do the value handling here i->setNonKDECustomProperty( curname, QString::fromUtf8(s = fakeCString(vObjectUStringZValue(cur)))); deleteStr(s); } } } void VCalFormat::writeCustomProperties(VObject *o, const Incidence::Ptr &i) { const QMap custom = i->customProperties(); for (QMap::ConstIterator c = custom.begin(); c != custom.end(); ++c) { if (d->mManuallyWrittenExtensionFields.contains(c.key()) || c.key().startsWith("X-KDE-VOLATILE")) { //krazy:exclude=strings continue; } addPropValue(o, c.key().constData(), c.value().toUtf8().constData()); } } void VCalFormat::virtual_hook(int id, void *data) { Q_UNUSED(id); Q_UNUSED(data); Q_ASSERT(false); }