1/****************************************************************************
2**
3** Copyright (C) 2015 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the QtVersitOrganizer module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL21$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 2.1 or version 3 as published by the Free
20** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22** following information to ensure the GNU Lesser General Public License
23** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25**
26** As a special exception, The Qt Company gives you certain additional
27** rights. These rights are described in The Qt Company LGPL Exception
28** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29**
30** $QT_END_LICENSE$
31**
32****************************************************************************/
33
34#include "qversitorganizerimporter_p.h"
35
36#include <QtOrganizer/qorganizer.h>
37
38#include <QtVersit/qversitdocument.h>
39#include <QtVersit/qversitproperty.h>
40#include <QtVersit/private/qversitutils_p.h>
41
42#include "qversitorganizerdefs_p.h"
43#include "qversitorganizerhandler.h"
44#include "qversitorganizerpluginloader_p.h"
45#include "qversittimezonehandler.h"
46
47QTORGANIZER_USE_NAMESPACE
48QTVERSIT_USE_NAMESPACE
49
50QT_BEGIN_NAMESPACE_VERSITORGANIZER
51
52QVersitOrganizerImporterPrivate::QVersitOrganizerImporterPrivate(const QString& profile) :
53 mPropertyHandler(NULL),
54 mTimeZoneHandler(NULL),
55 mDurationSpecified(false)
56{
57 int versitPropertyCount =
58 sizeof(versitOrganizerDetailMappings)/sizeof(VersitOrganizerDetailMapping);
59 for (int i = 0; i < versitPropertyCount; i++) {
60 mPropertyMappings.insert(
61 akey: QLatin1String(versitOrganizerDetailMappings[i].versitPropertyName),
62 avalue: QPair<QOrganizerItemDetail::DetailType, int>(
63 versitOrganizerDetailMappings[i].detailType,
64 versitOrganizerDetailMappings[i].detailField));
65 }
66
67 mPluginPropertyHandlers = QVersitOrganizerPluginLoader::instance()->createOrganizerHandlers(profile);
68 mTimeZoneHandler = QVersitOrganizerPluginLoader::instance()->timeZoneHandler();
69}
70
71QVersitOrganizerImporterPrivate::~QVersitOrganizerImporterPrivate()
72{
73 foreach (QVersitOrganizerHandler* pluginHandler, mPluginPropertyHandlers) {
74 delete pluginHandler;
75 }
76}
77
78bool QVersitOrganizerImporterPrivate::importDocument(
79 const QVersitDocument& topLevel,
80 const QVersitDocument& subDocument,
81 QOrganizerItem* item,
82 QVersitOrganizerImporter::Error* error)
83{
84 if (subDocument.componentType() == QStringLiteral("VEVENT")) {
85 item->setType(QOrganizerItemType::TypeEvent);
86 } else if (subDocument.componentType() == QStringLiteral("VTODO")) {
87 item->setType(QOrganizerItemType::TypeTodo);
88 } else if (subDocument.componentType() == QStringLiteral("VJOURNAL")) {
89 item->setType(QOrganizerItemType::TypeJournal);
90 } else if (subDocument.componentType() == QStringLiteral("VTIMEZONE")) {
91 mTimeZones.addTimeZone(timezone: importTimeZone(document: subDocument));
92 *error = QVersitOrganizerImporter::NoError;
93 return false;
94 } else {
95 *error = QVersitOrganizerImporter::InvalidDocumentError;
96 return false;
97 }
98 const QList<QVersitProperty> properties = subDocument.properties();
99 if ( (subDocument.subDocuments().isEmpty()) && (properties.isEmpty())) {
100 *error = QVersitOrganizerImporter::EmptyDocumentError;
101 return false;
102 }
103 foreach (const QVersitProperty& property, properties) {
104 importProperty(document: subDocument, property, item);
105 }
106
107 if (!subDocument.subDocuments().isEmpty()) {
108 foreach (const QVersitDocument &nestedSubDoc, subDocument.subDocuments())
109 foreach (const QVersitProperty &nestedProp, nestedSubDoc.properties()) {
110 importProperty(document: nestedSubDoc, property: nestedProp, item);
111 if (nestedSubDoc.componentType() == QStringLiteral("VALARM"))
112 break;
113 }
114 }
115 // run plugin handlers
116 foreach (QVersitOrganizerImporterPropertyHandler* handler, mPluginPropertyHandlers) {
117 handler->subDocumentProcessed(topLevel, subDocument, item);
118 }
119 // run property handlers
120 if (mPropertyHandler) {
121 mPropertyHandler->subDocumentProcessed(topLevel, subDocument, item);
122 }
123 return true;
124}
125
126void QVersitOrganizerImporterPrivate::importProperty(
127 const QVersitDocument& document,
128 const QVersitProperty& property,
129 QOrganizerItem* item)
130{
131 QList<QOrganizerItemDetail> updatedDetails;
132
133 bool success = false;
134 if (property.name() == QStringLiteral("CREATED")) {
135 success = createTimestampCreated(property, item, updatedDetails: &updatedDetails);
136 } else if (property.name() == QStringLiteral("LAST-MODIFIED")) {
137 success = createTimestampModified(property, item, updatedDetails: &updatedDetails);
138 } else if (property.name() == QStringLiteral("X-QTPROJECT-VERSION")) {
139 success = createVersion(property, item, updatedDetails: &updatedDetails);
140 } else if (property.name() == QStringLiteral("PRIORITY")) {
141 success = createPriority(property, item, updatedDetails: &updatedDetails);
142 } else if (property.name() == QStringLiteral("COMMENT")) {
143 success = createComment(property, updatedDetails: &updatedDetails);
144 } else if (property.name() == QStringLiteral("X-QTPROJECT-EXTENDED-DETAIL")) {
145 success = createExtendedDetail(property, updatedDetails: &updatedDetails);
146 } else if (mPropertyMappings.contains(akey: property.name())) {
147 success = createSimpleDetail(property, item, updatedDetails: &updatedDetails);
148 } else if (document.componentType() == QStringLiteral("VEVENT")) {
149 if (property.name() == QStringLiteral("DTSTART")) {
150 success = createStartDateTime(property, item, updatedDetails: &updatedDetails);
151 } else if (property.name() == QStringLiteral("DTEND")) {
152 success = createEndDateTime(property, item, updatedDetails: &updatedDetails);
153 } else if (property.name() == QStringLiteral("DURATION")) {
154 success = createDuration(property, item, updatedDetails: &updatedDetails);
155 } else if (property.name() == QStringLiteral("RRULE")
156 || (property.name() == QStringLiteral("EXRULE"))) {
157 success = createRecurrenceRule(property, item, updatedDetails: &updatedDetails);
158 } else if (property.name() == QStringLiteral("RDATE")
159 || (property.name() == QStringLiteral("EXDATE"))) {
160 success = createRecurrenceDates(property, item, updatedDetails: &updatedDetails);
161 } else if (property.name() == QStringLiteral("RECURRENCE-ID")) {
162 success = createRecurrenceId(property, item, updatedDetails: &updatedDetails);
163 }
164 } else if (document.componentType() == QStringLiteral("VTODO")) {
165 if (property.name() == QStringLiteral("DTSTART")) {
166 success = createTodoStartDateTime(property, item, updatedDetails: &updatedDetails);
167 } else if (property.name() == QStringLiteral("DUE")) {
168 success = createDueDateTime(property, item, updatedDetails: &updatedDetails);
169 } else if (property.name() == QStringLiteral("RRULE")
170 || (property.name() == QStringLiteral("EXRULE"))) {
171 success = createRecurrenceRule(property, item, updatedDetails: &updatedDetails);
172 } else if (property.name() == QStringLiteral("RDATE")
173 || (property.name() == QStringLiteral("EXDATE"))) {
174 success = createRecurrenceDates(property, item, updatedDetails: &updatedDetails);
175 } else if (property.name() == QStringLiteral("STATUS")) {
176 success = createStatus(property, item, updatedDetails: &updatedDetails);
177 } else if (property.name() == QStringLiteral("PERCENT-COMPLETE")) {
178 success = createPercentageComplete(property, item, updatedDetails: &updatedDetails);
179 } else if (property.name() == QStringLiteral("COMPLETED")) {
180 success = createFinishedDateTime(property, item, updatedDetails: &updatedDetails);
181 } else if (property.name() == QStringLiteral("RECURRENCE-ID")) {
182 success = createRecurrenceId(property, item, updatedDetails: &updatedDetails);
183 }
184 } else if (document.componentType() == QStringLiteral("VALARM")) {
185 success = createItemReminder(valarmDocument: document, item, updatedDetails: &updatedDetails);
186 } else if (document.componentType() == QStringLiteral("VJOURNAL")) {
187 if (property.name() == QStringLiteral("DTSTART")) {
188 success = createJournalEntryDateTime(property, item, updatedDetails: &updatedDetails);
189 }
190 }
191
192 // run plugin handlers
193 foreach (QVersitOrganizerImporterPropertyHandler* handler, mPluginPropertyHandlers) {
194 handler->propertyProcessed(document, property, item: *item, alreadyProcessed: &success, updatedDetails: &updatedDetails);
195 }
196 // run the handler, if set
197 if (mPropertyHandler) {
198 mPropertyHandler->propertyProcessed(document, property, item: *item, alreadyProcessed: &success, updatedDetails: &updatedDetails);
199 }
200
201 foreach (QOrganizerItemDetail detail, updatedDetails) {
202 item->saveDetail(detail: &detail);
203 }
204}
205
206bool QVersitOrganizerImporterPrivate::createSimpleDetail(
207 const QVersitProperty& property,
208 QOrganizerItem* item,
209 QList<QOrganizerItemDetail>* updatedDetails)
210{
211 if (property.value().isEmpty())
212 return false;
213 QPair<QOrganizerItemDetail::DetailType, int> mapping = mPropertyMappings[property.name()];
214 QOrganizerItemDetail::DetailType definitionName = mapping.first;
215 int fieldName = mapping.second;
216 QOrganizerItemDetail detail(item->detail(detailType: definitionName));
217 if (detail.isEmpty())
218 detail = QOrganizerItemDetail(definitionName);
219 detail.setValue(field: fieldName, value: property.value());
220 updatedDetails->append(t: detail);
221 return true;
222}
223
224bool QVersitOrganizerImporterPrivate::createTimestampCreated(
225 const QVersitProperty& property,
226 QOrganizerItem* item,
227 QList<QOrganizerItemDetail>* updatedDetails) {
228 if (property.value().isEmpty())
229 return false;
230 QDateTime datetime = parseDateTime(property);
231 if (!datetime.isValid())
232 return false;
233 QOrganizerItemTimestamp timestamp(item->detail(detailType: QOrganizerItemDetail::TypeTimestamp));
234 timestamp.setCreated(datetime);
235 updatedDetails->append(t: timestamp);
236 return true;
237}
238
239bool QVersitOrganizerImporterPrivate::createTimestampModified(
240 const QVersitProperty& property,
241 QOrganizerItem* item,
242 QList<QOrganizerItemDetail>* updatedDetails) {
243 if (property.value().isEmpty())
244 return false;
245 QDateTime datetime = parseDateTime(property);
246 if (!datetime.isValid())
247 return false;
248 QOrganizerItemTimestamp timestamp(item->detail(detailType: QOrganizerItemDetail::TypeTimestamp));
249 timestamp.setLastModified(datetime);
250 updatedDetails->append(t: timestamp);
251 return true;
252}
253
254/*!
255 * Takes the first value in \a list, or an empty QString is if the list is empty.
256 */
257QString QVersitOrganizerImporterPrivate::takeFirst(QList<QString>& list) const
258{
259 return list.empty() ? QString() : list.takeFirst();
260}
261
262bool QVersitOrganizerImporterPrivate::createVersion(
263 const QVersitProperty& property,
264 QOrganizerItem* item,
265 QList<QOrganizerItemDetail>* updatedDetails) {
266
267 QVariant variant = property.variantValue();
268 if (property.valueType() != QVersitProperty::CompoundType
269 || variant.type() != QVariant::StringList) {
270 return false;
271 }
272 QStringList values = variant.toStringList();
273
274 bool conversionOk;
275 QOrganizerItemVersion version(item->detail(detailType: QOrganizerItemDetail::TypeVersion));
276 version.setVersion(takeFirst(list&: values).toInt(ok: &conversionOk));
277 QString extendedVersionString = takeFirst(list&: values);
278 if (!extendedVersionString.isEmpty())
279 version.setExtendedVersion(extendedVersionString.toLocal8Bit());
280
281 if (conversionOk) {
282 updatedDetails->append(t: version);
283 return true;
284 } else {
285 return false;
286 }
287}
288
289bool QVersitOrganizerImporterPrivate::createPriority(
290 const QVersitProperty& property,
291 QOrganizerItem* item,
292 QList<QOrganizerItemDetail>* updatedDetails) {
293 if (property.value().length() != 1)
294 return false;
295
296 bool ok;
297 int p = property.value().toInt(ok: &ok);
298 if (!ok)
299 return false;
300 QOrganizerItemPriority priority(item->detail(detailType: QOrganizerItemDetail::TypePriority));
301 priority.setPriority(QOrganizerItemPriority::Priority(p));
302 updatedDetails->append(t: priority);
303 return true;
304}
305
306bool QVersitOrganizerImporterPrivate::createComment(
307 const QVersitProperty& property,
308 QList<QOrganizerItemDetail>* updatedDetails) {
309 if (property.value().isEmpty())
310 return false;
311 QOrganizerItemComment comment;
312 comment.setComment(property.value());
313 updatedDetails->append(t: comment);
314 return true;
315}
316
317bool QVersitOrganizerImporterPrivate::createItemReminder(
318 const QVersitDocument& valarmDocument,
319 QOrganizerItem* item,
320 QList<QOrganizerItemDetail>* updatedDetails)
321{
322 int repetitionCount = 0;
323 int repetitionDelay = 0;
324 int secondsBeforeStart = 0;
325 bool alreadySetSecondsBeforeStart = false;
326 const QList<QVersitProperty> valarmProperties = valarmDocument.properties();
327 QString actionValue;
328 QVariantList attachValues;
329 QString descriptionValue;
330 QString summaryValue;
331 QStringList attendees;
332
333 foreach (const QVersitProperty &valarmProperty, valarmProperties) {
334 if (valarmProperty.name() == QStringLiteral("TRIGGER")) {
335 secondsBeforeStart = triggerToSecondsBeforeStart(triggerProperty: valarmProperty, item: *item);
336 alreadySetSecondsBeforeStart = true;
337 } else if (valarmProperty.name() == QStringLiteral("REPEAT")) {
338 repetitionCount = valarmProperty.value().toInt();
339 } else if (valarmProperty.name() == QStringLiteral("DURATION")) {
340 repetitionDelay = Duration::parseDuration(str: valarmProperty.value()).toSeconds();
341 } else if (valarmProperty.name() == QStringLiteral("ACTION")) {
342 actionValue = valarmProperty.value().toUpper();
343 } else if (valarmProperty.name() == QStringLiteral("ATTACH")) {
344 attachValues.append(t: valarmProperty.variantValue());
345 } else if (valarmProperty.name() == QStringLiteral("DESCRIPTION")) {
346 descriptionValue = valarmProperty.value();
347 } else if (valarmProperty.name() == QStringLiteral("SUMMARY")) {
348 summaryValue = valarmProperty.value();
349 } else if (valarmProperty.name() == QStringLiteral("ATTENDEE")) {
350 attendees << valarmProperty.value();
351 }
352 }
353 if ((actionValue.isEmpty()) || (!alreadySetSecondsBeforeStart) ) {
354 //ACTION and TRIGGER are mandatory in a VALARM component.
355 return false;
356 } else if (actionValue == QStringLiteral("AUDIO")) {
357 QOrganizerItemAudibleReminder audibleReminder;
358 audibleReminder.setRepetition(count: repetitionCount, delaySeconds: repetitionDelay);
359 audibleReminder.setSecondsBeforeStart(secondsBeforeStart);
360 if (!attachValues.isEmpty())
361 audibleReminder.setDataUrl(QUrl(attachValues.first().toString()));
362 updatedDetails->append(t: audibleReminder);
363 return true;
364 } else if (actionValue == QStringLiteral("DISPLAY")) {
365 if (descriptionValue.isNull())
366 return false;//Invalid since REQUIRED properties are not found
367 QOrganizerItemVisualReminder visualReminder;
368 visualReminder.setRepetition(count: repetitionCount, delaySeconds: repetitionDelay);
369 visualReminder.setSecondsBeforeStart(secondsBeforeStart);
370 if (!descriptionValue.isEmpty()) {
371 visualReminder.setMessage(descriptionValue);
372 updatedDetails->append(t: visualReminder);
373 return true;
374 } else {
375 return false;
376 }
377 } else if (actionValue == QStringLiteral("EMAIL")) {
378 if (descriptionValue.isNull() || summaryValue.isNull() || attendees.isEmpty())
379 return false;//Invalid since REQUIRED properties are not found
380 QOrganizerItemEmailReminder emailReminder;
381 emailReminder.setRepetition(count: repetitionCount, delaySeconds: repetitionDelay);
382 emailReminder.setSecondsBeforeStart(secondsBeforeStart);
383 emailReminder.setValue(field: QOrganizerItemEmailReminder::FieldBody, value: descriptionValue);
384 emailReminder.setValue(field: QOrganizerItemEmailReminder::FieldSubject, value: summaryValue);
385 if (!attachValues.isEmpty())
386 emailReminder.setValue(field: QOrganizerItemEmailReminder::FieldAttachments, value: attachValues);
387 emailReminder.setContents(subject: summaryValue, body: descriptionValue, attachments: attachValues);
388 emailReminder.setRecipients(attendees);
389 updatedDetails->append(t: emailReminder);
390 return true;
391 } else { //ACTION property had an invalid value
392 return false;
393 }
394}
395
396int QVersitOrganizerImporterPrivate::triggerToSecondsBeforeStart(const QVersitProperty &triggerProperty, const QOrganizerItem &item)
397{
398 int result = 0;
399
400 //The default value type for TRIGGER property is DURATION.
401 bool encodedAsDuration = true;
402
403 if (!triggerProperty.parameters().isEmpty()) {
404 const QString triggerValue = triggerProperty.parameters().value(QStringLiteral("VALUE")).toUpper();
405 if (triggerValue == QStringLiteral("DATE-TIME"))
406 encodedAsDuration = false;
407 else if ( (!triggerValue.isEmpty()) &&
408 (triggerValue != QStringLiteral("DURATION")) ) {
409 return 0;//Invalid trigger property...just return default value.
410 }
411 }
412
413 if (encodedAsDuration) {
414 const QString related = triggerProperty.parameters().value(QStringLiteral("RELATED")).toUpper();
415 result = Duration::parseDuration(str: triggerProperty.value()).toSeconds();
416 switch (item.type()) {
417 case QOrganizerItemType::TypeTodo:
418 case QOrganizerItemType::TypeTodoOccurrence: {
419 if (related == QStringLiteral("START")) {
420 QOrganizerTodoTime todoTime = item.detail(detailType: QOrganizerItemDetail::TypeTodoTime);
421 QDateTime relativeTrigger = todoTime.startDateTime().addSecs(secs: result);
422 result = relativeTrigger.secsTo(todoTime.dueDateTime());
423 } else if ( (related.isEmpty()) || (related == QStringLiteral("END")) ) {
424 result = (-1 * result);
425 }
426 break;
427 }
428 case QOrganizerItemType::TypeEvent:
429 case QOrganizerItemType::TypeEventOccurrence: {
430 if (related == QStringLiteral("END")) {
431 QOrganizerEventTime eventTime = item.detail(detailType: QOrganizerItemDetail::TypeEventTime);
432 QDateTime relativeTrigger = eventTime.endDateTime().addSecs(secs: result);
433 result = relativeTrigger.secsTo(eventTime.startDateTime());
434 } else if ( (related.isEmpty()) || (related == QStringLiteral("START")) ) {
435 result = (-1 * result);
436 }
437 break;
438 }
439 default:
440 break;
441 }
442 } else {
443 QDateTime absoluteTrigger = parseDateTime(str: triggerProperty.value());
444 switch (item.type()) {
445 case QOrganizerItemType::TypeTodo:
446 case QOrganizerItemType::TypeTodoOccurrence: {
447 QOrganizerTodoTime todoTime = item.detail(detailType: QOrganizerItemDetail::TypeTodoTime);
448 result = absoluteTrigger.secsTo(todoTime.dueDateTime());
449 break;
450 }
451 case QOrganizerItemType::TypeEvent:
452 case QOrganizerItemType::TypeEventOccurrence: {
453 QOrganizerEventTime eventTime = item.detail(detailType: QOrganizerItemDetail::TypeEventTime);
454 result = absoluteTrigger.secsTo(eventTime.startDateTime());
455 break;
456 }
457 default:
458 break;
459 }
460 }
461 return result >= 0 ? result: 0;
462}
463
464bool QVersitOrganizerImporterPrivate::createExtendedDetail(
465 const QVersitProperty &property,
466 QList<QOrganizerItemDetail> *updatedDetails) {
467 QOrganizerItemExtendedDetail extendedDetail;
468 const QVariant variant = property.variantValue();
469 if (property.valueType() != QVersitProperty::CompoundType
470 || variant.type() != QVariant::StringList)
471 return false;
472
473 QStringList values = variant.toStringList();
474 extendedDetail.setName(takeFirst(list&: values));
475 QVariant data;
476 if (VersitUtils::convertFromJson(json: takeFirst(list&: values), data: &data))
477 extendedDetail.setData(data);
478 else
479 return false;
480
481 updatedDetails->append(t: extendedDetail);
482 return true;
483}
484
485bool QVersitOrganizerImporterPrivate::createRecurrenceId(
486 const QVersitProperty& property,
487 QOrganizerItem* item,
488 QList<QOrganizerItemDetail>* updatedDetails) {
489 QDate date = parseDate(str: property.value());
490 if (!date.isValid())
491 return false;
492 QOrganizerItemParent origin(item->detail(detailType: QOrganizerItemDetail::TypeParent));
493 origin.setOriginalDate(date);
494 updatedDetails->append(t: origin);
495 item->setType(QOrganizerItemType::TypeEventOccurrence);
496 return true;
497}
498
499/*! Set the startDateTime field of the EventTimeRange detail. If the end date has been set from a
500 * DURATION, it will be updated.
501 */
502bool QVersitOrganizerImporterPrivate::createStartDateTime(
503 const QVersitProperty& property,
504 QOrganizerItem* item,
505 QList<QOrganizerItemDetail>* updatedDetails) {
506 if (property.value().isEmpty())
507 return false;
508 bool hasTime;
509 QDateTime newStart = parseDateTime(property, hasTime: &hasTime);
510 if (!newStart.isValid())
511 return false;
512 QOrganizerEventTime etr(item->detail(detailType: QOrganizerItemDetail::TypeEventTime));
513 if (mDurationSpecified) {
514 // Need to fix up the end date to match the duration of the event
515 QDateTime start = etr.startDateTime();
516 QDateTime end = etr.endDateTime();
517 if (!start.isValid()) {
518 // not having a start date set is treated as a start date of epoch
519 start = QDateTime(QDate(1970, 1, 1));
520 }
521 // newEnd = end + (newStart - start)
522 int durationDays = start.daysTo(newStart);
523 QDateTime newEnd = end.addDays(days: durationDays);
524 int durationSecs = start.addDays(days: durationDays).secsTo(newStart);
525 newEnd = newEnd.addSecs(secs: durationSecs);
526 etr.setEndDateTime(newEnd);
527 }
528 etr.setStartDateTime(newStart);
529 if (!etr.isAllDay() && !hasTime)
530 etr.setAllDay(true);
531 updatedDetails->append(t: etr);
532 return true;
533}
534
535/*! Set the endDateTime field of the EventTimeRange detail.
536 */
537bool QVersitOrganizerImporterPrivate::createEndDateTime(
538 const QVersitProperty& property,
539 QOrganizerItem* item,
540 QList<QOrganizerItemDetail>* updatedDetails) {
541 if (property.value().isEmpty())
542 return false;
543 bool hasTime;
544 QDateTime newEnd = parseDateTime(property, hasTime: &hasTime);
545 if (!newEnd.isValid())
546 return false;
547 QOrganizerEventTime etr(item->detail(detailType: QOrganizerItemDetail::TypeEventTime));
548 if (!etr.isAllDay() && !hasTime)
549 etr.setAllDay(true);
550
551 // In iCalendar, the end date is exclusive while in Qt Organizer, it is inclusive.
552 if (etr.isAllDay())
553 etr.setEndDateTime(newEnd.addDays(days: -1));
554 else
555 etr.setEndDateTime(newEnd);
556
557 updatedDetails->append(t: etr);
558 mDurationSpecified = false;
559 return true;
560}
561
562
563/*! Sets the endDateTime field of the EventTimeRange detail using a DURATION property.
564 */
565bool QVersitOrganizerImporterPrivate::createDuration(
566 const QVersitProperty& property,
567 QOrganizerItem* item,
568 QList<QOrganizerItemDetail>* updatedDetails) {
569 if (property.value().isEmpty())
570 return false;
571 Duration duration = Duration::parseDuration(str: property.value());
572 if (!duration.isValid())
573 return false;
574 QOrganizerEventTime etr(item->detail(detailType: QOrganizerItemDetail::TypeEventTime));
575 QDateTime startTime = etr.startDateTime();
576 if (!startTime.isValid()) {
577 // not having a start date set is treated as a start date of epoch
578 startTime = QDateTime(QDate(1970, 1, 1));
579 }
580 etr.setEndDateTime(
581 startTime.addDays(days: 7*duration.weeks() + duration.days())
582 .addSecs(secs: 3600*duration.hours() + 60*duration.minutes() + duration.seconds()));
583 updatedDetails->append(t: etr);
584 mDurationSpecified = true;
585 return true;
586}
587
588/*! Set the StartDateTime field of the TodoTimeRange detail.
589 */
590bool QVersitOrganizerImporterPrivate::createTodoStartDateTime(
591 const QVersitProperty& property,
592 QOrganizerItem* item,
593 QList<QOrganizerItemDetail>* updatedDetails) {
594 if (property.value().isEmpty())
595 return false;
596 bool hasTime;
597 QDateTime newStart = parseDateTime(property, hasTime: &hasTime);
598 if (!newStart.isValid())
599 return false;
600 QOrganizerTodoTime ttr(item->detail(detailType: QOrganizerItemDetail::TypeTodoTime));
601 ttr.setStartDateTime(newStart);
602 if (!ttr.isAllDay() && !hasTime)
603 ttr.setAllDay(true);
604 updatedDetails->append(t: ttr);
605 return true;
606}
607
608/*! Set the DueDateTime field of the TodoTimeRange detail.
609 */
610bool QVersitOrganizerImporterPrivate::createDueDateTime(
611 const QVersitProperty& property,
612 QOrganizerItem* item,
613 QList<QOrganizerItemDetail>* updatedDetails) {
614 if (property.value().isEmpty())
615 return false;
616 bool hasTime;
617 QDateTime newEnd = parseDateTime(property, hasTime: &hasTime);
618 if (!newEnd.isValid())
619 return false;
620 QOrganizerTodoTime ttr(item->detail(detailType: QOrganizerItemDetail::TypeTodoTime));
621 ttr.setDueDateTime(newEnd);
622 if (!ttr.isAllDay() && !hasTime)
623 ttr.setAllDay(true);
624 updatedDetails->append(t: ttr);
625 mDurationSpecified = false;
626 return true;
627}
628
629/*! Set the EntryDateTime field of the JournalTimeRange detail.
630 */
631bool QVersitOrganizerImporterPrivate::createJournalEntryDateTime(
632 const QVersitProperty& property,
633 QOrganizerItem* item,
634 QList<QOrganizerItemDetail>* updatedDetails) {
635 if (property.value().isEmpty())
636 return false;
637 QDateTime dateTime = parseDateTime(property);
638 if (!dateTime.isValid())
639 return false;
640 QOrganizerJournalTime jtr(item->detail(detailType: QOrganizerItemDetail::TypeJournalTime));
641 jtr.setEntryDateTime(dateTime);
642 updatedDetails->append(t: jtr);
643 return true;
644}
645
646/*! Parses a datetime stored in the \a property as an ISO 8601 datetime in basic format, either in
647 * UTC time zone, floating time zone, or (if a TZID parameter exists in \a property), as a foreign
648 * time zone (returned as a UTC datetime). Returns an invalid QDateTime if the string cannot be
649 * parsed.
650 *
651 * \a hasTime is set to true if the parsed date-time has a time, or false if it is a date only.
652 * (the time portion is set to some valid but arbitrary value).
653 */
654QDateTime QVersitOrganizerImporterPrivate::parseDateTime(const QVersitProperty& property,
655 bool* hasTime) const
656{
657 const QMultiHash<QString, QString> parameters = property.parameters();
658 if (parameters.find(QStringLiteral("VALUE"), QStringLiteral("DATE")) == parameters.constEnd()) {
659 // try parsing a datetime
660 if (hasTime)
661 *hasTime = true;
662 QDateTime datetime(parseDateTime(str: property.value()));
663 if (datetime.isValid() && datetime.timeSpec() == Qt::LocalTime) {
664 QMultiHash<QString, QString> params = property.parameters();
665 QString tzid = params.value(QStringLiteral("TZID"));
666 if (!tzid.isEmpty()) {
667 if (tzid.at(i: 0) == QLatin1Char('/') && mTimeZoneHandler)
668 datetime = mTimeZoneHandler->convertTimeZoneToUtc(datetime, timeZoneName: tzid);
669 else
670 datetime = mTimeZones.convert(dateTime: datetime, tzid);
671 }
672 }
673 return datetime;
674 } else {
675 if (hasTime)
676 *hasTime = false;
677 QDateTime retn;
678 retn.setDate(QDate::fromString(s: property.value(), QStringLiteral("yyyyMMdd")));
679 retn.setTime(QTime(0, 0, 0));
680 return retn;
681 }
682}
683
684/*! Parses \a str as an ISO 8601 datetime in basic format, either in UTC timezone or floating
685 * timezone. Returns an invalid QDateTime if the string cannot be parsed.
686 */
687QDateTime QVersitOrganizerImporterPrivate::parseDateTime(QString str) const
688{
689 bool utc = str.endsWith(c: QLatin1Char('Z'), cs: Qt::CaseInsensitive);
690 if (utc)
691 str.chop(n: 1); // take away z from end;
692 QDateTime dt(QDateTime::fromString(s: str, QStringLiteral("yyyyMMddTHHmmss")));
693 if (utc)
694 dt.setTimeSpec(Qt::UTC);
695 return dt;
696}
697
698/*!
699 * Imports a RRULE, EXRULE, RDATE or EXDATE property
700 */
701bool QVersitOrganizerImporterPrivate::createRecurrenceRule(
702 const QVersitProperty& property,
703 QOrganizerItem* item,
704 QList<QOrganizerItemDetail>* updatedDetails) {
705 if (property.value().isEmpty())
706 return false;
707 QOrganizerRecurrenceRule rule;
708 if (!parseRecurRule(str: property.value(), rule: &rule))
709 return false;
710 QOrganizerItemRecurrence detail(item->detail(detailType: QOrganizerItemDetail::TypeRecurrence));
711 if (property.name() == QStringLiteral("RRULE")) {
712 detail.setRecurrenceRules(detail.recurrenceRules() << rule);
713 } else if (property.name() == QStringLiteral("EXRULE")) {
714 detail.setExceptionRules(detail.exceptionRules() << rule);
715 }
716 updatedDetails->append(t: detail);
717 return true;
718}
719
720/*!
721 * Parses an iCalendar recurrence rule string \a str and puts the result in \a rule.
722 * Return true on success, false on failure.
723 */
724bool QVersitOrganizerImporterPrivate::parseRecurRule(const QString& str, QOrganizerRecurrenceRule* rule) const
725{
726 QStringList parts = str.split(sep: QLatin1Char(';'));
727 if (parts.size() == 0)
728 return false;
729
730 QString freqPart = parts.takeFirst();
731 QStringList freqParts = freqPart.split(sep: QLatin1Char('='));
732 if (freqParts.size() != 2)
733 return false;
734 if (freqParts.at(i: 0) != QStringLiteral("FREQ"))
735 return false;
736 QString freqValue = freqParts.at(i: 1);
737 if (freqValue == QStringLiteral("DAILY")) {
738 rule->setFrequency(QOrganizerRecurrenceRule::Daily);
739 } else if (freqValue == QStringLiteral("WEEKLY")) {
740 rule->setFrequency(QOrganizerRecurrenceRule::Weekly);
741 } else if (freqValue == QStringLiteral("MONTHLY")) {
742 rule->setFrequency(QOrganizerRecurrenceRule::Monthly);
743 } else if (freqValue == QStringLiteral("YEARLY")) {
744 rule->setFrequency(QOrganizerRecurrenceRule::Yearly);
745 } else {
746 return false;
747 }
748
749 foreach (const QString& part, parts) {
750 QStringList keyValue = part.split(sep: QLatin1Char('='));
751 if (keyValue.size() != 2)
752 return false;
753 parseRecurFragment(key: keyValue.at(i: 0), value: keyValue.at(i: 1), rule);
754 }
755 return true;
756}
757
758/*!
759 * Parses a fragment of an iCalendar string (the part between the semicolons) and updates \a rule.
760 * \a key is the part of the fragment before the equals sign and \a value is the part after.
761 */
762void QVersitOrganizerImporterPrivate::parseRecurFragment(const QString& key, const QString& value,
763 QOrganizerRecurrenceRule* rule) const
764{
765 if (key == QStringLiteral("INTERVAL")) {
766 bool ok;
767 int n = value.toInt(ok: &ok);
768 if (ok && n >= 1)
769 rule->setInterval(n);
770 } else if (key == QStringLiteral("COUNT")) {
771 bool ok;
772 int count = value.toInt(ok: &ok);
773 if (ok && count >= 0) {
774 rule->setLimit(count);
775 }
776 } else if (key == QStringLiteral("UNTIL")) {
777 QDate date;
778 if (value.contains(c: QLatin1Char('T'))) {
779 QDateTime dt = parseDateTime(str: value);
780 date = dt.date();
781 } else {
782 date = QDate::fromString(s: value, QStringLiteral("yyyyMMdd"));
783 }
784 if (date.isValid())
785 rule->setLimit(date);
786 } else if (key == QStringLiteral("BYDAY")) {
787 QSet<Qt::DayOfWeek> days;
788 QStringList dayParts = value.split(sep: QLatin1Char(','));
789 foreach (QString dayStr, dayParts) {
790 if (dayStr.length() < 2) {
791 // bad day specifier
792 continue;
793 } else if (dayStr.length() > 2) {
794 // parse something like -2SU, meaning the second-last Sunday
795 QString posStr = dayStr;
796 dayStr = dayStr.right(n: 2); // dayStr = last two chars
797 posStr.chop(n: 2); // posStr = all except last two chars
798 bool ok;
799 int pos = posStr.toInt(ok: &ok);
800 if (!ok)
801 continue;
802 rule->setPositions(QSet<int>() << pos);
803 }
804 int day = parseDayOfWeek(str: dayStr);
805 if (day != -1) {
806 days << (Qt::DayOfWeek)day;
807 }
808 }
809 if (!days.isEmpty()) {
810 rule->setDaysOfWeek(days);
811 }
812 } else if (key == QStringLiteral("BYMONTHDAY")) {
813 QSet<int> days = parseInts(str: value, min: -31, max: 31);
814 if (!days.isEmpty()) {
815 rule->setDaysOfMonth(days);
816 }
817 } else if (key == QStringLiteral("BYWEEKNO")) {
818 QSet<int> weeks = parseInts(str: value, min: -53, max: 53);
819 if (!weeks.isEmpty()) {
820 rule->setWeeksOfYear(weeks);
821 }
822 } else if (key == QStringLiteral("BYMONTH")) {
823 QSet<QOrganizerRecurrenceRule::Month> months;
824 QStringList monthParts = value.split(sep: QLatin1Char(','));
825 foreach (const QString& monthPart, monthParts) {
826 bool ok;
827 int month = monthPart.toInt(ok: &ok);
828 if (ok && month >= 1 && month <= 12) {
829 months << (QOrganizerRecurrenceRule::Month)month;
830 }
831 }
832 if (!months.isEmpty()) {
833 rule->setMonthsOfYear(months);
834 }
835 } else if (key == QStringLiteral("BYYEARDAY")) {
836 QSet<int> days = parseInts(str: value, min: -366, max: 366);
837 if (!days.isEmpty()) {
838 rule->setDaysOfYear(days);
839 }
840 } else if (key == QStringLiteral("BYSETPOS")) {
841 QSet<int> poss = parseInts(str: value, min: -366, max: 366);
842 if (!poss.isEmpty()) {
843 rule->setPositions(poss);
844 }
845 } else if (key == QStringLiteral("WKST")) {
846 int day = parseDayOfWeek(str: value);
847 if (day != -1) {
848 rule->setFirstDayOfWeek((Qt::DayOfWeek)day);
849 }
850 }
851}
852
853/*!
854 * Parses and returns a comma-separated list of integers. Only non-zero values between \a min and
855 * \a max (inclusive) are added
856 */
857QSet<int> QVersitOrganizerImporterPrivate::parseInts(const QString& str, int min, int max) const
858{
859 QSet<int> values;
860 QStringList parts = str.split(sep: QLatin1Char(','));
861 foreach (const QString& part, parts) {
862 bool ok;
863 int value = part.toInt(ok: &ok);
864 if (ok && value >= min && value <= max && value != 0) {
865 values << value;
866 }
867 }
868 return values;
869}
870
871/*!
872 * Parses an iCalendar two-character string representing a day of week and returns an int
873 * corresponding to Qt::DayOfWeek. Returns -1 on parse failure.
874 */
875int QVersitOrganizerImporterPrivate::parseDayOfWeek(const QString& str) const
876{
877 if (str == QStringLiteral("MO")) {
878 return Qt::Monday;
879 } else if (str == QStringLiteral("TU")) {
880 return Qt::Tuesday;
881 } else if (str == QStringLiteral("WE")) {
882 return Qt::Wednesday;
883 } else if (str == QStringLiteral("TH")) {
884 return Qt::Thursday;
885 } else if (str == QStringLiteral("FR")) {
886 return Qt::Friday;
887 } else if (str == QStringLiteral("SA")) {
888 return Qt::Saturday;
889 } else if (str == QStringLiteral("SU")) {
890 return Qt::Sunday;
891 } else {
892 return -1;
893 }
894}
895
896/*!
897 * Parses an iCalendar RDATE or EXDATE property and updates the recurrenceDates or
898 * exceptionDates in the Recurrence detail.
899 */
900bool QVersitOrganizerImporterPrivate::createRecurrenceDates(
901 const QVersitProperty& property,
902 QOrganizerItem* item,
903 QList<QOrganizerItemDetail>* updatedDetails)
904{
905 if (property.value().isEmpty())
906 return false;
907 QSet<QDate> dates;
908 if (!parseDates(str: property.value(), dates: &dates))
909 return false;
910 QOrganizerItemRecurrence detail(item->detail(detailType: QOrganizerItemDetail::TypeRecurrence));
911 if (property.name() == QStringLiteral("RDATE")) {
912 detail.setRecurrenceDates(detail.recurrenceDates() + dates);
913 } else if (property.name() == QStringLiteral("EXDATE")) {
914 detail.setExceptionDates(detail.exceptionDates() + dates);
915 }
916 updatedDetails->append(t: detail);
917 return true;
918}
919
920/*!
921 * Parses a string like "19970304,19970504,19970704" into a list of QDates
922 */
923bool QVersitOrganizerImporterPrivate::parseDates(const QString& str, QSet<QDate>* dates) const
924{
925 QStringList parts = str.split(sep: QLatin1Char(','));
926 if (parts.size() == 0)
927 return false;
928
929 foreach (QString part, parts) {
930 QDate date = parseDate(str: part);
931 if (date.isValid())
932 *dates << date;
933 else
934 return false;
935 }
936 return true;
937}
938
939/*!
940 * Parses a date in either yyyyMMdd or yyyyMMddTHHmmss format (in the latter case, ignoring the
941 * time)
942 */
943QDate QVersitOrganizerImporterPrivate::parseDate(QString str) const
944{
945 int tIndex = str.indexOf(c: QLatin1Char('T'));
946 if (tIndex >= 0) {
947 str = str.left(n: tIndex);
948 }
949 return QDate::fromString(s: str, QStringLiteral("yyyyMMdd"));
950}
951
952bool QVersitOrganizerImporterPrivate::createStatus(
953 const QVersitProperty& property,
954 QOrganizerItem* item,
955 QList<QOrganizerItemDetail>* updatedDetails) {
956 QOrganizerTodoProgress::Status status;
957 if (property.value() == QStringLiteral("COMPLETED"))
958 status = QOrganizerTodoProgress::StatusComplete;
959 else if (property.value() == QStringLiteral("NEEDS-ACTION"))
960 status = QOrganizerTodoProgress::StatusNotStarted;
961 else if (property.value() == QStringLiteral("IN-PROCESS"))
962 status = QOrganizerTodoProgress::StatusInProgress;
963 else
964 return false;
965
966 QOrganizerTodoProgress progress(item->detail(detailType: QOrganizerItemDetail::TypeTodoProgress));
967 progress.setStatus(status);
968 updatedDetails->append(t: progress);
969 return true;
970}
971
972bool QVersitOrganizerImporterPrivate::createPercentageComplete(
973 const QVersitProperty& property,
974 QOrganizerItem* item,
975 QList<QOrganizerItemDetail>* updatedDetails) {
976 bool ok = false;
977 int percent = property.value().toInt(ok: &ok);
978 if (!ok)
979 return false;
980
981 QOrganizerTodoProgress progress(item->detail(detailType: QOrganizerItemDetail::TypeTodoProgress));
982 progress.setPercentageComplete(percent);
983 updatedDetails->append(t: progress);
984 return true;
985}
986
987bool QVersitOrganizerImporterPrivate::createFinishedDateTime(
988 const QVersitProperty& property,
989 QOrganizerItem* item,
990 QList<QOrganizerItemDetail>* updatedDetails) {
991 if (property.value().isEmpty())
992 return false;
993 QDateTime datetime = parseDateTime(property);
994 if (!datetime.isValid())
995 return false;
996 QOrganizerTodoProgress progress(item->detail(detailType: QOrganizerItemDetail::TypeTodoProgress));
997 progress.setFinishedDateTime(datetime);
998 updatedDetails->append(t: progress);
999 return true;
1000}
1001
1002/*! Parse the iCalendar duration string \a str in an RDP fashion with a two symbol lookahead, and
1003 * returns a Duration that represents it. */
1004Duration Duration::parseDuration(QString str)
1005{
1006 QString token = nextToken(str: &str);
1007 if (token.isEmpty())
1008 return invalidDuration();
1009
1010 Duration dur;
1011 // Accept a + or - if present
1012 if (token == QStringLiteral("+")) {
1013 token = nextToken(str: &str);
1014 } else if (token == QStringLiteral("-")) {
1015 dur.setNegative(true);
1016 token = nextToken(str: &str);
1017 } else if (token.isEmpty()) {
1018 return invalidDuration();
1019 } else {
1020 // There was no + or - so keep parsing
1021 }
1022
1023 // Accept a P
1024 if (token != QStringLiteral("P")) {
1025 return invalidDuration();
1026 }
1027
1028 token = nextToken(str: &str);
1029 if (token.isEmpty()) {
1030 return invalidDuration();
1031 } else if (token == QStringLiteral("T")) {
1032 // we see a time
1033 parseDurationTime(str: &str, dur: &dur);
1034 } else if (token.at(i: 0).isDigit()) {
1035 // it's either a date or a week - we're not sure yet
1036 int value = token.toInt(); // always succeeds because nextToken next returns a mix of digits/nondigits
1037 token = nextToken(str: &str);
1038 if (token == QStringLiteral("D")) {
1039 // it's a date
1040 dur.setDays(value);
1041 token = nextToken(str: &str);
1042 // dates optionally define a time
1043 if (token == QStringLiteral("T"))
1044 parseDurationTime(str: &str, dur: &dur);
1045 } else if (token == QStringLiteral("W")) {
1046 dur.setWeeks(value);
1047 } else {
1048 return invalidDuration();
1049 }
1050 } else {
1051 return invalidDuration();
1052 }
1053
1054 // check that there aren't extra characters on the end
1055 if (!str.isEmpty())
1056 dur.setValid(false);
1057 return dur;
1058
1059}
1060
1061/*! Parse a duration string starting from after the "T" character. Removes parsed part from \a str
1062 * and updates \a dur with the findings.
1063 */
1064void Duration::parseDurationTime(QString* str, Duration* dur)
1065{
1066 QString token = nextToken(str);
1067 if (token.isEmpty() || !token.at(i: 0).isDigit())
1068 dur->setValid(false);
1069
1070 int value = token.toInt(); // always succeeds
1071
1072 token = nextToken(str);
1073 if (token == QStringLiteral("H")) {
1074 dur->setHours(value);
1075 if (!str->isEmpty())
1076 parseDurationMinutes(str, dur);
1077 } else if (token == QStringLiteral("M")) {
1078 dur->setMinutes(value);
1079 if (!str->isEmpty())
1080 parseDurationSeconds(str, dur);
1081 } else if (token == QStringLiteral("S")) {
1082 dur->setSeconds(value);
1083 }
1084}
1085
1086/*! Parse a duration string starting from the part describing the number of minutes. Removes parsed
1087 * part from \a str and updates \a dur with the findings.
1088 */
1089void Duration::parseDurationMinutes(QString* str, Duration* dur)
1090{
1091 QString token = nextToken(str);
1092 if (token.isEmpty() || !token.at(i: 0).isDigit())
1093 dur->setValid(false);
1094
1095 int value = token.toInt(); // always succeeds
1096 token = nextToken(str);
1097 if (token != QStringLiteral("M")) {
1098 dur->setValid(false);
1099 return;
1100 }
1101 dur->setMinutes(value);
1102
1103 if (!str->isEmpty())
1104 parseDurationSeconds(str, dur);
1105}
1106
1107/*! Parse a duration string starting from the part describing the number of seconds. Removes parsed
1108 * part from \a str and updates \a dur with the findings.
1109 */
1110void Duration::parseDurationSeconds(QString* str, Duration* dur)
1111{
1112 QString token = nextToken(str);
1113 if (token.isEmpty() || !token.at(i: 0).isDigit())
1114 dur->setValid(false);
1115
1116 int value = token.toInt(); // always succeeds
1117 token = nextToken(str);
1118 if (token != QStringLiteral("S")) {
1119 dur->setValid(false);
1120 return;
1121 }
1122 dur->setSeconds(value);
1123}
1124
1125/*! Removes and returns a "token" from the start of an iCalendar DURATION string, \a str. A token
1126 * is either a single +, - or upper-case letter, or a string of digits. If \a str is empty, an
1127 * empty string is returned. If \a str is not empty but starts with an invalid character, a null
1128 * string is returned.
1129 */
1130QString Duration::nextToken(QString* str)
1131{
1132 int len = str->length();
1133 if (len == 0)
1134 return QString::fromLatin1(str: ""); // empty (not null) QString
1135 QChar first = str->at(i: 0);
1136 if (first == QLatin1Char('+') || first == QLatin1Char('-') || first.isUpper()) {
1137 QString ret(str->left(n: 1));
1138 *str = str->mid(position: 1);
1139 return ret;
1140 } else if (first.isDigit()) {
1141 // find the largest n such that the leftmost n characters are digits
1142 int n = 1;
1143 for (n = 1; n < len && str->at(i: n).isDigit(); n++) {
1144 }
1145 QString ret = str->left(n);
1146 *str = str->mid(position: n);
1147 return ret;
1148 } else {
1149 return QString(); // null QString
1150 }
1151}
1152
1153TimeZone QVersitOrganizerImporterPrivate::importTimeZone(const QVersitDocument& document) const
1154{
1155 TimeZone timeZone;
1156 foreach (const QVersitProperty& property, document.properties()) {
1157 if (property.name() == QStringLiteral("TZID") && !property.value().isEmpty()) {
1158 timeZone.setTzid(property.value());
1159 }
1160 }
1161 foreach (const QVersitDocument& subDocument, document.subDocuments()) {
1162 timeZone.addPhase(phase: importTimeZonePhase(document: subDocument));
1163 }
1164 return timeZone;
1165}
1166
1167TimeZonePhase QVersitOrganizerImporterPrivate::importTimeZonePhase(const QVersitDocument& document) const
1168{
1169 TimeZonePhase phase;
1170 phase.setStandard(document.componentType() == QStringLiteral("STANDARD"));
1171
1172 foreach (const QVersitProperty& property, document.properties()) {
1173 if (property.name() == QStringLiteral("TZOFFSETTO")) {
1174 QString value(property.value());
1175 if (value.size() == 5
1176 && (value.at(i: 0) == QLatin1Char('+') || value.at(i: 0) == QLatin1Char('-'))
1177 && value.at(i: 1).isDigit()
1178 && value.at(i: 2).isDigit()
1179 && value.at(i: 3).isDigit()
1180 && value.at(i: 4).isDigit()) {
1181 phase.setUtcOffset((value.at(i: 0) == QLatin1Char('+') ? 1 : -1) * ( // deal with sign
1182 value.mid(position: 1, n: 2).toInt() * 3600 // hours part
1183 + value.mid(position: 3, n: 2).toInt() * 60 // minutes part
1184 ));
1185 }
1186 } else if (property.name() == QStringLiteral("DTSTART")) {
1187 QDateTime dt(parseDateTime(str: property.value()));
1188 if (dt.isValid() && dt.timeSpec() == Qt::LocalTime) {
1189 phase.setStartDateTime(dt);
1190 }
1191 } else if (property.name() == QStringLiteral("RRULE")) {
1192 QOrganizerRecurrenceRule rrule;
1193 if (parseRecurRule(str: property.value(), rule: &rrule)) {
1194 phase.setRecurrenceRule(rrule);
1195 }
1196 } else if (property.name() == QStringLiteral("RDATE")) {
1197 QSet<QDate> dates;
1198 if (parseDates(str: property.value(), dates: &dates)) {
1199 phase.setRecurrenceDates(dates);
1200 }
1201 }
1202 }
1203 return phase;
1204}
1205
1206QT_END_NAMESPACE_VERSITORGANIZER
1207

source code of qtpim/src/versitorganizer/qversitorganizerimporter_p.cpp