diff --git a/CMakeLists.txt b/CMakeLists.txt index e0359e1af..242894075 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,96 +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 ) -include(ConfigureChecks.cmake) - ########### 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) ########### 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/ConfigureChecks.cmake b/ConfigureChecks.cmake deleted file mode 100644 index 202ca8010..000000000 --- a/ConfigureChecks.cmake +++ /dev/null @@ -1,8 +0,0 @@ -include(CheckIncludeFiles) -include(CheckLibraryExists) - -check_include_files(uuid/uuid.h HAVE_UUID_UUID_H) -find_library(UUID_LIBRARY NAMES e2fs-uuid uuid) -if(UUID_LIBRARY) - check_library_exists("${UUID_LIBRARY}" uuid_generate_random "${LIB_INSTALL_DIR}" HAVE_UUID_LIBRARY) -endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e4d228292..285aa7445 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,146 +1,139 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-kcalcore.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kcalcore.h ) ########### next target ############### bison_target(VersitParser ${CMAKE_CURRENT_SOURCE_DIR}/versit/vcc.y ${CMAKE_CURRENT_BINARY_DIR}/vcc-parser.c ) set(libversit_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/versit/vobject.c ${BISON_VersitParser_OUTPUTS} ) set(kcalcore_LIB_SRCS ${libversit_SRCS} alarm.cpp attachment.cpp attendee.cpp calendar.cpp calfilter.cpp calformat.cpp calstorage.cpp compat.cpp customproperties.cpp duration.cpp event.cpp exceptions.cpp filestorage.cpp freebusy.cpp freebusycache.cpp freebusyperiod.cpp icalformat.cpp icalformat_p.cpp icaltimezones.cpp incidence.cpp incidencebase.cpp journal.cpp memorycalendar.cpp occurrenceiterator.cpp period.cpp person.cpp recurrence.cpp recurrencerule.cpp schedulemessage.cpp sorting.cpp todo.cpp utils.cpp vcalformat.cpp visitor.cpp ) ecm_qt_declare_logging_category(kcalcore_LIB_SRCS HEADER kcalcore_debug.h IDENTIFIER KCALCORE_LOG CATEGORY_NAME org.kde.pim.kcalcore) add_library(KF5CalendarCore ${kcalcore_LIB_SRCS}) generate_export_header(KF5CalendarCore BASE_NAME kcalcore) add_library(KF5::CalendarCore ALIAS KF5CalendarCore) target_include_directories(KF5CalendarCore INTERFACE "$") target_include_directories(KF5CalendarCore PUBLIC "$") target_include_directories(KF5CalendarCore PUBLIC "$") target_link_libraries(KF5CalendarCore PUBLIC Qt5::Core PRIVATE KF5::KDELibs4Support ${LibIcal_LIBRARIES} ) -if(HAVE_UUID_LIBRARY) - target_link_libraries(KF5CalendarCore -PRIVATE - ${UUID_LIBRARY} - ) -endif() - set_target_properties(KF5CalendarCore PROPERTIES VERSION ${KCALCORE_VERSION_STRING} SOVERSION ${KCALCORE_SOVERSION} EXPORT_NAME CalendarCore ) install(TARGETS KF5CalendarCore EXPORT KF5CalendarCoreTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) ########### Generate Headers ############### ecm_generate_headers(KCalCore_CamelCase_HEADERS HEADER_NAMES Alarm Attachment Attendee CalFilter CalFormat CalStorage Calendar CustomProperties Duration Event Exceptions # NOTE: Used to be called 'Exception' in KDE4 FileStorage FreeBusy FreeBusyCache FreeBusyPeriod ICalFormat Incidence IncidenceBase Journal MemoryCalendar OccurrenceIterator Period Person Recurrence RecurrenceRule ScheduleMessage SortableList Sorting Todo VCalFormat Visitor PREFIX KCalCore REQUIRED_HEADERS KCalCore_HEADERS ) ########### install files ############### install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kcalcore_export.h ${KCalCore_HEADERS} supertrait.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KCalCore/kcalcore COMPONENT Devel ) install(FILES ${KCalCore_CamelCase_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KCalCore/KCalCore COMPONENT Devel ) ecm_generate_pri_file(BASE_NAME KCalCore LIB_NAME KF5CalendarCore DEPS "Core" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KCalCore/KCalCore) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/calformat.cpp b/src/calformat.cpp index ed529d42a..7da209838 100644 --- a/src/calformat.cpp +++ b/src/calformat.cpp @@ -1,148 +1,129 @@ /* This file is part of the kcalcore library. 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 CalFormat base class. @brief Base class providing an interface to various calendar formats. @author Cornelius Schumacher \ */ #include #include "calformat.h" #include "exceptions.h" -#if defined(HAVE_UUID_UUID_H) -#include -#else -#include -#endif +#include using namespace KCalCore; /** Private class that helps to provide binary compatibility between releases. @internal */ //@cond PRIVATE class Q_DECL_HIDDEN KCalCore::CalFormat::Private { public: Private() : mException(nullptr) {} ~Private() { delete mException; } static QString mApplication; // Name of application, for creating unique ID strings static QString mProductId; // PRODID string to write to calendar files QString mLoadedProductId; // PRODID string loaded from calendar file Exception *mException = nullptr; }; QString CalFormat::Private::mApplication = QStringLiteral("libkcal"); QString CalFormat::Private::mProductId = QStringLiteral("-//K Desktop Environment//NONSGML libkcal 4.3//EN"); //@endcond CalFormat::CalFormat() : d(new KCalCore::CalFormat::Private) { } CalFormat::~CalFormat() { clearException(); delete d; } void CalFormat::clearException() { delete d->mException; d->mException = nullptr; } void CalFormat::setException(Exception *exception) { delete d->mException; d->mException = exception; } Exception *CalFormat::exception() const { return d->mException; } void CalFormat::setApplication(const QString &application, const QString &productID) { Private::mApplication = application; Private::mProductId = productID; } const QString &CalFormat::application() { return Private::mApplication; } const QString &CalFormat::productId() { return Private::mProductId; } QString CalFormat::loadedProductId() { return d->mLoadedProductId; } void CalFormat::setLoadedProductId(const QString &id) { d->mLoadedProductId = id; } QString CalFormat::createUniqueId() { -#if defined(HAVE_UUID_UUID_H) - uuid_t uuid; - char suuid[64]; - - uuid_generate_random(uuid); - uuid_unparse(uuid, suuid); - return QString::fromLatin1(suuid); -#else - const QTime now = QTime::currentTime(); - int hashTime = now.hour() + now.minute() + now.second() + now.msec(); - QString uidStr = QStringLiteral("%1-%2.%3"). - arg(Private::mApplication). - arg(KRandom::random()). - arg(hashTime); - return uidStr; -#endif + return QUuid::createUuid().toString().mid(1, 36); } void CalFormat::virtual_hook(int id, void *data) { Q_UNUSED(id); Q_UNUSED(data); Q_ASSERT(false); } diff --git a/src/config-kcalcore.h.cmake b/src/config-kcalcore.h.cmake index 89f792dc4..5c98f84de 100644 --- a/src/config-kcalcore.h.cmake +++ b/src/config-kcalcore.h.cmake @@ -1,8 +1,5 @@ -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_UUID_UUID_H - /* Define to 1 if the libical version is equal or greater than 0.46 */ #cmakedefine USE_ICAL_0_46 /* Define to 1 if the libical version is equal or greater than 1.0 */ #cmakedefine USE_ICAL_1_0 diff --git a/src/icaltimezones.cpp b/src/icaltimezones.cpp index 17d4870ba..e07d42943 100644 --- a/src/icaltimezones.cpp +++ b/src/icaltimezones.cpp @@ -1,1270 +1,1266 @@ /* 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 #include "icaltimezones.h" #include "icalformat.h" #include "icalformat_p.h" #include "recurrence.h" #include "recurrencerule.h" #include "utils.h" #include "kcalcore_debug.h" #include #include #include #include #include extern "C" { #include #include } -#if defined(HAVE_UUID_UUID_H) -#include -#endif - using namespace KCalCore; // Minimum repetition counts for VTIMEZONE RRULEs static const int minRuleCount = 5; // for any RRULE static const int minPhaseCount = 8; // for separate STANDARD/DAYLIGHT component // Convert an ical time to QDateTime, preserving the UTC indicator static QDateTime toQDateTime(const icaltimetype &t) { return QDateTime(QDate(t.year, t.month, t.day), QTime(t.hour, t.minute, t.second), (t.is_utc ? Qt::UTC : Qt::LocalTime)); } // Maximum date for time zone data. // It's not sensible to try to predict them very far in advance, because // they can easily change. Plus, it limits the processing required. static QDateTime MAX_DATE() { static QDateTime dt; if (!dt.isValid()) { dt = QDateTime(QDate::currentDate().addYears(20), QTime(0, 0, 0)); } return dt; } static icaltimetype writeLocalICalDateTime(const QDateTime &utc, int offset) { const QDateTime local = utc.addSecs(offset); icaltimetype t = icaltime_null_time(); t.year = local.date().year(); t.month = local.date().month(); t.day = local.date().day(); t.hour = local.time().hour(); t.minute = local.time().minute(); t.second = local.time().second(); t.is_date = 0; t.zone = nullptr; t.is_utc = 0; return t; } namespace KCalCore { /******************************************************************************/ //@cond PRIVATE class ICalTimeZonesPrivate { public: ICalTimeZonesPrivate() {} ICalTimeZones::ZoneMap zones; }; //@endcond ICalTimeZones::ICalTimeZones() : d(new ICalTimeZonesPrivate) { } ICalTimeZones::ICalTimeZones(const ICalTimeZones &rhs) : d(new ICalTimeZonesPrivate()) { d->zones = rhs.d->zones; } ICalTimeZones &ICalTimeZones::operator=(const ICalTimeZones &rhs) { // check for self assignment if (&rhs == this) { return *this; } d->zones = rhs.d->zones; return *this; } ICalTimeZones::~ICalTimeZones() { delete d; } const ICalTimeZones::ZoneMap ICalTimeZones::zones() const { return d->zones; } bool ICalTimeZones::add(const ICalTimeZone &zone) { if (!zone.isValid()) { return false; } if (d->zones.constFind(zone.name()) != d->zones.cend()) { return false; // name already exists } d->zones.insert(zone.name(), zone); return true; } ICalTimeZone ICalTimeZones::remove(const ICalTimeZone &zone) { if (zone.isValid()) { for (ZoneMap::Iterator it = d->zones.begin(), end = d->zones.end(); it != end; ++it) { if (it.value() == zone) { d->zones.erase(it); return (zone == ICalTimeZone::utc()) ? ICalTimeZone() : zone; } } } return ICalTimeZone(); } ICalTimeZone ICalTimeZones::remove(const QString &name) { if (!name.isEmpty()) { ZoneMap::Iterator it = d->zones.find(name); if (it != d->zones.end()) { const ICalTimeZone zone = it.value(); d->zones.erase(it); return (zone == ICalTimeZone::utc()) ? ICalTimeZone() : zone; } } return ICalTimeZone(); } void ICalTimeZones::clear() { d->zones.clear(); } int ICalTimeZones::count() { return d->zones.count(); } ICalTimeZone ICalTimeZones::zone(const QString &name) const { if (!name.isEmpty()) { ZoneMap::ConstIterator it = d->zones.constFind(name); if (it != d->zones.constEnd()) { return it.value(); } } return ICalTimeZone(); // error } ICalTimeZone ICalTimeZones::zone(const ICalTimeZone &zone) const { if (zone.isValid()) { QMapIterator it(d->zones); while (it.hasNext()) { it.next(); const ICalTimeZone tz = it.value(); const QList list1 = tz.transitions(); const QList list2 = zone.transitions(); if (list1.size() == list2.size()) { int i = 0; int matches = 0; for (; i < list1.size(); ++i) { const KTimeZone::Transition t1 = list1[ i ]; const KTimeZone::Transition t2 = list2[ i ]; if ((t1.time() == t2.time()) && (t1.phase().utcOffset() == t2.phase().utcOffset()) && (t1.phase().isDst() == t2.phase().isDst())) { matches++; } } if (matches == i) { // Existing zone has all the transitions of the given zone. return tz; } } } } return ICalTimeZone(); // not found } /******************************************************************************/ ICalTimeZoneBackend::ICalTimeZoneBackend() : KTimeZoneBackend() {} ICalTimeZoneBackend::ICalTimeZoneBackend(ICalTimeZoneSource *source, const QString &name, const QString &countryCode, float latitude, float longitude, const QString &comment) : KTimeZoneBackend(source, name, countryCode, latitude, longitude, comment) {} ICalTimeZoneBackend::ICalTimeZoneBackend(const KTimeZone &tz, const QDate &earliest) : KTimeZoneBackend(nullptr, tz.name(), tz.countryCode(), tz.latitude(), tz.longitude(), tz.comment()) { Q_UNUSED(earliest); } ICalTimeZoneBackend::~ICalTimeZoneBackend() {} KTimeZoneBackend *ICalTimeZoneBackend::clone() const { return new ICalTimeZoneBackend(*this); } QByteArray ICalTimeZoneBackend::type() const { return "ICalTimeZone"; } bool ICalTimeZoneBackend::hasTransitions(const KTimeZone *caller) const { Q_UNUSED(caller); return true; } void ICalTimeZoneBackend::virtual_hook(int id, void *data) { Q_UNUSED(id); Q_UNUSED(data); } /******************************************************************************/ ICalTimeZone::ICalTimeZone() : KTimeZone(new ICalTimeZoneBackend()) {} ICalTimeZone::ICalTimeZone(ICalTimeZoneSource *source, const QString &name, ICalTimeZoneData *data) : KTimeZone(new ICalTimeZoneBackend(source, name)) { setData(data); } ICalTimeZone::ICalTimeZone(const KTimeZone &tz, const QDate &earliest) : KTimeZone(new ICalTimeZoneBackend(nullptr, tz.name(), tz.countryCode(), tz.latitude(), tz.longitude(), tz.comment())) { const KTimeZoneData *data = tz.data(true); if (data) { const ICalTimeZoneData *icaldata = dynamic_cast(data); if (icaldata) { setData(new ICalTimeZoneData(*icaldata)); } else { setData(new ICalTimeZoneData(*data, tz, earliest)); } } } ICalTimeZone::~ICalTimeZone() {} QString ICalTimeZone::city() const { const ICalTimeZoneData *dat = static_cast(data()); return dat ? dat->city() : QString(); } QByteArray ICalTimeZone::url() const { const ICalTimeZoneData *dat = static_cast(data()); return dat ? dat->url() : QByteArray(); } QDateTime ICalTimeZone::lastModified() const { const ICalTimeZoneData *dat = static_cast(data()); return dat ? dat->lastModified() : QDateTime(); } QByteArray ICalTimeZone::vtimezone() const { const ICalTimeZoneData *dat = static_cast(data()); return dat ? dat->vtimezone() : QByteArray(); } icaltimezone *ICalTimeZone::icalTimezone() const { const ICalTimeZoneData *dat = static_cast(data()); return dat ? dat->icalTimezone() : nullptr; } bool ICalTimeZone::update(const ICalTimeZone &other) { if (!updateBase(other)) { return false; } KTimeZoneData *otherData = other.data() ? other.data()->clone() : nullptr; setData(otherData, other.source()); return true; } ICalTimeZone ICalTimeZone::utc() { static ICalTimeZone utcZone; if (!utcZone.isValid()) { ICalTimeZoneSource tzs; utcZone = tzs.parse(icaltimezone_get_utc_timezone()); } return utcZone; } void ICalTimeZone::virtual_hook(int id, void *data) { Q_UNUSED(id); Q_UNUSED(data); } /******************************************************************************/ //@cond PRIVATE class ICalTimeZoneDataPrivate { public: ICalTimeZoneDataPrivate() : icalComponent(nullptr) {} ~ICalTimeZoneDataPrivate() { if (icalComponent) { icalcomponent_free(icalComponent); } } icalcomponent *component() const { return icalComponent; } void setComponent(icalcomponent *c) { if (icalComponent) { icalcomponent_free(icalComponent); } icalComponent = c; } QString location; // name of city for this time zone QByteArray url; // URL of published VTIMEZONE definition (optional) QDateTime lastModified; // time of last modification of the VTIMEZONE component (optional) private: icalcomponent *icalComponent; // ical component representing this time zone }; //@endcond ICalTimeZoneData::ICalTimeZoneData() : d(new ICalTimeZoneDataPrivate()) { } ICalTimeZoneData::ICalTimeZoneData(const ICalTimeZoneData &rhs) : KTimeZoneData(rhs), d(new ICalTimeZoneDataPrivate()) { d->location = rhs.d->location; d->url = rhs.d->url; d->lastModified = rhs.d->lastModified; d->setComponent(icalcomponent_new_clone(rhs.d->component())); } ICalTimeZoneData::ICalTimeZoneData(const KTimeZoneData &rhs, const KTimeZone &tz, const QDate &earliest) : KTimeZoneData(rhs), d(new ICalTimeZoneDataPrivate()) { // VTIMEZONE RRULE types enum { DAY_OF_MONTH = 0x01, WEEKDAY_OF_MONTH = 0x02, LAST_WEEKDAY_OF_MONTH = 0x04 }; if (tz.type() == "KSystemTimeZone") { // Try to fetch a system time zone in preference, on the grounds // that system time zones are more likely to be up to date than // built-in libical ones. icalcomponent *c = nullptr; const KTimeZone ktz = KSystemTimeZones::readZone(tz.name()); if (ktz.isValid()) { if (ktz.data(true)) { const ICalTimeZone icaltz(ktz, earliest); icaltimezone *itz = icaltz.icalTimezone(); if (itz) { c = icalcomponent_new_clone(icaltimezone_get_component(itz)); icaltimezone_free(itz, 1); } } } if (!c) { // Try to fetch a built-in libical time zone. icaltimezone *itz = icaltimezone_get_builtin_timezone(tz.name().toUtf8().constData()); c = icalcomponent_new_clone(icaltimezone_get_component(itz)); } if (c) { // TZID in built-in libical time zones has a standard prefix. // To make the VTIMEZONE TZID match TZID references in incidences // (as required by RFC2445), strip off the prefix. icalproperty *prop = icalcomponent_get_first_property(c, ICAL_TZID_PROPERTY); if (prop) { icalvalue *value = icalproperty_get_value(prop); const char *tzid = icalvalue_get_text(value); const QByteArray icalprefix = ICalTimeZoneSource::icalTzidPrefix(); const int len = icalprefix.size(); if (!strncmp(icalprefix.constData(), tzid, len)) { const char *s = strchr(tzid + len, '/'); // find third '/' if (s) { const QByteArray tzidShort(s + 1); // deep copy (needed by icalvalue_set_text()) icalvalue_set_text(value, tzidShort.constData()); // Remove the X-LIC-LOCATION property, which is only used by libical prop = icalcomponent_get_first_property(c, ICAL_X_PROPERTY); const char *xname = icalproperty_get_x_name(prop); if (xname && !strcmp(xname, "X-LIC-LOCATION")) { icalcomponent_remove_property(c, prop); icalproperty_free(prop); } } } } } d->setComponent(c); } else { // Write the time zone data into an iCal component icalcomponent *tzcomp = icalcomponent_new(ICAL_VTIMEZONE_COMPONENT); icalcomponent_add_property(tzcomp, icalproperty_new_tzid(tz.name().toUtf8().constData())); // icalcomponent_add_property(tzcomp, icalproperty_new_location( tz.name().toUtf8() )); // Compile an ordered list of transitions so that we can know the phases // which occur before and after each transition. QList transits = transitions(); if (transits.isEmpty()) { // If there is no way to compile a complete list of transitions // transitions() can return an empty list // In that case try get one transition to write a valid VTIMEZONE entry. if (transits.isEmpty()) { qCDebug(KCALCORE_LOG) << "No transition information available VTIMEZONE will be invalid."; } } if (earliest.isValid()) { // Remove all transitions earlier than those we are interested in for (int i = 0, end = transits.count(); i < end; ++i) { if (transits.at(i).time().date() >= earliest) { if (i > 0) { transits.erase(transits.begin(), transits.begin() + i); } break; } } } int trcount = transits.count(); QVector transitionsDone(trcount); transitionsDone.fill(false); // Go through the list of transitions and create an iCal component for each // distinct combination of phase after and UTC offset before the transition. icaldatetimeperiodtype dtperiod; dtperiod.period = icalperiodtype_null_period(); for (;;) { int i = 0; for (; i < trcount && transitionsDone[i]; ++i) { ; } if (i >= trcount) { break; } // Found a phase combination which hasn't yet been processed const int preOffset = (i > 0) ? transits.at(i - 1).phase().utcOffset() : rhs.previousUtcOffset(); const KTimeZone::Phase phase = transits.at(i).phase(); if (phase.utcOffset() == preOffset) { transitionsDone[i] = true; while (++i < trcount) { if (transitionsDone[i] || transits.at(i).phase() != phase || transits.at(i - 1).phase().utcOffset() != preOffset) { continue; } transitionsDone[i] = true; } continue; } icalcomponent *phaseComp = icalcomponent_new(phase.isDst() ? ICAL_XDAYLIGHT_COMPONENT : ICAL_XSTANDARD_COMPONENT); const QList abbrevs = phase.abbreviations(); for (int a = 0, aend = abbrevs.count(); a < aend; ++a) { icalcomponent_add_property(phaseComp, icalproperty_new_tzname( static_cast(abbrevs[a].constData()))); } if (!phase.comment().isEmpty()) { icalcomponent_add_property(phaseComp, icalproperty_new_comment(phase.comment().toUtf8().constData())); } icalcomponent_add_property(phaseComp, icalproperty_new_tzoffsetfrom(preOffset)); icalcomponent_add_property(phaseComp, icalproperty_new_tzoffsetto(phase.utcOffset())); // Create a component to hold initial RRULE if any, plus all RDATEs icalcomponent *phaseComp1 = icalcomponent_new_clone(phaseComp); icalcomponent_add_property(phaseComp1, icalproperty_new_dtstart( writeLocalICalDateTime(transits.at(i).time(), preOffset))); bool useNewRRULE = false; // Compile the list of UTC transition dates/times, and check // if the list can be reduced to an RRULE instead of multiple RDATEs. QTime time; QDate date; int year = 0, month = 0, daysInMonth = 0, dayOfMonth = 0; // avoid compiler warnings int dayOfWeek = 0; // Monday = 1 int nthFromStart = 0; // nth (weekday) of month int nthFromEnd = 0; // nth last (weekday) of month int newRule; int rule = 0; QList rdates;// dates which (probably) need to be written as RDATEs QList times; QDateTime qdt = transits.at(i).time(); // set 'qdt' for start of loop times += qdt; transitionsDone[i] = true; do { if (!rule) { // Initialise data for detecting a new rule rule = DAY_OF_MONTH | WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH; time = qdt.time(); date = qdt.date(); year = date.year(); month = date.month(); daysInMonth = date.daysInMonth(); dayOfWeek = date.dayOfWeek(); // Monday = 1 dayOfMonth = date.day(); nthFromStart = (dayOfMonth - 1) / 7 + 1; // nth (weekday) of month nthFromEnd = (daysInMonth - dayOfMonth) / 7 + 1; // nth last (weekday) of month } if (++i >= trcount) { newRule = 0; times += QDateTime(); // append a dummy value since last value in list is ignored } else { if (transitionsDone[i] || transits.at(i).phase() != phase || transits.at(i - 1).phase().utcOffset() != preOffset) { continue; } transitionsDone[i] = true; qdt = transits.at(i).time(); if (!qdt.isValid()) { continue; } newRule = rule; times += qdt; date = qdt.date(); if (qdt.time() != time || date.month() != month || date.year() != ++year) { newRule = 0; } else { const int day = date.day(); if ((newRule & DAY_OF_MONTH) && day != dayOfMonth) { newRule &= ~DAY_OF_MONTH; } if (newRule & (WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH)) { if (date.dayOfWeek() != dayOfWeek) { newRule &= ~(WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH); } else { if ((newRule & WEEKDAY_OF_MONTH) && (day - 1) / 7 + 1 != nthFromStart) { newRule &= ~WEEKDAY_OF_MONTH; } if ((newRule & LAST_WEEKDAY_OF_MONTH) && (daysInMonth - day) / 7 + 1 != nthFromEnd) { newRule &= ~LAST_WEEKDAY_OF_MONTH; } } } } } if (!newRule) { // The previous rule (if any) no longer applies. // Write all the times up to but not including the current one. // First check whether any of the last RDATE values fit this rule. int yr = times[0].date().year(); while (!rdates.isEmpty()) { qdt = rdates.last(); date = qdt.date(); if (qdt.time() != time || date.month() != month || date.year() != --yr) { break; } const int day = date.day(); if (rule & DAY_OF_MONTH) { if (day != dayOfMonth) { break; } } else { if (date.dayOfWeek() != dayOfWeek || ((rule & WEEKDAY_OF_MONTH) && (day - 1) / 7 + 1 != nthFromStart) || ((rule & LAST_WEEKDAY_OF_MONTH) && (daysInMonth - day) / 7 + 1 != nthFromEnd)) { break; } } times.prepend(qdt); rdates.pop_back(); } if (times.count() > (useNewRRULE ? minPhaseCount : minRuleCount)) { // There are enough dates to combine into an RRULE icalrecurrencetype r; icalrecurrencetype_clear(&r); r.freq = ICAL_YEARLY_RECURRENCE; r.count = (year >= 2030) ? 0 : times.count() - 1; r.by_month[0] = month; if (rule & DAY_OF_MONTH) { r.by_month_day[0] = dayOfMonth; } else if (rule & WEEKDAY_OF_MONTH) { r.by_day[0] = (dayOfWeek % 7 + 1) + (nthFromStart * 8); // Sunday = 1 } else if (rule & LAST_WEEKDAY_OF_MONTH) { r.by_day[0] = -(dayOfWeek % 7 + 1) - (nthFromEnd * 8); // Sunday = 1 } r.until = writeLocalICalDateTime(times.takeAt(times.size() - 1), preOffset); icalproperty *prop = icalproperty_new_rrule(r); if (useNewRRULE) { // This RRULE doesn't start from the phase start date, so set it into // a new STANDARD/DAYLIGHT component in the VTIMEZONE. icalcomponent *c = icalcomponent_new_clone(phaseComp); icalcomponent_add_property( c, icalproperty_new_dtstart(writeLocalICalDateTime(times[0], preOffset))); icalcomponent_add_property(c, prop); icalcomponent_add_component(tzcomp, c); } else { icalcomponent_add_property(phaseComp1, prop); } } else { // Save dates for writing as RDATEs for (int t = 0, tend = times.count() - 1; t < tend; ++t) { rdates += times[t]; } } useNewRRULE = true; // All date/time values but the last have been added to the VTIMEZONE. // Remove them from the list. qdt = times.last(); // set 'qdt' for start of loop times.clear(); times += qdt; } rule = newRule; } while (i < trcount); // Write remaining dates as RDATEs for (int rd = 0, rdend = rdates.count(); rd < rdend; ++rd) { dtperiod.time = writeLocalICalDateTime(rdates[rd], preOffset); icalcomponent_add_property(phaseComp1, icalproperty_new_rdate(dtperiod)); } icalcomponent_add_component(tzcomp, phaseComp1); icalcomponent_free(phaseComp); } d->setComponent(tzcomp); } } ICalTimeZoneData::~ICalTimeZoneData() { delete d; } ICalTimeZoneData &ICalTimeZoneData::operator=(const ICalTimeZoneData &rhs) { // check for self assignment if (&rhs == this) { return *this; } KTimeZoneData::operator=(rhs); d->location = rhs.d->location; d->url = rhs.d->url; d->lastModified = rhs.d->lastModified; d->setComponent(icalcomponent_new_clone(rhs.d->component())); return *this; } KTimeZoneData *ICalTimeZoneData::clone() const { return new ICalTimeZoneData(*this); } QString ICalTimeZoneData::city() const { return d->location; } QByteArray ICalTimeZoneData::url() const { return d->url; } QDateTime ICalTimeZoneData::lastModified() const { return d->lastModified; } QByteArray ICalTimeZoneData::vtimezone() const { const QByteArray result(icalcomponent_as_ical_string(d->component())); icalmemory_free_ring(); return result; } icaltimezone *ICalTimeZoneData::icalTimezone() const { icaltimezone *icaltz = icaltimezone_new(); if (!icaltz) { return nullptr; } icalcomponent *c = icalcomponent_new_clone(d->component()); if (!icaltimezone_set_component(icaltz, c)) { icalcomponent_free(c); icaltimezone_free(icaltz, 1); return nullptr; } return icaltz; } bool ICalTimeZoneData::hasTransitions() const { return true; } void ICalTimeZoneData::virtual_hook(int id, void *data) { Q_UNUSED(id); Q_UNUSED(data); } /******************************************************************************/ //@cond PRIVATE class ICalTimeZoneSourcePrivate { public: static QList parsePhase(icalcomponent *, bool daylight, int &prevOffset, KTimeZone::Phase &); static QByteArray icalTzidPrefix; }; QByteArray ICalTimeZoneSourcePrivate::icalTzidPrefix; //@endcond ICalTimeZoneSource::ICalTimeZoneSource() : KTimeZoneSource(false), d(nullptr) { Q_UNUSED(d); } ICalTimeZoneSource::~ICalTimeZoneSource() { } bool ICalTimeZoneSource::parse(const QString &fileName, ICalTimeZones &zones) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { return false; } QTextStream ts(&file); ts.setCodec("ISO 8859-1"); const QByteArray text = ts.readAll().trimmed().toLatin1(); file.close(); bool result = false; icalcomponent *calendar = icalcomponent_new_from_string(text.data()); if (calendar) { if (icalcomponent_isa(calendar) == ICAL_VCALENDAR_COMPONENT) { result = parse(calendar, zones); } icalcomponent_free(calendar); } return result; } bool ICalTimeZoneSource::parse(icalcomponent *calendar, ICalTimeZones &zones) { for (icalcomponent *c = icalcomponent_get_first_component(calendar, ICAL_VTIMEZONE_COMPONENT); c; c = icalcomponent_get_next_component(calendar, ICAL_VTIMEZONE_COMPONENT)) { const ICalTimeZone zone = parse(c); if (!zone.isValid()) { return false; } ICalTimeZone oldzone = zones.zone(zone.name()); if (oldzone.isValid()) { // The zone already exists in the collection, so update the definition // of the zone rather than using a newly created one. oldzone.update(zone); } else if (!zones.add(zone)) { return false; } } return true; } ICalTimeZone ICalTimeZoneSource::parse(icalcomponent *vtimezone) { QString name; QString xlocation; ICalTimeZoneData *data = new ICalTimeZoneData(); // Read the fixed properties which can only appear once in VTIMEZONE icalproperty *p = icalcomponent_get_first_property(vtimezone, ICAL_ANY_PROPERTY); while (p) { icalproperty_kind kind = icalproperty_isa(p); switch (kind) { case ICAL_TZID_PROPERTY: name = QString::fromUtf8(icalproperty_get_tzid(p)); break; case ICAL_TZURL_PROPERTY: data->d->url = icalproperty_get_tzurl(p); break; case ICAL_LOCATION_PROPERTY: // This isn't mentioned in RFC2445, but libical reads it ... data->d->location = QString::fromUtf8(icalproperty_get_location(p)); break; case ICAL_X_PROPERTY: { // use X-LIC-LOCATION if LOCATION is missing const char *xname = icalproperty_get_x_name(p); if (xname && !strcmp(xname, "X-LIC-LOCATION")) { xlocation = QString::fromUtf8(icalproperty_get_x(p)); } break; } case ICAL_LASTMODIFIED_PROPERTY: { const icaltimetype t = icalproperty_get_lastmodified(p); if (t.is_utc) { data->d->lastModified = toQDateTime(t); } else { qCDebug(KCALCORE_LOG) << "LAST-MODIFIED not UTC"; } break; } default: break; } p = icalcomponent_get_next_property(vtimezone, ICAL_ANY_PROPERTY); } if (name.isEmpty()) { qCDebug(KCALCORE_LOG) << "TZID missing"; delete data; return ICalTimeZone(); } if (data->d->location.isEmpty() && !xlocation.isEmpty()) { data->d->location = xlocation; } const QString prefix = QString::fromUtf8(icalTzidPrefix()); if (name.startsWith(prefix)) { // Remove the prefix from libical built in time zone TZID const int i = name.indexOf(QLatin1Char('/'), prefix.length()); if (i > 0) { name = name.mid(i + 1); } } //qCDebug(KCALCORE_LOG) << "---zoneId: \"" << name << '"'; /* * Iterate through all time zone rules for this VTIMEZONE, * and create a Phase object containing details for each one. */ int prevOffset = 0; QList transitions; QDateTime earliest; QList phases; for (icalcomponent *c = icalcomponent_get_first_component(vtimezone, ICAL_ANY_COMPONENT); c; c = icalcomponent_get_next_component(vtimezone, ICAL_ANY_COMPONENT)) { int prevoff = 0; KTimeZone::Phase phase; QList times; icalcomponent_kind kind = icalcomponent_isa(c); switch (kind) { case ICAL_XSTANDARD_COMPONENT: //qCDebug(KCALCORE_LOG) << "---standard phase: found"; times = ICalTimeZoneSourcePrivate::parsePhase(c, false, prevoff, phase); break; case ICAL_XDAYLIGHT_COMPONENT: //qCDebug(KCALCORE_LOG) << "---daylight phase: found"; times = ICalTimeZoneSourcePrivate::parsePhase(c, true, prevoff, phase); break; default: qCDebug(KCALCORE_LOG) << "Unknown component:" << int(kind); break; } const int tcount = times.count(); if (tcount) { phases += phase; transitions.reserve(tcount); for (int t = 0; t < tcount; ++t) { transitions += KTimeZone::Transition(times[t], phase); } if (!earliest.isValid() || times[0] < earliest) { prevOffset = prevoff; earliest = times[0]; } } } // Set phases used by the time zone, but note that VTIMEZONE doesn't contain // time zone abbreviation before first transition. data->setPhases(phases, prevOffset); // Remove any "duplicate" transitions, i.e. those where two consecutive // transitions have the same phase. std::sort(transitions.begin(), transitions.end()); for (int t = 1, tend = transitions.count(); t < tend;) { if (transitions[t].phase() == transitions[t - 1].phase()) { transitions.removeAt(t); --tend; } else { ++t; } } data->setTransitions(transitions); data->d->setComponent(icalcomponent_new_clone(vtimezone)); //qCDebug(KCALCORE_LOG) << "VTIMEZONE" << name; return ICalTimeZone(this, name, data); } ICalTimeZone ICalTimeZoneSource::parse(const QString &name, const QStringList &tzList, ICalTimeZones &zones) { const ICalTimeZone zone = parse(name, tzList); if (!zone.isValid()) { return ICalTimeZone(); // error } ICalTimeZone oldzone = zones.zone(zone); // First off see if the zone is same as oldzone - _exactly_ same if (oldzone.isValid()) { return oldzone; } oldzone = zones.zone(name); if (oldzone.isValid()) { // The zone already exists, so update oldzone.update(zone); return zone; } else if (zones.add(zone)) { // No similar zone, add and return new one. return zone; } return ICalTimeZone(); // error } ICalTimeZone ICalTimeZoneSource::parse(const QString &name, const QStringList &tzList) { ICalTimeZoneData kdata; QList phases; QList transitions; bool daylight; for (QStringList::ConstIterator it = tzList.begin(); it != tzList.end(); ++it) { QString value = *it; daylight = false; const QString tzName = value.mid(0, value.indexOf(QLatin1Char(';'))); value = value.mid((value.indexOf(QLatin1Char(';')) + 1)); const QString tzOffset = value.mid(0, value.indexOf(QLatin1Char(';'))); value = value.mid((value.indexOf(QLatin1Char(';')) + 1)); const QString tzDaylight = value.mid(0, value.indexOf(QLatin1Char(';'))); const KDateTime tzDate = KDateTime::fromString(value.mid((value.lastIndexOf(QLatin1Char(';')) + 1))); if (tzDaylight == QLatin1String("true")) { daylight = true; } const KTimeZone::Phase tzPhase( tzOffset.toInt(), QByteArray(tzName.toLatin1()), daylight, QStringLiteral("VCAL_TZ_INFORMATION")); phases += tzPhase; transitions += KTimeZone::Transition(tzDate.dateTime(), tzPhase); } kdata.setPhases(phases, 0); std::sort(transitions.begin(), transitions.end()); kdata.setTransitions(transitions); ICalTimeZoneData *idata = new ICalTimeZoneData(kdata, KTimeZone(name), QDate()); return ICalTimeZone(this, name, idata); } ICalTimeZone ICalTimeZoneSource::parse(icaltimezone *tz) { /* Parse the VTIMEZONE component stored in the icaltimezone structure. * This is both easier and provides more complete information than * extracting already parsed data from icaltimezone. */ return tz ? parse(icaltimezone_get_component(tz)) : ICalTimeZone(); } //@cond PRIVATE QList ICalTimeZoneSourcePrivate::parsePhase(icalcomponent *c, bool daylight, int &prevOffset, KTimeZone::Phase &phase) { QList transitions; // Read the observance data for this standard/daylight savings phase QList abbrevs; QString comment; prevOffset = 0; int utcOffset = 0; bool recurs = false; bool found_dtstart = false; bool found_tzoffsetfrom = false; bool found_tzoffsetto = false; icaltimetype dtstart = icaltime_null_time(); // Now do the ical reading. icalproperty *p = icalcomponent_get_first_property(c, ICAL_ANY_PROPERTY); while (p) { icalproperty_kind kind = icalproperty_isa(p); switch (kind) { case ICAL_TZNAME_PROPERTY: { // abbreviated name for this time offset // TZNAME can appear multiple times in order to provide language // translations of the time zone offset name. // TODO: Does this cope with multiple language specifications? QByteArray name = icalproperty_get_tzname(p); // Outlook (2000) places "Standard Time" and "Daylight Time" in the TZNAME // strings, which is totally useless. So ignore those. if ((!daylight && name == "Standard Time") || (daylight && name == "Daylight Time")) { break; } if (!abbrevs.contains(name)) { abbrevs += name; } break; } case ICAL_DTSTART_PROPERTY: // local time at which phase starts dtstart = icalproperty_get_dtstart(p); found_dtstart = true; break; case ICAL_TZOFFSETFROM_PROPERTY: // UTC offset immediately before start of phase prevOffset = icalproperty_get_tzoffsetfrom(p); found_tzoffsetfrom = true; break; case ICAL_TZOFFSETTO_PROPERTY: utcOffset = icalproperty_get_tzoffsetto(p); found_tzoffsetto = true; break; case ICAL_COMMENT_PROPERTY: comment = QString::fromUtf8(icalproperty_get_comment(p)); break; case ICAL_RDATE_PROPERTY: case ICAL_RRULE_PROPERTY: recurs = true; break; default: qCDebug(KCALCORE_LOG) << "Unknown property:" << int(kind); break; } p = icalcomponent_get_next_property(c, ICAL_ANY_PROPERTY); } // Validate the phase data if (!found_dtstart || !found_tzoffsetfrom || !found_tzoffsetto) { qCDebug(KCALCORE_LOG) << "DTSTART/TZOFFSETFROM/TZOFFSETTO missing"; return transitions; } // Convert DTSTART to QDateTime, and from local time to UTC const QDateTime localStart = toQDateTime(dtstart); // local time dtstart.second -= prevOffset; dtstart.is_utc = 1; const QDateTime utcStart = toQDateTime(icaltime_normalize(dtstart)); // UTC transitions += utcStart; if (recurs) { /* RDATE or RRULE is specified. There should only be one or the other, but * it doesn't really matter - the code can cope with both. * Note that we had to get DTSTART, TZOFFSETFROM, TZOFFSETTO before reading * recurrences. */ const KDateTime klocalStart(localStart, KDateTime::Spec::ClockTime()); const KDateTime maxTime(MAX_DATE(), KDateTime::Spec::ClockTime()); Recurrence recur; icalproperty *p = icalcomponent_get_first_property(c, ICAL_ANY_PROPERTY); while (p) { icalproperty_kind kind = icalproperty_isa(p); switch (kind) { case ICAL_RDATE_PROPERTY: { icaltimetype t = icalproperty_get_rdate(p).time; if (icaltime_is_date(t)) { // RDATE with a DATE value inherits the (local) time from DTSTART t.hour = dtstart.hour; t.minute = dtstart.minute; t.second = dtstart.second; t.is_date = 0; t.is_utc = 0; // dtstart is in local time } // RFC2445 states that RDATE must be in local time, // but we support UTC as well to be safe. if (!t.is_utc) { t.second -= prevOffset; // convert to UTC t.is_utc = 1; t = icaltime_normalize(t); } transitions += toQDateTime(t); break; } case ICAL_RRULE_PROPERTY: { RecurrenceRule r; ICalFormat icf; ICalFormatImpl impl(&icf); impl.readRecurrence(icalproperty_get_rrule(p), &r); r.setStartDt(k2q(klocalStart)); // The end date time specified in an RRULE should be in UTC. // Convert to local time to avoid timesInInterval() getting things wrong. if (r.duration() == 0) { KDateTime end(r.endDt()); if (end.timeSpec() == KDateTime::Spec::UTC()) { end.setTimeSpec(KDateTime::Spec::ClockTime()); r.setEndDt(k2q(end.addSecs(prevOffset))); } } const auto dts = r.timesInInterval(k2q(klocalStart), k2q(maxTime)); for (int i = 0, end = dts.count(); i < end; ++i) { QDateTime utc = dts[i]; utc.setTimeSpec(Qt::UTC); transitions += utc.addSecs(-prevOffset); } break; } default: break; } p = icalcomponent_get_next_property(c, ICAL_ANY_PROPERTY); } qSortUnique(transitions); } phase = KTimeZone::Phase(utcOffset, abbrevs, daylight, comment); return transitions; } //@endcond ICalTimeZone ICalTimeZoneSource::standardZone(const QString &zone, bool icalBuiltIn) { if (!icalBuiltIn) { // Try to fetch a system time zone in preference, on the grounds // that system time zones are more likely to be up to date than // built-in libical ones. QString tzid = zone; const QString prefix = QString::fromUtf8(icalTzidPrefix()); if (zone.startsWith(prefix)) { const int i = zone.indexOf(QLatin1Char('/'), prefix.length()); if (i > 0) { tzid = zone.mid(i + 1); // strip off the libical prefix } } const KTimeZone ktz = KSystemTimeZones::readZone(tzid); if (ktz.isValid()) { if (ktz.data(true)) { const ICalTimeZone icaltz(ktz); //qCDebug(KCALCORE_LOG) << zone << " read from system database"; return icaltz; } } } // Try to fetch a built-in libical time zone. // First try to look it up as a geographical location (e.g. Europe/London) const QByteArray zoneName = zone.toUtf8(); icaltimezone *icaltz = icaltimezone_get_builtin_timezone(zoneName.constData()); if (!icaltz) { // This will find it if it includes the libical prefix icaltz = icaltimezone_get_builtin_timezone_from_tzid(zoneName.constData()); if (!icaltz) { return ICalTimeZone(); } } return parse(icaltz); } QByteArray ICalTimeZoneSource::icalTzidPrefix() { if (ICalTimeZoneSourcePrivate::icalTzidPrefix.isEmpty()) { icaltimezone *icaltz = icaltimezone_get_builtin_timezone("Europe/London"); const QByteArray tzid = icaltimezone_get_tzid(icaltz); if (tzid.right(13) == "Europe/London") { int i = tzid.indexOf('/', 1); if (i > 0) { ICalTimeZoneSourcePrivate::icalTzidPrefix = tzid.left(i + 1); return ICalTimeZoneSourcePrivate::icalTzidPrefix; } } qCritical() << "failed to get libical TZID prefix"; } return ICalTimeZoneSourcePrivate::icalTzidPrefix; } void ICalTimeZoneSource::virtual_hook(int id, void *data) { Q_UNUSED(id); Q_UNUSED(data); Q_ASSERT(false); } } // namespace KCalCore