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

KDE's Doxygen guidelines are available online.