Kgapi

calendarservice.cpp
1/*
2 * This file is part of LibKGAPI library
3 *
4 * SPDX-FileCopyrightText: 2013 Daniel Vrátil <dvratil@redhat.com>
5 *
6 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7 */
8
9#include "calendarservice.h"
10#include "calendar.h"
11#include "debug.h"
12#include "event.h"
13#include "reminder.h"
14#include "utils.h"
15
16#include <KCalendarCore/Alarm>
17#include <KCalendarCore/Attendee>
18#include <KCalendarCore/Event>
19#include <KCalendarCore/ICalFormat>
20#include <KCalendarCore/Person>
21#include <KCalendarCore/Recurrence>
22#include <KCalendarCore/RecurrenceRule>
23
24#include <QJsonDocument>
25#include <QNetworkRequest>
26#include <QTimeZone>
27#include <QUrlQuery>
28#include <QVariant>
29
30#include <map>
31#include <memory>
32
33namespace KGAPI2
34{
35
36namespace CalendarService
37{
38
39namespace Private
40{
41KCalendarCore::DateList parseRDate(const QString &rule);
42
43ObjectPtr JSONToCalendar(const QVariantMap &data);
44ObjectPtr JSONToEvent(const QVariantMap &data, const QString &timezone = QString());
45
46/**
47 * Checks whether TZID is in Olson format and converts it to it if necessary
48 *
49 * This is mainly to handle crazy Microsoft TZIDs like
50 * "(GMT) Greenwich Mean Time/Dublin/Edinburgh/London", because Google only
51 * accepts TZIDs in Olson format ("Europe/London").
52 *
53 * It first tries to match the given \p tzid to all TZIDs in KTimeZones::zones().
54 * If it fails, it parses the \p event, looking for X-MICROSOFT-CDO-TZID
55 * property and than matches it to Olson-formatted TZID using a table.
56 *
57 * When the method fails to process the TZID, it returns the original \p tzid
58 * in hope, that Google will cope with it.
59 */
60QString checkAndConverCDOTZID(const QString &tzid, const EventPtr &event);
61
62static const QUrl GoogleApisUrl(QStringLiteral("https://www.googleapis.com"));
63static const QString CalendarListBasePath(QStringLiteral("/calendar/v3/users/me/calendarList"));
64static const QString CalendarBasePath(QStringLiteral("/calendar/v3/calendars"));
65}
66
68{
69 QNetworkRequest request(url);
70 request.setRawHeader("GData-Version", CalendarService::APIVersion().toLatin1());
71
72 return request;
73}
74
75/************* URLS **************/
76
78{
79 QUrl url(Private::GoogleApisUrl);
80 url.setPath(Private::CalendarListBasePath);
81 return url;
82}
83
84QUrl fetchCalendarUrl(const QString &calendarID)
85{
86 QUrl url(Private::GoogleApisUrl);
87 url.setPath(Private::CalendarListBasePath % QLatin1Char('/') % calendarID);
88 return url;
89}
90
91QUrl updateCalendarUrl(const QString &calendarID)
92{
93 QUrl url(Private::GoogleApisUrl);
94 url.setPath(Private::CalendarBasePath % QLatin1Char('/') % calendarID);
95 return url;
96}
97
99{
100 QUrl url(Private::GoogleApisUrl);
101 url.setPath(Private::CalendarBasePath);
102 return url;
103}
104
106{
107 QUrl url(Private::GoogleApisUrl);
108 url.setPath(Private::CalendarBasePath % QLatin1Char('/') % calendarID);
109 return url;
110}
111
112QUrl fetchEventsUrl(const QString &calendarID)
113{
114 QUrl url(Private::GoogleApisUrl);
115 url.setPath(Private::CalendarBasePath % QLatin1Char('/') % calendarID % QLatin1StringView("/events"));
116 return url;
117}
118
119QUrl fetchEventUrl(const QString &calendarID, const QString &eventID)
120{
121 QUrl url(Private::GoogleApisUrl);
122 url.setPath(Private::CalendarBasePath % QLatin1Char('/') % calendarID % QLatin1StringView("/events/") % eventID);
123 return url;
124}
125
126namespace
127{
128
129QString sendUpdatesPolicyToString(SendUpdatesPolicy policy)
130{
131 switch (policy) {
132 case SendUpdatesPolicy::All:
133 return QStringLiteral("all");
135 return QStringLiteral("externalOnly");
137 return QStringLiteral("none");
138 }
139 Q_UNREACHABLE();
140}
141
142static const QString sendUpatesQueryParam = QStringLiteral("sendUpdates");
143static const QString destinationQueryParam = QStringLiteral("destination");
144}
145
146QUrl updateEventUrl(const QString &calendarID, const QString &eventID, SendUpdatesPolicy updatePolicy)
147{
148 QUrl url(Private::GoogleApisUrl);
149 url.setPath(Private::CalendarBasePath % QLatin1Char('/') % calendarID % QLatin1StringView("/events/") % eventID);
150 QUrlQuery query(url);
151 query.addQueryItem(sendUpatesQueryParam, sendUpdatesPolicyToString(updatePolicy));
152 url.setQuery(query);
153 return url;
154}
155
156QUrl createEventUrl(const QString &calendarID, SendUpdatesPolicy updatePolicy)
157{
158 QUrl url(Private::GoogleApisUrl);
159 url.setPath(Private::CalendarBasePath % QLatin1Char('/') % calendarID % QLatin1StringView("/events"));
160 QUrlQuery query(url);
161 query.addQueryItem(sendUpatesQueryParam, sendUpdatesPolicyToString(updatePolicy));
162 url.setQuery(query);
163 return url;
164}
165
166QUrl importEventUrl(const QString &calendarID, SendUpdatesPolicy updatePolicy)
167{
168 QUrl url(Private::GoogleApisUrl);
169 url.setPath(Private::CalendarBasePath % QLatin1Char('/') % calendarID % QLatin1StringView("/events") % QLatin1StringView("/import"));
170 QUrlQuery query(url);
171 query.addQueryItem(sendUpatesQueryParam, sendUpdatesPolicyToString(updatePolicy));
172 url.setQuery(query);
173 return url;
174}
175
176QUrl removeEventUrl(const QString &calendarID, const QString &eventID)
177{
178 QUrl url(Private::GoogleApisUrl);
179 url.setPath(Private::CalendarBasePath % QLatin1Char('/') % calendarID % QLatin1StringView("/events/") % eventID);
180 return url;
181}
182
183QUrl moveEventUrl(const QString &sourceCalendar, const QString &destCalendar, const QString &eventID)
184{
185 QUrl url(Private::GoogleApisUrl);
186 url.setPath(Private::CalendarBasePath % QLatin1Char('/') % sourceCalendar % QLatin1StringView("/events/") % eventID % QLatin1StringView("/move"));
187 QUrlQuery query(url);
188 query.addQueryItem(destinationQueryParam, destCalendar);
189 url.setQuery(query);
190 return url;
191}
192
194{
195 QUrl url(Private::GoogleApisUrl);
196 url.setPath(QStringLiteral("/calendar/v3/freeBusy"));
197 return url;
198}
199
200namespace
201{
202
203static const auto kindParam = QStringLiteral("kind");
204static const auto idParam = QStringLiteral("id");
205static const auto etagParam = QStringLiteral("etag");
206
207static const auto nextSyncTokenParam = QStringLiteral("nextSyncToken");
208static const auto nextPageTokenParam = QStringLiteral("nextPageToken");
209static const auto pageTokenParam = QStringLiteral("pageToken");
210static const auto itemsParam = QStringLiteral("items");
211
212static const auto calendarSummaryParam = QStringLiteral("summary");
213static const auto calendarDescriptionParam = QStringLiteral("description");
214static const auto calendarLocationParam = QStringLiteral("location");
215static const auto calendarTimezoneParam = QStringLiteral("timeZone");
216static const auto calendarBackgroundColorParam = QStringLiteral("backgroundColor");
217static const auto calendarForegroundColorParam = QStringLiteral("foregroundColor");
218static const auto calendarAccessRoleParam = QStringLiteral("accessRole");
219static const auto calendarDefaultRemindersParam = QStringLiteral("defaultReminders");
220static const auto reminderMethodParam = QStringLiteral("method");
221static const auto reminderMinutesParam = QStringLiteral("minutes");
222
223static const auto eventiCalUIDParam = QStringLiteral("iCalUID");
224static const auto eventStatusParam = QStringLiteral("status");
225static const auto eventCreatedParam = QStringLiteral("created");
226static const auto eventUpdatedParam = QStringLiteral("updated");
227static const auto eventSummaryParam = QStringLiteral("summary");
228static const auto eventDescriptionParam = QStringLiteral("description");
229static const auto eventLocationParam = QStringLiteral("location");
230static const auto eventStartPram = QStringLiteral("start");
231static const auto eventEndParam = QStringLiteral("end");
232static const auto eventOriginalStartTimeParam = QStringLiteral("originalStartTime");
233static const auto eventTransparencyParam = QStringLiteral("transparency");
234static const auto eventOrganizerParam = QStringLiteral("organizer");
235static const auto eventAttendeesParam = QStringLiteral("attendees");
236static const auto eventRecurrenceParam = QStringLiteral("recurrence");
237static const auto eventRemindersParam = QStringLiteral("reminders");
238static const auto eventExtendedPropertiesParam = QStringLiteral("extendedProperties");
239static const auto eventRecurringEventIdParam = QStringLiteral("recurringEventId");
240
241static const auto attendeeDisplayNameParam = QStringLiteral("displayName");
242static const auto attendeeEmailParam = QStringLiteral("email");
243static const auto attendeeResponseStatusParam = QStringLiteral("responseStatus");
244static const auto attendeeOptionalParam = QStringLiteral("optional");
245
246static const auto organizerDisplayNameParam = QStringLiteral("displayName");
247static const auto organizerEmailParam = QStringLiteral("email");
248
249static const auto reminderUseDefaultParam = QStringLiteral("useDefault");
250static const auto reminderOverridesParam = QStringLiteral("overrides");
251
252static const auto propertyPrivateParam = QStringLiteral("private");
253static const auto propertySharedParam = QStringLiteral("shared");
254
255static const auto dateParam = QStringLiteral("date");
256static const auto dateTimeParam = QStringLiteral("dateTime");
257static const auto timeZoneParam = QStringLiteral("timeZone");
258
259static const auto calendarListKind = QLatin1StringView("calendar#calendarList");
260static const auto calendarListEntryKind = QLatin1StringView("calendar#calendarListEntry");
261static const auto calendarKind = QLatin1StringView("calendar#calendar");
262static const auto eventKind = QLatin1StringView("calendar#event");
263static const auto eventsKind = QLatin1StringView("calendar#events");
264
265static const auto writerAccessRole = QLatin1StringView("writer");
266static const auto ownerAccessRole = QLatin1StringView("owner");
267static const auto emailMethod = QLatin1StringView("email");
268static const auto popupMethod = QLatin1StringView("popup");
269
270static const auto confirmedStatus = QLatin1StringView("confirmed");
271static const auto canceledStatus = QLatin1StringView("cancelled");
272static const auto tentativeStatus = QLatin1StringView("tentative");
273static const auto acceptedStatus = QLatin1StringView("accepted");
274static const auto needsActionStatus = QLatin1StringView("needsAction");
275static const auto transparentTransparency = QLatin1StringView("transparent");
276static const auto opaqueTransparency = QLatin1StringView("opaque");
277static const auto declinedStatus = QLatin1StringView("declined");
278static const auto categoriesProperty = QLatin1StringView("categories");
279
280static const auto hangoutLinkParam = QStringLiteral("hangoutLink");
281
282static const auto eventTypeParam = QStringLiteral("eventType");
283
284}
285
287{
288 return QStringLiteral("3");
289}
290
292{
293 const auto document = QJsonDocument::fromJson(jsonData);
294 const auto calendar = document.toVariant().toMap();
295
296 if (calendar.value(kindParam).toString() != calendarListEntryKind && calendar.value(kindParam).toString() != calendarKind) {
297 return CalendarPtr();
298 }
299
300 return Private::JSONToCalendar(calendar).staticCast<Calendar>();
301}
302
303ObjectPtr Private::JSONToCalendar(const QVariantMap &data)
304{
305 auto calendar = CalendarPtr::create();
306
307 const auto id = QUrl::fromPercentEncoding(data.value(idParam).toByteArray());
308 calendar->setUid(id);
309 calendar->setEtag(data.value(etagParam).toString());
310 calendar->setTitle(data.value(calendarSummaryParam).toString());
311 calendar->setDetails(data.value(calendarDescriptionParam).toString());
312 calendar->setLocation(data.value(calendarLocationParam).toString());
313 calendar->setTimezone(data.value(calendarTimezoneParam).toString());
314 calendar->setBackgroundColor(QColor(data.value(calendarBackgroundColorParam).toString()));
315 calendar->setForegroundColor(QColor(data.value(calendarForegroundColorParam).toString()));
316
317 if ((data.value(calendarAccessRoleParam).toString() == writerAccessRole) || (data.value(calendarAccessRoleParam).toString() == ownerAccessRole)) {
318 calendar->setEditable(true);
319 } else {
320 calendar->setEditable(false);
321 }
322
323 const auto reminders = data.value(calendarDefaultRemindersParam).toList();
324 for (const auto &r : reminders) {
325 const auto reminder = r.toMap();
326
327 auto rem = ReminderPtr::create();
328 if (reminder.value(reminderMethodParam).toString() == emailMethod) {
329 rem->setType(KCalendarCore::Alarm::Email);
330 } else if (reminder.value(reminderMethodParam).toString() == popupMethod) {
331 rem->setType(KCalendarCore::Alarm::Display);
332 } else {
333 rem->setType(KCalendarCore::Alarm::Invalid);
334 }
335
336 rem->setStartOffset(KCalendarCore::Duration(reminder.value(reminderMinutesParam).toInt() * (-60)));
337
338 calendar->addDefaultReminer(rem);
339 }
340
341 return calendar.dynamicCast<Object>();
342}
343
345{
346 QVariantMap entry;
347
348 if (!calendar->uid().isEmpty()) {
349 entry.insert(idParam, calendar->uid());
350 }
351
352 entry.insert(calendarSummaryParam, calendar->title());
353 entry.insert(calendarDescriptionParam, calendar->details());
354 entry.insert(calendarLocationParam, calendar->location());
355 if (!calendar->timezone().isEmpty()) {
356 entry.insert(calendarTimezoneParam, calendar->timezone());
357 }
358
359 const auto document = QJsonDocument::fromVariant(entry);
360 return document.toJson(QJsonDocument::Compact);
361}
362
364{
365 const auto document = QJsonDocument::fromJson(jsonFeed);
366 const auto data = document.toVariant().toMap();
367
368 ObjectsList list;
369
370 if (data.value(kindParam).toString() == calendarListKind) {
371 if (data.contains(nextPageTokenParam)) {
372 feedData.nextPageUrl = fetchCalendarsUrl();
373 QUrlQuery query(feedData.nextPageUrl);
374 query.addQueryItem(pageTokenParam, data.value(nextPageTokenParam).toString());
375 feedData.nextPageUrl.setQuery(query);
376 }
377 } else {
378 return {};
379 }
380
381 const auto items = data.value(itemsParam).toList();
382 list.reserve(items.size());
383 for (const auto &i : items) {
384 list.push_back(Private::JSONToCalendar(i.toMap()));
385 }
386
387 return list;
388}
389
391{
392 QJsonParseError error;
393 QJsonDocument document = QJsonDocument::fromJson(jsonData, &error);
394 if (error.error != QJsonParseError::NoError) {
395 qCWarning(KGAPIDebug) << "Error parsing event JSON: " << error.errorString();
396 }
397 QVariantMap data = document.toVariant().toMap();
398 if (data.value(kindParam).toString() != eventKind) {
399 return EventPtr();
400 }
401
402 return Private::JSONToEvent(data).staticCast<Event>();
403}
404
405namespace
406{
407
408struct ParsedDt {
409 QDateTime dt;
410 bool isAllDay;
411};
412
413ParsedDt parseDt(const QVariantMap &data, const QString &timezone, bool isDtEnd)
414{
415 if (data.contains(dateParam)) {
416 auto dt = QDateTime::fromString(data.value(dateParam).toString(), Qt::ISODate);
417 if (isDtEnd) {
418 // Google reports all-day events to end on the next day, e.g. a
419 // Monday all-day event will be reporting as starting on Monday and
420 // ending on Tuesday, while KCalendarCore/iCal uses the same day for
421 // dtEnd, so adjust the end date here.
422 dt = dt.addDays(-1);
423 }
424 return {dt, true};
425 } else if (data.contains(dateTimeParam)) {
426 auto dt = Utils::rfc3339DateFromString(data.value(dateTimeParam).toString());
427 // If there's a timezone specified in the "start" entity, then use it
428 if (data.contains(timeZoneParam)) {
429 const QTimeZone tz = QTimeZone(data.value(timeZoneParam).toString().toUtf8());
430 if (tz.isValid()) {
431 dt = dt.toTimeZone(tz);
432 } else {
433 qCWarning(KGAPIDebug) << "Invalid timezone" << data.value(timeZoneParam).toString();
434 }
435
436 // Otherwise try to fallback to calendar-wide timezone
437 } else if (!timezone.isEmpty()) {
438 const QTimeZone tz(timezone.toUtf8());
439 if (tz.isValid()) {
440 dt.setTimeZone(tz);
441 } else {
442 qCWarning(KGAPIDebug) << "Invalid timezone" << timezone;
443 }
444 }
445 return {dt, false};
446 } else {
447 return {{}, false};
448 }
449}
450
451void setEventCategories(EventPtr &event, const QVariantMap &properties)
452{
453 for (auto iter = properties.cbegin(), end = properties.cend(); iter != end; ++iter) {
454 if (iter.key() == categoriesProperty) {
455 event->setCategories(iter.value().toString());
456 }
457 }
458}
459
460} // namespace
461
462ObjectPtr Private::JSONToEvent(const QVariantMap &data, const QString &timezone)
463{
464 auto event = EventPtr::create();
465
466 event->setId(data.value(idParam).toString());
467 event->setHangoutLink(data.value(hangoutLinkParam).toString());
468 event->setUid(data.value(eventiCalUIDParam).toString());
469 event->setEtag(data.value(etagParam).toString());
470
471 if (data.value(eventStatusParam).toString() == confirmedStatus) {
473 } else if (data.value(eventStatusParam).toString() == canceledStatus) {
475 event->setDeleted(true);
476 } else if (data.value(eventStatusParam).toString() == tentativeStatus) {
478 } else {
479 event->setStatus(KCalendarCore::Incidence::StatusNone);
480 }
481
482 event->setCreated(Utils::rfc3339DateFromString(data.value(eventCreatedParam).toString()));
483 event->setLastModified(Utils::rfc3339DateFromString(data.value(eventUpdatedParam).toString()));
484 event->setSummary(data.value(eventSummaryParam).toString());
485 event->setDescription(data.value(eventDescriptionParam).toString());
486 event->setLocation(data.value(eventLocationParam).toString());
487
488 const auto dtStart = parseDt(data.value(eventStartPram).toMap(), timezone, false);
489 event->setDtStart(dtStart.dt);
490 event->setAllDay(dtStart.isAllDay);
491
492 const auto dtEnd = parseDt(data.value(eventEndParam).toMap(), timezone, true);
493 event->setDtEnd(dtEnd.dt);
494
495 if (data.contains(eventOriginalStartTimeParam)) {
496 const auto recurrenceId = parseDt(data.value(eventOriginalStartTimeParam).toMap(), timezone, false);
497 event->setRecurrenceId(recurrenceId.dt);
498 }
499
500 if (data.value(eventTransparencyParam).toString() == transparentTransparency) {
501 event->setTransparency(Event::Transparent);
502 } else { /* Assume opaque as default transparency */
503 event->setTransparency(Event::Opaque);
504 }
505
506 const auto attendees = data.value(eventAttendeesParam).toList();
507 for (const auto &a : attendees) {
508 const auto att = a.toMap();
509 KCalendarCore::Attendee attendee(att.value(attendeeDisplayNameParam).toString(), att.value(attendeeEmailParam).toString());
510 const auto responseStatus = att.value(attendeeResponseStatusParam).toString();
511 if (responseStatus == acceptedStatus) {
512 attendee.setStatus(KCalendarCore::Attendee::Accepted);
513 } else if (responseStatus == declinedStatus) {
514 attendee.setStatus(KCalendarCore::Attendee::Declined);
515 } else if (responseStatus == tentativeStatus) {
516 attendee.setStatus(KCalendarCore::Attendee::Tentative);
517 } else {
518 attendee.setStatus(KCalendarCore::Attendee::NeedsAction);
519 }
520
521 if (att.value(attendeeOptionalParam).toBool()) {
523 }
524 const auto uid = att.value(idParam).toString();
525 if (!uid.isEmpty()) {
526 attendee.setUid(uid);
527 } else {
528 // Set some UID, just so that the results are reproducible
529 attendee.setUid(QString::number(qHash(attendee.email())));
530 }
531 event->addAttendee(attendee, true);
532 }
533
534 /* According to RFC, only events with attendees can have an organizer.
535 * Google seems to ignore it, so we must take care of it here */
536 if (event->attendeeCount() > 0) {
537 KCalendarCore::Person organizer;
538 const auto organizerData = data.value(eventOrganizerParam).toMap();
539 organizer.setName(organizerData.value(organizerDisplayNameParam).toString());
540 organizer.setEmail(organizerData.value(organizerEmailParam).toString());
541 event->setOrganizer(organizer);
542 }
543
544 const QStringList recrs = data.value(eventRecurrenceParam).toStringList();
545 for (const QString &rec : recrs) {
547 const QStringView recView(rec);
548 if (recView.left(5) == QLatin1StringView("RRULE")) {
549 auto recurrenceRule = std::make_unique<KCalendarCore::RecurrenceRule>();
550 const auto ok = format.fromString(recurrenceRule.get(), rec.mid(6));
551 Q_UNUSED(ok)
552 recurrenceRule->setRRule(rec);
553 event->recurrence()->addRRule(recurrenceRule.release());
554 } else if (recView.left(6) == QLatin1StringView("EXRULE")) {
555 auto recurrenceRule = std::make_unique<KCalendarCore::RecurrenceRule>();
556 const auto ok = format.fromString(recurrenceRule.get(), rec.mid(7));
557 Q_UNUSED(ok)
558 recurrenceRule->setRRule(rec);
559 event->recurrence()->addExRule(recurrenceRule.release());
560 } else if (recView.left(6) == QLatin1StringView("EXDATE")) {
561 KCalendarCore::DateList exdates = Private::parseRDate(rec);
562 event->recurrence()->setExDates(exdates);
563 } else if (recView.left(5) == QLatin1StringView("RDATE")) {
564 KCalendarCore::DateList rdates = Private::parseRDate(rec);
565 event->recurrence()->setRDates(rdates);
566 }
567 }
568
569 const auto reminders = data.value(eventRemindersParam).toMap();
570 if (reminders.contains(reminderUseDefaultParam) && reminders.value(reminderUseDefaultParam).toBool()) {
571 event->setUseDefaultReminders(true);
572 } else {
573 event->setUseDefaultReminders(false);
574 }
575
576 const auto overrides = reminders.value(reminderOverridesParam).toList();
577 for (const auto &r : overrides) {
578 const auto reminderOverride = r.toMap();
580 alarm->setTime(event->dtStart());
581
582 if (reminderOverride.value(reminderMethodParam).toString() == popupMethod) {
583 alarm->setType(KCalendarCore::Alarm::Display);
584 } else if (reminderOverride.value(reminderMethodParam).toString() == emailMethod) {
585 alarm->setType(KCalendarCore::Alarm::Email);
586 } else {
587 alarm->setType(KCalendarCore::Alarm::Invalid);
588 continue;
589 }
590
591 alarm->setStartOffset(KCalendarCore::Duration(reminderOverride.value(reminderMinutesParam).toInt() * (-60)));
592 alarm->setEnabled(true);
593 event->addAlarm(alarm);
594 }
595
596 const auto extendedProperties = data.value(eventExtendedPropertiesParam).toMap();
597 setEventCategories(event, extendedProperties.value(propertyPrivateParam).toMap());
598 setEventCategories(event, extendedProperties.value(propertySharedParam).toMap());
599
600 if (const auto eventType = data.value(eventTypeParam).toString(); !eventType.isEmpty()) {
601 event->setEventType(eventTypeFromString(eventType));
602 } else {
603 event->setEventType(Event::EventType::Default);
604 }
605
606 return event.dynamicCast<Object>();
607}
608
609namespace
610{
611
612enum class SerializeDtFlag { AllDay = 1 << 0, IsDtEnd = 1 << 1, HasRecurrence = 1 << 2 };
613using SerializeDtFlags = QFlags<SerializeDtFlag>;
614
615QVariantMap serializeDt(const EventPtr &event, const QDateTime &dt, SerializeDtFlags flags)
616{
617 QVariantMap rv;
618 if (flags & SerializeDtFlag::AllDay) {
619 /* For Google, all-day events starts on Monday and ends on Tuesday,
620 * while in KDE, it both starts and ends on Monday. */
621 const auto adjusted = dt.addDays((flags & SerializeDtFlag::IsDtEnd) ? 1 : 0);
622 rv.insert(dateParam, adjusted.toString(QStringLiteral("yyyy-MM-dd")));
623 } else {
624 rv.insert(dateTimeParam, Utils::rfc3339DateToString(dt));
625 QString tzEnd = QString::fromUtf8(dt.timeZone().id());
626 if (flags & SerializeDtFlag::HasRecurrence && tzEnd.isEmpty()) {
627 tzEnd = QString::fromUtf8(QTimeZone::utc().id());
628 }
629 if (!tzEnd.isEmpty()) {
630 rv.insert(timeZoneParam, Private::checkAndConverCDOTZID(tzEnd, event));
631 }
632 }
633
634 return rv;
635}
636
637} // namespace
638
640{
641 QVariantMap data;
642
643 data.insert(kindParam, eventKind);
644
645 if (!(flags & EventSerializeFlag::NoID)) {
646 data.insert(idParam, event->id());
647 }
648
649 data.insert(eventiCalUIDParam, event->uid());
650
651 if (event->status() == KCalendarCore::Incidence::StatusConfirmed) {
652 data.insert(eventStatusParam, confirmedStatus);
653 } else if (event->status() == KCalendarCore::Incidence::StatusCanceled) {
654 data.insert(eventStatusParam, canceledStatus);
655 } else if (event->status() == KCalendarCore::Incidence::StatusTentative) {
656 data.insert(eventStatusParam, tentativeStatus);
657 }
658
659 data.insert(eventSummaryParam, event->summary());
660 data.insert(eventDescriptionParam, event->description());
661 data.insert(eventLocationParam, event->location());
662
663 QVariantList recurrence;
665 const auto exRules = event->recurrence()->exRules();
666 const auto rRules = event->recurrence()->rRules();
667 recurrence.reserve(rRules.size() + rRules.size() + 2);
668 for (KCalendarCore::RecurrenceRule *rRule : rRules) {
669 recurrence.push_back(format.toString(rRule).remove(QStringLiteral("\r\n")));
670 }
671 for (KCalendarCore::RecurrenceRule *rRule : exRules) {
672 recurrence.push_back(format.toString(rRule).remove(QStringLiteral("\r\n")));
673 }
674
675 QStringList dates;
676 const auto rDates = event->recurrence()->rDates();
677 dates.reserve(rDates.size());
678 for (const auto &rDate : rDates) {
679 dates.push_back(rDate.toString(QStringLiteral("yyyyMMdd")));
680 }
681
682 if (!dates.isEmpty()) {
683 recurrence.push_back(QString(QStringLiteral("RDATE;VALUE=DATA:") + dates.join(QLatin1Char(','))));
684 }
685
686 dates.clear();
687 const auto exDates = event->recurrence()->exDates();
688 dates.reserve(exDates.size());
689 for (const auto &exDate : exDates) {
690 dates.push_back(exDate.toString(QStringLiteral("yyyyMMdd")));
691 }
692
693 if (!dates.isEmpty()) {
694 recurrence.push_back(QString(QStringLiteral("EXDATE;VALUE=DATE:") + dates.join(QLatin1Char(','))));
695 }
696
697 if (!recurrence.isEmpty()) {
698 data.insert(eventRecurrenceParam, recurrence);
699 }
700
701 SerializeDtFlags dtFlags;
702 if (event->allDay()) {
703 dtFlags |= SerializeDtFlag::AllDay;
704 }
705 if (!recurrence.isEmpty()) {
706 dtFlags |= SerializeDtFlag::HasRecurrence;
707 }
708
709 data.insert(eventStartPram, serializeDt(event, event->dtStart(), dtFlags));
710 data.insert(eventEndParam, serializeDt(event, event->dtEnd(), dtFlags | SerializeDtFlag::IsDtEnd));
711
712 if (event->hasRecurrenceId()) {
713 data.insert(eventOrganizerParam, serializeDt(event, event->recurrenceId(), dtFlags));
714 data.insert(eventRecurringEventIdParam, event->id());
715 }
716
717 if (event->transparency() == Event::Transparent) {
718 data.insert(eventTransparencyParam, transparentTransparency);
719 } else {
720 data.insert(eventTransparencyParam, opaqueTransparency);
721 }
722
723 QVariantList atts;
724 const auto attendees = event->attendees();
725 for (const auto &attee : attendees) {
726 QVariantMap att{{attendeeDisplayNameParam, attee.name()}, {attendeeEmailParam, attee.email()}};
727
728 if (attee.status() == KCalendarCore::Attendee::Accepted) {
729 att.insert(attendeeResponseStatusParam, acceptedStatus);
730 } else if (attee.status() == KCalendarCore::Attendee::Declined) {
731 att.insert(attendeeResponseStatusParam, declinedStatus);
732 } else if (attee.status() == KCalendarCore::Attendee::Tentative) {
733 att.insert(attendeeResponseStatusParam, tentativeStatus);
734 } else {
735 att.insert(attendeeResponseStatusParam, needsActionStatus);
736 }
737
738 if (attee.role() == KCalendarCore::Attendee::OptParticipant) {
739 att.insert(attendeeOptionalParam, true);
740 }
741 if (!attee.uid().isEmpty()) {
742 att.insert(idParam, attee.uid());
743 }
744 atts.append(att);
745 }
746
747 if (!atts.isEmpty()) {
748 data.insert(eventAttendeesParam, atts);
749
750 /* According to RFC, event without attendees should not have
751 * any organizer. */
752 const auto organizer = event->organizer();
753 if (!organizer.isEmpty()) {
754 data.insert(eventOrganizerParam, QVariantMap{{organizerDisplayNameParam, organizer.fullName()}, {organizerEmailParam, organizer.email()}});
755 }
756 }
757
758 QVariantList overrides;
759 const auto alarms = event->alarms();
760 for (const auto &alarm : alarms) {
761 QVariantMap reminderOverride;
762 if (alarm->type() == KCalendarCore::Alarm::Display) {
763 reminderOverride.insert(reminderMethodParam, popupMethod);
764 } else if (alarm->type() == KCalendarCore::Alarm::Email) {
765 reminderOverride.insert(reminderMethodParam, emailMethod);
766 } else {
767 continue;
768 }
769 reminderOverride.insert(reminderMinutesParam, (int)(alarm->startOffset().asSeconds() / -60));
770
771 overrides.push_back(reminderOverride);
772 }
773
774 data.insert(eventRemindersParam, QVariantMap{{reminderUseDefaultParam, false}, {reminderOverridesParam, overrides}});
775
776 if (!event->categories().isEmpty()) {
777 data.insert(eventExtendedPropertiesParam, QVariantMap{{propertySharedParam, QVariantMap{{categoriesProperty, event->categoriesStr()}}}});
778 }
779
780 // eventType not allowed in update, only in create
781 if (!data.contains(idParam)) {
782 data.insert(eventTypeParam, eventTypeToString(event->eventType()));
783 }
784
785 /* TODO: Implement support for additional features:
786 * https://developers.google.com/calendar/api/v3/reference/events/insert
787 */
788
789 const auto document = QJsonDocument::fromVariant(data);
790 return document.toJson(QJsonDocument::Compact);
791}
792
794{
795 const auto document = QJsonDocument::fromJson(jsonFeed);
796 const auto data = document.toVariant().toMap();
797
798 QString timezone;
799 if (data.value(kindParam) == eventsKind) {
800 if (data.contains(nextPageTokenParam)) {
801 QString calendarId = feedData.requestUrl.toString().remove(QStringLiteral("https://www.googleapis.com/calendar/v3/calendars/"));
802 calendarId = calendarId.left(calendarId.indexOf(QLatin1Char('/')));
803 feedData.nextPageUrl = feedData.requestUrl;
804 // replace the old pageToken with a new one
805 QUrlQuery query(feedData.nextPageUrl);
806 query.removeQueryItem(pageTokenParam);
807 query.addQueryItem(pageTokenParam, data.value(nextPageTokenParam).toString());
808 feedData.nextPageUrl.setQuery(query);
809 }
810 if (data.contains(timeZoneParam)) {
811 // This should always be in Olson format
812 timezone = data.value(timeZoneParam).toString();
813 }
814 if (data.contains(nextSyncTokenParam)) {
815 feedData.syncToken = data[nextSyncTokenParam].toString();
816 }
817 } else {
818 return {};
819 }
820
821 ObjectsList list;
822 const auto items = data.value(itemsParam).toList();
823 list.reserve(items.size());
824 for (const auto &i : items) {
825 list.push_back(Private::JSONToEvent(i.toMap(), timezone));
826 }
827
828 return list;
829}
830
831QString eventTypeToString(Event::EventType eventType)
832{
833 switch (eventType) {
834 case Event::EventType::Default:
835 return QStringLiteral("default");
836 case Event::EventType::FocusTime:
837 return QStringLiteral("focusTime");
838 case Event::EventType::OutOfOffice:
839 return QStringLiteral("outOfOffice");
840 case Event::EventType::WorkingLocation:
841 return QStringLiteral("workingLocation");
842 }
843
844 Q_UNREACHABLE();
845 return {};
846}
847
848Event::EventType eventTypeFromString(const QString &eventType)
849{
850 const auto eventTypeLc = eventType.toLower();
851 if (eventTypeLc == u"default") {
852 return Event::EventType::Default;
853 }
854 if (eventTypeLc == u"outofoffice") {
855 return Event::EventType::OutOfOffice;
856 }
857 if (eventTypeLc == u"focustime") {
858 return Event::EventType::FocusTime;
859 }
860 if (eventTypeLc == u"workinglocation") {
861 return Event::EventType::WorkingLocation;
862 }
863
864 return Event::EventType::Default;
865}
866
867/******************************** PRIVATE ***************************************/
868
869KCalendarCore::DateList Private::parseRDate(const QString &rule)
870{
872 QTimeZone tz;
873 QStringView value;
874 const auto left = QStringView(rule).left(rule.indexOf(QLatin1Char(':')));
875
876 const auto params = left.split(QLatin1Char(';'));
877 for (const auto &param : params) {
878 if (param.startsWith(QLatin1StringView("VALUE"))) {
879 value = param.mid(param.indexOf(QLatin1Char('=')) + 1);
880 } else if (param.startsWith(QLatin1StringView("TZID"))) {
881 auto _name = param.mid(param.indexOf(QLatin1Char('=')) + 1);
882 tz = QTimeZone(_name.toUtf8());
883 }
884 }
885 const auto datesStr = QStringView(rule).mid(rule.lastIndexOf(QLatin1Char(':')) + 1);
886 const auto dates = datesStr.split(QLatin1Char(','));
887 for (const auto &date : dates) {
888 QDate dt;
889
890 if (value == QLatin1StringView("DATE")) {
891 dt = QDate::fromString(date.toString(), QStringLiteral("yyyyMMdd"));
892 } else if (value == QLatin1StringView("PERIOD")) {
893 const auto start = date.left(date.indexOf(QLatin1Char('/')));
894 QDateTime kdt = Utils::rfc3339DateFromString(start.toString());
895 if (tz.isValid()) {
896 kdt.setTimeZone(tz);
897 }
898
899 dt = kdt.date();
900 } else {
901 QDateTime kdt = Utils::rfc3339DateFromString(date.toString());
902 if (tz.isValid()) {
903 kdt.setTimeZone(tz);
904 }
905
906 dt = kdt.date();
907 }
908
909 list.push_back(dt);
910 }
911
912 return list;
913}
914
915namespace
916{
917
918/* Based on "Time Zone to CdoTimeZoneId Map"
919 * https://docs.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2007/aa563018(v=exchg.80)
920 *
921 * The mapping is not exact, since the CdoTimeZoneId usually refers to a
922 * region of multiple countries, so I always picked one of the countries
923 * in the specified region and used it's TZID.
924 */
925static const std::map<int, QLatin1StringView> MSCDOTZIDTable = {
926 {0, QLatin1StringView("UTC")},
927 {1, QLatin1StringView("Europe/London")}, /* GMT Greenwich Mean Time; Dublin, Edinburgh, London */
928 /* Seriously? *sigh* Let's handle these two in checkAndConvertCDOTZID() */
929 //{2, QLatin1StringView("Europe/Lisbon")}, /* GMT Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London */
930 //{2, QLatin1StringView("Europe/Sarajevo")}, /* GMT+01:00 Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb */
931 {3, QLatin1StringView("Europe/Paris")}, /* GMT+01:00 Paris, Madrid, Brussels, Copenhagen */
932 {4, QLatin1StringView("Europe/Berlin")}, /* GMT+01:00 Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna */
933 {5, QLatin1StringView("Europe/Bucharest")}, /* GMT+02:00 Bucharest */
934 {6, QLatin1StringView("Europe/Prague")}, /* GMT+01:00 Prague, Central Europe */
935 {7, QLatin1StringView("Europe/Athens")}, /* GMT+02:00 Athens, Istanbul, Minsk */
936 {8, QLatin1StringView("America/Brazil")}, /* GMT-03:00 Brasilia */
937 {9, QLatin1StringView("America/Halifax")}, /* GMT-04:00 Atlantic time (Canada) */
938 {10, QLatin1StringView("America/New_York")}, /* GMT-05:00 Eastern Time (US & Canada) */
939 {11, QLatin1StringView("America/Chicago")}, /* GMT-06:00 Central Time (US & Canada) */
940 {12, QLatin1StringView("America/Denver")}, /* GMT-07:00 Mountain Time (US & Canada) */
941 {13, QLatin1StringView("America/Los_Angeles")}, /* GMT-08:00 Pacific Time (US & Canada); Tijuana */
942 {14, QLatin1StringView("America/Anchorage")}, /* GMT-09:00 Alaska */
943 {15, QLatin1StringView("Pacific/Honolulu")}, /* GMT-10:00 Hawaii */
944 {16, QLatin1StringView("Pacific/Apia")}, /* GMT-11:00 Midway Island, Samoa */
945 {17, QLatin1StringView("Pacific/Auckland")}, /* GMT+12:00 Auckland, Wellington */
946 {18, QLatin1StringView("Australia/Brisbane")}, /* GMT+10:00 Brisbane, East Australia */
947 {19, QLatin1StringView("Australia/Adelaide")}, /* GMT+09:30 Adelaide, Central Australia */
948 {20, QLatin1StringView("Asia/Tokyo")}, /* GMT+09:00 Osaka, Sapporo, Tokyo */
949 {21, QLatin1StringView("Asia/Singapore")}, /* GMT+08:00 Kuala Lumpur, Singapore */
950 {22, QLatin1StringView("Asia/Bangkok")}, /* GMT+07:00 Bangkok, Hanoi, Jakarta */
951 {23, QLatin1StringView("Asia/Calcutta")}, /* GMT+05:30 Kolkata, Chennai, Mumbai, New Delhi, India Standard Time */
952 {24, QLatin1StringView("Asia/Dubai")}, /* GMT+04:00 Abu Dhabi, Muscat */
953 {25, QLatin1StringView("Asia/Tehran")}, /* GMT+03:30 Tehran */
954 {26, QLatin1StringView("Asia/Baghdad")}, /* GMT+03:00 Baghdad */
955 {27, QLatin1StringView("Asia/Jerusalem")}, /* GMT+02:00 Israel, Jerusalem Standard Time */
956 {28, QLatin1StringView("America/St_Johns")}, /* GMT-03:30 Newfoundland */
957 {29, QLatin1StringView("Atlantic/Portugal")}, /* GMT-01:00 Azores */
958 {30, QLatin1StringView("America/Noronha")}, /* GMT-02:00 Mid-Atlantic */
959 {31, QLatin1StringView("Africa/Monrovia")}, /* GMT Casablanca, Monrovia */
960 {32, QLatin1StringView("America/Argentina/Buenos_Aires")}, /* GMT-03:00 Buenos Aires, Georgetown */
961 {33, QLatin1StringView("America/La_Paz")}, /* GMT-04:00 Caracas, La Paz */
962 {34, QLatin1StringView("America/New_York")}, /* GMT-05:00 Indiana (East) */
963 {35, QLatin1StringView("America/Bogota")}, /* GMT-05:00 Bogota, Lima, Quito */
964 {36, QLatin1StringView("America/Winnipeg")}, /* GMT-06:00 Saskatchewan */
965 {37, QLatin1StringView("America/Mexico_City")}, /* GMT-06:00 Mexico City, Tegucigalpa */
966 {38, QLatin1StringView("America/Phoenix")}, /* GMT-07:00 Arizona */
967 {39, QLatin1StringView("Pacific/Kwajalein")}, /* GMT-12:00 Eniwetok, Kwajalein, Dateline Time */
968 {40, QLatin1StringView("Pacific/Fiji")}, /* GMT+12:00 Fušál, Kamchatka, Mashall Is. */
969 {41, QLatin1StringView("Pacific/Noumea")}, /* GMT+11:00 Magadan, Solomon Is., New Caledonia */
970 {42, QLatin1StringView("Australia/Hobart")}, /* GMT+10:00 Hobart, Tasmania */
971 {43, QLatin1StringView("Pacific/Guam")}, /* GMT+10:00 Guam, Port Moresby */
972 {44, QLatin1StringView("Australia/Darwin")}, /* GMT+09:30 Darwin */
973 {45, QLatin1StringView("Asia/Shanghai")}, /* GMT+08:00 Beijing, Chongqing, Hong Kong SAR, Urumqi */
974 {46, QLatin1StringView("Asia/Omsk")}, /* GMT+06:00 Almaty, Novosibirsk, North Central Asia */
975 {47, QLatin1StringView("Asia/Karachi")}, /* GMT+05:00 Islamabad, Karachi, Tashkent */
976 {48, QLatin1StringView("Asia/Kabul")}, /* GMT+04:30 Kabul */
977 {49, QLatin1StringView("Africa/Cairo")}, /* GMT+02:00 Cairo */
978 {50, QLatin1StringView("Africa/Harare")}, /* GMT+02:00 Harare, Pretoria */
979 {51, QLatin1StringView("Europe/Moscow")}, /* GMT+03:00 Moscow, St. Petersburg, Volgograd */
980 {53, QLatin1StringView("Atlantic/Cape_Verde")}, /* GMT-01:00 Cape Verde Is. */
981 {54, QLatin1StringView("Asia/Tbilisi")}, /* GMT+04:00 Baku, Tbilisi, Yerevan */
982 {55, QLatin1StringView("America/Tegucigalpa")}, /* GMT-06:00 Central America */
983 {56, QLatin1StringView("Africa/Nairobi")}, /* GMT+03:00 East Africa, Nairobi */
984 {58, QLatin1StringView("Asia/Yekaterinburg")}, /* GMT+05:00 Ekaterinburg */
985 {59, QLatin1StringView("Europe/Helsinki")}, /* GMT+02:00 Helsinki, Riga, Tallinn */
986 {60, QLatin1StringView("America/Greenland")}, /* GMT-03:00 Greenland */
987 {61, QLatin1StringView("Asia/Rangoon")}, /* GMT+06:30 Yangon (Rangoon) */
988 {62, QLatin1StringView("Asia/Katmandu")}, /* GMT+05:45 Kathmandu, Nepal */
989 {63, QLatin1StringView("Asia/Irkutsk")}, /* GMT+08:00 Irkutsk, Ulaan Bataar */
990 {64, QLatin1StringView("Asia/Krasnoyarsk")}, /* GMT+07:00 Krasnoyarsk */
991 {65, QLatin1StringView("America/Santiago")}, /* GMT-04:00 Santiago */
992 {66, QLatin1StringView("Asia/Colombo")}, /* GMT+06:00 Sri Jayawardenepura, Sri Lanka */
993 {67, QLatin1StringView("Pacific/Tongatapu")}, /* GMT+13:00 Nuku'alofa, Tonga */
994 {68, QLatin1StringView("Asia/Vladivostok")}, /* GMT+10:00 Vladivostok */
995 {69, QLatin1StringView("Africa/Bangui")}, /* GMT+01:00 West Central Africa */
996 {70, QLatin1StringView("Asia/Yakutsk")}, /* GMT+09:00 Yakutsk */
997 {71, QLatin1StringView("Asia/Dhaka")}, /* GMT+06:00 Astana, Dhaka */
998 {72, QLatin1StringView("Asia/Seoul")}, /* GMT+09:00 Seoul, Korea Standard time */
999 {73, QLatin1StringView("Australia/Perth")}, /* GMT+08:00 Perth, Western Australia */
1000 {74, QLatin1StringView("Asia/Kuwait")}, /* GMT+03:00 Arab, Kuwait, Riyadh */
1001 {75, QLatin1StringView("Asia/Taipei")}, /* GMT+08:00 Taipei */
1002 {76, QLatin1StringView("Australia/Sydney")} /* GMT+10:00 Canberra, Melbourne, Sydney */
1003};
1004
1005/* Based on "Microsoft Time Zone Index Values"
1006 * https://support.microsoft.com/en-gb/help/973627/microsoft-time-zone-index-values
1007 *
1008 * The mapping is not exact, since the TZID usually refers to a
1009 * region of multiple countries, so I always picked one of the countries
1010 * in the specified region and used it's TZID.
1011 *
1012 * The Olson timezones are taken from https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
1013 *
1014 * Note: using std::map, because it allows heterogeneous lookup, i.e. I can lookup the QLatin1StringView
1015 * keys by using QString value, which is not possible with Qt containers.
1016 */
1017static const std::map<QLatin1StringView, QLatin1StringView, std::less<>> MSSTTZTable = {
1018 {QLatin1StringView("Dateline Standard Time"), QLatin1StringView("Pacific/Kwajalein")}, /* (GMT-12:00) International Date Line West */
1019 {QLatin1StringView("Samoa Standard Time"), QLatin1StringView("Pacific/Apia")}, /* (GMT-11:00) Midway Island, Samoa */
1020 {QLatin1StringView("Hawaiian Standard Time"), QLatin1StringView("Pacific/Honolulu")}, /* (GMT-10:00) Hawaii */
1021 {QLatin1StringView("Alaskan Standard Time"), QLatin1StringView("America/Anchorage")}, /* (GMT-09:00) Alaska */
1022 {QLatin1StringView("Pacific Standard Time"), QLatin1StringView("America/Los_Angeles")}, /* (GMT-08:00) Pacific Time (US and Canada); Tijuana */
1023 {QLatin1StringView("Mountain Standard Time"), QLatin1StringView("America/Denver")}, /* (GMT-07:00) Mountain Time (US and Canada) */
1024 {QLatin1StringView("Mexico Standard Time 2"), QLatin1StringView("America/Chihuahua")}, /* (GMT-07:00) Chihuahua, La Paz, Mazatlan */
1025 {QLatin1StringView("U.S. Mountain Standard Time"), QLatin1StringView("America/Phoenix")}, /* (GMT-07:00) Arizona */
1026 {QLatin1StringView("Central Standard Time"), QLatin1StringView("America/Chicago")}, /* (GMT-06:00) Central Time (US and Canada */
1027 {QLatin1StringView("Canada Central Standard Time"), QLatin1StringView("America/Winnipeg")}, /* (GMT-06:00) Saskatchewan */
1028 {QLatin1StringView("Mexico Standard Time"), QLatin1StringView("America/Mexico_City")}, /* (GMT-06:00) Guadalajara, Mexico City, Monterrey */
1029 {QLatin1StringView("Central America Standard Time"), QLatin1StringView("America/Chicago")}, /* (GMT-06:00) Central America */
1030 {QLatin1StringView("Eastern Standard Time"), QLatin1StringView("America/New_York")}, /* (GMT-05:00) Eastern Time (US and Canada) */
1031 {QLatin1StringView("U.S. Eastern Standard Time"), QLatin1StringView("America/New_York")}, /* (GMT-05:00) Indiana (East) */
1032 {QLatin1StringView("S.A. Pacific Standard Time"), QLatin1StringView("America/Bogota")}, /* (GMT-05:00) Bogota, Lima, Quito */
1033 {QLatin1StringView("Atlantic Standard Time"), QLatin1StringView("America/Halifax")}, /* (GMT-04:00) Atlantic Time (Canada) */
1034 {QLatin1StringView("S.A. Western Standard Time"), QLatin1StringView("America/La_Paz")}, /* (GMT-04:00) Caracas, La Paz */
1035 {QLatin1StringView("Pacific S.A. Standard Time"), QLatin1StringView("America/Santiago")}, /* (GMT-04:00) Santiago */
1036 {QLatin1StringView("Newfoundland and Labrador Standard Time"), QLatin1StringView("America/St_Johns")}, /* (GMT-03:30) Newfoundland and Labrador */
1037 {QLatin1StringView("E. South America Standard Time"), QLatin1StringView("America/Brazil")}, /* (GMT-03:00) Brasilia */
1038 {QLatin1StringView("S.A. Eastern Standard Time"), QLatin1StringView("America/Argentina/Buenos_Aires")}, /* (GMT-03:00) Buenos Aires, Georgetown */
1039 {QLatin1StringView("Greenland Standard Time"), QLatin1StringView("America/Greenland")}, /* (GMT-03:00) Greenland */
1040 {QLatin1StringView("Mid-Atlantic Standard Time"), QLatin1StringView("America/Noronha")}, /* (GMT-02:00) Mid-Atlantic */
1041 {QLatin1StringView("Azores Standard Time"), QLatin1StringView("Atlantic/Portugal")}, /* (GMT-01:00) Azores */
1042 {QLatin1StringView("Cape Verde Standard Time"), QLatin1StringView("Atlantic/Cape_Verde")}, /* (GMT-01:00) Cape Verde Islands */
1043 {QLatin1StringView("GMT Standard Time"), QLatin1StringView("Europe/London")}, /* (GMT) Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London */
1044 {QLatin1StringView("Greenwich Standard Time"), QLatin1StringView("Africa/Casablanca")}, /* (GMT) Casablanca, Monrovia */
1045 {QLatin1StringView("Central Europe Standard Time"), QLatin1StringView("Europe/Prague")}, /* (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague */
1046 {QLatin1StringView("Central European Standard Time"), QLatin1StringView("Europe/Sarajevo")}, /* (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb */
1047 {QLatin1StringView("Romance Standard Time"), QLatin1StringView("Europe/Brussels")}, /* (GMT+01:00) Brussels, Copenhagen, Madrid, Paris */
1048 {QLatin1StringView("W. Europe Standard Time"), QLatin1StringView("Europe/Amsterdam")}, /* (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna */
1049 {QLatin1StringView("W. Central Africa Standard Time"), QLatin1StringView("Africa/Bangui")}, /* (GMT+01:00) West Central Africa */
1050 {QLatin1StringView("E. Europe Standard Time"), QLatin1StringView("Europe/Bucharest")}, /* (GMT+02:00) Bucharest */
1051 {QLatin1StringView("Egypt Standard Time"), QLatin1StringView("Africa/Cairo")}, /* (GMT+02:00) Cairo */
1052 {QLatin1StringView("FLE Standard Time"), QLatin1StringView("Europe/Helsinki")}, /* (GMT+02:00) Helsinki, Kiev, Riga, Sofia, Tallinn, Vilnius */
1053 {QLatin1StringView("GTB Standard Time"), QLatin1StringView("Europe/Athens")}, /* (GMT+02:00) Athens, Istanbul, Minsk */
1054 {QLatin1StringView("Israel Standard Time"), QLatin1StringView("Europe/Athens")}, /* (GMT+02:00) Jerusalem */
1055 {QLatin1StringView("South Africa Standard Time"), QLatin1StringView("Africa/Harare")}, /* (GMT+02:00) Harare, Pretoria */
1056 {QLatin1StringView("Russian Standard Time"), QLatin1StringView("Europe/Moscow")}, /* (GMT+03:00) Moscow, St. Petersburg, Volgograd */
1057 {QLatin1StringView("Arab Standard Time"), QLatin1StringView("Asia/Kuwait")}, /* (GMT+03:00) Kuwait, Riyadh */
1058 {QLatin1StringView("E. Africa Standard Time"), QLatin1StringView("Africa/Nairobi")}, /* (GMT+03:00) Nairobi */
1059 {QLatin1StringView("Arabic Standard Time"), QLatin1StringView("Asia/Baghdad")}, /* (GMT+03:00) Baghdad */
1060 {QLatin1StringView("Iran Standard Time"), QLatin1StringView("Asia/Tehran")}, /* (GMT+03:30) Tehran */
1061 {QLatin1StringView("Arabian Standard Time"), QLatin1StringView("Asia/Dubai")}, /* (GMT+04:00) Abu Dhabi, Muscat */
1062 {QLatin1StringView("Caucasus Standard Time"), QLatin1StringView("Asia/Tbilisi")}, /* (GMT+04:00) Baku, Tbilisi, Yerevan */
1063 {QLatin1StringView("Transitional Islamic State of Afghanistan Standard Time"), QLatin1StringView("Asia/Kabul")}, /* (GMT+04:30) Kabul */
1064 {QLatin1StringView("Ekaterinburg Standard Time"), QLatin1StringView("Asia/Yekaterinburg")}, /* (GMT+05:00) Ekaterinburg */
1065 {QLatin1StringView("West Asia Standard Time"), QLatin1StringView("Asia/Karachi")}, /* (GMT+05:00) Islamabad, Karachi, Tashkent */
1066 {QLatin1StringView("India Standard Time"), QLatin1StringView("Asia/Calcutta")}, /* (GMT+05:30) Chennai, Kolkata, Mumbai, New Delhi */
1067 {QLatin1StringView("Nepal Standard Time"), QLatin1StringView("Asia/Calcutta")}, /* (GMT+05:45) Kathmandu */
1068 {QLatin1StringView("Central Asia Standard Time"), QLatin1StringView("Asia/Dhaka")}, /* (GMT+06:00) Astana, Dhaka */
1069 {QLatin1StringView("Sri Lanka Standard Time"), QLatin1StringView("Asia/Colombo")}, /* (GMT+06:00) Sri Jayawardenepura */
1070 {QLatin1StringView("N. Central Asia Standard Time"), QLatin1StringView("Asia/Omsk")}, /* (GMT+06:00) Almaty, Novosibirsk */
1071 {QLatin1StringView("Myanmar Standard Time"), QLatin1StringView("Asia/Rangoon")}, /* (GMT+06:30) Yangon Rangoon */
1072 {QLatin1StringView("S.E. Asia Standard Time"), QLatin1StringView("Asia/Bangkok")}, /* (GMT+07:00) Bangkok, Hanoi, Jakarta */
1073 {QLatin1StringView("North Asia Standard Time"), QLatin1StringView("Asia/Krasnoyarsk")}, /* (GMT+07:00) Krasnoyarsk */
1074 {QLatin1StringView("China Standard Time"), QLatin1StringView("Asia/Shanghai")}, /* (GMT+08:00) Beijing, Chongqing, Hong Kong SAR, Urumqi */
1075 {QLatin1StringView("Singapore Standard Time"), QLatin1StringView("Asia/Singapore")}, /* (GMT+08:00) Kuala Lumpur, Singapore */
1076 {QLatin1StringView("Taipei Standard Time"), QLatin1StringView("Asia/Taipei")}, /* (GMT+08:00) Taipei */
1077 {QLatin1StringView("W. Australia Standard Time"), QLatin1StringView("Australia/Perth")}, /* (GMT+08:00) Perth */
1078 {QLatin1StringView("North Asia East Standard Time"), QLatin1StringView("Asia/Irkutsk")}, /* (GMT+08:00) Irkutsk, Ulaanbaatar */
1079 {QLatin1StringView("Korea Standard Time"), QLatin1StringView("Asia/Seoul")}, /* (GMT+09:00) Seoul */
1080 {QLatin1StringView("Tokyo Standard Time"), QLatin1StringView("Asia/Tokyo")}, /* (GMT+09:00) Osaka, Sapporo, Tokyo */
1081 {QLatin1StringView("Yakutsk Standard Time"), QLatin1StringView("Asia/Yakutsk")}, /* (GMT+09:00) Yakutsk */
1082 {QLatin1StringView("A.U.S. Central Standard Time"), QLatin1StringView("Australia/Darwin")}, /* (GMT+09:30) Darwin */
1083 {QLatin1StringView("Cen. Australia Standard Time"), QLatin1StringView("Australia/Adelaide")}, /* (GMT+09:30) Adelaide */
1084 {QLatin1StringView("A.U.S. Eastern Standard Time"), QLatin1StringView("Australia/Sydney")}, /* (GMT+10:00) Canberra, Melbourne, Sydney */
1085 {QLatin1StringView("E. Australia Standard Time"), QLatin1StringView("Australia/Brisbane")}, /* (GMT+10:00) Brisbane */
1086 {QLatin1StringView("Tasmania Standard Time"), QLatin1StringView("Australia/Hobart")}, /* (GMT+10:00) Hobart */
1087 {QLatin1StringView("Vladivostok Standard Time"), QLatin1StringView("Asia/Vladivostok")}, /* (GMT+10:00) Vladivostok */
1088 {QLatin1StringView("West Pacific Standard Time"), QLatin1StringView("Pacific/Guam")}, /* (GMT+10:00) Guam, Port Moresby */
1089 {QLatin1StringView("Central Pacific Standard Time"), QLatin1StringView("Pacific/Noumea")}, /* (GMT+11:00) Magadan, Solomon Islands, New Caledonia */
1090 {QLatin1StringView("Fiji Islands Standard Time"), QLatin1StringView("Pacific/Fiji")}, /* (GMT+12:00) Fiji Islands, Kamchatka, Marshall Islands */
1091 {QLatin1StringView("New Zealand Standard Time"), QLatin1StringView("Pacific/Auckland")}, /* (GMT+12:00) Auckland, Wellington */
1092 {QLatin1StringView("Tonga Standard Time"), QLatin1StringView("Pacific/Tongatapu")}, /* (GMT+13:00) Nuku'alofa */
1093 {QLatin1StringView("Azerbaijan Standard Time"), QLatin1StringView("America/Argentina/Buenos_Aires")}, /* (GMT-03:00) Buenos Aires */
1094 {QLatin1StringView("Middle East Standard Time"), QLatin1StringView("Asia/Beirut")}, /* (GMT+02:00) Beirut */
1095 {QLatin1StringView("Jordan Standard Time"), QLatin1StringView("Asia/Amman")}, /* (GMT+02:00) Amman */
1096 {QLatin1StringView("Central Standard Time (Mexico)"), QLatin1StringView("America/Mexico_City")}, /* (GMT-06:00) Guadalajara, Mexico City, Monterrey - New */
1097 {QLatin1StringView("Mountain Standard Time (Mexico)"), QLatin1StringView("America/Ojinaga")}, /* (GMT-07:00) Chihuahua, La Paz, Mazatlan - New */
1098 {QLatin1StringView("Pacific Standard Time (Mexico)"), QLatin1StringView("America/Tijuana")}, /* (GMT-08:00) Tijuana, Baja California */
1099 {QLatin1StringView("Namibia Standard Time"), QLatin1StringView("Africa/Windhoek")}, /* (GMT+02:00) Windhoek */
1100 {QLatin1StringView("Georgian Standard Time"), QLatin1StringView("Asia/Tbilisi")}, /* (GMT+03:00) Tbilisi */
1101 {QLatin1StringView("Central Brazilian Standard Time"), QLatin1StringView("America/Manaus")}, /*(GMT-04:00) Manaus */
1102 {QLatin1StringView("Montevideo Standard Time"), QLatin1StringView("America/Montevideo")}, /* (GMT-03:00) Montevideo */
1103 {QLatin1StringView("Armenian Standard Time"), QLatin1StringView("Asia/Yerevan")}, /* (GMT+04:00) Yerevan */
1104 {QLatin1StringView("Venezuela Standard Time"), QLatin1StringView("America/Caracas")}, /* (GMT-04:30) Caracas */
1105 {QLatin1StringView("Argentina Standard Time"), QLatin1StringView("America/Argentina/Buenos_Aires")}, /* (GMT-03:00) Buenos Aires */
1106 {QLatin1StringView("Morocco Standard Time"), QLatin1StringView("Africa/Casablanca")}, /* (GMT) Casablanca */
1107 {QLatin1StringView("Pakistan Standard Time"), QLatin1StringView("Asia/Karachi")}, /* (GMT+05:00) Islamabad, Karachi */
1108 {QLatin1StringView("Mauritius Standard Time"), QLatin1StringView("Indian/Mauritius")}, /* (GMT+04:00) Port Louis */
1109 {QLatin1StringView("UTC"), QLatin1StringView("UTC")}, /* (GMT) Coordinated Universal Time */
1110 {QLatin1StringView("Paraguay Standard Time"), QLatin1StringView("America/Asuncion")}, /* (GMT-04:00) Asuncion */
1111 {QLatin1StringView("Kamchatka Standard Time"), QLatin1StringView("Asia/Kamchatka")}, /* (GMT+12:00) Petropavlovsk-Kamchatsky */
1112};
1113} // namespace
1114
1115QString Private::checkAndConverCDOTZID(const QString &tzid, const EventPtr &event)
1116{
1117 /* Try to match the @tzid to any valid timezone we know. */
1118 QTimeZone tz(tzid.toLatin1());
1119 if (tz.isValid()) {
1120 /* Yay, @tzid is a valid TZID in Olson format */
1121 return tzid;
1122 }
1123
1124 /* Damn, no match. Parse the iCal and try to find X-MICROSOFT-CDO-TZID
1125 * property that we can match against the MSCDOTZIDTable */
1127 /* Use a copy of @event, otherwise it would be deleted when ptr is destroyed */
1129 const QString vcard = format.toICalString(incidence);
1130 const QStringList properties = vcard.split(QLatin1Char('\n'));
1131 int CDOId = -1;
1132 for (const QString &property : properties) {
1133 if (property.startsWith(u"X-MICROSOFT-CDO-TZID")) {
1134 QStringList parsed = property.split(QLatin1Char(':'));
1135 if (parsed.length() != 2) {
1136 break;
1137 }
1138
1139 CDOId = parsed.at(1).toInt();
1140 break;
1141 }
1142 }
1143
1144 /* Wheeee, we have X-MICROSOFT-CDO-TZID, try to map it to Olson format */
1145 if (CDOId > -1) {
1146 /* *sigh* Some expert in MS assigned the same ID to two different timezones... */
1147 if (CDOId == 2) {
1148 /* GMT Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London */
1149 if (tzid.contains(QLatin1StringView("Dublin")) || tzid.contains(QLatin1StringView("Edinburgh")) || tzid.contains(QLatin1StringView("Lisbon"))
1150 || tzid.contains(QLatin1StringView("London"))) {
1151 return QStringLiteral("Europe/London");
1152 }
1153
1154 /* GMT+01:00 Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb */
1155 else if (tzid.contains(QLatin1StringView("Sarajevo")) || tzid.contains(QLatin1StringView("Skopje")) || tzid.contains(QLatin1StringView("Sofija"))
1156 || tzid.contains(QLatin1StringView("Vilnius")) || tzid.contains(QLatin1StringView("Warsaw")) || tzid.contains(QLatin1StringView("Zagreb"))) {
1157 return QStringLiteral("Europe/Sarajevo");
1158 }
1159 }
1160
1161 const auto it = MSCDOTZIDTable.find(CDOId);
1162 if (it != MSCDOTZIDTable.cend()) {
1163 return it->second;
1164 }
1165 }
1166
1167 /* We failed to map to X-MICROSOFT-CDO-TZID. Let's try mapping the TZID
1168 * onto the Microsoft Standard Time Zones */
1169 const auto it = MSSTTZTable.find(tzid);
1170 if (it != MSSTTZTable.cend()) {
1171 return it->second;
1172 }
1173
1174 /* Fail/ Just return the original TZID and hope Google will accept it
1175 * (though we know it won't) */
1176 return tzid;
1177}
1178
1179} // namespace CalendarService
1180
1181} // namespace KGAPI2
QString toString(const Calendar::Ptr &calendar) override
QString toICalString(const Incidence::Ptr &incidence)
bool fromString(const Calendar::Ptr &calendar, const QString &string)
QString email() const
void setEmail(const QString &email)
QString fullName() const
void setName(const QString &name)
bool isEmpty() const
An object that represents a Google calendar.
Definition calendar.h:28
Represents a single event from Google Calendar.
Structure to store additional information about a feed.
Definition types.h:24
QString syncToken
Sync token that can be used for incremental updates by some of the services.
Definition types.h:42
QUrl requestUrl
Original URL of the request.
Definition types.h:39
QUrl nextPageUrl
Link to next page of feed.
Definition types.h:38
Q_SCRIPTABLE Q_NOREPLY void start()
AKONADI_CALENDAR_EXPORT KCalendarCore::Incidence::Ptr incidence(const Akonadi::Item &item)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
QByteArray eventToJSON(const EventPtr &event, EventSerializeFlags flags)
Serializes Event into JSON.
QUrl updateCalendarUrl(const QString &calendarID)
Returns URL for updating existing calendar.
QUrl fetchCalendarsUrl()
Returns URL for fetching calendars list.
QUrl removeCalendarUrl(const QString &calendarID)
Returns URL for removing an existing calendar.
QUrl updateEventUrl(const QString &calendarID, const QString &eventID, SendUpdatesPolicy updatePolicy)
Returns URL for updating a single event.
QUrl createEventUrl(const QString &calendarID, SendUpdatesPolicy updatePolicy)
Returns URL creating new events.
ObjectsList parseEventJSONFeed(const QByteArray &jsonFeed, FeedData &feedData)
Parses JSON feed into list of Events.
QUrl importEventUrl(const QString &calendarID, SendUpdatesPolicy updatePolicy)
Returns URL importing private copies of existing events.
QUrl createCalendarUrl()
Returns URL for creating a new calendar.
QUrl moveEventUrl(const QString &sourceCalendar, const QString &destCalendar, const QString &eventID)
Returns URL for moving event between calendars.
QByteArray calendarToJSON(const CalendarPtr &calendar)
Serializes calendar into JSON.
QUrl fetchCalendarUrl(const QString &calendarID)
Returns URL for fetching single calendar.
CalendarPtr JSONToCalendar(const QByteArray &jsonData)
Parses calendar JSON data into Calendar object.
QNetworkRequest prepareRequest(const QUrl &url)
Preparse a QNetworkRequest for given URL.
QString eventTypeToString(Event::EventType eventType)
Converts event type enum value to string.
EventPtr JSONToEvent(const QByteArray &jsonData)
Parses event JSON into Event object.
ObjectsList parseCalendarJSONFeed(const QByteArray &jsonFeed, FeedData &feedData)
Parses JSON feed into list of Calendars.
QString APIVersion()
Supported API version.
QUrl freeBusyQueryUrl()
Returns URL for freebusy queries.
QUrl fetchEventsUrl(const QString &calendarID)
Returns URL for fetching all events from a specific calendar.
Event::EventType eventTypeFromString(const QString &eventType)
Converts event type string to enum value.
QUrl fetchEventUrl(const QString &calendarID, const QString &eventID)
Returns URL for fetching a single event from a specific calendar.
QUrl removeEventUrl(const QString &calendarID, const QString &eventID)
Returns URL for removing events.
A job to fetch a single map tile described by a StaticMapUrl.
Definition blog.h:16
SendUpdatesPolicy
Determines whether Google Calendar should send updates to participants.
Definition enums.h:22
@ ExternalOnly
Send updates to all attendees.
@ None
Only send updates to non-Google Calendar participants.
KIOCORE_EXPORT QStringList list(const QString &fileClass)
KGuiItem properties()
const QList< QKeySequence > & end()
KTEXTEDITOR_EXPORT size_t qHash(KTextEditor::Cursor cursor, size_t seed=0) noexcept
QByteArray & insert(qsizetype i, QByteArrayView data)
QDate fromString(QStringView string, QStringView format, QCalendar cal)
QDateTime addDays(qint64 ndays) const const
QDate date() const const
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
void setTimeZone(const QTimeZone &toZone)
QTimeZone timeZone() const const
QString toString(QStringView format, QCalendar cal) const const
QDateTime toTimeZone(const QTimeZone &timeZone) const const
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonDocument fromVariant(const QVariant &variant)
QVariant toVariant() const const
const_reference at(qsizetype i) const const
void clear()
bool isEmpty() const const
qsizetype length() const const
void push_back(parameter_type value)
void reserve(qsizetype size)
T value(qsizetype i) const const
void setRawHeader(const QByteArray &headerName, const QByteArray &headerValue)
QSharedPointer< T > create(Args &&... args)
T * data() const const
QSharedPointer< X > dynamicCast() const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
QString number(double n, char format, int precision)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QByteArray toLatin1() const const
QString toLower() const const
QByteArray toUtf8() const const
QString join(QChar separator) const const
QStringView left(qsizetype length) const const
QStringView mid(qsizetype start, qsizetype length) const const
QList< QStringView > split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QByteArray id() const const
bool isValid() const const
QTimeZone utc()
QString fromPercentEncoding(const QByteArray &input)
void setPath(const QString &path, ParsingMode mode)
void setQuery(const QString &query, ParsingMode mode)
QString toString(FormattingOptions options) const const
QMap< QString, QVariant > toMap() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:51 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.