KCalendarCore

vcalformat.cpp
Go to the documentation of this file.
1/*
2 This file is part of the kcalcore library.
3
4 SPDX-FileCopyrightText: 1998 Preston Brown <pbrown@kde.org>
5 SPDX-FileCopyrightText: 2001 Cornelius Schumacher <schumacher@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9/**
10 @file
11 This file is part of the API for handling calendar data and
12 defines the VCalFormat base class.
13
14 This class implements the vCalendar format. It provides methods for
15 loading/saving/converting vCalendar format data into the internal
16 representation as Calendar and Incidences.
17
18 @brief
19 vCalendar format implementation.
20
21 @author Preston Brown <pbrown@kde.org>
22 @author Cornelius Schumacher <schumacher@kde.org>
23*/
24#include "vcalformat.h"
25#include "calendar.h"
26#include "calformat_p.h"
27#include "exceptions.h"
28
29#include "kcalendarcore_debug.h"
30
31extern "C" {
32#include <libical/vcc.h>
33#include <libical/vobject.h>
34}
35
36#include <QBitArray>
37#include <QFile>
38#include <QTextDocument> // for .toHtmlEscaped() and Qt::mightBeRichText()
39#include <QTimeZone>
40
41using namespace KCalendarCore;
42
43/**
44 Private class that helps to provide binary compatibility between releases.
45 @internal
46*/
47//@cond PRIVATE
48template<typename K>
49void removeAllVCal(QList<QSharedPointer<K>> &c, const QSharedPointer<K> &x)
50{
51 if (c.count() < 1) {
52 return;
53 }
54
55 int cnt = c.count(x);
56 if (cnt != 1) {
57 qCritical() << "There number of relatedTos for this incidence is " << cnt << " (there must be 1 relatedTo only)";
58 Q_ASSERT_X(false, "removeAllVCal", "Count is not 1.");
59 return;
60 }
61
62 c.remove(c.indexOf(x));
63}
64
65class KCalendarCore::VCalFormatPrivate : public CalFormatPrivate
66{
67public:
68 Calendar::Ptr mCalendar;
69 Event::List mEventsRelate; // Events with relations
70 Todo::List mTodosRelate; // To-dos with relations
71 QSet<QByteArray> mManuallyWrittenExtensionFields; // X- fields that are manually dumped
72};
73//@endcond
74
76 : CalFormat(new KCalendarCore::VCalFormatPrivate)
77{
78}
79
83
84static void mimeErrorHandler(char *e)
85{
86 qCWarning(KCALCORE_LOG) << "Error parsing vCalendar file:" << e;
87}
88
89bool VCalFormat::load(const Calendar::Ptr &calendar, const QString &fileName)
90{
92 d->mCalendar = calendar;
93
95
96 // this is not necessarily only 1 vcal. Could be many vcals, or include
97 // a vcard...
98 registerMimeErrorHandler(&mimeErrorHandler); // Note: vCalendar error handler provided by libical.
99 VObject *vcal = Parse_MIME_FromFileName(const_cast<char *>(QFile::encodeName(fileName).data()));
100 registerMimeErrorHandler(nullptr);
101
102 if (!vcal) {
104 return false;
105 }
106
107 // any other top-level calendar stuff should be added/initialized here
108
109 // put all vobjects into their proper places
110 auto savedTimeZoneId = d->mCalendar->timeZoneId();
111 populate(vcal);
112 d->mCalendar->setTimeZoneId(savedTimeZoneId);
113
114 // clean up from vcal API stuff
115 cleanVObjects(vcal);
116 cleanStrTbl();
117
118 return true;
119}
120
121bool VCalFormat::save(const Calendar::Ptr &calendar, const QString &fileName)
122{
123 Q_UNUSED(calendar);
124 Q_UNUSED(fileName);
125 qCWarning(KCALCORE_LOG) << "Saving VCAL is not supported";
126 return false;
127}
128
129bool VCalFormat::fromRawString(const Calendar::Ptr &calendar, const QByteArray &string)
130{
132 d->mCalendar = calendar;
133
134 if (!string.size()) {
135 return false;
136 }
137
138 VObject *vcal = Parse_MIME(string.data(), string.size());
139 if (!vcal) {
140 return false;
141 }
142
143 VObjectIterator i;
144 initPropIterator(&i, vcal);
145
146 // put all vobjects into their proper places
147 auto savedTimeZoneId = d->mCalendar->timeZoneId();
148 populate(vcal);
149 d->mCalendar->setTimeZoneId(savedTimeZoneId);
150
151 // clean up from vcal API stuff
152 cleanVObjects(vcal);
153 cleanStrTbl();
154
155 return true;
156}
157
159{
160 Q_UNUSED(calendar);
161
162 qCWarning(KCALCORE_LOG) << "Exporting into VCAL is not supported";
163 return {};
164}
165
167{
169 VObject *vo = nullptr;
170 VObjectIterator voi;
171 char *s = nullptr;
172
173 Todo::Ptr anEvent(new Todo);
174
175 // creation date
176 if ((vo = isAPropertyOf(vtodo, VCDCreatedProp)) != nullptr) {
177 anEvent->setCreated(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
178 deleteStr(s);
179 }
180
181 // unique id
182 vo = isAPropertyOf(vtodo, VCUniqueStringProp);
183 // while the UID property is preferred, it is not required. We'll use the
184 // default Event UID if none is given.
185 if (vo) {
186 anEvent->setUid(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
187 deleteStr(s);
188 }
189
190 // last modification date
191 if ((vo = isAPropertyOf(vtodo, VCLastModifiedProp)) != nullptr) {
192 anEvent->setLastModified(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
193 deleteStr(s);
194 } else {
195 anEvent->setLastModified(QDateTime::currentDateTimeUtc());
196 }
197
198 // organizer
199 // if our extension property for the event's ORGANIZER exists, add it.
200 if ((vo = isAPropertyOf(vtodo, ICOrganizerProp)) != nullptr) {
201 anEvent->setOrganizer(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
202 deleteStr(s);
203 } else {
204 if (d->mCalendar->owner().name() != QLatin1String("Unknown Name")) {
205 anEvent->setOrganizer(d->mCalendar->owner());
206 }
207 }
208
209 // attendees.
210 initPropIterator(&voi, vtodo);
211 while (moreIteration(&voi)) {
212 vo = nextVObject(&voi);
213 if (strcmp(vObjectName(vo), VCAttendeeProp) == 0) {
214 Attendee a;
215 VObject *vp;
216 s = fakeCString(vObjectUStringZValue(vo));
217 QString tmpStr = QString::fromUtf8(s);
218 deleteStr(s);
219 tmpStr = tmpStr.simplified();
220 int emailPos1;
221 if ((emailPos1 = tmpStr.indexOf(QLatin1Char('<'))) > 0) {
222 // both email address and name
223 int emailPos2 = tmpStr.lastIndexOf(QLatin1Char('>'));
224 a = Attendee(tmpStr.left(emailPos1 - 1), tmpStr.mid(emailPos1 + 1, emailPos2 - (emailPos1 + 1)));
225 } else if (tmpStr.indexOf(QLatin1Char('@')) > 0) {
226 // just an email address
227 a = Attendee(QString(), tmpStr);
228 } else {
229 // just a name
230 // WTF??? Replacing the spaces of a name and using this as email?
231 QString email = tmpStr.replace(QLatin1Char(' '), QLatin1Char('.'));
232 a = Attendee(tmpStr, email);
233 }
234
235 // is there an RSVP property?
236 if ((vp = isAPropertyOf(vo, VCRSVPProp)) != nullptr) {
237 a.setRSVP(vObjectStringZValue(vp));
238 }
239 // is there a status property?
240 if ((vp = isAPropertyOf(vo, VCStatusProp)) != nullptr) {
241 a.setStatus(readStatus(vObjectStringZValue(vp)));
242 }
243 // add the attendee
244 anEvent->addAttendee(a);
245 }
246 }
247
248 // description for todo
249 if ((vo = isAPropertyOf(vtodo, VCDescriptionProp)) != nullptr) {
250 s = fakeCString(vObjectUStringZValue(vo));
251 anEvent->setDescription(QString::fromUtf8(s), Qt::mightBeRichText(QString::fromUtf8(s)));
252 deleteStr(s);
253 }
254
255 // summary
256 if ((vo = isAPropertyOf(vtodo, VCSummaryProp))) {
257 s = fakeCString(vObjectUStringZValue(vo));
258 anEvent->setSummary(QString::fromUtf8(s), Qt::mightBeRichText(QString::fromUtf8(s)));
259 deleteStr(s);
260 }
261
262 // location
263 if ((vo = isAPropertyOf(vtodo, VCLocationProp)) != nullptr) {
264 s = fakeCString(vObjectUStringZValue(vo));
265 anEvent->setLocation(QString::fromUtf8(s), Qt::mightBeRichText(QString::fromUtf8(s)));
266 deleteStr(s);
267 }
268
269 // completed
270 // was: status
271 if ((vo = isAPropertyOf(vtodo, VCStatusProp)) != nullptr) {
272 s = fakeCString(vObjectUStringZValue(vo));
273 if (s && strcmp(s, "COMPLETED") == 0) {
274 anEvent->setCompleted(true);
275 } else {
276 anEvent->setCompleted(false);
277 }
278 deleteStr(s);
279 } else {
280 anEvent->setCompleted(false);
281 }
282
283 // completion date
284 if ((vo = isAPropertyOf(vtodo, VCCompletedProp)) != nullptr) {
285 anEvent->setCompleted(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
286 deleteStr(s);
287 }
288
289 // priority
290 if ((vo = isAPropertyOf(vtodo, VCPriorityProp))) {
291 s = fakeCString(vObjectUStringZValue(vo));
292 if (s) {
293 anEvent->setPriority(atoi(s));
294 deleteStr(s);
295 }
296 }
297
298 anEvent->setAllDay(false);
299
300 // due date
301 if ((vo = isAPropertyOf(vtodo, VCDueProp)) != nullptr) {
302 anEvent->setDtDue(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
303 deleteStr(s);
304 if (anEvent->dtDue().time().hour() == 0 && anEvent->dtDue().time().minute() == 0 && anEvent->dtDue().time().second() == 0) {
305 anEvent->setAllDay(true);
306 }
307 } else {
308 anEvent->setDtDue(QDateTime());
309 }
310
311 // start time
312 if ((vo = isAPropertyOf(vtodo, VCDTstartProp)) != nullptr) {
313 anEvent->setDtStart(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
314 deleteStr(s);
315 if (anEvent->dtStart().time().hour() == 0 && anEvent->dtStart().time().minute() == 0 && anEvent->dtStart().time().second() == 0) {
316 anEvent->setAllDay(true);
317 }
318 } else {
319 anEvent->setDtStart(QDateTime());
320 }
321
322 // recurrence stuff
323 if ((vo = isAPropertyOf(vtodo, VCRRuleProp)) != nullptr) {
324 uint recurrenceType = Recurrence::rNone;
325 int recurrenceTypeAbbrLen = 0;
326
327 s = fakeCString(vObjectUStringZValue(vo));
328 QString tmpStr = QString::fromUtf8(s);
329 deleteStr(s);
330 tmpStr = tmpStr.simplified();
331 const int tmpStrLen = tmpStr.length();
332 if (tmpStrLen > 0) {
333 tmpStr = tmpStr.toUpper();
334 QStringView prefix = QStringView(tmpStr).left(2);
335
336 // first, read the type of the recurrence
337 recurrenceTypeAbbrLen = 1;
338 if (tmpStr.at(0) == QLatin1Char('D')) {
339 recurrenceType = Recurrence::rDaily;
340 } else if (tmpStr.at(0) == QLatin1Char('W')) {
341 recurrenceType = Recurrence::rWeekly;
342 } else if (tmpStrLen > 1) {
343 recurrenceTypeAbbrLen = 2;
344 if (prefix == QLatin1String("MP")) {
345 recurrenceType = Recurrence::rMonthlyPos;
346 } else if (prefix == QLatin1String("MD")) {
347 recurrenceType = Recurrence::rMonthlyDay;
348 } else if (prefix == QLatin1String("YM")) {
349 recurrenceType = Recurrence::rYearlyMonth;
350 } else if (prefix == QLatin1String("YD")) {
351 recurrenceType = Recurrence::rYearlyDay;
352 }
353 }
354 }
355
356 if (recurrenceType != Recurrence::rNone) {
357 // Immediately after the type is the frequency
358 int index = tmpStr.indexOf(QLatin1Char(' '));
359 int last = tmpStr.lastIndexOf(QLatin1Char(' ')) + 1; // find last entry
360 const int rFreq = QStringView(tmpStr).mid(recurrenceTypeAbbrLen, (index - 1)).toInt();
361 ++index; // advance to beginning of stuff after freq
362
363 // Read the type-specific settings
364 switch (recurrenceType) {
365 case Recurrence::rDaily:
366 anEvent->recurrence()->setDaily(rFreq);
367 break;
368
369 case Recurrence::rWeekly: {
370 QBitArray qba(7);
371 QString dayStr;
372 if (index == last) {
373 // e.g. W1 #0
374 qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1);
375 } else {
376 // e.g. W1 SU #0
377 while (index < last) {
378 dayStr = tmpStr.mid(index, 3);
379 int dayNum = numFromDay(dayStr);
380 if (dayNum >= 0) {
381 qba.setBit(dayNum);
382 }
383 index += 3; // advance to next day, or possibly "#"
384 }
385 }
386 anEvent->recurrence()->setWeekly(rFreq, qba);
387 break;
388 }
389
390 case Recurrence::rMonthlyPos: {
391 anEvent->recurrence()->setMonthly(rFreq);
392
393 QBitArray qba(7);
394 short tmpPos;
395 if (index == last) {
396 // e.g. MP1 #0
397 tmpPos = anEvent->dtStart().date().day() / 7 + 1;
398 if (tmpPos == 5) {
399 tmpPos = -1;
400 }
401 qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1);
402 anEvent->recurrence()->addMonthlyPos(tmpPos, qba);
403 } else {
404 // e.g. MP1 1+ SU #0
405 while (index < last) {
406 tmpPos = tmpStr.mid(index, 1).toShort();
407 index += 1;
408 if (tmpStr.mid(index, 1) == QLatin1String("-")) {
409 // convert tmpPos to negative
410 tmpPos = 0 - tmpPos;
411 }
412 index += 2; // advance to day(s)
413 while (numFromDay(tmpStr.mid(index, 3)) >= 0) {
414 int dayNum = numFromDay(tmpStr.mid(index, 3));
415 qba.setBit(dayNum);
416 index += 3; // advance to next day, or possibly pos or "#"
417 }
418 anEvent->recurrence()->addMonthlyPos(tmpPos, qba);
419 qba.detach();
420 qba.fill(false); // clear out
421 } // while != "#"
422 }
423 break;
424 }
425
426 case Recurrence::rMonthlyDay:
427 anEvent->recurrence()->setMonthly(rFreq);
428 if (index == last) {
429 // e.g. MD1 #0
430 short tmpDay = anEvent->dtStart().date().day();
431 anEvent->recurrence()->addMonthlyDate(tmpDay);
432 } else {
433 // e.g. MD1 3 #0
434 while (index < last) {
435 int index2 = tmpStr.indexOf(QLatin1Char(' '), index);
436 if ((tmpStr.mid((index2 - 1), 1) == QLatin1String("-")) || (tmpStr.mid((index2 - 1), 1) == QLatin1String("+"))) {
437 index2 = index2 - 1;
438 }
439 short tmpDay = tmpStr.mid(index, (index2 - index)).toShort();
440 index = index2;
441 if (tmpStr.mid(index, 1) == QLatin1String("-")) {
442 tmpDay = 0 - tmpDay;
443 }
444 index += 2; // advance the index;
445 anEvent->recurrence()->addMonthlyDate(tmpDay);
446 } // while != #
447 }
448 break;
449
450 case Recurrence::rYearlyMonth:
451 anEvent->recurrence()->setYearly(rFreq);
452
453 if (index == last) {
454 // e.g. YM1 #0
455 short tmpMonth = anEvent->dtStart().date().month();
456 anEvent->recurrence()->addYearlyMonth(tmpMonth);
457 } else {
458 // e.g. YM1 3 #0
459 while (index < last) {
460 int index2 = tmpStr.indexOf(QLatin1Char(' '), index);
461 short tmpMonth = tmpStr.mid(index, (index2 - index)).toShort();
462 index = index2 + 1;
463 anEvent->recurrence()->addYearlyMonth(tmpMonth);
464 } // while != #
465 }
466 break;
467
468 case Recurrence::rYearlyDay:
469 anEvent->recurrence()->setYearly(rFreq);
470
471 if (index == last) {
472 // e.g. YD1 #0
473 short tmpDay = anEvent->dtStart().date().dayOfYear();
474 anEvent->recurrence()->addYearlyDay(tmpDay);
475 } else {
476 // e.g. YD1 123 #0
477 while (index < last) {
478 int index2 = tmpStr.indexOf(QLatin1Char(' '), index);
479 short tmpDay = tmpStr.mid(index, (index2 - index)).toShort();
480 index = index2 + 1;
481 anEvent->recurrence()->addYearlyDay(tmpDay);
482 } // while != #
483 }
484 break;
485
486 default:
487 break;
488 }
489
490 // find the last field, which is either the duration or the end date
491 index = last;
492 if (tmpStr.mid(index, 1) == QLatin1String("#")) {
493 // Nr of occurrences
494 ++index;
495 const int rDuration = QStringView(tmpStr).mid(index, tmpStr.length() - index).toInt();
496 if (rDuration > 0) {
497 anEvent->recurrence()->setDuration(rDuration);
498 }
499 } else if (tmpStr.indexOf(QLatin1Char('T'), index) != -1) {
500 QDateTime rEndDate = ISOToQDateTime(tmpStr.mid(index, tmpStr.length() - index));
501 anEvent->recurrence()->setEndDateTime(rEndDate);
502 }
503 } else {
504 qCDebug(KCALCORE_LOG) << "we don't understand this type of recurrence!";
505 } // if known recurrence type
506 } // repeats
507
508 // recurrence exceptions
509 if ((vo = isAPropertyOf(vtodo, VCExpDateProp)) != nullptr) {
510 s = fakeCString(vObjectUStringZValue(vo));
511 const QStringList exDates = QString::fromUtf8(s).split(QLatin1Char(','));
512 for (const auto &date : exDates) {
513 const QDateTime exDate = ISOToQDateTime(date);
514 if (exDate.time().hour() == 0 && exDate.time().minute() == 0 && exDate.time().second() == 0) {
515 anEvent->recurrence()->addExDate(ISOToQDate(date));
516 } else {
517 anEvent->recurrence()->addExDateTime(exDate);
518 }
519 }
520 deleteStr(s);
521 }
522
523 // alarm stuff
524 if ((vo = isAPropertyOf(vtodo, VCDAlarmProp))) {
525 Alarm::Ptr alarm;
526 VObject *a = isAPropertyOf(vo, VCRunTimeProp);
527 VObject *b = isAPropertyOf(vo, VCDisplayStringProp);
528
529 if (a || b) {
530 alarm = anEvent->newAlarm();
531 if (a) {
532 alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a)))));
533 deleteStr(s);
534 }
535 alarm->setEnabled(true);
536 if (b) {
537 s = fakeCString(vObjectUStringZValue(b));
538 alarm->setDisplayAlarm(QString::fromUtf8(s));
539 deleteStr(s);
540 } else {
541 alarm->setDisplayAlarm(QString());
542 }
543 }
544 }
545
546 if ((vo = isAPropertyOf(vtodo, VCAAlarmProp))) {
547 Alarm::Ptr alarm;
548 VObject *a;
549 VObject *b;
550 a = isAPropertyOf(vo, VCRunTimeProp);
551 b = isAPropertyOf(vo, VCAudioContentProp);
552
553 if (a || b) {
554 alarm = anEvent->newAlarm();
555 if (a) {
556 alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a)))));
557 deleteStr(s);
558 }
559 alarm->setEnabled(true);
560 if (b) {
561 s = fakeCString(vObjectUStringZValue(b));
562 alarm->setAudioAlarm(QFile::decodeName(s));
563 deleteStr(s);
564 } else {
565 alarm->setAudioAlarm(QString());
566 }
567 }
568 }
569
570 if ((vo = isAPropertyOf(vtodo, VCPAlarmProp))) {
571 Alarm::Ptr alarm;
572 VObject *a = isAPropertyOf(vo, VCRunTimeProp);
573 VObject *b = isAPropertyOf(vo, VCProcedureNameProp);
574
575 if (a || b) {
576 alarm = anEvent->newAlarm();
577 if (a) {
578 alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a)))));
579 deleteStr(s);
580 }
581 alarm->setEnabled(true);
582
583 if (b) {
584 s = fakeCString(vObjectUStringZValue(b));
585 alarm->setProcedureAlarm(QFile::decodeName(s));
586 deleteStr(s);
587 } else {
588 alarm->setProcedureAlarm(QString());
589 }
590 }
591 }
592
593 // related todo
594 if ((vo = isAPropertyOf(vtodo, VCRelatedToProp)) != nullptr) {
595 anEvent->setRelatedTo(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
596 deleteStr(s);
597 d->mTodosRelate.append(anEvent);
598 }
599
600 // secrecy
602 if ((vo = isAPropertyOf(vtodo, VCClassProp)) != nullptr) {
603 s = fakeCString(vObjectUStringZValue(vo));
604 if (s && strcmp(s, "PRIVATE") == 0) {
606 } else if (s && strcmp(s, "CONFIDENTIAL") == 0) {
608 }
609 deleteStr(s);
610 }
611 anEvent->setSecrecy(secrecy);
612
613 // categories
614 if ((vo = isAPropertyOf(vtodo, VCCategoriesProp)) != nullptr) {
615 s = fakeCString(vObjectUStringZValue(vo));
616 QString categories = QString::fromUtf8(s);
617 deleteStr(s);
618 QStringList tmpStrList = categories.split(QLatin1Char(';'));
619 anEvent->setCategories(tmpStrList);
620 }
621
622 return anEvent;
623}
624
626{
628 VObject *vo = nullptr;
629 VObjectIterator voi;
630 char *s = nullptr;
631
632 Event::Ptr anEvent(new Event);
633
634 // creation date
635 if ((vo = isAPropertyOf(vevent, VCDCreatedProp)) != nullptr) {
636 anEvent->setCreated(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
637 deleteStr(s);
638 }
639
640 // unique id
641 vo = isAPropertyOf(vevent, VCUniqueStringProp);
642 // while the UID property is preferred, it is not required. We'll use the
643 // default Event UID if none is given.
644 if (vo) {
645 anEvent->setUid(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
646 deleteStr(s);
647 }
648
649 // revision
650 // again NSCAL doesn't give us much to work with, so we improvise...
651 anEvent->setRevision(0);
652 if ((vo = isAPropertyOf(vevent, VCSequenceProp)) != nullptr) {
653 s = fakeCString(vObjectUStringZValue(vo));
654 if (s) {
655 anEvent->setRevision(atoi(s));
656 deleteStr(s);
657 }
658 }
659
660 // last modification date
661 if ((vo = isAPropertyOf(vevent, VCLastModifiedProp)) != nullptr) {
662 anEvent->setLastModified(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
663 deleteStr(s);
664 } else {
665 anEvent->setLastModified(QDateTime::currentDateTimeUtc());
666 }
667
668 // organizer
669 // if our extension property for the event's ORGANIZER exists, add it.
670 if ((vo = isAPropertyOf(vevent, ICOrganizerProp)) != nullptr) {
671 // FIXME: Also use the full name, not just the email address
672 anEvent->setOrganizer(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
673 deleteStr(s);
674 } else {
675 if (d->mCalendar->owner().name() != QLatin1String("Unknown Name")) {
676 anEvent->setOrganizer(d->mCalendar->owner());
677 }
678 }
679
680 // deal with attendees.
681 initPropIterator(&voi, vevent);
682 while (moreIteration(&voi)) {
683 vo = nextVObject(&voi);
684 if (strcmp(vObjectName(vo), VCAttendeeProp) == 0) {
685 Attendee a;
686 VObject *vp = nullptr;
687 s = fakeCString(vObjectUStringZValue(vo));
688 QString tmpStr = QString::fromUtf8(s);
689 deleteStr(s);
690 tmpStr = tmpStr.simplified();
691 int emailPos1;
692 if ((emailPos1 = tmpStr.indexOf(QLatin1Char('<'))) > 0) {
693 // both email address and name
694 int emailPos2 = tmpStr.lastIndexOf(QLatin1Char('>'));
695 a = Attendee(tmpStr.left(emailPos1 - 1), tmpStr.mid(emailPos1 + 1, emailPos2 - (emailPos1 + 1)));
696 } else if (tmpStr.indexOf(QLatin1Char('@')) > 0) {
697 // just an email address
698 a = Attendee(QString(), tmpStr);
699 } else {
700 // just a name
701 QString email = tmpStr.replace(QLatin1Char(' '), QLatin1Char('.'));
702 a = Attendee(tmpStr, email);
703 }
704
705 // is there an RSVP property?
706 if ((vp = isAPropertyOf(vo, VCRSVPProp)) != nullptr) {
707 a.setRSVP(vObjectStringZValue(vp));
708 }
709 // is there a status property?
710 if ((vp = isAPropertyOf(vo, VCStatusProp)) != nullptr) {
711 a.setStatus(readStatus(vObjectStringZValue(vp)));
712 }
713 // add the attendee
714 anEvent->addAttendee(a);
715 }
716 }
717
718 // This isn't strictly true. An event that doesn't have a start time
719 // or an end time isn't all-day, it has an anchor in time but it doesn't
720 // "take up" any time.
721 /*if ((isAPropertyOf(vevent, VCDTstartProp) == 0) ||
722 (isAPropertyOf(vevent, VCDTendProp) == 0)) {
723 anEvent->setAllDay(true);
724 } else {
725 }*/
726
727 anEvent->setAllDay(false);
728
729 // start time
730 if ((vo = isAPropertyOf(vevent, VCDTstartProp)) != nullptr) {
731 anEvent->setDtStart(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
732 deleteStr(s);
733
734 if (anEvent->dtStart().time().hour() == 0 && anEvent->dtStart().time().minute() == 0 && anEvent->dtStart().time().second() == 0) {
735 anEvent->setAllDay(true);
736 }
737 }
738
739 // stop time
740 if ((vo = isAPropertyOf(vevent, VCDTendProp)) != nullptr) {
741 anEvent->setDtEnd(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo)))));
742 deleteStr(s);
743
744 if (anEvent->dtEnd().time().hour() == 0 && anEvent->dtEnd().time().minute() == 0 && anEvent->dtEnd().time().second() == 0) {
745 anEvent->setAllDay(true);
746 }
747 }
748
749 // at this point, there should be at least a start or end time.
750 // fix up for events that take up no time but have a time associated
751 if (!isAPropertyOf(vevent, VCDTstartProp)) {
752 anEvent->setDtStart(anEvent->dtEnd());
753 }
754 if (!isAPropertyOf(vevent, VCDTendProp)) {
755 anEvent->setDtEnd(anEvent->dtStart());
756 }
757
758 ///////////////////////////////////////////////////////////////////////////
759
760 // recurrence stuff
761 if ((vo = isAPropertyOf(vevent, VCRRuleProp)) != nullptr) {
762 uint recurrenceType = Recurrence::rNone;
763 int recurrenceTypeAbbrLen = 0;
764
765 QString tmpStr = (QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
766 deleteStr(s);
767 tmpStr = tmpStr.simplified();
768 const int tmpStrLen = tmpStr.length();
769 if (tmpStrLen > 0) {
770 tmpStr = tmpStr.toUpper();
771 const QStringView prefix(tmpStr.left(2));
772
773 // first, read the type of the recurrence
774 recurrenceTypeAbbrLen = 1;
775 if (tmpStr.at(0) == QLatin1Char('D')) {
776 recurrenceType = Recurrence::rDaily;
777 } else if (tmpStr.at(0) == QLatin1Char('W')) {
778 recurrenceType = Recurrence::rWeekly;
779 } else if (tmpStrLen > 1) {
780 recurrenceTypeAbbrLen = 2;
781 if (prefix == QLatin1String("MP")) {
782 recurrenceType = Recurrence::rMonthlyPos;
783 } else if (prefix == QLatin1String("MD")) {
784 recurrenceType = Recurrence::rMonthlyDay;
785 } else if (prefix == QLatin1String("YM")) {
786 recurrenceType = Recurrence::rYearlyMonth;
787 } else if (prefix == QLatin1String("YD")) {
788 recurrenceType = Recurrence::rYearlyDay;
789 }
790 }
791 }
792
793 if (recurrenceType != Recurrence::rNone) {
794 // Immediately after the type is the frequency
795 int index = tmpStr.indexOf(QLatin1Char(' '));
796 int last = tmpStr.lastIndexOf(QLatin1Char(' ')) + 1; // find last entry
797 const int rFreq = QStringView(tmpStr).mid(recurrenceTypeAbbrLen, (index - 1)).toInt();
798 ++index; // advance to beginning of stuff after freq
799
800 // Read the type-specific settings
801 switch (recurrenceType) {
802 case Recurrence::rDaily:
803 anEvent->recurrence()->setDaily(rFreq);
804 break;
805
806 case Recurrence::rWeekly: {
807 QBitArray qba(7);
808 QString dayStr;
809 if (index == last) {
810 // e.g. W1 #0
811 qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1);
812 } else {
813 // e.g. W1 SU #0
814 while (index < last) {
815 dayStr = tmpStr.mid(index, 3);
816 int dayNum = numFromDay(dayStr);
817 if (dayNum >= 0) {
818 qba.setBit(dayNum);
819 }
820 index += 3; // advance to next day, or possibly "#"
821 }
822 }
823 anEvent->recurrence()->setWeekly(rFreq, qba);
824 break;
825 }
826
827 case Recurrence::rMonthlyPos: {
828 anEvent->recurrence()->setMonthly(rFreq);
829
830 QBitArray qba(7);
831 short tmpPos;
832 if (index == last) {
833 // e.g. MP1 #0
834 tmpPos = anEvent->dtStart().date().day() / 7 + 1;
835 if (tmpPos == 5) {
836 tmpPos = -1;
837 }
838 qba.setBit(anEvent->dtStart().date().dayOfWeek() - 1);
839 anEvent->recurrence()->addMonthlyPos(tmpPos, qba);
840 } else {
841 // e.g. MP1 1+ SU #0
842 while (index < last) {
843 tmpPos = tmpStr.mid(index, 1).toShort();
844 index += 1;
845 if (tmpStr.mid(index, 1) == QLatin1String("-")) {
846 // convert tmpPos to negative
847 tmpPos = 0 - tmpPos;
848 }
849 index += 2; // advance to day(s)
850 while (numFromDay(tmpStr.mid(index, 3)) >= 0) {
851 int dayNum = numFromDay(tmpStr.mid(index, 3));
852 qba.setBit(dayNum);
853 index += 3; // advance to next day, or possibly pos or "#"
854 }
855 anEvent->recurrence()->addMonthlyPos(tmpPos, qba);
856 qba.detach();
857 qba.fill(false); // clear out
858 } // while != "#"
859 }
860 break;
861 }
862
863 case Recurrence::rMonthlyDay:
864 anEvent->recurrence()->setMonthly(rFreq);
865 if (index == last) {
866 // e.g. MD1 #0
867 short tmpDay = anEvent->dtStart().date().day();
868 anEvent->recurrence()->addMonthlyDate(tmpDay);
869 } else {
870 // e.g. MD1 3 #0
871 while (index < last) {
872 int index2 = tmpStr.indexOf(QLatin1Char(' '), index);
873 if ((tmpStr.mid((index2 - 1), 1) == QLatin1String("-")) || (tmpStr.mid((index2 - 1), 1) == QLatin1String("+"))) {
874 index2 = index2 - 1;
875 }
876 short tmpDay = tmpStr.mid(index, (index2 - index)).toShort();
877 index = index2;
878 if (tmpStr.mid(index, 1) == QLatin1String("-")) {
879 tmpDay = 0 - tmpDay;
880 }
881 index += 2; // advance the index;
882 anEvent->recurrence()->addMonthlyDate(tmpDay);
883 } // while != #
884 }
885 break;
886
887 case Recurrence::rYearlyMonth:
888 anEvent->recurrence()->setYearly(rFreq);
889
890 if (index == last) {
891 // e.g. YM1 #0
892 short tmpMonth = anEvent->dtStart().date().month();
893 anEvent->recurrence()->addYearlyMonth(tmpMonth);
894 } else {
895 // e.g. YM1 3 #0
896 while (index < last) {
897 int index2 = tmpStr.indexOf(QLatin1Char(' '), index);
898 short tmpMonth = tmpStr.mid(index, (index2 - index)).toShort();
899 index = index2 + 1;
900 anEvent->recurrence()->addYearlyMonth(tmpMonth);
901 } // while != #
902 }
903 break;
904
905 case Recurrence::rYearlyDay:
906 anEvent->recurrence()->setYearly(rFreq);
907
908 if (index == last) {
909 // e.g. YD1 #0
910 const int tmpDay = anEvent->dtStart().date().dayOfYear();
911 anEvent->recurrence()->addYearlyDay(tmpDay);
912 } else {
913 // e.g. YD1 123 #0
914 while (index < last) {
915 int index2 = tmpStr.indexOf(QLatin1Char(' '), index);
916 short tmpDay = tmpStr.mid(index, (index2 - index)).toShort();
917 index = index2 + 1;
918 anEvent->recurrence()->addYearlyDay(tmpDay);
919 } // while != #
920 }
921 break;
922
923 default:
924 break;
925 }
926
927 // find the last field, which is either the duration or the end date
928 index = last;
929 if (tmpStr.mid(index, 1) == QLatin1String("#")) {
930 // Nr of occurrences
931 ++index;
932 const int rDuration = QStringView(tmpStr).mid(index, tmpStr.length() - index).toInt();
933 if (rDuration > 0) {
934 anEvent->recurrence()->setDuration(rDuration);
935 }
936 } else if (tmpStr.indexOf(QLatin1Char('T'), index) != -1) {
937 QDateTime rEndDate = ISOToQDateTime(tmpStr.mid(index, tmpStr.length() - index));
938 anEvent->recurrence()->setEndDateTime(rEndDate);
939 }
940 // anEvent->recurrence()->dump();
941
942 } else {
943 qCDebug(KCALCORE_LOG) << "we don't understand this type of recurrence!";
944 } // if known recurrence type
945 } // repeats
946
947 // recurrence exceptions
948 if ((vo = isAPropertyOf(vevent, VCExpDateProp)) != nullptr) {
949 s = fakeCString(vObjectUStringZValue(vo));
950 const QStringList exDates = QString::fromUtf8(s).split(QLatin1Char(','));
951 for (const auto &date : exDates) {
952 const QDateTime exDate = ISOToQDateTime(date);
953 if (exDate.time().hour() == 0 && exDate.time().minute() == 0 && exDate.time().second() == 0) {
954 anEvent->recurrence()->addExDate(ISOToQDate(date));
955 } else {
956 anEvent->recurrence()->addExDateTime(exDate);
957 }
958 }
959 deleteStr(s);
960 }
961
962 // summary
963 if ((vo = isAPropertyOf(vevent, VCSummaryProp))) {
964 s = fakeCString(vObjectUStringZValue(vo));
965 anEvent->setSummary(QString::fromUtf8(s), Qt::mightBeRichText(QString::fromUtf8(s)));
966 deleteStr(s);
967 }
968
969 // description
970 if ((vo = isAPropertyOf(vevent, VCDescriptionProp)) != nullptr) {
971 s = fakeCString(vObjectUStringZValue(vo));
972 bool isRich = Qt::mightBeRichText(QString::fromUtf8(s));
973 if (!anEvent->description().isEmpty()) {
974 anEvent->setDescription(anEvent->description() + QLatin1Char('\n') + QString::fromUtf8(s), isRich);
975 } else {
976 anEvent->setDescription(QString::fromUtf8(s), isRich);
977 }
978 deleteStr(s);
979 }
980
981 // location
982 if ((vo = isAPropertyOf(vevent, VCLocationProp)) != nullptr) {
983 s = fakeCString(vObjectUStringZValue(vo));
984 anEvent->setLocation(QString::fromUtf8(s), Qt::mightBeRichText(QString::fromUtf8(s)));
985 deleteStr(s);
986 }
987
988 // some stupid vCal exporters ignore the standard and use Description
989 // instead of Summary for the default field. Correct for this.
990 if (anEvent->summary().isEmpty() && !(anEvent->description().isEmpty())) {
991 QString tmpStr = anEvent->description().simplified();
992 anEvent->setDescription(QString());
993 anEvent->setSummary(tmpStr);
994 }
995
996#if 0
997 // status
998 if ((vo = isAPropertyOf(vevent, VCStatusProp)) != 0) {
999 QString tmpStr(s = fakeCString(vObjectUStringZValue(vo)));
1000 deleteStr(s);
1001// TODO: Define Event status
1002// anEvent->setStatus( tmpStr );
1003 } else {
1004// anEvent->setStatus( "NEEDS ACTION" );
1005 }
1006#endif
1007
1008 // secrecy
1010 if ((vo = isAPropertyOf(vevent, VCClassProp)) != nullptr) {
1011 s = fakeCString(vObjectUStringZValue(vo));
1012 if (s && strcmp(s, "PRIVATE") == 0) {
1013 secrecy = Incidence::SecrecyPrivate;
1014 } else if (s && strcmp(s, "CONFIDENTIAL") == 0) {
1016 }
1017 deleteStr(s);
1018 }
1019 anEvent->setSecrecy(secrecy);
1020
1021 // categories
1022 if ((vo = isAPropertyOf(vevent, VCCategoriesProp)) != nullptr) {
1023 s = fakeCString(vObjectUStringZValue(vo));
1024 QString categories = QString::fromUtf8(s);
1025 deleteStr(s);
1026 QStringList tmpStrList = categories.split(QLatin1Char(','));
1027 anEvent->setCategories(tmpStrList);
1028 }
1029
1030 // attachments
1031 initPropIterator(&voi, vevent);
1032 while (moreIteration(&voi)) {
1033 vo = nextVObject(&voi);
1034 if (strcmp(vObjectName(vo), VCAttachProp) == 0) {
1035 s = fakeCString(vObjectUStringZValue(vo));
1036 anEvent->addAttachment(Attachment(QString::fromUtf8(s)));
1037 deleteStr(s);
1038 }
1039 }
1040
1041 // resources
1042 if ((vo = isAPropertyOf(vevent, VCResourcesProp)) != nullptr) {
1043 QString resources = (QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
1044 deleteStr(s);
1045 QStringList tmpStrList = resources.split(QLatin1Char(';'));
1046 anEvent->setResources(tmpStrList);
1047 }
1048
1049 // alarm stuff
1050 if ((vo = isAPropertyOf(vevent, VCDAlarmProp))) {
1051 Alarm::Ptr alarm;
1052 VObject *a = isAPropertyOf(vo, VCRunTimeProp);
1053 VObject *b = isAPropertyOf(vo, VCDisplayStringProp);
1054
1055 if (a || b) {
1056 alarm = anEvent->newAlarm();
1057 if (a) {
1058 alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a)))));
1059 deleteStr(s);
1060 }
1061 alarm->setEnabled(true);
1062
1063 if (b) {
1064 s = fakeCString(vObjectUStringZValue(b));
1065 alarm->setDisplayAlarm(QString::fromUtf8(s));
1066 deleteStr(s);
1067 } else {
1068 alarm->setDisplayAlarm(QString());
1069 }
1070 }
1071 }
1072
1073 if ((vo = isAPropertyOf(vevent, VCAAlarmProp))) {
1074 Alarm::Ptr alarm;
1075 VObject *a;
1076 VObject *b;
1077 a = isAPropertyOf(vo, VCRunTimeProp);
1078 b = isAPropertyOf(vo, VCAudioContentProp);
1079
1080 if (a || b) {
1081 alarm = anEvent->newAlarm();
1082 if (a) {
1083 alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a)))));
1084 deleteStr(s);
1085 }
1086 alarm->setEnabled(true);
1087
1088 if (b) {
1089 s = fakeCString(vObjectUStringZValue(b));
1090 alarm->setAudioAlarm(QFile::decodeName(s));
1091 deleteStr(s);
1092 } else {
1093 alarm->setAudioAlarm(QString());
1094 }
1095 }
1096 }
1097
1098 if ((vo = isAPropertyOf(vevent, VCPAlarmProp))) {
1099 Alarm::Ptr alarm;
1100 VObject *a;
1101 VObject *b;
1102 a = isAPropertyOf(vo, VCRunTimeProp);
1103 b = isAPropertyOf(vo, VCProcedureNameProp);
1104
1105 if (a || b) {
1106 alarm = anEvent->newAlarm();
1107 if (a) {
1108 alarm->setTime(ISOToQDateTime(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(a)))));
1109 deleteStr(s);
1110 }
1111 alarm->setEnabled(true);
1112
1113 if (b) {
1114 s = fakeCString(vObjectUStringZValue(b));
1115 alarm->setProcedureAlarm(QFile::decodeName(s));
1116 deleteStr(s);
1117 } else {
1118 alarm->setProcedureAlarm(QString());
1119 }
1120 }
1121 }
1122
1123 // priority
1124 if ((vo = isAPropertyOf(vevent, VCPriorityProp))) {
1125 s = fakeCString(vObjectUStringZValue(vo));
1126 if (s) {
1127 anEvent->setPriority(atoi(s));
1128 deleteStr(s);
1129 }
1130 }
1131
1132 // transparency
1133 if ((vo = isAPropertyOf(vevent, VCTranspProp)) != nullptr) {
1134 s = fakeCString(vObjectUStringZValue(vo));
1135 if (s) {
1136 int i = atoi(s);
1137 anEvent->setTransparency(i == 1 ? Event::Transparent : Event::Opaque);
1138 deleteStr(s);
1139 }
1140 }
1141
1142 // related event
1143 if ((vo = isAPropertyOf(vevent, VCRelatedToProp)) != nullptr) {
1144 anEvent->setRelatedTo(QString::fromUtf8(s = fakeCString(vObjectUStringZValue(vo))));
1145 deleteStr(s);
1146 d->mEventsRelate.append(anEvent);
1147 }
1148
1149 /* Rest of the custom properties */
1150 readCustomProperties(vevent, anEvent);
1151
1152 return anEvent;
1153}
1154
1156{
1157 // qCDebug(KCALCORE_LOG) << timezone;
1158 QString pZone = QString::fromUtf8(timezone.mid(timezone.indexOf("TZID:VCAL") + 9));
1159 return pZone.mid(0, pZone.indexOf(QLatin1Char('\n')));
1160}
1161
1163{
1164 if (!timezone.contains("BEGIN:DAYLIGHT")) {
1165 return QString();
1166 }
1167
1168 timezone = timezone.mid(timezone.indexOf("BEGIN:DAYLIGHT"));
1169 timezone = timezone.mid(timezone.indexOf("TZNAME:") + 7);
1170 QString sStart = QString::fromUtf8(timezone.mid(0, (timezone.indexOf("COMMENT:"))));
1171 sStart.chop(2);
1172 timezone = timezone.mid(timezone.indexOf("TZOFFSETTO:") + 11);
1173 QString sOffset = QString::fromUtf8(timezone.mid(0, (timezone.indexOf("DTSTART:"))));
1174 sOffset.chop(2);
1175 sOffset.insert(3, QLatin1Char(':'));
1176 timezone = timezone.mid(timezone.indexOf("TZNAME:") + 7);
1177 QString sEnd = QString::fromUtf8(timezone.mid(0, (timezone.indexOf("COMMENT:"))));
1178 sEnd.chop(2);
1179
1180 return QStringLiteral("TRUE;") + sOffset + QLatin1Char(';') + sStart + QLatin1Char(';') + sEnd + QLatin1String(";;");
1181}
1182
1184{
1185 if (!qd.isValid()) {
1186 return QString();
1187 }
1188
1189 return QString::asprintf("%.2d%.2d%.2d", qd.year(), qd.month(), qd.day());
1190}
1191
1193{
1194 Q_D(VCalFormat);
1195 if (!dt.isValid()) {
1196 return QString();
1197 }
1198
1199 QDateTime tmpDT;
1200 if (zulu) {
1201 tmpDT = dt.toUTC();
1202 } else {
1203 tmpDT = dt.toTimeZone(d->mCalendar->timeZone());
1204 }
1205 QString tmpStr = QString::asprintf("%.2d%.2d%.2dT%.2d%.2d%.2d",
1206 tmpDT.date().year(),
1207 tmpDT.date().month(),
1208 tmpDT.date().day(),
1209 tmpDT.time().hour(),
1210 tmpDT.time().minute(),
1211 tmpDT.time().second());
1212 if (zulu || dt.timeZone() == QTimeZone::utc()) {
1213 tmpStr += QLatin1Char('Z');
1214 }
1215 return tmpStr;
1216}
1217
1219{
1220 Q_D(VCalFormat);
1221 auto noAllocString = QStringView{dtStr};
1222
1223 int year = noAllocString.left(4).toInt();
1224 int month = noAllocString.mid(4, 2).toInt();
1225 int day = noAllocString.mid(6, 2).toInt();
1226 int hour = noAllocString.mid(9, 2).toInt();
1227 int minute = noAllocString.mid(11, 2).toInt();
1228 int second = noAllocString.mid(13, 2).toInt();
1229
1230 QDate tmpDate;
1231 tmpDate.setDate(year, month, day);
1232 QTime tmpTime;
1233 tmpTime.setHMS(hour, minute, second);
1234
1235 if (tmpDate.isValid() && tmpTime.isValid()) {
1236 // correct for GMT if string is in Zulu format
1237 if (dtStr.at(dtStr.length() - 1) == QLatin1Char('Z')) {
1238 return QDateTime(tmpDate, tmpTime, QTimeZone::UTC);
1239 } else {
1240 return QDateTime(tmpDate, tmpTime, d->mCalendar->timeZone());
1241 }
1242 }
1243
1244 return {};
1245}
1246
1248{
1249 auto noAllocString = QStringView{dateStr};
1250
1251 const int year = noAllocString.left(4).toInt();
1252 const int month = noAllocString.mid(4, 2).toInt();
1253 const int day = noAllocString.mid(6, 2).toInt();
1254
1255 return QDate(year, month, day);
1256}
1257
1259{
1260 // ISO8601 format(s):
1261 // +- hh : mm
1262 // +- hh mm
1263 // +- hh
1264
1265 // We also accept broken one without +
1266 int mod = 1;
1267 int v = 0;
1268 const QString str = s.trimmed();
1269 int ofs = 0;
1270 result = 0;
1271
1272 // Check for end
1273 if (str.size() <= ofs) {
1274 return false;
1275 }
1276 if (str[ofs] == QLatin1Char('-')) {
1277 mod = -1;
1278 ofs++;
1279 } else if (str[ofs] == QLatin1Char('+')) {
1280 ofs++;
1281 }
1282 if (str.size() <= ofs) {
1283 return false;
1284 }
1285
1286 // Make sure next two values are numbers
1287 bool ok;
1288
1289 if (str.size() < (ofs + 2)) {
1290 return false;
1291 }
1292
1293 v = QStringView(str).mid(ofs, 2).toInt(&ok) * 60;
1294 if (!ok) {
1295 return false;
1296 }
1297 ofs += 2;
1298
1299 if (str.size() > ofs) {
1300 if (str[ofs] == QLatin1Char(':')) {
1301 ofs++;
1302 }
1303 if (str.size() > ofs) {
1304 if (str.size() < (ofs + 2)) {
1305 return false;
1306 }
1307 v += QStringView(str).mid(ofs, 2).toInt(&ok);
1308 if (!ok) {
1309 return false;
1310 }
1311 }
1312 }
1313 result = v * mod * 60;
1314 return true;
1315}
1316
1317// take a raw vcalendar (i.e. from a file on disk, clipboard, etc. etc.
1318// and break it down from it's tree-like format into the dictionary format
1319// that is used internally in the VCalFormat.
1320void VCalFormat::populate(VObject *vcal)
1321{
1322 Q_D(VCalFormat);
1323 // this function will populate the caldict dictionary and other event
1324 // lists. It turns vevents into Events and then inserts them.
1325
1326 VObjectIterator i;
1327 VObject *curVO;
1328 Event::Ptr anEvent;
1329 bool hasTimeZone = false; // The calendar came with a TZ and not UTC
1330 QTimeZone previousZone; // If we add a new TZ we should leave the spec as it was before
1331
1332 if ((curVO = isAPropertyOf(vcal, ICMethodProp)) != nullptr) {
1333 char *methodType = fakeCString(vObjectUStringZValue(curVO));
1334 // qCDebug(KCALCORE_LOG) << "This calendar is an iTIP transaction of type '" << methodType << "'";
1335 deleteStr(methodType);
1336 }
1337
1338 // warn the user that we might have trouble reading non-known calendar.
1339 if ((curVO = isAPropertyOf(vcal, VCProdIdProp)) != nullptr) {
1340 char *s = fakeCString(vObjectUStringZValue(curVO));
1341 if (!s || strcmp(productId().toUtf8().constData(), s) != 0) {
1342 qCDebug(KCALCORE_LOG) << "This vCalendar file was not created by KOrganizer or"
1343 << "any other product we support. Loading anyway...";
1344 }
1346 deleteStr(s);
1347 }
1348
1349 // warn the user we might have trouble reading this unknown version.
1350 if ((curVO = isAPropertyOf(vcal, VCVersionProp)) != nullptr) {
1351 char *s = fakeCString(vObjectUStringZValue(curVO));
1352 if (!s || strcmp(_VCAL_VERSION, s) != 0) {
1353 qCDebug(KCALCORE_LOG) << "This vCalendar file has version" << s << "We only support" << _VCAL_VERSION;
1354 }
1355 deleteStr(s);
1356 }
1357
1358 // set the time zone (this is a property of the view, so just discard!)
1359 if ((curVO = isAPropertyOf(vcal, VCTimeZoneProp)) != nullptr) {
1360 char *s = fakeCString(vObjectUStringZValue(curVO));
1361 QString ts = QString::fromUtf8(s);
1362 QString name = QLatin1String("VCAL") + ts;
1363 deleteStr(s);
1364
1365 // TODO: While using the timezone-offset + vcal as timezone is is
1366 // most likely unique, we should REALLY actually create something
1367 // like vcal-tzoffset-daylightoffsets, or better yet,
1368 // vcal-hash<the former>
1369
1370 QStringList tzList;
1371 QString tz;
1372 int utcOffset;
1373 if (parseTZOffsetISO8601(ts, utcOffset)) {
1374 // qCDebug(KCALCORE_LOG) << "got standard offset" << ts << utcOffset;
1375 // standard from tz
1376 // starting date for now 01011900
1377 QDateTime dt = QDateTime(QDateTime(QDate(1900, 1, 1), QTime(0, 0, 0)));
1378 tz = QStringLiteral("STD;%1;false;%2").arg(QString::number(utcOffset), dt.toString());
1379 tzList.append(tz);
1380
1381 // go through all the daylight tags
1382 initPropIterator(&i, vcal);
1383 while (moreIteration(&i)) {
1384 curVO = nextVObject(&i);
1385 if (strcmp(vObjectName(curVO), VCDayLightProp) == 0) {
1386 char *s = fakeCString(vObjectUStringZValue(curVO));
1387 QString dst = QLatin1String(s);
1388 QStringList argl = dst.split(QLatin1Char(','));
1389 deleteStr(s);
1390
1391 // Too short -> not interesting
1392 if (argl.size() < 4) {
1393 continue;
1394 }
1395
1396 // We don't care about the non-DST periods
1397 if (argl[0] != QLatin1String("TRUE")) {
1398 continue;
1399 }
1400
1401 int utcOffsetDst;
1402 if (parseTZOffsetISO8601(argl[1], utcOffsetDst)) {
1403 // qCDebug(KCALCORE_LOG) << "got DST offset" << argl[1] << utcOffsetDst;
1404 // standard
1405 QString strEndDate = argl[3];
1406 QDateTime endDate = ISOToQDateTime(strEndDate);
1407 // daylight
1408 QString strStartDate = argl[2];
1409 QDateTime startDate = ISOToQDateTime(strStartDate);
1410
1411 QString strRealEndDate = strEndDate;
1412 QString strRealStartDate = strStartDate;
1413 QDateTime realEndDate = endDate;
1414 QDateTime realStartDate = startDate;
1415 // if we get dates for some reason in wrong order, earlier is used for dst
1416 if (endDate < startDate) {
1417 strRealEndDate = strStartDate;
1418 strRealStartDate = strEndDate;
1419 realEndDate = startDate;
1420 realStartDate = endDate;
1421 }
1422 tz = QStringLiteral("%1;%2;false;%3").arg(strRealEndDate, QString::number(utcOffset), realEndDate.toString());
1423 tzList.append(tz);
1424
1425 tz = QStringLiteral("%1;%2;true;%3").arg(strRealStartDate, QString::number(utcOffsetDst), realStartDate.toString());
1426 tzList.append(tz);
1427 } else {
1428 qCDebug(KCALCORE_LOG) << "unable to parse dst" << argl[1];
1429 }
1430 }
1431 }
1433 qCDebug(KCALCORE_LOG) << "zone is not valid, parsing error" << tzList;
1434 } else {
1435 previousZone = d->mCalendar->timeZone();
1436 d->mCalendar->setTimeZoneId(name.toUtf8());
1437 hasTimeZone = true;
1438 }
1439 } else {
1440 qCDebug(KCALCORE_LOG) << "unable to parse tzoffset" << ts;
1441 }
1442 }
1443
1444 // Store all events with a relatedTo property in a list for post-processing
1445 d->mEventsRelate.clear();
1446 d->mTodosRelate.clear();
1447
1448 initPropIterator(&i, vcal);
1449
1450 // go through all the vobjects in the vcal
1451 while (moreIteration(&i)) {
1452 curVO = nextVObject(&i);
1453
1454 /************************************************************************/
1455
1456 // now, check to see that the object is an event or todo.
1457 if (strcmp(vObjectName(curVO), VCEventProp) == 0) {
1458 if (!isAPropertyOf(curVO, VCDTstartProp) && !isAPropertyOf(curVO, VCDTendProp)) {
1459 qCDebug(KCALCORE_LOG) << "found a VEvent with no DTSTART and no DTEND! Skipping...";
1460 goto SKIP;
1461 }
1462
1463 anEvent = VEventToEvent(curVO);
1464 if (anEvent) {
1465 if (hasTimeZone && !anEvent->allDay() && anEvent->dtStart().timeZone() == QTimeZone::utc()) {
1466 // This sounds stupid but is how others are doing it, so here
1467 // we go. If there is a TZ in the VCALENDAR even if the dtStart
1468 // and dtend are in UTC, clients interpret it using also the TZ defined
1469 // in the Calendar. I know it sounds braindead but oh well
1470 int utcOffSet = anEvent->dtStart().offsetFromUtc();
1471 QDateTime dtStart(anEvent->dtStart().addSecs(utcOffSet));
1472 dtStart.setTimeZone(d->mCalendar->timeZone());
1473 QDateTime dtEnd(anEvent->dtEnd().addSecs(utcOffSet));
1474 dtEnd.setTimeZone(d->mCalendar->timeZone());
1475 anEvent->setDtStart(dtStart);
1476 anEvent->setDtEnd(dtEnd);
1477 }
1478 Event::Ptr old =
1479 !anEvent->hasRecurrenceId() ? d->mCalendar->event(anEvent->uid()) : d->mCalendar->event(anEvent->uid(), anEvent->recurrenceId());
1480
1481 if (old) {
1482 if (anEvent->revision() > old->revision()) {
1483 d->mCalendar->deleteEvent(old); // move old to deleted
1484 removeAllVCal(d->mEventsRelate, old);
1485 d->mCalendar->addEvent(anEvent); // and replace it with this one
1486 }
1487 } else {
1488 d->mCalendar->addEvent(anEvent); // just add this one
1489 }
1490 }
1491 } else if (strcmp(vObjectName(curVO), VCTodoProp) == 0) {
1492 Todo::Ptr aTodo = VTodoToEvent(curVO);
1493 if (aTodo) {
1494 if (hasTimeZone && !aTodo->allDay() && aTodo->dtStart().timeZone() == QTimeZone::utc()) {
1495 // This sounds stupid but is how others are doing it, so here
1496 // we go. If there is a TZ in the VCALENDAR even if the dtStart
1497 // and dtend are in UTC, clients interpret it using also the TZ defined
1498 // in the Calendar. I know it sounds braindead but oh well
1499 int utcOffSet = aTodo->dtStart().offsetFromUtc();
1500 QDateTime dtStart(aTodo->dtStart().addSecs(utcOffSet));
1501 dtStart.setTimeZone(d->mCalendar->timeZone());
1502 aTodo->setDtStart(dtStart);
1503 if (aTodo->hasDueDate()) {
1504 QDateTime dtDue(aTodo->dtDue().addSecs(utcOffSet));
1505 dtDue.setTimeZone(d->mCalendar->timeZone());
1506 aTodo->setDtDue(dtDue);
1507 }
1508 }
1509 Todo::Ptr old = !aTodo->hasRecurrenceId() ? d->mCalendar->todo(aTodo->uid()) : d->mCalendar->todo(aTodo->uid(), aTodo->recurrenceId());
1510 if (old) {
1511 if (aTodo->revision() > old->revision()) {
1512 d->mCalendar->deleteTodo(old); // move old to deleted
1513 removeAllVCal(d->mTodosRelate, old);
1514 d->mCalendar->addTodo(aTodo); // and replace it with this one
1515 }
1516 } else {
1517 d->mCalendar->addTodo(aTodo); // just add this one
1518 }
1519 }
1520 } else if ((strcmp(vObjectName(curVO), VCVersionProp) == 0) || (strcmp(vObjectName(curVO), VCProdIdProp) == 0)
1521 || (strcmp(vObjectName(curVO), VCTimeZoneProp) == 0)) {
1522 // do nothing, we know these properties and we want to skip them.
1523 // we have either already processed them or are ignoring them.
1524 ;
1525 } else if (strcmp(vObjectName(curVO), VCDayLightProp) == 0) {
1526 // do nothing daylights are already processed
1527 ;
1528 } else {
1529 qCDebug(KCALCORE_LOG) << "Ignoring unknown vObject \"" << vObjectName(curVO) << "\"";
1530 }
1531 SKIP:;
1532 } // while
1533
1534 // Post-Process list of events with relations, put Event objects in relation
1535 for (const auto &eventPtr : std::as_const(d->mEventsRelate)) {
1536 eventPtr->setRelatedTo(eventPtr->relatedTo());
1537 }
1538
1539 for (const auto &todoPtr : std::as_const(d->mTodosRelate)) {
1540 todoPtr->setRelatedTo(todoPtr->relatedTo());
1541 }
1542
1543 // Now lets put the TZ back as it was if we have changed it.
1544 if (hasTimeZone) {
1545 d->mCalendar->setTimeZone(previousZone);
1546 }
1547}
1548
1550{
1551 if (day == QLatin1String("MO ")) {
1552 return 0;
1553 }
1554 if (day == QLatin1String("TU ")) {
1555 return 1;
1556 }
1557 if (day == QLatin1String("WE ")) {
1558 return 2;
1559 }
1560 if (day == QLatin1String("TH ")) {
1561 return 3;
1562 }
1563 if (day == QLatin1String("FR ")) {
1564 return 4;
1565 }
1566 if (day == QLatin1String("SA ")) {
1567 return 5;
1568 }
1569 if (day == QLatin1String("SU ")) {
1570 return 6;
1571 }
1572
1573 return -1; // something bad happened. :)
1574}
1575
1577{
1578 QString statStr = QString::fromUtf8(s);
1579 statStr = statStr.toUpper();
1581
1582 if (statStr == QLatin1String("X-ACTION")) {
1584 } else if (statStr == QLatin1String("NEEDS ACTION")) {
1586 } else if (statStr == QLatin1String("ACCEPTED")) {
1588 } else if (statStr == QLatin1String("SENT")) {
1590 } else if (statStr == QLatin1String("TENTATIVE")) {
1592 } else if (statStr == QLatin1String("CONFIRMED")) {
1594 } else if (statStr == QLatin1String("DECLINED")) {
1596 } else if (statStr == QLatin1String("COMPLETED")) {
1598 } else if (statStr == QLatin1String("DELEGATED")) {
1600 } else {
1601 qCDebug(KCALCORE_LOG) << "error setting attendee mStatus, unknown mStatus!";
1603 }
1604
1605 return status;
1606}
1607
1609{
1610 switch (status) {
1611 default:
1613 return "NEEDS ACTION";
1614 case Attendee::Accepted:
1615 return "ACCEPTED";
1616 case Attendee::Declined:
1617 return "DECLINED";
1619 return "TENTATIVE";
1621 return "DELEGATED";
1623 return "COMPLETED";
1625 return "NEEDS ACTION";
1626 }
1627}
1628
1629void VCalFormat::readCustomProperties(VObject *o, const Incidence::Ptr &i)
1630{
1631 VObjectIterator iter;
1632 char *s;
1633
1634 initPropIterator(&iter, o);
1635 while (moreIteration(&iter)) {
1636 VObject *cur = nextVObject(&iter);
1637 const char *curname = vObjectName(cur);
1638 Q_ASSERT(curname);
1639 if ((curname[0] == 'X' && curname[1] == '-') && strcmp(curname, ICOrganizerProp) != 0) {
1640 // TODO - for the time being, we ignore the parameters part
1641 // and just do the value handling here
1642 i->setNonKDECustomProperty(curname, QString::fromUtf8(s = fakeCString(vObjectUStringZValue(cur))));
1643 deleteStr(s);
1644 }
1645 }
1646}
1647
1648void VCalFormat::writeCustomProperties(VObject *o, const Incidence::Ptr &i)
1649{
1650 Q_D(VCalFormat);
1651 const QMap<QByteArray, QString> custom = i->customProperties();
1652 for (auto cIt = custom.cbegin(); cIt != custom.cend(); ++cIt) {
1653 const QByteArray property = cIt.key();
1654 if (d->mManuallyWrittenExtensionFields.contains(property) || property.startsWith("X-KDE-VOLATILE")) { // krazy:exclude=strings
1655 continue;
1656 }
1657
1658 addPropValue(o, property.constData(), cIt.value().toUtf8().constData());
1659 }
1660}
This file is part of the API for handling calendar data and defines the Calendar class.
Represents information related to an attachment for a Calendar Incidence.
Definition attachment.h:47
Represents information related to an attendee of an Calendar Incidence, typically a meeting or task (...
Definition attendee.h:45
void setStatus(PartStat status)
Sets the PartStat of the attendee to status.
Definition attendee.cpp:205
PartStat
The different types of participant status.
Definition attendee.h:64
@ InProcess
To-do in process of being completed.
Definition attendee.h:71
@ Delegated
Event or to-do delegated.
Definition attendee.h:69
@ Completed
To-do completed.
Definition attendee.h:70
@ Tentative
Event or to-do tentatively accepted.
Definition attendee.h:68
@ NeedsAction
Event, to-do or journal needs action (default)
Definition attendee.h:65
@ Declined
Event, to-do or journal declined.
Definition attendee.h:67
@ Accepted
Event, to-do or journal accepted.
Definition attendee.h:66
void setRSVP(bool rsvp)
Sets the RSVP flag of the attendee to rsvp.
Definition attendee.cpp:195
An abstract base class that provides an interface to various calendar formats.
Definition calformat.h:39
void setLoadedProductId(const QString &id)
Sets the PRODID string loaded from calendar file.
Definition calformat.cpp:83
void clearException()
Clears the exception status.
Definition calformat.cpp:47
static const QString & productId()
Returns the our library's PRODID string to write into calendar files.
Definition calformat.cpp:73
void setException(Exception *error)
Sets an exception that is to be used by the functions of this class to report errors.
Definition calformat.cpp:52
This class provides an Event in the sense of RFC2445.
Definition event.h:33
@ Opaque
Event appears in free/busy time.
Definition event.h:42
@ Transparent
Event does not appear in free/busy time.
Definition event.h:43
Exception base class, currently used as a fancy kind of error code and not as an C++ exception.
Definition exceptions.h:42
@ CalVersionUnknown
Unknown calendar format detected.
Definition exceptions.h:55
Secrecy
The different types of incidence access classifications.
Definition incidence.h:97
@ SecrecyPrivate
Secret to the owner.
Definition incidence.h:99
@ SecrecyConfidential
Secret to the owner and some others.
Definition incidence.h:100
@ SecrecyPublic
Not secret (default)
Definition incidence.h:98
Provides a To-do in the sense of RFC2445.
Definition todo.h:34
vCalendar format implementation.
Definition vcalformat.h:61
QString qDateTimeToISO(const QDateTime &date, bool zulu=true)
Takes a QDateTime and returns a string in format YYYYMMDDTHHMMSS.
QDateTime ISOToQDateTime(const QString &dtStr)
Takes a string in YYYYMMDDTHHMMSS format and returns a valid QDateTime.
QByteArray writeStatus(Attendee::PartStat status) const
Converts an Attendee::PartStat into a QByteArray string.
QString qDateToISO(const QDate &date)
Takes a QDate and returns a string in the format YYYYMMDDTHHMMSS.
Todo::Ptr VTodoToEvent(VObject *vtodo)
Translates a VObject of the TODO type into an Event.
VCalFormat()
Constructor a new vCalendar Format object.
void populate(VObject *vcal)
Takes a vCalendar tree of VObjects, and puts all of them that have the "event" property into the dict...
QString parseDst(QByteArray &timezone) const
Parse DAYLIGHT tag from vtimezone.
Attendee::PartStat readStatus(const char *s) const
Converts a status string into an Attendee::PartStat.
bool load(const Calendar::Ptr &calendar, const QString &fileName) override
bool parseTZOffsetISO8601(const QString &s, int &result)
Parse one of the myriad of ISO8601 timezone offset formats, e.g.
QString parseTZ(const QByteArray &timezone) const
Parse TZ tag from vtimezone.
QDate ISOToQDate(const QString &dtStr)
Takes a string in the YYYYMMDD format and returns a valid QDate.
Event::Ptr VEventToEvent(VObject *vevent)
Translates a VObject into a Event and returns a pointer to it.
QString toString(const Calendar::Ptr &calendar) override
bool save(const Calendar::Ptr &calendar, const QString &fileName) override
bool fromRawString(const Calendar::Ptr &calendar, const QByteArray &string) override
~VCalFormat() override
Destructor.
int numFromDay(const QString &day)
Converts a two letter representation of the day (i.e.
This file is part of the API for handling calendar data and defines the Exception class.
Q_SCRIPTABLE CaptureState status()
Namespace for all KCalendarCore types.
Definition alarm.h:37
bool fill(bool value, qsizetype size)
void setBit(qsizetype i)
bool contains(QByteArrayView bv) const const
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
QByteArray mid(qsizetype pos, qsizetype len) const const
int day() const const
bool isValid(int year, int month, int day)
int month() const const
bool setDate(int year, int month, int day)
int year() const const
QDateTime currentDateTimeUtc()
QDate date() const const
bool isValid() const const
void setTimeZone(const QTimeZone &toZone)
QTime time() const const
QTimeZone timeZone() const const
QString toString(QStringView format, QCalendar cal) const const
QDateTime toTimeZone(const QTimeZone &timeZone) const const
QDateTime toUTC() const const
QString decodeName(const QByteArray &localFileName)
QByteArray encodeName(const QString &fileName)
void append(QList< T > &&value)
qsizetype size() const const
const_iterator cbegin() const const
const_iterator cend() const const
QString arg(Args &&... args) const const
QString asprintf(const char *cformat,...)
const QChar at(qsizetype position) const const
void chop(qsizetype n)
void clear()
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
QString & insert(qsizetype position, QChar ch)
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QString simplified() const const
qsizetype size() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
QByteArray toLatin1() const const
short toShort(bool *ok, int base) const const
QString toUpper() const const
QByteArray toUtf8() const const
QString trimmed() const const
QStringView left(qsizetype length) const const
QStringView mid(qsizetype start, qsizetype length) const const
int toInt(bool *ok, int base) const const
bool mightBeRichText(const QString &text)
int hour() const const
bool isValid(int h, int m, int s, int ms)
int minute() const const
int second() const const
bool setHMS(int h, int m, int s, int ms)
bool isTimeZoneIdAvailable(const QByteArray &ianaId)
QTimeZone utc()
Q_D(Todo)
Private class that helps to provide binary compatibility between releases.
This file is part of the API for handling calendar data and defines the VCalFormat base class.
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:13:47 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.