diff --git a/akonadi/calendar/CMakeLists.txt b/akonadi/calendar/CMakeLists.txt index 96c5aa06c..7d615ea8e 100644 --- a/akonadi/calendar/CMakeLists.txt +++ b/akonadi/calendar/CMakeLists.txt @@ -1,113 +1,114 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) # TODO: Add a cmake option for this and enable it on jenkins set( PLEASE_TEST_INVITATIONS TRUE) if ( PLEASE_TEST_INVITATIONS ) add_definitions( -DPLEASE_TEST_INVITATIONS ) endif() add_subdirectory( tests ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}") remove_definitions(-DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII) set(akonadicalendar_LIB_SRC blockalarmsattribute.cpp calendarbase.cpp calendarclipboard.cpp calendarmodel.cpp calfilterproxymodel_p.cpp calfilterpartstatusproxymodel_p.cpp etmcalendar.cpp history.cpp history_p.cpp icalimporter.cpp incidencefetchjob_p.cpp incidencechanger.cpp incidencechanger_p.cpp itiphandler.cpp itiphandler_p.cpp itiphandlerhelper_p.cpp kcolumnfilterproxymodel.cpp fetchjobcalendar.cpp freebusydownloadjob_p.cpp freebusymanager.cpp freebusyproviderbase.cpp mailclient_p.cpp mailscheduler_p.cpp publishdialog.cpp publishdialog_p.cpp scheduler_p.cpp standardcalendaractionmanager.cpp todopurger.cpp + timezoneconverter.cpp utils_p.cpp ) include_directories( ${CMAKE_BINARY_DIR}/kpimutils ${CMAKE_BINARY_DIR}/kmime ${CMAKE_BINARY_DIR}/kcalcore ${CMAKE_BINARY_DIR}/kcalutils ${CMAKE_BINARY_DIR}/akonadi ${CMAKE_BINARY_DIR}/mailtransport ${CMAKE_BINARY_DIR}/kpimidentities ${CMAKE_BINARY_DIR}/akonadi/contact ${CMAKE_BINARY_DIR}/akonadi/kmime ${CMAKE_BINARY_DIR}/kabc ) kde4_add_kcfg_files(akonadicalendar_LIB_SRC calendarsettings.kcfgc) kde4_add_ui_files(akonadicalendar_LIB_SRC publishdialog_base.ui) qt4_add_dbus_adaptor( akonadicalendar_LIB_SRC ../interfaces/org.freedesktop.Akonadi.Resource.FreeBusyProvider.xml freebusyproviderbase_p.h Akonadi::FreeBusyProviderBasePrivate freebusyprovideradaptor Akonadi__FreeBusyProviderAdaptor ) add_library(akonadi-calendar ${LIBRARY_TYPE} ${akonadicalendar_LIB_SRC}) if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.12) set_property(TARGET akonadi-calendar PROPERTY DEFINE_SYMBOL akonadi_calendar) endif() generate_export_header(akonadi-calendar BASE_NAME akonadi_calendar EXPORT_FILE_NAME akonadi-calendar_export.h ) target_link_libraries(akonadi-calendar akonadi-contact akonadi-kmime akonadi-kde kpimidentities kpimutils kcalcore kcalutils kmime mailtransport ${KDE4_KDEUI_LIBS} ${KDE4_KIO_LIBS} ${KDE4_PHONON_LIBS}) set_target_properties(akonadi-calendar PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION}) install(TARGETS akonadi-calendar EXPORT kdepimlibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS}) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/akonadi-calendar_export.h blockalarmsattribute.h calendarbase.h calendarclipboard.h etmcalendar.h history.h icalimporter.h incidencechanger.h itiphandler.h fetchjobcalendar.h freebusymanager.h freebusyproviderbase.h publishdialog.h standardcalendaractionmanager.h todopurger.h ${CMAKE_CURRENT_BINARY_DIR}/calendarsettings.h DESTINATION ${INCLUDE_INSTALL_DIR}/akonadi/calendar COMPONENT Devel ) diff --git a/akonadi/calendar/itiphandler.cpp b/akonadi/calendar/itiphandler.cpp index 52362ded9..f906ebf4f 100644 --- a/akonadi/calendar/itiphandler.cpp +++ b/akonadi/calendar/itiphandler.cpp @@ -1,425 +1,436 @@ /* Copyright (c) 2002-2004 Klarälvdalens Datakonsult AB Copyright (C) 2010 Bertjan Broeksema Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company Copyright (C) 2012 Sérgio Martins 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 "itiphandler.h" #include "itiphandler_p.h" #include "itiphandlerhelper_p.h" #include "calendarsettings.h" #include "publishdialog.h" #include "utils_p.h" #include "mailclient_p.h" #include #include #include #include #include #include #include #include #include #include #include +#include +#include "timezoneconverter.h" using namespace Akonadi; // async emittion static void emitiTipMessageProcessed(ITIPHandler *handler, ITIPHandler::Result resultCode, const QString &errorString) { QMetaObject::invokeMethod(handler, "iTipMessageProcessed", Qt::QueuedConnection, Q_ARG(Akonadi::ITIPHandler::Result, resultCode), Q_ARG(QString, errorString)); } GroupwareUiDelegate::~GroupwareUiDelegate() { } ITIPHandlerComponentFactory::ITIPHandlerComponentFactory(QObject *parent) : QObject(parent) { } ITIPHandlerComponentFactory::~ITIPHandlerComponentFactory() { } MailTransport::MessageQueueJob *ITIPHandlerComponentFactory::createMessageQueueJob(const KCalCore::IncidenceBase::Ptr &incidence, const KPIMIdentities::Identity &identity, QObject *parent) { Q_UNUSED(incidence); Q_UNUSED(identity); return new MailTransport::MessageQueueJob(parent); } ITIPHandlerDialogDelegate *ITIPHandlerComponentFactory::createITIPHanderDialogDelegate(const KCalCore::Incidence::Ptr &incidence, KCalCore::iTIPMethod method, QWidget *parent) { return new ITIPHandlerDialogDelegate(incidence, method, parent); } ITIPHandler::ITIPHandler(QObject *parent) : QObject(parent) , d(new Private(/*factory=*/0, this)) { qRegisterMetaType("Akonadi::ITIPHandler::Result"); } ITIPHandler::ITIPHandler(ITIPHandlerComponentFactory *factory, QObject *parent) : QObject(parent) , d(new Private(factory, this)) { qRegisterMetaType("Akonadi::ITIPHandler::Result"); } ITIPHandler::~ITIPHandler() { delete d; } +static KTimeZone normalizeTimezone(const KTimeZone &timezone) +{ + return KSystemTimeZones::zone(TimezoneConverter::normalizeTimezone(timezone.name())); +} + void ITIPHandler::processiTIPMessage(const QString &receiver, const QString &iCal, const QString &action) { kDebug() << "processiTIPMessage called with receiver=" << receiver << "; action=" << action; if (d->m_currentOperation != OperationNone) { d->m_currentOperation = OperationNone; kFatal() << "There can't be an operation in progress!" << d->m_currentOperation; return; } d->m_currentOperation = OperationProcessiTIPMessage; if (!d->isLoaded()) { d->m_queuedInvitation.receiver = receiver; d->m_queuedInvitation.iCal = iCal; d->m_queuedInvitation.action = action; return; } if (d->m_calendarLoadError) { d->m_currentOperation = OperationNone; kError() << "Error loading calendar"; emitiTipMessageProcessed(this, ResultError, i18n("Error loading calendar.")); return; } KCalCore::ICalFormat format; KCalCore::ScheduleMessage::Ptr message = format.parseScheduleMessage(d->calendar(), iCal); if (!message) { const QString errorMessage = format.exception() ? i18n("Error message: %1", KCalUtils::Stringify::errorMessage(*format.exception())) : i18n("Unknown error while parsing iCal invitation"); kError() << "Error parsing" << errorMessage; if (d->m_showDialogsOnError) { KMessageBox::detailedError(0, // mParent, TODO i18n("Error while processing an invitation or update."), errorMessage); } d->m_currentOperation = OperationNone; emitiTipMessageProcessed(this, ResultError, errorMessage); return; } d->m_method = static_cast(message->method()); KCalCore::ScheduleMessage::Status status = message->status(); d->m_incidence = message->event().dynamicCast(); + + //Don't let any non-olson timezones through. The kolab resource would convert it anyways, and the akonadi serializer doesn't deal with arbitrary timezones and will end up loosing the timezone. + d->m_incidence->shiftTimes(d->m_incidence->dtStart().timeZone(), normalizeTimezone(d->m_incidence->dtStart().timeZone())); + if (!d->m_incidence) { kError() << "Invalid incidence"; d->m_currentOperation = OperationNone; emitiTipMessageProcessed(this, ResultError, i18n("Invalid incidence")); return; } if (action.startsWith(QLatin1String("accepted")) || action.startsWith(QLatin1String("tentative")) || action.startsWith(QLatin1String("delegated")) || action.startsWith(QLatin1String("counter")) || action.startsWith(QLatin1String("cancel"))) { // Find myself and set my status. This can't be done in the scheduler, // since this does not know the choice I made in the KMail bpf const KCalCore::Attendee::List attendees = d->m_incidence->attendees(); foreach(KCalCore::Attendee::Ptr attendee, attendees) { if (attendee->email() == receiver) { if (action.startsWith(QLatin1String("accepted"))) { attendee->setStatus(KCalCore::Attendee::Accepted); } else if (action.startsWith(QLatin1String("tentative"))) { attendee->setStatus(KCalCore::Attendee::Tentative); } else if (CalendarSettings::self()->outlookCompatCounterProposals() && action.startsWith(QLatin1String("counter"))) { attendee->setStatus(KCalCore::Attendee::Tentative); } else if (action.startsWith(QLatin1String("delegated"))) { attendee->setStatus(KCalCore::Attendee::Delegated); } else if (action.startsWith(QLatin1String("cancel"))) { attendee->setStatus(KCalCore::Attendee::Declined); } break; } } } if (action.startsWith(QLatin1String("accepted")) || action.startsWith(QLatin1String("tentative")) || action.startsWith(QLatin1String("delegated")) || action.startsWith(QLatin1String("counter"))) { if (CalendarSettings::self()->outlookCompatCounterProposals() || !action.startsWith(QLatin1String("counter"))) { d->m_scheduler->acceptTransaction(d->m_incidence, d->calendar(), d->m_method, status, receiver); return; // signal emitted in onSchedulerFinished(). } //TODO: what happens here? we must emit a signal } else if (action.startsWith(QLatin1String("cancel"))) { // Update the old incidence, if one is present KCalCore::Incidence::Ptr existingIncidence = d->calendar()->incidenceFromSchedulingID(d->m_incidence->uid()); if (existingIncidence) { d->m_scheduler->acceptTransaction(d->m_incidence, d->calendar(), KCalCore::iTIPCancel, status, receiver); return; // signal emitted in onSchedulerFinished(). } else { // We don't have the incidence, nothing to cancel kWarning() << "Couldn't find the incidence to delete.\n" << "You deleted it previously or didn't even accept the invitation it in the first place.\n" << "; uid=" << d->m_incidence->uid() << "; identifier=" << d->m_incidence->instanceIdentifier() << "; summary=" << d->m_incidence->summary(); kDebug() << "\n Here's what we do have with such a summary:"; KCalCore::Incidence::List knownIncidences = calendar()->incidences(); foreach(const KCalCore::Incidence::Ptr &knownIncidence, knownIncidences) { if (knownIncidence->summary() == d->m_incidence->summary()) { kDebug() << "\nFound: uid=" << knownIncidence->uid() << "; identifier=" << knownIncidence->instanceIdentifier() << "; schedulingId" << knownIncidence->schedulingID(); } } emitiTipMessageProcessed(this, ResultSuccess, QString()); } } else if (action.startsWith(QLatin1String("reply"))) { if (d->m_method != KCalCore::iTIPCounter) { d->m_scheduler->acceptTransaction(d->m_incidence, d->calendar(), d->m_method, status, QString()); } else { d->m_scheduler->acceptCounterProposal(d->m_incidence, d->calendar()); } return; // signal emitted in onSchedulerFinished(). } else if (action.startsWith(QLatin1String("request"))) { d->m_scheduler->acceptTransaction(d->m_incidence, d->calendar(), d->m_method, status, receiver); return; } else { kError() << "Unknown incoming action" << action; d->m_currentOperation = OperationNone; emitiTipMessageProcessed(this, ResultError, i18n("Invalid action: %1", action)); } if (action.startsWith(QLatin1String("counter"))) { if (d->m_uiDelegate) { Akonadi::Item item; item.setMimeType(d->m_incidence->mimeType()); item.setPayload(KCalCore::Incidence::Ptr(d->m_incidence->clone())); // TODO_KDE5: This blocks because m_uiDelegate is not a QObject and can't emit a finished() // signal. Make async in kde5 d->m_uiDelegate->requestIncidenceEditor(item); KCalCore::Incidence::Ptr newIncidence; if (item.hasPayload()) { newIncidence = item.payload(); } if (*newIncidence == *d->m_incidence) { emitiTipMessageProcessed(this, ResultCancelled, QString()); } else { ITIPHandlerHelper::SendResult result = d->m_helper->sendCounterProposal(d->m_incidence, newIncidence); if (result != ITIPHandlerHelper::ResultSuccess) { // It gives success in all paths, this never happens emitiTipMessageProcessed(this, ResultError, i18n("Error sending counter proposal")); Q_ASSERT(false); } } } else { // This should never happen kWarning() << "No UI delegate is set"; emitiTipMessageProcessed(this, ResultError, i18n("Could not start editor to edit counter proposal")); } } } void ITIPHandler::sendiTIPMessage(KCalCore::iTIPMethod method, const KCalCore::Incidence::Ptr &incidence, QWidget *parentWidget) { if (!incidence) { Q_ASSERT(false); kError() << "Invalid incidence"; return; } d->m_queuedInvitation.method = method; d->m_queuedInvitation.incidence = incidence; d->m_parentWidget = parentWidget; if (!d->isLoaded()) { d->m_currentOperation = OperationSendiTIPMessage; // This method will be called again once the calendar is loaded. return; } Q_ASSERT(d->m_currentOperation == OperationNone); if (d->m_currentOperation != OperationNone) { kError() << "There can't be an operation in progress!" << d->m_currentOperation; return; } if (incidence->attendeeCount() == 0 && method != KCalCore::iTIPPublish) { if (d->m_showDialogsOnError) { KMessageBox::information(parentWidget, i18n("The item '%1' has no attendees. " "Therefore no groupware message will be sent.", incidence->summary()), i18n("Message Not Sent"), QLatin1String("ScheduleNoAttendees")); } return; } d->m_currentOperation = OperationSendiTIPMessage; KCalCore::Incidence *incidenceCopy = incidence->clone(); incidenceCopy->registerObserver(0); incidenceCopy->clearAttendees(); d->m_scheduler->performTransaction(incidence, method); } void ITIPHandler::publishInformation(const KCalCore::Incidence::Ptr &incidence, QWidget *parentWidget) { Q_ASSERT(incidence); if (!incidence) { kError() << "Invalid incidence. Aborting."; return; } Q_ASSERT(d->m_currentOperation == OperationNone); if (d->m_currentOperation != OperationNone) { kError() << "There can't be an operation in progress!" << d->m_currentOperation; return; } d->m_queuedInvitation.incidence = incidence; d->m_parentWidget = parentWidget; d->m_currentOperation = OperationPublishInformation; QPointer publishdlg = new Akonadi::PublishDialog(); if (incidence->attendeeCount() > 0) { KCalCore::Attendee::List attendees = incidence->attendees(); KCalCore::Attendee::List::ConstIterator it; KCalCore::Attendee::List::ConstIterator end(attendees.constEnd()); for (it = attendees.constBegin(); it != end; ++it) { publishdlg->addAttendee(*it); } } if (publishdlg->exec() == QDialog::Accepted && publishdlg) d->m_scheduler->publish(incidence, publishdlg->addresses()); else emit informationPublished(ResultSuccess, QString()); // Canceled. delete publishdlg; } void ITIPHandler::sendAsICalendar(const KCalCore::Incidence::Ptr &originalIncidence, QWidget *parentWidget) { Q_UNUSED(parentWidget); Q_ASSERT(originalIncidence); if (!originalIncidence) { kError() << "Invalid incidence"; return; } // Clone so we can change organizer and recurid KCalCore::Incidence::Ptr incidence = KCalCore::Incidence::Ptr(originalIncidence->clone()); KPIMIdentities::IdentityManager identityManager; QPointer publishdlg = new Akonadi::PublishDialog; if (publishdlg->exec() == QDialog::Accepted && publishdlg) { const QString recipients = publishdlg->addresses(); if (incidence->organizer()->isEmpty()) { incidence->setOrganizer(KCalCore::Person::Ptr( new KCalCore::Person(Akonadi::CalendarUtils::fullName(), Akonadi::CalendarUtils::email()))); } if (incidence->hasRecurrenceId()) { // For an individual occurrence, recur id doesn't make sense, since we're not sending the whole recurrence series. incidence->setRecurrenceId(KDateTime()); } KCalCore::ICalFormat format; const QString from = Akonadi::CalendarUtils::email(); const bool bccMe = Akonadi::CalendarSettings::self()->bcc(); const QString messageText = format.createScheduleMessage(incidence, KCalCore::iTIPRequest); MailClient *mailer = new MailClient(d->m_factory); d->m_queuedInvitation.incidence = incidence; connect(mailer, SIGNAL(finished(Akonadi::MailClient::Result,QString)), d, SLOT(finishSendAsICalendar(Akonadi::MailScheduler::Result,QString))); mailer->mailTo(incidence, identityManager.identityForAddress(from), from, bccMe, recipients, messageText, MailTransport::TransportManager::self()->defaultTransportName()); } } void ITIPHandler::setGroupwareUiDelegate(GroupwareUiDelegate *delegate) { d->m_uiDelegate = delegate; } void ITIPHandler::setCalendar(const Akonadi::CalendarBase::Ptr &calendar) { if (d->m_calendar != calendar) { d->m_calendar = calendar; } } void ITIPHandler::setShowDialogsOnError(bool enable) { d->m_showDialogsOnError = enable; } Akonadi::CalendarBase::Ptr ITIPHandler::calendar() const { return d->m_calendar; } diff --git a/akonadi/calendar/timezoneconverter.cpp b/akonadi/calendar/timezoneconverter.cpp new file mode 100644 index 000000000..00414979c --- /dev/null +++ b/akonadi/calendar/timezoneconverter.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2012 Christian Mollekopf + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "timezoneconverter.h" +#include +#include +#include +#include +#include + +QString TimezoneConverter::normalizeTimezone(const QString& tz) +{ + KTimeZone timezone = KSystemTimeZones::zone(tz); //Needs ktimezoned (timezone daemon running) http://api.kde.org/4.x-api/kdelibs-apidocs/kdecore/html/classKSystemTimeZones.html + if (timezone.isValid()) { + return tz; + } else if (!KSystemTimeZones::isTimeZoneDaemonAvailable()) { + kWarning() << "ktimezoned is not available and required for timezone interpretation"; + } + //We're dealing with an invalid or unknown timezone, try to parse it + QString guessedTimezone = fromCityName(tz); + if (guessedTimezone.isEmpty()) { + guessedTimezone = fromHardcodedList(tz); + } + kDebug() << "Guessed timezone and found: " << guessedTimezone; + return guessedTimezone; +} + +QString TimezoneConverter::fromCityName(const QString& tz) +{ + const KTimeZones::ZoneMap zones = KSystemTimeZones::zones(); + KTimeZones::ZoneMap::const_iterator it = zones.constBegin(); + QHash countryMap; + for(;it != zones.constEnd(); it++) { + const QString cityName = it.key().split('/').last(); +// kDebug() << it.key() << it.value().name() << cityName; + Q_ASSERT(!countryMap.contains(cityName)); + countryMap.insert(cityName, it.key()); + } + + QRegExp locationFinder("\\b([a-zA-Z])+\\b", Qt::CaseSensitive, QRegExp::RegExp2); + int pos = 0; + while (pos >= 0) { + pos = locationFinder.indexIn(tz, pos); + if (pos >= 0) { + ++pos; + } + const QString location = locationFinder.capturedTexts().first(); +// kDebug() << "location " << location; + if (countryMap.contains(location)) { +// kDebug() << "found match " << countryMap.value(location); + return countryMap.value(location); + } + } + return QString(); +} + + +//Based on +// * http://msdn.microsoft.com/en-us/library/ms912391(v=winembedded.11).aspx +// * http://technet.microsoft.com/en-us/library/cc749073(v=ws.10).aspx +// * http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml +// * http://stackoverflow.com/questions/4967903/linux-windows-timezone-mapping +static const struct WindowsTimezone { +// const int gmtOffset; + const char *timezoneSpecifier; //This one should be stable and always in english + const char *name; //The display name (which is in some cases still useful to try guessing) + const char *olson[28]; //Corresponding olson timezones we can map to +} windowsTimezones[] = { + {"Afghanistan Standard Time", "Kabul", {"Asia/Kabul", "Asia/Kabul"}}, + {"Alaskan Standard Time", "Alaska", {"America/Anchorage", "America/Anchorage America/Juneau America/Nome America/Sitka America/Yakutat"}}, + {"Arab Standard Time", "Kuwait, Riyadh", {"Asia/Riyadh", "Asia/Bahrain", "Asia/Kuwait", "Asia/Qatar", "Asia/Riyadh", "Asia/Aden"}}, + {"Arabian Standard Time", "Abu Dhabi, Muscat", {"Asia/Dubai", "Asia/Dubai", "Asia/Muscat", "Etc/GMT-4"}}, + {"Arabic Standard Time", "Baghdad", {"Asia/Baghdad", "Asia/Baghdad"}}, + {"Atlantic Standard Time", "Atlantic Time (Canada)", {"America/Halifax", "Atlantic/Bermuda", "America/Halifax America/Glace_Bay America/Goose_Bay America/Moncton", "America/Thule"}}, + {"AUS Central Standard Time", "Darwin", {"Australia/Darwin", "Australia/Darwin"}}, + {"AUS Eastern Standard Time", "Canberra, Melbourne, Sydney", {"Australia/Sydney", "Australia/Sydney Australia/Melbourne"}}, + {"Azerbaijan Standard Time", "Baku", {"Asia/Baku", "Asia/Baku"}}, + {"Azores Standard Time", "Azores", {"Atlantic/Azores", "America/Scoresbysund", "Atlantic/Azores"}}, + {"Canada Central Standard Time", "Saskatchewan", {"America/Regina", "America/Regina America/Swift_Current"}}, + {"Cape Verde Standard Time", "Cape Verde Islands", {"Atlantic/Cape_Verde", "Atlantic/Cape_Verde", "Etc/GMT+1"}}, + {"Caucasus Standard Time", "Yerevan", {"Asia/Yerevan", "Asia/Yerevan"}}, + {"Cen. Australia Standard Time", "Adelaide", {"Australia/Adelaide", "Australia/Adelaide Australia/Broken_Hill"}}, + {"Central America Standard Time", "Central America", {"America/Guatemala", "America/Belize", "America/Costa_Rica", "Pacific/Galapagos", "America/Guatemala", "America/Tegucigalpa", "America/Managua", "America/El_Salvador", "Etc/GMT+6"}}, + {"Central Asia Standard Time", "Astana, Dhaka", {"Asia/Almaty", "Antarctica/Vostok", "Indian/Chagos", "Asia/Bishkek", "Asia/Almaty Asia/Qyzylorda", "Etc/GMT-6"}}, + {"Central Brazilian Standard Time", "Manaus", {"America/Cuiaba", "America/Cuiaba America/Campo_Grande"}}, + {"Central Europe Standard Time", "Belgrade, Bratislava, Budapest, Ljubljana, Prague", {"Europe/Budapest", "Europe/Tirane", "Europe/Prague", "Europe/Budapest", "Europe/Podgorica", "Europe/Belgrade", "Europe/Ljubljana", "Europe/Bratislava"}}, + {"Central European Standard Time", "Sarajevo, Skopje, Warsaw, Zagreb", {"Europe/Warsaw", "Europe/Sarajevo", "Europe/Zagreb", "Europe/Skopje", "Europe/Warsaw"}}, + {"Central Pacific Standard Time", "Magadan, Solomon Islands, New Caledonia", {"Pacific/Guadalcanal", "Antarctica/Macquarie", "Pacific/Ponape Pacific/Kosrae", "Pacific/Noumea", "Pacific/Guadalcanal", "Pacific/Efate", "Etc/GMT-11"}}, + {"Central Standard Time", "Central Time (US and Canada)", {"America/Chicago", "America/Winnipeg America/Rainy_River America/Rankin_Inlet America/Resolute", "America/Matamoros", "America/Chicago America/Indiana/Knox America/Indiana/Tell_City America/Menominee America/North_Dakota/Beulah America/North_Dakota/Center America/North_Dakota/New_Salem", "CST6CDT"}}, + {"Central Standard Time (Mexico)", "Guadalajara, Mexico City, Monterrey", {"America/Mexico_City", "America/Mexico_City America/Bahia_Banderas America/Cancun America/Merida America/Monterrey"}}, + {"China Standard Time", "Beijing, Chongqing, Hong Kong SAR, Urumqi", {"Asia/Shanghai", "Asia/Shanghai Asia/Chongqing Asia/Harbin Asia/Kashgar Asia/Urumqi", "Asia/Hong_Kong", "Asia/Macau"}}, + {"Dateline Standard Time", "International Date Line West", {"Etc/GMT+12", "Etc/GMT+12"}}, + {"E. Africa Standard Time", "Nairobi", {"Africa/Nairobi", "Antarctica/Syowa", "Africa/Djibouti", "Africa/Asmera", "Africa/Addis_Ababa", "Africa/Nairobi", "Indian/Comoro", "Indian/Antananarivo", "Africa/Khartoum", "Africa/Mogadishu", "Africa/Juba", "Africa/Dar_es_Salaam", "Africa/Kampala", "Indian/Mayotte", "Etc/GMT-3"}}, + {"E. Australia Standard Time", "Brisbane", {"Australia/Brisbane", "Australia/Brisbane Australia/Lindeman"}}, + {"E. Europe Standard Time", "Minsk", {"Asia/Nicosia", "Asia/Nicosia"}}, + {"E. South America Standard Time", "Brasilia", {"America/Sao_Paulo", "America/Sao_Paulo"}}, + {"Eastern Standard Time", "Eastern Time (US and Canada)", {"America/New_York", "America/Nassau", "America/Toronto America/Iqaluit America/Montreal America/Nipigon America/Pangnirtung America/Thunder_Bay", "America/Grand_Turk", "America/New_York America/Detroit America/Indiana/Petersburg America/Indiana/Vincennes America/Indiana/Winamac America/Kentucky/Monticello America/Louisville", "EST5EDT"}}, + {"Egypt Standard Time", "Cairo", {"Africa/Cairo", "Africa/Cairo", "Asia/Gaza Asia/Hebron"}}, + {"Ekaterinburg Standard Time", "Ekaterinburg", {"Asia/Yekaterinburg", "Asia/Yekaterinburg"}}, + {"Fiji Standard Time", "Fiji Islands, Kamchatka, Marshall Islands", {"Pacific/Fiji", "Pacific/Fiji"}}, + {"FLE Standard Time", "Helsinki, Kiev, Riga, Sofia, Tallinn, Vilnius", {"Europe/Kiev", "Europe/Mariehamn", "Europe/Sofia", "Europe/Tallinn", "Europe/Helsinki", "Europe/Vilnius", "Europe/Riga", "Europe/Kiev Europe/Simferopol Europe/Uzhgorod Europe/Zaporozhye"}}, + {"Georgian Standard Time", "Tblisi", {"Asia/Tbilisi", "Asia/Tbilisi"}}, + {"GMT Standard Time", "Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London", {"Europe/London", "Atlantic/Canary", "Atlantic/Faeroe", "Europe/London", "Europe/Guernsey", "Europe/Dublin", "Europe/Isle_of_Man", "Europe/Jersey", "Europe/Lisbon Atlantic/Madeira"}}, + {"Greenland Standard Time", "Greenland", {"America/Godthab", "America/Godthab"}}, + {"Greenwich Standard Time", "Casablanca, Monrovia", {"Atlantic/Reykjavik", "Africa/Ouagadougou", "Africa/Abidjan", "Africa/El_Aaiun", "Africa/Accra", "Africa/Banjul", "Africa/Conakry", "Africa/Bissau", "Atlantic/Reykjavik", "Africa/Monrovia", "Africa/Bamako", "Africa/Nouakchott", "Atlantic/St_Helena", "Africa/Freetown", "Africa/Dakar", "Africa/Sao_Tome", "Africa/Lome"}}, + {"GTB Standard Time", "Athens, Bucharest, Istanbul", {"Europe/Bucharest", "Europe/Athens", "Europe/Chisinau", "Europe/Bucharest"}}, + {"Hawaiian Standard Time", "Hawaii", {"Pacific/Honolulu", "Pacific/Rarotonga", "Pacific/Tahiti", "Pacific/Johnston", "Pacific/Honolulu", "Etc/GMT+10"}}, + {"India Standard Time", "Chennai, Kolkata, Mumbai, New Delhi", {"Asia/Calcutta", "Asia/Calcutta"}}, + {"Iran Standard Time", "Tehran", {"Asia/Tehran", "Asia/Tehran"}}, + {"Israel Standard Time", "Jerusalem", {"Asia/Jerusalem", "Asia/Jerusalem"}}, + {"Korea Standard Time", "Seoul", {"Asia/Seoul", "Asia/Pyongyang", "Asia/Seoul"}}, +// {"Mid-Atlantic Standard Time", "Mid-Atlantic", {"}}, + {"Mountain Standard Time", "Mountain Time (US and Canada)", {"America/Denver", "America/Edmonton America/Cambridge_Bay America/Inuvik America/Yellowknife", "America/Ojinaga", "America/Denver America/Boise America/Shiprock", "MST7MDT"}}, + {"Mountain Standard Time (Mexico)", "Chihuahua, La Paz, Mazatlan", {"America/Chihuahua", "America/Chihuahua America/Mazatlan"}}, + {"Myanmar Standard Time", "Yangon (Rangoon)", {"Asia/Rangoon", "Indian/Cocos", "Asia/Rangoon"}}, + {"N. Central Asia Standard Time", "Almaty, Novosibirsk", {"Asia/Novosibirsk", "Asia/Novosibirsk Asia/Novokuznetsk Asia/Omsk"}}, + {"Namibia Standard Time", "Windhoek", {"Africa/Windhoek", "Africa/Windhoek"}}, + {"Nepal Standard Time", "Kathmandu", {"Asia/Katmandu", "Asia/Katmandu"}}, + {"New Zealand Standard Time", "Auckland, Wellington", {"Pacific/Auckland", "Antarctica/South_Pole Antarctica/McMurdo", "Pacific/Auckland"}}, + {"Newfoundland Standard Time", "Newfoundland and Labrador", {"America/St_Johns", "America/St_Johns"}}, + {"North Asia East Standard Time", "Irkutsk, Ulaanbaatar", {"Asia/Irkutsk", "Asia/Irkutsk"}}, + {"North Asia Standard Time", "Krasnoyarsk", {"Asia/Krasnoyarsk", "Asia/Krasnoyarsk"}}, + {"Pacific SA Standard Time", "Santiago", {"America/Santiago", "Antarctica/Palmer", "America/Santiago"}}, + {"Pacific Standard Time", "Pacific Time (US and Canada); Tijuana", {"America/Los_Angeles", "America/Vancouver America/Dawson America/Whitehorse", "America/Tijuana", "America/Los_Angeles", "PST8PDT"}}, + {"Romance Standard Time", "Brussels, Copenhagen, Madrid, Paris", {"Europe/Paris", "Europe/Brussels", "Europe/Copenhagen", "Europe/Madrid Africa/Ceuta", "Europe/Paris"}}, + {"Russian Standard Time", "Moscow, St. Petersburg, Volgograd", {"Europe/Moscow", "Europe/Moscow Europe/Samara Europe/Volgograd"}}, + {"SA Eastern Standard Time", "Buenos Aires, Georgetown", {"America/Cayenne", "Antarctica/Rothera", "America/Fortaleza America/Araguaina America/Belem America/Maceio America/Recife America/Santarem", "Atlantic/Stanley", "America/Cayenne", "America/Paramaribo", "Etc/GMT+3"}}, + {"SA Pacific Standard Time", "Bogota, Lima, Quito", {"America/Bogota", "America/Coral_Harbour", "America/Bogota", "America/Guayaquil", "America/Port-au-Prince", "America/Jamaica", "America/Cayman", "America/Panama", "America/Lima", "Etc/GMT+5"}}, + {"SA Western Standard Time", "Caracas, La Paz", {"America/La_Paz", "America/Antigua", "America/Anguilla", "America/Aruba", "America/Barbados", "America/St_Barthelemy", "America/La_Paz", "America/Kralendijk", "America/Manaus America/Boa_Vista America/Eirunepe America/Porto_Velho America/Rio_Branco", "America/Blanc-Sablon", "America/Curacao", "America/Dominica", "America/Santo_Domingo", "America/Grenada", "America/Guadeloupe", "America/Guyana", "America/St_Kitts", "America/St_Lucia", "America/Marigot", "America/Martinique", "America/Montserrat", "America/Puerto_Rico", "America/Lower_Princes", "America/Port_of_Spain", "America/St_Vincent", "America/Tortola", "America/St_Thomas", "Etc/GMT+4"}}, + {"Samoa Standard Time", "Midway Island, Samoa", {"Pacific/Apia", "Pacific/Apia"}}, + {"SE Asia Standard Time", "Bangkok, Hanoi, Jakarta", {"Asia/Bangkok", "Antarctica/Davis", "Indian/Christmas", "Asia/Jakarta Asia/Pontianak", "Asia/Phnom_Penh", "Asia/Vientiane", "Asia/Hovd", "Asia/Bangkok", "Asia/Saigon", "Etc/GMT-7"}}, + {"Singapore Standard Time", "Kuala Lumpur, Singapore", {"Asia/Singapore", "Asia/Brunei", "Asia/Makassar", "Asia/Kuala_Lumpur Asia/Kuching", "Asia/Manila", "Asia/Singapore", "Etc/GMT-8"}}, + {"South Africa Standard Time", "Harare, Pretoria", {"Africa/Johannesburg", "Africa/Bujumbura", "Africa/Gaborone", "Africa/Lubumbashi", "Africa/Maseru", "Africa/Blantyre", "Africa/Maputo", "Africa/Kigali", "Africa/Mbabane", "Africa/Johannesburg", "Africa/Lusaka", "Africa/Harare", "Etc/GMT-2"}}, + {"Sri Lanka Standard Time", "Sri Jayawardenepura", {"Asia/Colombo", "Asia/Colombo"}}, + {"Taipei Standard Time", "Taipei", {"Asia/Taipei", "Asia/Taipei"}}, + {"Tasmania Standard Time", "Hobart", {"Australia/Hobart", "Australia/Hobart Australia/Currie"}}, + {"Tokyo Standard Time", "Osaka, Sapporo, Tokyo", {"Asia/Tokyo", "Asia/Jayapura", "Asia/Tokyo", "Pacific/Palau", "Asia/Dili", "Etc/GMT-9"}}, + {"Tonga Standard Time", "Nuku'alofa", {"Pacific/Tongatapu", "Pacific/Enderbury", "Pacific/Fakaofo", "Pacific/Tongatapu", "Etc/GMT-13"}}, + {"US Eastern Standard Time", "Indiana (East)", {"America/Indianapolis", "America/Indianapolis America/Indiana/Marengo America/Indiana/Vevay"}}, + {"US Mountain Standard Time", "Arizona", {"America/Phoenix", "America/Dawson_Creek America/Creston", "America/Hermosillo", "America/Phoenix", "Etc/GMT+7"}}, + {"Vladivostok Standard Time", "Vladivostok", {"Asia/Vladivostok", "Asia/Vladivostok Asia/Sakhalin"}}, + {"W. Australia Standard Time", "Perth", {"Australia/Perth", "Antarctica/Casey", "Australia/Perth"}}, + {"W. Central Africa Standard Time", "West Central Africa", {"Africa/Lagos", "Africa/Luanda", "Africa/Porto-Novo", "Africa/Kinshasa", "Africa/Bangui", "Africa/Brazzaville", "Africa/Douala", "Africa/Algiers", "Africa/Libreville", "Africa/Malabo", "Africa/Niamey", "Africa/Lagos", "Africa/Ndjamena", "Africa/Tunis", "Etc/GMT-1"}}, + {"W. Europe Standard Time", "Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna", {"Europe/Berlin", "Europe/Andorra", "Europe/Vienna", "Europe/Zurich", "Europe/Berlin", "Europe/Gibraltar", "Europe/Rome", "Europe/Vaduz", "Europe/Luxembourg", "Africa/Tripoli", "Europe/Monaco", "Europe/Malta", "Europe/Amsterdam", "Europe/Oslo", "Europe/Stockholm", "Arctic/Longyearbyen", "Europe/San_Marino", "Europe/Vatican"}}, + {"West Asia Standard Time", "Islamabad, Karachi, Tashkent", {"Asia/Tashkent", "Antarctica/Mawson", "Asia/Oral Asia/Aqtau Asia/Aqtobe", "Indian/Maldives", "Indian/Kerguelen", "Asia/Dushanbe", "Asia/Ashgabat", "Asia/Tashkent Asia/Samarkand", "Etc/GMT-5"}}, + {"West Pacific Standard Time", "Guam, Port Moresby", {"Pacific/Port_Moresby", "Antarctica/DumontDUrville", "Pacific/Truk", "Pacific/Guam", "Pacific/Saipan", "Pacific/Port_Moresby", "Etc/GMT-10"}}, + {"Yakutsk Standard Time", "Yakuts", {"Asia/Yakutsk", "Asia/Yakutsk"}} +}; +static const int numWindowsTimezones = sizeof windowsTimezones / sizeof *windowsTimezones; + +QString TimezoneConverter::fromHardcodedList(const QString& tz) +{ + for (int i = 0; i + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef TIMEZONECONVERTER_H +#define TIMEZONECONVERTER_H + +#include + +class TimezoneConverter +{ +public: + static QString normalizeTimezone(const QString &tz); +private: + static QString fromCityName(const QString &tz); + static QString fromHardcodedList(const QString &tz); + static QString fromGMTOffsetTimezone(const QString &tz); +}; + +#endif // TIMEZONECONVERTER_H