• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.14.10 API Reference
  • KDE Home
  • Contact Us
 

KAlarm Library

  • kalarmcal
karecurrence.cpp
1/*
2 * karecurrence.cpp - recurrence with special yearly February 29th handling
3 * This file is part of kalarmcal library, which provides access to KAlarm
4 * calendar data.
5 * Copyright © 2005-2013 by David Jarvie <djarvie@kde.org>
6 *
7 * This library is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU Library General Public License as published
9 * by the Free Software Foundation; either version 2 of the License, or (at
10 * your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
15 * License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB. If not, write to the
19 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 * MA 02110-1301, USA.
21 */
22
23#include "karecurrence.h"
24
25#ifndef KALARMCAL_USE_KRESOURCES
26#include <kcalcore/recurrence.h>
27#include <kcalcore/icalformat.h>
28#else
29#include <kcal/recurrence.h>
30#include <kcal/icalformat.h>
31#endif
32#include <kglobal.h>
33#include <klocale.h>
34#include <klocalizedstring.h>
35#include <kdebug.h>
36
37#include <QBitArray>
38
39#ifndef KALARMCAL_USE_KRESOURCES
40using namespace KCalCore;
41#else
42using namespace KCal;
43#endif
44
45namespace KAlarmCal
46{
47
48class Recurrence_p : public Recurrence
49{
50 public:
51 using Recurrence::setNewRecurrenceType;
52 Recurrence_p() : Recurrence() {}
53 Recurrence_p(const Recurrence& r) : Recurrence(r) {}
54 Recurrence_p(const Recurrence_p& r) : Recurrence(r) {}
55};
56
57class KARecurrence::Private
58{
59 public:
60 Private()
61 : mFeb29Type(Feb29_None), mCachedType(-1) {}
62 explicit Private(const Recurrence& r)
63 : mRecurrence(r), mFeb29Type(Feb29_None), mCachedType(-1) {}
64 void clear()
65 {
66 mRecurrence.clear();
67 mFeb29Type = Feb29_None;
68 mCachedType = -1;
69 }
70 bool set(Type, int freq, int count, int f29, const KDateTime& start, const KDateTime& end);
71 bool init(RecurrenceRule::PeriodType, int freq, int count, int feb29Type, const KDateTime& start, const KDateTime& end);
72 void fix();
73 void writeRecurrence(const KARecurrence* q, Recurrence& recur) const;
74 KDateTime endDateTime() const;
75 int combineDurations(const RecurrenceRule*, const RecurrenceRule*, QDate& end) const;
76
77 static Feb29Type mDefaultFeb29;
78 Recurrence_p mRecurrence;
79 Feb29Type mFeb29Type; // yearly recurrence on Feb 29th (leap years) / Mar 1st (non-leap years)
80 mutable int mCachedType;
81};
82
83/*=============================================================================
84= Class KARecurrence
85= The purpose of this class is to represent the restricted range of recurrence
86= types which are handled by KAlarm, and to translate between these and the
87= libkcal Recurrence class. In particular, it handles yearly recurrences on
88= 29th February specially:
89=
90= KARecurrence allows annual 29th February recurrences to fall on 28th
91= February or 1st March, or not at all, in non-leap years. It allows such
92= 29th February recurrences to be combined with the 29th of other months in
93= a simple way, represented simply as the 29th of multiple months including
94= February. For storage in the libkcal calendar, the 29th day of the month
95= recurrence for other months is combined with a last-day-of-February or a
96= 60th-day-of-the-year recurrence rule, thereby conforming to RFC2445.
97=============================================================================*/
98
99KARecurrence::Feb29Type KARecurrence::Private::mDefaultFeb29 = KARecurrence::Feb29_None;
100
101KARecurrence::KARecurrence()
102 : d(new Private)
103{ }
104
105KARecurrence::KARecurrence(const Recurrence& r)
106 : d(new Private(r))
107{
108 fix();
109}
110
111KARecurrence::KARecurrence(const KARecurrence& r)
112 : d(new Private(*r.d))
113{ }
114
115KARecurrence::~KARecurrence()
116{
117 delete d;
118}
119
120KARecurrence& KARecurrence::operator=(const KARecurrence& r)
121{
122 if (&r != this)
123 *d = *r.d;
124 return *this;
125}
126
127bool KARecurrence::operator==(const KARecurrence& r) const
128{
129 return d->mRecurrence == r.d->mRecurrence
130 && d->mFeb29Type == r.d->mFeb29Type;
131}
132
133KARecurrence::Feb29Type KARecurrence::feb29Type() const
134{
135 return d->mFeb29Type;
136}
137
138KARecurrence::Feb29Type KARecurrence::defaultFeb29Type()
139{
140 return Private::mDefaultFeb29;
141}
142
143void KARecurrence::setDefaultFeb29Type(Feb29Type t)
144{
145 Private::mDefaultFeb29 = t;
146}
147
148/******************************************************************************
149* Set up a KARecurrence from recurrence parameters, using the start date to
150* determine the recurrence day/month as appropriate.
151* Only a restricted subset of recurrence types is allowed.
152* Reply = true if successful.
153*/
154bool KARecurrence::set(Type t, int freq, int count, const KDateTime& start, const KDateTime& end)
155{
156 return d->set(t, freq, count, -1, start, end);
157}
158
159bool KARecurrence::set(Type t, int freq, int count, const KDateTime& start, const KDateTime& end, Feb29Type f29)
160{
161 return d->set(t, freq, count, f29, start, end);
162}
163
164bool KARecurrence::Private::set(Type recurType, int freq, int count, int f29, const KDateTime& start, const KDateTime& end)
165{
166 mCachedType = -1;
167 RecurrenceRule::PeriodType rrtype;
168 switch (recurType)
169 {
170 case MINUTELY: rrtype = RecurrenceRule::rMinutely; break;
171 case DAILY: rrtype = RecurrenceRule::rDaily; break;
172 case WEEKLY: rrtype = RecurrenceRule::rWeekly; break;
173 case MONTHLY_DAY: rrtype = RecurrenceRule::rMonthly; break;
174 case ANNUAL_DATE: rrtype = RecurrenceRule::rYearly; break;
175 case NO_RECUR: rrtype = RecurrenceRule::rNone; break;
176 default:
177 return false;
178 }
179 if (!init(rrtype, freq, count, f29, start, end))
180 return false;
181 switch (recurType)
182 {
183 case WEEKLY:
184 {
185 QBitArray days(7);
186 days.setBit(start.date().dayOfWeek() - 1);
187 mRecurrence.addWeeklyDays(days);
188 break;
189 }
190 case MONTHLY_DAY:
191 mRecurrence.addMonthlyDate(start.date().day());
192 break;
193 case ANNUAL_DATE:
194 mRecurrence.addYearlyDate(start.date().day());
195 mRecurrence.addYearlyMonth(start.date().month());
196 break;
197 default:
198 break;
199 }
200 return true;
201}
202
203/******************************************************************************
204* Initialise a KARecurrence from recurrence parameters.
205* Reply = true if successful.
206*/
207bool KARecurrence::init(RecurrenceRule::PeriodType t, int freq, int count, const KDateTime& start, const KDateTime& end)
208{
209 return d->init(t, freq, count, -1, start, end);
210}
211
212bool KARecurrence::init(RecurrenceRule::PeriodType t, int freq, int count, const KDateTime& start, const KDateTime& end, Feb29Type f29)
213{
214 return d->init(t, freq, count, f29, start, end);
215}
216
217bool KARecurrence::Private::init(RecurrenceRule::PeriodType recurType, int freq, int count, int f29, const KDateTime& start,
218 const KDateTime& end)
219{
220 clear();
221 const Feb29Type feb29Type = (f29 == -1) ? mDefaultFeb29 : static_cast<Feb29Type>(f29);
222 if (count < -1)
223 return false;
224 const bool dateOnly = start.isDateOnly();
225 if (!count && ((!dateOnly && !end.isValid())
226 || (dateOnly && !end.date().isValid())))
227 return false;
228 switch (recurType)
229 {
230 case RecurrenceRule::rMinutely:
231 case RecurrenceRule::rDaily:
232 case RecurrenceRule::rWeekly:
233 case RecurrenceRule::rMonthly:
234 case RecurrenceRule::rYearly:
235 break;
236 case RecurrenceRule::rNone:
237 return true;
238 default:
239 return false;
240 }
241 mRecurrence.setNewRecurrenceType(recurType, freq);
242 if (count)
243 mRecurrence.setDuration(count);
244 else if (dateOnly)
245 mRecurrence.setEndDate(end.date());
246 else
247 mRecurrence.setEndDateTime(end);
248 KDateTime startdt = start;
249 if (recurType == RecurrenceRule::rYearly
250 && (feb29Type == Feb29_Feb28 || feb29Type == Feb29_Mar1))
251 {
252 int year = startdt.date().year();
253 if (!QDate::isLeapYear(year)
254 && startdt.date().dayOfYear() == (feb29Type == Feb29_Mar1 ? 60 : 59))
255 {
256 /* The event start date is February 28th or March 1st, but it
257 * is a recurrence on February 29th (recurring on February 28th
258 * or March 1st in non-leap years). Adjust the start date to
259 * be on February 29th in the last previous leap year.
260 * This is necessary because KARecurrence represents all types
261 * of 29th February recurrences by a simple 29th February.
262 */
263 while (!QDate::isLeapYear(--year)) ;
264 startdt.setDate(QDate(year, 2, 29));
265 }
266 mFeb29Type = feb29Type;
267 }
268 mRecurrence.setStartDateTime(startdt); // sets recurrence all-day if date-only
269 return true;
270}
271
272/******************************************************************************
273* Initialise the recurrence from an iCalendar RRULE string.
274*/
275bool KARecurrence::set(const QString& icalRRULE)
276{
277 static const QString RRULE = QLatin1String("RRULE:");
278 d->clear();
279 if (icalRRULE.isEmpty())
280 return true;
281 ICalFormat format;
282 if (!format.fromString(d->mRecurrence.defaultRRule(true),
283 (icalRRULE.startsWith(RRULE) ? icalRRULE.mid(RRULE.length()) : icalRRULE)))
284 return false;
285 fix();
286 return true;
287}
288
289void KARecurrence::clear()
290{
291 d->clear();
292}
293
294/******************************************************************************
295* Must be called after presetting with a KCal::Recurrence, to convert the
296* recurrence to KARecurrence types:
297* - Convert hourly recurrences to minutely.
298* - Remove all but the first day in yearly date recurrences.
299* - Check for yearly recurrences falling on February 29th and adjust them as
300* necessary. A 29th of the month rule can be combined with either a 60th day
301* of the year rule or a last day of February rule.
302*/
303void KARecurrence::fix()
304{
305 d->fix();
306}
307
308void KARecurrence::Private::fix()
309{
310 mCachedType = -1;
311 mFeb29Type = Feb29_None;
312 int convert = 0;
313 int days[2] = { 0, 0 };
314 RecurrenceRule* rrules[2];
315 const RecurrenceRule::List rrulelist = mRecurrence.rRules();
316 int rri = 0;
317 const int rrend = rrulelist.count();
318 for (int i = 0; i < 2 && rri < rrend; ++i, ++rri)
319 {
320 RecurrenceRule* rrule = rrulelist[rri];
321 rrules[i] = rrule;
322 bool stop = true;
323 switch (mRecurrence.recurrenceType(rrule))
324 {
325 case Recurrence::rHourly:
326 // Convert an hourly recurrence to a minutely one
327 rrule->setRecurrenceType(RecurrenceRule::rMinutely);
328 rrule->setFrequency(rrule->frequency() * 60);
329 // fall through to rMinutely
330 case Recurrence::rMinutely:
331 case Recurrence::rDaily:
332 case Recurrence::rWeekly:
333 case Recurrence::rMonthlyDay:
334 case Recurrence::rMonthlyPos:
335 case Recurrence::rYearlyPos:
336 if (!convert)
337 ++rri; // remove all rules except the first
338 break;
339 case Recurrence::rOther:
340 if (dailyType(rrule))
341 { // it's a daily rule with BYDAYS
342 if (!convert)
343 ++rri; // remove all rules except the first
344 }
345 break;
346 case Recurrence::rYearlyDay:
347 {
348 // Ensure that the yearly day number is 60 (i.e. Feb 29th/Mar 1st)
349 if (convert)
350 {
351 // This is the second rule.
352 // Ensure that it can be combined with the first one.
353 if (days[0] != 29
354 || rrule->frequency() != rrules[0]->frequency()
355 || rrule->startDt() != rrules[0]->startDt())
356 break;
357 }
358 const QList<int> ds = rrule->byYearDays();
359 if (!ds.isEmpty() && ds.first() == 60)
360 {
361 ++convert; // this rule needs to be converted
362 days[i] = 60;
363 stop = false;
364 break;
365 }
366 break; // not day 60, so remove this rule
367 }
368 case Recurrence::rYearlyMonth:
369 {
370 QList<int> ds = rrule->byMonthDays();
371 if (!ds.isEmpty())
372 {
373 int day = ds.first();
374 if (convert)
375 {
376 // This is the second rule.
377 // Ensure that it can be combined with the first one.
378 if (day == days[0] || (day == -1 && days[0] == 60)
379 || rrule->frequency() != rrules[0]->frequency()
380 || rrule->startDt() != rrules[0]->startDt())
381 break;
382 }
383 if (ds.count() > 1)
384 {
385 ds.clear(); // remove all but the first day
386 ds.append(day);
387 rrule->setByMonthDays(ds);
388 }
389 if (day == -1)
390 {
391 // Last day of the month - only combine if it's February
392 const QList<int> months = rrule->byMonths();
393 if (months.count() != 1 || months.first() != 2)
394 day = 0;
395 }
396 if (day == 29 || day == -1)
397 {
398 ++convert; // this rule may need to be converted
399 days[i] = day;
400 stop = false;
401 break;
402 }
403 }
404 if (!convert)
405 ++rri;
406 break;
407 }
408 default:
409 break;
410 }
411 if (stop)
412 break;
413 }
414
415 // Remove surplus rules
416 for ( ; rri < rrend; ++rri)
417 mRecurrence.deleteRRule(rrulelist[rri]);
418
419 QDate end;
420 int count;
421 QList<int> months;
422 if (convert == 2)
423 {
424 // There are two yearly recurrence rules to combine into a February 29th recurrence.
425 // Combine the two recurrence rules into a single rYearlyMonth rule falling on Feb 29th.
426 // Find the duration of the two RRULEs combined, using the shorter of the two if they differ.
427 if (days[0] != 29)
428 {
429 // Swap the two rules so that the 29th rule is the first
430 RecurrenceRule* rr = rrules[0];
431 rrules[0] = rrules[1]; // the 29th rule
432 rrules[1] = rr;
433 const int d = days[0];
434 days[0] = days[1];
435 days[1] = d; // the non-29th day
436 }
437 // If February is included in the 29th rule, remove it to avoid duplication
438 months = rrules[0]->byMonths();
439 if (months.removeAll(2))
440 rrules[0]->setByMonths(months);
441
442 count = combineDurations(rrules[0], rrules[1], end);
443 mFeb29Type = (days[1] == 60) ? Feb29_Mar1 : Feb29_Feb28;
444 }
445 else if (convert == 1 && days[0] == 60)
446 {
447 // There is a single 60th day of the year rule.
448 // Convert it to a February 29th recurrence.
449 count = mRecurrence.duration();
450 if (!count)
451 end = mRecurrence.endDate();
452 mFeb29Type = Feb29_Mar1;
453 }
454 else
455 return;
456
457 // Create the new February 29th recurrence
458 mRecurrence.setNewRecurrenceType(RecurrenceRule::rYearly, mRecurrence.frequency());
459 RecurrenceRule* rrule = mRecurrence.defaultRRule();
460 months.append(2);
461 rrule->setByMonths(months);
462 QList<int> ds;
463 ds.append(29);
464 rrule->setByMonthDays(ds);
465 if (count)
466 mRecurrence.setDuration(count);
467 else
468 mRecurrence.setEndDate(end);
469}
470
471/******************************************************************************
472* Initialise a KCal::Recurrence to be the same as this instance.
473* Additional recurrence rules are created as necessary if it recurs on Feb 29th.
474*/
475void KARecurrence::writeRecurrence(Recurrence& recur) const
476{
477 d->writeRecurrence(this, recur);
478}
479
480void KARecurrence::Private::writeRecurrence(const KARecurrence* q, Recurrence& recur) const
481{
482 recur.clear();
483 recur.setStartDateTime(mRecurrence.startDateTime());
484 recur.setExDates(mRecurrence.exDates());
485 recur.setExDateTimes(mRecurrence.exDateTimes());
486 const RecurrenceRule* rrule = mRecurrence.defaultRRuleConst();
487 if (!rrule)
488 return;
489 int freq = mRecurrence.frequency();
490 int count = mRecurrence.duration();
491 static_cast<Recurrence_p*>(&recur)->setNewRecurrenceType(rrule->recurrenceType(), freq);
492 if (count)
493 recur.setDuration(count);
494 else
495 recur.setEndDateTime(endDateTime());
496 switch (q->type())
497 {
498 case DAILY:
499 if (rrule->byDays().isEmpty())
500 break;
501 // fall through to rWeekly
502 case WEEKLY:
503 case MONTHLY_POS:
504 recur.defaultRRule(true)->setByDays(rrule->byDays());
505 break;
506 case MONTHLY_DAY:
507 recur.defaultRRule(true)->setByMonthDays(rrule->byMonthDays());
508 break;
509 case ANNUAL_POS:
510 recur.defaultRRule(true)->setByMonths(rrule->byMonths());
511 recur.defaultRRule()->setByDays(rrule->byDays());
512 break;
513 case ANNUAL_DATE:
514 {
515 QList<int> months = rrule->byMonths();
516 const QList<int> days = mRecurrence.monthDays();
517 const bool special = (mFeb29Type != Feb29_None && !days.isEmpty()
518 && days.first() == 29 && months.removeAll(2));
519 RecurrenceRule* rrule1 = recur.defaultRRule();
520 rrule1->setByMonths(months);
521 rrule1->setByMonthDays(days);
522 if (!special)
523 break;
524
525 // It recurs on the 29th February.
526 // Create an additional 60th day of the year, or last day of February, rule.
527 RecurrenceRule* rrule2 = new RecurrenceRule();
528 rrule2->setRecurrenceType(RecurrenceRule::rYearly);
529 rrule2->setFrequency(freq);
530 rrule2->setStartDt(mRecurrence.startDateTime());
531 rrule2->setAllDay(mRecurrence.allDay());
532 if (!count)
533 rrule2->setEndDt(endDateTime());
534 if (mFeb29Type == Feb29_Mar1)
535 {
536 QList<int> ds;
537 ds.append(60);
538 rrule2->setByYearDays(ds);
539 }
540 else
541 {
542 QList<int> ds;
543 ds.append(-1);
544 rrule2->setByMonthDays(ds);
545 QList<int> ms;
546 ms.append(2);
547 rrule2->setByMonths(ms);
548 }
549
550 if (months.isEmpty())
551 {
552 // Only February recurs.
553 // Replace the RRULE and keep the recurrence count the same.
554 if (count)
555 rrule2->setDuration(count);
556 recur.unsetRecurs();
557 }
558 else
559 {
560 // Months other than February also recur on the 29th.
561 // Remove February from the list and add a separate RRULE for February.
562 if (count)
563 {
564 rrule1->setDuration(-1);
565 rrule2->setDuration(-1);
566 if (count > 0)
567 {
568 /* Adjust counts in the two rules to keep the correct occurrence total.
569 * Note that durationTo() always includes the start date. Since for an
570 * individual RRULE the start date may not actually be included, we need
571 * to decrement the count if the start date doesn't actually recur in
572 * this RRULE.
573 * Note that if the count is small, one of the rules may not recur at
574 * all. In that case, retain it so that the February 29th characteristic
575 * is not lost should the user later change the recurrence count.
576 */
577 const KDateTime end = endDateTime();
578 const int count1 = rrule1->durationTo(end)
579 - (rrule1->recursOn(mRecurrence.startDate(), mRecurrence.startDateTime().timeSpec()) ? 0 : 1);
580 if (count1 > 0)
581 rrule1->setDuration(count1);
582 else
583 rrule1->setEndDt(mRecurrence.startDateTime());
584 const int count2 = rrule2->durationTo(end)
585 - (rrule2->recursOn(mRecurrence.startDate(), mRecurrence.startDateTime().timeSpec()) ? 0 : 1);
586 if (count2 > 0)
587 rrule2->setDuration(count2);
588 else
589 rrule2->setEndDt(mRecurrence.startDateTime());
590 }
591 }
592 }
593 recur.addRRule(rrule2);
594 break;
595 }
596 default:
597 break;
598 }
599}
600
601KDateTime KARecurrence::startDateTime() const
602{
603 return d->mRecurrence.startDateTime();
604}
605
606QDate KARecurrence::startDate() const
607{
608 return d->mRecurrence.startDate();
609}
610
611void KARecurrence::setStartDateTime(const KDateTime& dt, bool dateOnly)
612{
613 d->mRecurrence.setStartDateTime(dt);
614 if (dateOnly)
615 d->mRecurrence.setAllDay(true);
616}
617
618/******************************************************************************
619* Return the date/time of the last recurrence.
620*/
621KDateTime KARecurrence::endDateTime() const
622{
623 return d->endDateTime();
624}
625
626KDateTime KARecurrence::Private::endDateTime() const
627{
628 if (mFeb29Type == Feb29_None || mRecurrence.duration() <= 1)
629 {
630 /* Either it doesn't have any special February 29th treatment,
631 * it's infinite (count = -1), the end date is specified
632 * (count = 0), or it ends on the start date (count = 1).
633 * So just use the normal KCal end date calculation.
634 */
635 return mRecurrence.endDateTime();
636 }
637
638 /* Create a temporary recurrence rule to find the end date.
639 * In a standard KCal recurrence, the 29th February only occurs once every
640 * 4 years. So shift the temporary recurrence date to the 28th to ensure
641 * that it occurs every year, thus giving the correct occurrence count.
642 */
643 RecurrenceRule* rrule = new RecurrenceRule();
644 rrule->setRecurrenceType(RecurrenceRule::rYearly);
645 KDateTime dt = mRecurrence.startDateTime();
646 QDate da = dt.date();
647 switch (da.day())
648 {
649 case 29:
650 // The start date is definitely a recurrence date, so shift
651 // start date to the temporary recurrence date of the 28th
652 da.setYMD(da.year(), da.month(), 28);
653 break;
654 case 28:
655 if (da.month() != 2 || mFeb29Type != Feb29_Feb28 || QDate::isLeapYear(da.year()))
656 {
657 // Start date is not a recurrence date, so shift it to 27th
658 da.setYMD(da.year(), da.month(), 27);
659 }
660 break;
661 case 1:
662 if (da.month() == 3 && mFeb29Type == Feb29_Mar1 && !QDate::isLeapYear(da.year()))
663 {
664 // Start date is a March 1st recurrence date, so shift
665 // start date to the temporary recurrence date of the 28th
666 da.setYMD(da.year(), 2, 28);
667 }
668 break;
669 default:
670 break;
671 }
672 dt.setDate(da);
673 rrule->setStartDt(dt);
674 rrule->setAllDay(mRecurrence.allDay());
675 rrule->setFrequency(mRecurrence.frequency());
676 rrule->setDuration(mRecurrence.duration());
677 QList<int> ds;
678 ds.append(28);
679 rrule->setByMonthDays(ds);
680 rrule->setByMonths(mRecurrence.defaultRRuleConst()->byMonths());
681 dt = rrule->endDt();
682 delete rrule;
683
684 // We've found the end date for a recurrence on the 28th. Unless that date
685 // is a real February 28th recurrence, adjust to the actual recurrence date.
686 if (mFeb29Type == Feb29_Feb28 && dt.date().month() == 2 && !QDate::isLeapYear(dt.date().year()))
687 return dt;
688 return dt.addDays(1);
689}
690
691/******************************************************************************
692* Return the date of the last recurrence.
693*/
694QDate KARecurrence::endDate() const
695{
696 KDateTime end = endDateTime();
697 return end.isValid() ? end.date() : QDate();
698}
699
700void KARecurrence::setEndDate(const QDate& endDate)
701{
702 d->mRecurrence.setEndDate(endDate);
703}
704
705void KARecurrence::setEndDateTime(const KDateTime& endDateTime)
706{
707 d->mRecurrence.setEndDateTime(endDateTime);
708}
709
710bool KARecurrence::allDay() const
711{
712 return d->mRecurrence.allDay();
713}
714
715void KARecurrence::setRecurReadOnly(bool readOnly)
716{
717 d->mRecurrence.setRecurReadOnly(readOnly);
718}
719
720bool KARecurrence::recurReadOnly() const
721{
722 return d->mRecurrence.recurReadOnly();
723}
724
725bool KARecurrence::recurs() const
726{
727 return d->mRecurrence.recurs();
728}
729
730QBitArray KARecurrence::days() const
731{
732 return d->mRecurrence.days();
733}
734
735QList<RecurrenceRule::WDayPos> KARecurrence::monthPositions() const
736{
737 return d->mRecurrence.monthPositions();
738}
739
740QList<int> KARecurrence::monthDays() const
741{
742 return d->mRecurrence.monthDays();
743}
744
745QList<int> KARecurrence::yearDays() const
746{
747 return d->mRecurrence.yearDays();
748}
749
750QList<int> KARecurrence::yearDates() const
751{
752 return d->mRecurrence.yearDates();
753}
754
755QList<int> KARecurrence::yearMonths() const
756{
757 return d->mRecurrence.yearMonths();
758}
759
760QList<RecurrenceRule::WDayPos> KARecurrence::yearPositions() const
761{
762 return d->mRecurrence.yearPositions();
763}
764
765void KARecurrence::addWeeklyDays(const QBitArray& days)
766{
767 d->mRecurrence.addWeeklyDays(days);
768}
769
770void KARecurrence::addYearlyDay(int day)
771{
772 d->mRecurrence.addYearlyDay(day);
773}
774
775void KARecurrence::addYearlyDate(int date)
776{
777 d->mRecurrence.addYearlyDate(date);
778}
779
780void KARecurrence::addYearlyMonth(short month)
781{
782 d->mRecurrence.addYearlyMonth(month);
783}
784
785void KARecurrence::addYearlyPos(short pos, const QBitArray& days)
786{
787 d->mRecurrence.addYearlyPos(pos, days);
788}
789
790void KARecurrence::addMonthlyPos(short pos, const QBitArray& days)
791{
792 d->mRecurrence.addMonthlyPos(pos, days);
793}
794
795void KARecurrence::addMonthlyPos(short pos, ushort day)
796{
797 d->mRecurrence.addMonthlyPos(pos, day);
798}
799
800void KARecurrence::addMonthlyDate(short day)
801{
802 d->mRecurrence.addMonthlyDate(day);
803}
804
805/******************************************************************************
806* Get the next time the recurrence occurs, strictly after a specified time.
807*/
808KDateTime KARecurrence::getNextDateTime(const KDateTime& preDateTime) const
809{
810 switch (type())
811 {
812 case ANNUAL_DATE:
813 case ANNUAL_POS:
814 {
815 Recurrence recur;
816 writeRecurrence(recur);
817 return recur.getNextDateTime(preDateTime);
818 }
819 default:
820 return d->mRecurrence.getNextDateTime(preDateTime);
821 }
822}
823
824/******************************************************************************
825* Get the previous time the recurrence occurred, strictly before a specified time.
826*/
827KDateTime KARecurrence::getPreviousDateTime(const KDateTime& afterDateTime) const
828{
829 switch (type())
830 {
831 case ANNUAL_DATE:
832 case ANNUAL_POS:
833 {
834 Recurrence recur;
835 writeRecurrence(recur);
836 return recur.getPreviousDateTime(afterDateTime);
837 }
838 default:
839 return d->mRecurrence.getPreviousDateTime(afterDateTime);
840 }
841}
842
843/******************************************************************************
844* Return whether the event will recur on the specified date.
845* The start date only returns true if it matches the recurrence rules.
846*/
847bool KARecurrence::recursOn(const QDate& dt, const KDateTime::Spec& timeSpec) const
848{
849 if (!d->mRecurrence.recursOn(dt, timeSpec))
850 return false;
851 if (dt != d->mRecurrence.startDate())
852 return true;
853 // We know now that it isn't in EXDATES or EXRULES,
854 // so we just need to check if it's in RDATES or RRULES
855 if (d->mRecurrence.rDates().contains(dt))
856 return true;
857 const RecurrenceRule::List rulelist = d->mRecurrence.rRules();
858 for (int rri = 0, rrend = rulelist.count(); rri < rrend; ++rri)
859 if (rulelist[rri]->recursOn(dt, timeSpec))
860 return true;
861 const DateTimeList dtlist = d->mRecurrence.rDateTimes();
862 for (int dti = 0, dtend = dtlist.count(); dti < dtend; ++dti)
863 if (dtlist[dti].date() == dt)
864 return true;
865 return false;
866}
867
868bool KARecurrence::recursAt(const KDateTime& dt) const
869{
870 return d->mRecurrence.recursAt(dt);
871}
872
873TimeList KARecurrence::recurTimesOn(const QDate& date, const KDateTime::Spec& timeSpec) const
874{
875 return d->mRecurrence.recurTimesOn(date, timeSpec);
876}
877
878DateTimeList KARecurrence::timesInInterval(const KDateTime& start, const KDateTime& end) const
879{
880 return d->mRecurrence.timesInInterval(start, end);
881}
882
883int KARecurrence::frequency() const
884{
885 return d->mRecurrence.frequency();
886}
887
888void KARecurrence::setFrequency(int freq)
889{
890 d->mRecurrence.setFrequency(freq);
891}
892
893int KARecurrence::duration() const
894{
895 return d->mRecurrence.duration();
896}
897
898void KARecurrence::setDuration(int duration)
899{
900 d->mRecurrence.setDuration(duration);
901}
902
903int KARecurrence::durationTo(const KDateTime& dt) const
904{
905 return d->mRecurrence.durationTo(dt);
906}
907
908int KARecurrence::durationTo(const QDate& date) const
909{
910 return d->mRecurrence.durationTo(date);
911}
912
913/******************************************************************************
914* Find the duration of two RRULEs combined.
915* Use the shorter of the two if they differ.
916*/
917int KARecurrence::Private::combineDurations(const RecurrenceRule* rrule1, const RecurrenceRule* rrule2, QDate& end) const
918{
919 int count1 = rrule1->duration();
920 int count2 = rrule2->duration();
921 if (count1 == -1 && count2 == -1)
922 return -1;
923
924 // One of the RRULEs may not recur at all if the recurrence count is small.
925 // In this case, its end date will have been set to the start date.
926 if (count1 && !count2 && rrule2->endDt().date() == mRecurrence.startDateTime().date())
927 return count1;
928 if (count2 && !count1 && rrule1->endDt().date() == mRecurrence.startDateTime().date())
929 return count2;
930
931 /* The duration counts will be different even for RRULEs of the same length,
932 * because the first RRULE only actually occurs every 4 years. So we need to
933 * compare the end dates.
934 */
935 if (!count1 || !count2)
936 count1 = count2 = 0;
937 // Get the two rules sorted by end date.
938 KDateTime end1 = rrule1->endDt();
939 KDateTime end2 = rrule2->endDt();
940 if (end1.date() == end2.date())
941 {
942 end = end1.date();
943 return count1 + count2;
944 }
945 const RecurrenceRule* rr1; // earlier end date
946 const RecurrenceRule* rr2; // later end date
947 if (end2.isValid()
948 && (!end1.isValid() || end1.date() > end2.date()))
949 {
950 // Swap the two rules to make rr1 have the earlier end date
951 rr1 = rrule2;
952 rr2 = rrule1;
953 const KDateTime e = end1;
954 end1 = end2;
955 end2 = e;
956 }
957 else
958 {
959 rr1 = rrule1;
960 rr2 = rrule2;
961 }
962
963 // Get the date of the next occurrence after the end of the earlier ending rule
964 RecurrenceRule rr(*rr1);
965 rr.setDuration(-1);
966 KDateTime next1(rr.getNextDate(end1));
967 next1.setDateOnly(true);
968 if (!next1.isValid())
969 end = end1.date();
970 else
971 {
972 if (end2.isValid() && next1 > end2)
973 {
974 // The next occurrence after the end of the earlier ending rule
975 // is later than the end of the later ending rule. So simply use
976 // the end date of the later rule.
977 end = end2.date();
978 return count1 + count2;
979 }
980 const QDate prev2 = rr2->getPreviousDate(next1).date();
981 end = (prev2 > end1.date()) ? prev2 : end1.date();
982 }
983 if (count2)
984 count2 = rr2->durationTo(end);
985 return count1 + count2;
986}
987
988/******************************************************************************
989* Return the longest interval between recurrences.
990* Reply = 0 if it never recurs.
991*/
992Duration KARecurrence::longestInterval() const
993{
994 const int freq = d->mRecurrence.frequency();
995 switch (type())
996 {
997 case MINUTELY:
998 return Duration(freq * 60, Duration::Seconds);
999
1000 case DAILY:
1001 {
1002 const QList<RecurrenceRule::WDayPos> days = d->mRecurrence.defaultRRuleConst()->byDays();
1003 if (days.isEmpty())
1004 return Duration(freq, Duration::Days);
1005
1006 // After applying the frequency, the specified days of the week
1007 // further restrict when the recurrence occurs.
1008 // So the maximum interval may be greater than the frequency.
1009 bool ds[7] = { false, false, false, false, false, false, false };
1010 for (int i = 0, end = days.count(); i < end; ++i)
1011 if (days[i].pos() == 0)
1012 ds[days[i].day() - 1] = true;
1013 if (freq % 7)
1014 {
1015 // It will recur on every day of the week in some week or other
1016 // (except for those days which are excluded).
1017 int first = -1;
1018 int last = -1;
1019 int maxgap = 1;
1020 for (int i = 0; i < freq*7; i += freq)
1021 {
1022 if (ds[i % 7])
1023 {
1024 if (first < 0)
1025 first = i;
1026 else if (i - last > maxgap)
1027 maxgap = i - last;
1028 last = i;
1029 }
1030 }
1031 const int wrap = freq*7 - last + first;
1032 if (wrap > maxgap)
1033 maxgap = wrap;
1034 return Duration(maxgap, Duration::Days);
1035 }
1036 else
1037 {
1038 // It will recur on the same day of the week every time.
1039 // Ensure that the day is a day which is not excluded.
1040 if (ds[d->mRecurrence.startDate().dayOfWeek() - 1])
1041 return Duration(freq, Duration::Days);
1042 break;
1043 }
1044 }
1045 case WEEKLY:
1046 {
1047 // Find which days of the week it recurs on, and if on more than
1048 // one, reduce the maximum interval accordingly.
1049 const QBitArray ds = d->mRecurrence.days();
1050 int first = -1;
1051 int last = -1;
1052 int maxgap = 1;
1053 // Use the user's definition of the week, starting at the
1054 // day of the week specified by the user's locale.
1055 const int weekStart = KGlobal::locale()->weekStartDay() - 1; // zero-based
1056 for (int i = 0; i < 7; ++i)
1057 {
1058 // Get the standard KDE day-of-week number (zero-based)
1059 // for the day-of-week number in the user's locale.
1060 if (ds.testBit((i + weekStart) % 7))
1061 {
1062 if (first < 0)
1063 first = i;
1064 else if (i - last > maxgap)
1065 maxgap = i - last;
1066 last = i;
1067 }
1068 }
1069 if (first < 0)
1070 break; // no days recur
1071 const int span = last - first;
1072 if (freq > 1)
1073 return Duration(freq*7 - span, Duration::Days);
1074 if (7 - span > maxgap)
1075 return Duration(7 - span, Duration::Days);
1076 return Duration(maxgap, Duration::Days);
1077 }
1078 case MONTHLY_DAY:
1079 case MONTHLY_POS:
1080 return Duration(freq * 31, Duration::Days);
1081
1082 case ANNUAL_DATE:
1083 case ANNUAL_POS:
1084 {
1085 // Find which months of the year it recurs on, and if on more than
1086 // one, reduce the maximum interval accordingly.
1087 const QList<int> months = d->mRecurrence.yearMonths(); // month list is sorted
1088 if (months.isEmpty())
1089 break; // no months recur
1090 if (months.count() == 1)
1091 return Duration(freq * 365, Duration::Days);
1092 int first = -1;
1093 int last = -1;
1094 int maxgap = 0;
1095 for (int i = 0, end = months.count(); i < end; ++i)
1096 {
1097 if (first < 0)
1098 first = months[i];
1099 else
1100 {
1101 const int span = QDate(2001, last, 1).daysTo(QDate(2001, months[i], 1));
1102 if (span > maxgap)
1103 maxgap = span;
1104 }
1105 last = months[i];
1106 }
1107 const int span = QDate(2001, first, 1).daysTo(QDate(2001, last, 1));
1108 if (freq > 1)
1109 return Duration(freq*365 - span, Duration::Days);
1110 if (365 - span > maxgap)
1111 return Duration(365 - span, Duration::Days);
1112 return Duration(maxgap, Duration::Days);
1113 }
1114 default:
1115 break;
1116 }
1117 return 0;
1118}
1119
1120/******************************************************************************
1121* Return the interval between recurrences, if the interval between successive
1122* occurrences does not vary.
1123* Reply = 0 if recurrence does not occur at fixed intervals.
1124*/
1125Duration KARecurrence::regularInterval() const
1126{
1127 int freq = d->mRecurrence.frequency();
1128 switch (type())
1129 {
1130 case MINUTELY:
1131 return Duration(freq * 60, Duration::Seconds);
1132 case DAILY:
1133 {
1134 const QList<RecurrenceRule::WDayPos> days = d->mRecurrence.defaultRRuleConst()->byDays();
1135 if (days.isEmpty())
1136 return Duration(freq, Duration::Days);
1137 // After applying the frequency, the specified days of the week
1138 // further restrict when the recurrence occurs.
1139 // Find which days occur, and count the number of days which occur.
1140 bool ds[7] = { false, false, false, false, false, false, false };
1141 for (int i = 0, end = days.count(); i < end; ++i)
1142 if (days[i].pos() == 0)
1143 ds[days[i].day() - 1] = true;
1144 if (!(freq % 7))
1145 {
1146 // It will recur on the same day of the week every time.
1147 // Check whether that day is in the list of included days.
1148 if (ds[d->mRecurrence.startDate().dayOfWeek() - 1])
1149 return Duration(freq, Duration::Days);
1150 break;
1151 }
1152 int n = 0; // number of days which occur
1153 for (int i = 0; i < 7; ++i)
1154 if (ds[i])
1155 ++n;
1156 if (n == 7)
1157 return Duration(freq, Duration::Days); // every day is included
1158 if (n == 1)
1159 return Duration(freq * 7, Duration::Days); // only one day of the week is included
1160 break;
1161 }
1162 case WEEKLY:
1163 {
1164 const QList<RecurrenceRule::WDayPos> days = d->mRecurrence.defaultRRuleConst()->byDays();
1165 if (days.isEmpty())
1166 return Duration(freq * 7, Duration::Days);
1167 // The specified days of the week occur every week in which the
1168 // recurrence occurs.
1169 // Find which days occur, and count the number of days which occur.
1170 bool ds[7] = { false, false, false, false, false, false, false };
1171 for (int i = 0, end = days.count(); i < end; ++i)
1172 if (days[i].pos() == 0)
1173 ds[days[i].day() - 1] = true;
1174 int n = 0; // number of days which occur
1175 for (int i = 0; i < 7; ++i)
1176 if (ds[i])
1177 ++n;
1178 if (n == 7)
1179 {
1180 if (freq == 1)
1181 return Duration(freq, Duration::Days); // every day is included
1182 break;
1183 }
1184 if (n == 1)
1185 return Duration(freq * 7, Duration::Days); // only one day of the week is included
1186 break;
1187 }
1188 default:
1189 break;
1190 }
1191 return 0;
1192}
1193
1194DateTimeList KARecurrence::exDateTimes() const
1195{
1196 return d->mRecurrence.exDateTimes();
1197}
1198
1199DateList KARecurrence::exDates() const
1200{
1201 return d->mRecurrence.exDates();
1202}
1203
1204void KARecurrence::setExDateTimes(const DateTimeList& exdates)
1205{
1206 d->mRecurrence.setExDateTimes(exdates);
1207}
1208
1209void KARecurrence::setExDates(const DateList& exdates)
1210{
1211 d->mRecurrence.setExDates(exdates);
1212}
1213
1214void KARecurrence::addExDateTime(const KDateTime& exdate)
1215{
1216 d->mRecurrence.addExDateTime(exdate);
1217}
1218
1219void KARecurrence::addExDate(const QDate& exdate)
1220{
1221 d->mRecurrence.addExDate(exdate);
1222}
1223
1224void KARecurrence::shiftTimes(const KDateTime::Spec& oldSpec, const KDateTime::Spec& newSpec)
1225{
1226 d->mRecurrence.shiftTimes(oldSpec, newSpec);
1227}
1228
1229RecurrenceRule* KARecurrence::defaultRRuleConst() const
1230{
1231 return d->mRecurrence.defaultRRuleConst();
1232}
1233
1234/******************************************************************************
1235* Return the recurrence's period type.
1236*/
1237KARecurrence::Type KARecurrence::type() const
1238{
1239 if (d->mCachedType == -1)
1240 d->mCachedType = type(d->mRecurrence.defaultRRuleConst());
1241 return static_cast<Type>(d->mCachedType);
1242}
1243
1244/******************************************************************************
1245* Return the recurrence rule type.
1246*/
1247KARecurrence::Type KARecurrence::type(const RecurrenceRule* rrule)
1248{
1249 switch (Recurrence::recurrenceType(rrule))
1250 {
1251 case Recurrence::rMinutely: return MINUTELY;
1252 case Recurrence::rDaily: return DAILY;
1253 case Recurrence::rWeekly: return WEEKLY;
1254 case Recurrence::rMonthlyDay: return MONTHLY_DAY;
1255 case Recurrence::rMonthlyPos: return MONTHLY_POS;
1256 case Recurrence::rYearlyMonth: return ANNUAL_DATE;
1257 case Recurrence::rYearlyPos: return ANNUAL_POS;
1258 default:
1259 if (dailyType(rrule))
1260 return DAILY;
1261 return NO_RECUR;
1262 }
1263}
1264
1265/******************************************************************************
1266* Check if the rule is a daily rule with or without BYDAYS specified.
1267*/
1268bool KARecurrence::dailyType(const RecurrenceRule* rrule)
1269{
1270 if (rrule->recurrenceType() != RecurrenceRule::rDaily
1271 || !rrule->bySeconds().isEmpty()
1272 || !rrule->byMinutes().isEmpty()
1273 || !rrule->byHours().isEmpty()
1274 || !rrule->byWeekNumbers().isEmpty()
1275 || !rrule->byMonthDays().isEmpty()
1276 || !rrule->byMonths().isEmpty()
1277 || !rrule->bySetPos().isEmpty()
1278 || !rrule->byYearDays().isEmpty())
1279 return false;
1280 const QList<RecurrenceRule::WDayPos> days = rrule->byDays();
1281 if (days.isEmpty())
1282 return true;
1283 // Check that all the positions are zero (i.e. every time)
1284 bool found = false;
1285 for (int i = 0, end = days.count(); i < end; ++i)
1286 {
1287 if (days[i].pos() != 0)
1288 return false;
1289 found = true;
1290 }
1291 return found;
1292}
1293
1294} // namespace KAlarmCal
1295
1296// vim: et sw=4:
KAlarmCal::KARecurrence
Represents recurrences for KAlarm.
Definition: karecurrence.h:62
KAlarmCal::KARecurrence::set
bool set(const QString &icalRRULE)
Initialise the recurrence from an iCalendar RRULE string.
Definition: karecurrence.cpp:275
KAlarmCal::KARecurrence::Type
Type
The recurrence's period type.
Definition: karecurrence.h:68
KAlarmCal::KARecurrence::Feb29Type
Feb29Type
When annual February 29th recurrences should occur in non-leap years.
Definition: karecurrence.h:80
KAlarmCal::KARecurrence::Feb29_None
@ Feb29_None
does not occur in non-leap years
Definition: karecurrence.h:83
KAlarmCal::KARecurrence::type
Type type() const
Return the recurrence's period type.
Definition: karecurrence.cpp:1237
KAlarmCal::KARecurrence::feb29Type
Feb29Type feb29Type() const
Return when 29th February annual recurrences should occur in non-leap years.
Definition: karecurrence.cpp:133
KCalCore::Duration
KCalCore::Duration::Days
Days
KCalCore::Duration::Seconds
Seconds
KCalCore::ICalFormat
KCalCore::ICalFormat::fromString
bool fromString(const Calendar::Ptr &calendar, const QString &string, bool deleted=false, const QString &notebook=QString())
KCalCore::RecurrenceRule
KCalCore::RecurrenceRule::setAllDay
void setAllDay(bool allDay)
KCalCore::RecurrenceRule::clear
void clear()
KCalCore::RecurrenceRule::PeriodType
PeriodType
KCalCore::RecurrenceRule::setDuration
void setDuration(int duration)
KCalCore::RecurrenceRule::setFrequency
void setFrequency(int freq)
KCalCore::RecurrenceRule::frequency
uint frequency() const
KCalCore::RecurrenceRule::duration
int duration() const
KCalCore::RecurrenceRule::durationTo
int durationTo(const KDateTime &dt) const
KCalCore::RecurrenceRule::startDt
KDateTime startDt() const
KCalCore::RecurrenceRule::setEndDt
void setEndDt(const KDateTime &endDateTime)
KCalCore::RecurrenceRule::recursOn
bool recursOn(const QDate &date, const KDateTime::Spec &timeSpec) const
KCalCore::RecurrenceRule::getPreviousDate
KDateTime getPreviousDate(const KDateTime &afterDateTime) const
KCalCore::RecurrenceRule::endDt
KDateTime endDt(bool *result=0) const
KCalCore::RecurrenceRule::setStartDt
void setStartDt(const KDateTime &start)
KCalCore::Recurrence
KCalCore::Recurrence::recurrenceType
ushort recurrenceType() const
KCalCore::Recurrence::setEndDateTime
void setEndDateTime(const KDateTime &endDateTime)
KCalCore::Recurrence::setStartDateTime
void setStartDateTime(const KDateTime &start)
KCalCore::Recurrence::getPreviousDateTime
KDateTime getPreviousDateTime(const KDateTime &afterDateTime) const
KCalCore::Recurrence::addRRule
void addRRule(RecurrenceRule *rrule)
KCalCore::Recurrence::unsetRecurs
void unsetRecurs()
KCalCore::Recurrence::clear
void clear()
KCalCore::Recurrence::getNextDateTime
KDateTime getNextDateTime(const KDateTime &preDateTime) const
KCalCore::Recurrence::setDuration
void setDuration(int duration)
KCalCore::SortableList
icalformat.h
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Jul 21 2022 00:00:00 by doxygen 1.9.5 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KAlarm Library

Skip menu "KAlarm Library"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Related Pages

kdepimlibs-4.14.10 API Reference

Skip menu "kdepimlibs-4.14.10 API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal