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

KCalCore Library

  • kcalcore
recurrence.cpp
1 /*
2  This file is part of kcalcore library.
3 
4  Copyright (c) 1998 Preston Brown <pbrown@kde.org>
5  Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
6  Copyright (c) 2002,2006 David Jarvie <software@astrojar.org.uk>
7  Copyright (C) 2005 Reinhold Kainhofer <kainhofer@kde.org>
8 
9  This library is free software; you can redistribute it and/or
10  modify it under the terms of the GNU Library General Public
11  License as published by the Free Software Foundation; either
12  version 2 of the License, or (at your option) any later version.
13 
14  This library is distributed in the hope that it will be useful,
15  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  Library General Public License for more details.
18 
19  You should have received a copy of the GNU Library General Public License
20  along with this library; see the file COPYING.LIB. If not, write to
21  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  Boston, MA 02110-1301, USA.
23 */
24 #include "recurrence.h"
25 
26 #include <KDebug>
27 
28 #include <QtCore/QBitArray>
29 
30 using namespace KCalCore;
31 
32 //@cond PRIVATE
33 class KCalCore::Recurrence::Private
34 {
35  public:
36  Private()
37  : mCachedType( rMax ),
38  mAllDay( false ),
39  mRecurReadOnly( false )
40  {
41  }
42 
43  Private( const Private &p )
44  : mRDateTimes( p.mRDateTimes ),
45  mRDates( p.mRDates ),
46  mExDateTimes( p.mExDateTimes ),
47  mExDates( p.mExDates ),
48  mStartDateTime( p.mStartDateTime ),
49  mCachedType( p.mCachedType ),
50  mAllDay( p.mAllDay ),
51  mRecurReadOnly( p.mRecurReadOnly )
52  {
53  }
54 
55  bool operator==( const Private &p ) const;
56 
57  RecurrenceRule::List mExRules;
58  RecurrenceRule::List mRRules;
59  DateTimeList mRDateTimes;
60  DateList mRDates;
61  DateTimeList mExDateTimes;
62  DateList mExDates;
63  KDateTime mStartDateTime; // date/time of first recurrence
64  QList<RecurrenceObserver*> mObservers;
65 
66  // Cache the type of the recurrence with the old system (e.g. MonthlyPos)
67  mutable ushort mCachedType;
68 
69  bool mAllDay; // the recurrence has no time, just a date
70  bool mRecurReadOnly;
71 };
72 
73 bool Recurrence::Private::operator==( const Recurrence::Private &p ) const
74 {
75  kDebug() << mStartDateTime << p.mStartDateTime;
76 
77  if ( ( mStartDateTime != p.mStartDateTime &&
78  ( mStartDateTime.isValid() || p.mStartDateTime.isValid() ) ) ||
79  mAllDay != p.mAllDay ||
80  mRecurReadOnly != p.mRecurReadOnly ||
81  mExDates != p.mExDates ||
82  mExDateTimes != p.mExDateTimes ||
83  mRDates != p.mRDates ||
84  mRDateTimes != p.mRDateTimes ) {
85  return false;
86  }
87 
88 // Compare the rrules, exrules! Assume they have the same order... This only
89 // matters if we have more than one rule (which shouldn't be the default anyway)
90  int i;
91  int end = mRRules.count();
92  if ( end != p.mRRules.count() ) {
93  return false;
94  }
95  for ( i = 0; i < end; ++i ) {
96  if ( *mRRules[i] != *p.mRRules[i] ) {
97  return false;
98  }
99  }
100  end = mExRules.count();
101  if ( end != p.mExRules.count() ) {
102  return false;
103  }
104  for ( i = 0; i < end; ++i ) {
105  if ( *mExRules[i] != *p.mExRules[i] ) {
106  return false;
107  }
108  }
109  return true;
110 }
111 //@endcond
112 
113 Recurrence::Recurrence()
114  : d( new KCalCore::Recurrence::Private() )
115 {
116 }
117 
118 Recurrence::Recurrence( const Recurrence &r )
119  : RecurrenceRule::RuleObserver(),
120  d( new KCalCore::Recurrence::Private( *r.d ) )
121 {
122  int i, end;
123  for ( i = 0, end = r.d->mRRules.count(); i < end; ++i ) {
124  RecurrenceRule *rule = new RecurrenceRule( *r.d->mRRules[i] );
125  d->mRRules.append( rule );
126  rule->addObserver( this );
127  }
128  for ( i = 0, end = r.d->mExRules.count(); i < end; ++i ) {
129  RecurrenceRule *rule = new RecurrenceRule( *r.d->mExRules[i] );
130  d->mExRules.append( rule );
131  rule->addObserver( this );
132  }
133 }
134 
135 Recurrence::~Recurrence()
136 {
137  qDeleteAll( d->mExRules );
138  qDeleteAll( d->mRRules );
139  delete d;
140 }
141 
142 bool Recurrence::operator==( const Recurrence &recurrence ) const
143 {
144  return *d == *recurrence.d;
145 }
146 
147 Recurrence &Recurrence::operator=( const Recurrence &recurrence )
148 {
149  // check for self assignment
150  if ( &recurrence == this ) {
151  return *this;
152  }
153 
154  *d = *recurrence.d;
155  return *this;
156 }
157 
158 void Recurrence::addObserver( RecurrenceObserver *observer )
159 {
160  if ( !d->mObservers.contains( observer ) ) {
161  d->mObservers.append( observer );
162  }
163 }
164 
165 void Recurrence::removeObserver( RecurrenceObserver *observer )
166 {
167  if ( d->mObservers.contains( observer ) ) {
168  d->mObservers.removeAll( observer );
169  }
170 }
171 
172 KDateTime Recurrence::startDateTime() const
173 {
174  return d->mStartDateTime;
175 }
176 
177 bool Recurrence::allDay() const
178 {
179  return d->mAllDay;
180 }
181 
182 void Recurrence::setAllDay( bool allDay )
183 {
184  if ( d->mRecurReadOnly || allDay == d->mAllDay ) {
185  return;
186  }
187 
188  d->mAllDay = allDay;
189  for ( int i = 0, end = d->mRRules.count(); i < end; ++i ) {
190  d->mRRules[i]->setAllDay( allDay );
191  }
192  for ( int i = 0, end = d->mExRules.count(); i < end; ++i ) {
193  d->mExRules[i]->setAllDay( allDay );
194  }
195  updated();
196 }
197 
198 RecurrenceRule *Recurrence::defaultRRule( bool create ) const
199 {
200  if ( d->mRRules.isEmpty() ) {
201  if ( !create || d->mRecurReadOnly ) {
202  return 0;
203  }
204  RecurrenceRule *rrule = new RecurrenceRule();
205  rrule->setStartDt( startDateTime() );
206  const_cast<KCalCore::Recurrence*>(this)->addRRule( rrule );
207  return rrule;
208  } else {
209  return d->mRRules[0];
210  }
211 }
212 
213 RecurrenceRule *Recurrence::defaultRRuleConst() const
214 {
215  return d->mRRules.isEmpty() ? 0 : d->mRRules[0];
216 }
217 
218 void Recurrence::updated()
219 {
220  // recurrenceType() re-calculates the type if it's rMax
221  d->mCachedType = rMax;
222  for ( int i = 0, end = d->mObservers.count(); i < end; ++i ) {
223  if ( d->mObservers[i] ) {
224  d->mObservers[i]->recurrenceUpdated( this );
225  }
226  }
227 }
228 
229 bool Recurrence::recurs() const
230 {
231  return !d->mRRules.isEmpty() || !d->mRDates.isEmpty() || !d->mRDateTimes.isEmpty();
232 }
233 
234 ushort Recurrence::recurrenceType() const
235 {
236  if ( d->mCachedType == rMax ) {
237  d->mCachedType = recurrenceType( defaultRRuleConst() );
238  }
239  return d->mCachedType;
240 }
241 
242 ushort Recurrence::recurrenceType( const RecurrenceRule *rrule )
243 {
244  if ( !rrule ) {
245  return rNone;
246  }
247  RecurrenceRule::PeriodType type = rrule->recurrenceType();
248 
249  // BYSETPOS, BYWEEKNUMBER and BYSECOND were not supported in old versions
250  if ( !rrule->bySetPos().isEmpty() ||
251  !rrule->bySeconds().isEmpty() ||
252  !rrule->byWeekNumbers().isEmpty() ) {
253  return rOther;
254  }
255 
256  // It wasn't possible to set BYMINUTES, BYHOUR etc. by the old code. So if
257  // it's set, it's none of the old types
258  if ( !rrule->byMinutes().isEmpty() || !rrule->byHours().isEmpty() ) {
259  return rOther;
260  }
261 
262  // Possible combinations were:
263  // BYDAY: with WEEKLY, MONTHLY, YEARLY
264  // BYMONTHDAY: with MONTHLY, YEARLY
265  // BYMONTH: with YEARLY
266  // BYYEARDAY: with YEARLY
267  if ( ( !rrule->byYearDays().isEmpty() && type != RecurrenceRule::rYearly ) ||
268  ( !rrule->byMonths().isEmpty() && type != RecurrenceRule::rYearly ) ) {
269  return rOther;
270  }
271  if ( !rrule->byDays().isEmpty() ) {
272  if ( type != RecurrenceRule::rYearly &&
273  type != RecurrenceRule::rMonthly &&
274  type != RecurrenceRule::rWeekly ) {
275  return rOther;
276  }
277  }
278 
279  switch ( type ) {
280  case RecurrenceRule::rNone:
281  return rNone;
282  case RecurrenceRule::rMinutely:
283  return rMinutely;
284  case RecurrenceRule::rHourly:
285  return rHourly;
286  case RecurrenceRule::rDaily:
287  return rDaily;
288  case RecurrenceRule::rWeekly:
289  return rWeekly;
290  case RecurrenceRule::rMonthly:
291  {
292  if ( rrule->byDays().isEmpty() ) {
293  return rMonthlyDay;
294  } else if ( rrule->byMonthDays().isEmpty() ) {
295  return rMonthlyPos;
296  } else {
297  return rOther; // both position and date specified
298  }
299  }
300  case RecurrenceRule::rYearly:
301  {
302  // Possible combinations:
303  // rYearlyMonth: [BYMONTH &] BYMONTHDAY
304  // rYearlyDay: BYYEARDAY
305  // rYearlyPos: [BYMONTH &] BYDAY
306  if ( !rrule->byDays().isEmpty() ) {
307  // can only by rYearlyPos
308  if ( rrule->byMonthDays().isEmpty() && rrule->byYearDays().isEmpty() ) {
309  return rYearlyPos;
310  } else {
311  return rOther;
312  }
313  } else if ( !rrule->byYearDays().isEmpty() ) {
314  // Can only be rYearlyDay
315  if ( rrule->byMonths().isEmpty() && rrule->byMonthDays().isEmpty() ) {
316  return rYearlyDay;
317  } else {
318  return rOther;
319  }
320  } else {
321  return rYearlyMonth;
322  }
323  break;
324  }
325  default: return rOther;
326  }
327  return rOther;
328 }
329 
330 bool Recurrence::recursOn( const QDate &qd, const KDateTime::Spec &timeSpec ) const
331 {
332  // Don't waste time if date is before the start of the recurrence
333  if ( KDateTime( qd, QTime( 23, 59, 59 ), timeSpec ) < d->mStartDateTime ) {
334  return false;
335  }
336 
337  // First handle dates. Exrules override
338  if ( d->mExDates.containsSorted( qd ) ) {
339  return false;
340  }
341 
342  int i, end;
343  TimeList tms;
344  // For all-day events a matching exrule excludes the whole day
345  // since exclusions take precedence over inclusions, we know it can't occur on that day.
346  if ( allDay() ) {
347  for ( i = 0, end = d->mExRules.count(); i < end; ++i ) {
348  if ( d->mExRules[i]->recursOn( qd, timeSpec ) ) {
349  return false;
350  }
351  }
352  }
353 
354  if ( d->mRDates.containsSorted( qd ) ) {
355  return true;
356  }
357 
358  // Check if it might recur today at all.
359  bool recurs = ( startDate() == qd );
360  for ( i = 0, end = d->mRDateTimes.count(); i < end && !recurs; ++i ) {
361  recurs = ( d->mRDateTimes[i].toTimeSpec( timeSpec ).date() == qd );
362  }
363  for ( i = 0, end = d->mRRules.count(); i < end && !recurs; ++i ) {
364  recurs = d->mRRules[i]->recursOn( qd, timeSpec );
365  }
366  // If the event wouldn't recur at all, simply return false, don't check ex*
367  if ( !recurs ) {
368  return false;
369  }
370 
371  // Check if there are any times for this day excluded, either by exdate or exrule:
372  bool exon = false;
373  for ( i = 0, end = d->mExDateTimes.count(); i < end && !exon; ++i ) {
374  exon = ( d->mExDateTimes[i].toTimeSpec( timeSpec ).date() == qd );
375  }
376  if ( !allDay() ) { // we have already checked all-day times above
377  for ( i = 0, end = d->mExRules.count(); i < end && !exon; ++i ) {
378  exon = d->mExRules[i]->recursOn( qd, timeSpec );
379  }
380  }
381 
382  if ( !exon ) {
383  // Simple case, nothing on that day excluded, return the value from before
384  return recurs;
385  } else {
386  // Harder part: I don't think there is any way other than to calculate the
387  // whole list of items for that day.
388 //TODO: consider whether it would be more efficient to call
389 // Rule::recurTimesOn() instead of Rule::recursOn() from the start
390  TimeList timesForDay( recurTimesOn( qd, timeSpec ) );
391  return !timesForDay.isEmpty();
392  }
393 }
394 
395 bool Recurrence::recursAt( const KDateTime &dt ) const
396 {
397  // Convert to recurrence's time zone for date comparisons, and for more efficient time comparisons
398  KDateTime dtrecur = dt.toTimeSpec( d->mStartDateTime.timeSpec() );
399 
400  // if it's excluded anyway, don't bother to check if it recurs at all.
401  if ( d->mExDateTimes.containsSorted( dtrecur ) ||
402  d->mExDates.containsSorted( dtrecur.date() ) ) {
403  return false;
404  }
405  int i, end;
406  for ( i = 0, end = d->mExRules.count(); i < end; ++i ) {
407  if ( d->mExRules[i]->recursAt( dtrecur ) ) {
408  return false;
409  }
410  }
411 
412  // Check explicit recurrences, then rrules.
413  if ( startDateTime() == dtrecur || d->mRDateTimes.containsSorted( dtrecur ) ) {
414  return true;
415  }
416  for ( i = 0, end = d->mRRules.count(); i < end; ++i ) {
417  if ( d->mRRules[i]->recursAt( dtrecur ) ) {
418  return true;
419  }
420  }
421 
422  return false;
423 }
424 
428 KDateTime Recurrence::endDateTime() const
429 {
430  DateTimeList dts;
431  dts << startDateTime();
432  if ( !d->mRDates.isEmpty() ) {
433  dts << KDateTime( d->mRDates.last(), QTime( 0, 0, 0 ), d->mStartDateTime.timeSpec() );
434  }
435  if ( !d->mRDateTimes.isEmpty() ) {
436  dts << d->mRDateTimes.last();
437  }
438  for ( int i = 0, end = d->mRRules.count(); i < end; ++i ) {
439  KDateTime rl( d->mRRules[i]->endDt() );
440  // if any of the rules is infinite, the whole recurrence is
441  if ( !rl.isValid() ) {
442  return KDateTime();
443  }
444  dts << rl;
445  }
446  dts.sortUnique();
447  return dts.isEmpty() ? KDateTime() : dts.last();
448 }
449 
453 QDate Recurrence::endDate() const
454 {
455  KDateTime end( endDateTime() );
456  return end.isValid() ? end.date() : QDate();
457 }
458 
459 void Recurrence::setEndDate( const QDate &date )
460 {
461  KDateTime dt( date, d->mStartDateTime.time(), d->mStartDateTime.timeSpec() );
462  if ( allDay() ) {
463  dt.setTime( QTime( 23, 59, 59 ) );
464  }
465  setEndDateTime( dt );
466 }
467 
468 void Recurrence::setEndDateTime( const KDateTime &dateTime )
469 {
470  if ( d->mRecurReadOnly ) {
471  return;
472  }
473  RecurrenceRule *rrule = defaultRRule( true );
474  if ( !rrule ) {
475  return;
476  }
477  rrule->setEndDt( dateTime );
478  updated();
479 }
480 
481 int Recurrence::duration() const
482 {
483  RecurrenceRule *rrule = defaultRRuleConst();
484  return rrule ? rrule->duration() : 0;
485 }
486 
487 int Recurrence::durationTo( const KDateTime &datetime ) const
488 {
489  // Emulate old behavior: This is just an interface to the first rule!
490  RecurrenceRule *rrule = defaultRRuleConst();
491  return rrule ? rrule->durationTo( datetime ) : 0;
492 }
493 
494 int Recurrence::durationTo( const QDate &date ) const
495 {
496  return durationTo( KDateTime( date, QTime( 23, 59, 59 ), d->mStartDateTime.timeSpec() ) );
497 }
498 
499 void Recurrence::setDuration( int duration )
500 {
501  if ( d->mRecurReadOnly ) {
502  return;
503  }
504 
505  RecurrenceRule *rrule = defaultRRule( true );
506  if ( !rrule ) {
507  return;
508  }
509  rrule->setDuration( duration );
510  updated();
511 }
512 
513 void Recurrence::shiftTimes( const KDateTime::Spec &oldSpec, const KDateTime::Spec &newSpec )
514 {
515  if ( d->mRecurReadOnly ) {
516  return;
517  }
518 
519  d->mStartDateTime = d->mStartDateTime.toTimeSpec( oldSpec );
520  d->mStartDateTime.setTimeSpec( newSpec );
521 
522  int i, end;
523  for ( i = 0, end = d->mRDateTimes.count(); i < end; ++i ) {
524  d->mRDateTimes[i] = d->mRDateTimes[i].toTimeSpec( oldSpec );
525  d->mRDateTimes[i].setTimeSpec( newSpec );
526  }
527  for ( i = 0, end = d->mExDateTimes.count(); i < end; ++i ) {
528  d->mExDateTimes[i] = d->mExDateTimes[i].toTimeSpec( oldSpec );
529  d->mExDateTimes[i].setTimeSpec( newSpec );
530  }
531  for ( i = 0, end = d->mRRules.count(); i < end; ++i ) {
532  d->mRRules[i]->shiftTimes( oldSpec, newSpec );
533  }
534  for ( i = 0, end = d->mExRules.count(); i < end; ++i ) {
535  d->mExRules[i]->shiftTimes( oldSpec, newSpec );
536  }
537 }
538 
539 void Recurrence::unsetRecurs()
540 {
541  if ( d->mRecurReadOnly ) {
542  return;
543  }
544  qDeleteAll( d->mRRules );
545  d->mRRules.clear();
546  updated();
547 }
548 
549 void Recurrence::clear()
550 {
551  if ( d->mRecurReadOnly ) {
552  return;
553  }
554  qDeleteAll( d->mRRules );
555  d->mRRules.clear();
556  qDeleteAll( d->mExRules );
557  d->mExRules.clear();
558  d->mRDates.clear();
559  d->mRDateTimes.clear();
560  d->mExDates.clear();
561  d->mExDateTimes.clear();
562  d->mCachedType = rMax;
563  updated();
564 }
565 
566 void Recurrence::setRecurReadOnly( bool readOnly )
567 {
568  d->mRecurReadOnly = readOnly;
569 }
570 
571 bool Recurrence::recurReadOnly() const
572 {
573  return d->mRecurReadOnly;
574 }
575 
576 QDate Recurrence::startDate() const
577 {
578  return d->mStartDateTime.date();
579 }
580 
581 void Recurrence::setStartDateTime( const KDateTime &start )
582 {
583  if ( d->mRecurReadOnly ) {
584  return;
585  }
586  d->mStartDateTime = start;
587  setAllDay( start.isDateOnly() ); // set all RRULEs and EXRULEs
588 
589  int i, end;
590  for ( i = 0, end = d->mRRules.count(); i < end; ++i ) {
591  d->mRRules[i]->setStartDt( start );
592  }
593  for ( i = 0, end = d->mExRules.count(); i < end; ++i ) {
594  d->mExRules[i]->setStartDt( start );
595  }
596  updated();
597 }
598 
599 int Recurrence::frequency() const
600 {
601  RecurrenceRule *rrule = defaultRRuleConst();
602  return rrule ? rrule->frequency() : 0;
603 }
604 
605 // Emulate the old behaviour. Make this methods just an interface to the
606 // first rrule
607 void Recurrence::setFrequency( int freq )
608 {
609  if ( d->mRecurReadOnly || freq <= 0 ) {
610  return;
611  }
612 
613  RecurrenceRule *rrule = defaultRRule( true );
614  if ( rrule ) {
615  rrule->setFrequency( freq );
616  }
617  updated();
618 }
619 
620 // WEEKLY
621 
622 int Recurrence::weekStart() const
623 {
624  RecurrenceRule *rrule = defaultRRuleConst();
625  return rrule ? rrule->weekStart() : 1;
626 }
627 
628 // Emulate the old behavior
629 QBitArray Recurrence::days() const
630 {
631  QBitArray days( 7 );
632  days.fill( 0 );
633  RecurrenceRule *rrule = defaultRRuleConst();
634  if ( rrule ) {
635  QList<RecurrenceRule::WDayPos> bydays = rrule->byDays();
636  for ( int i = 0; i < bydays.size(); ++i ) {
637  if ( bydays.at(i).pos() == 0 ) {
638  days.setBit( bydays.at( i ).day() - 1 );
639  }
640  }
641  }
642  return days;
643 }
644 
645 // MONTHLY
646 
647 // Emulate the old behavior
648 QList<int> Recurrence::monthDays() const
649 {
650  RecurrenceRule *rrule = defaultRRuleConst();
651  if ( rrule ) {
652  return rrule->byMonthDays();
653  } else {
654  return QList<int>();
655  }
656 }
657 
658 // Emulate the old behavior
659 QList<RecurrenceRule::WDayPos> Recurrence::monthPositions() const
660 {
661  RecurrenceRule *rrule = defaultRRuleConst();
662  return rrule ? rrule->byDays() : QList<RecurrenceRule::WDayPos>();
663 }
664 
665 // YEARLY
666 
667 QList<int> Recurrence::yearDays() const
668 {
669  RecurrenceRule *rrule = defaultRRuleConst();
670  return rrule ? rrule->byYearDays() : QList<int>();
671 }
672 
673 QList<int> Recurrence::yearDates() const
674 {
675  return monthDays();
676 }
677 
678 QList<int> Recurrence::yearMonths() const
679 {
680  RecurrenceRule *rrule = defaultRRuleConst();
681  return rrule ? rrule->byMonths() : QList<int>();
682 }
683 
684 QList<RecurrenceRule::WDayPos> Recurrence::yearPositions() const
685 {
686  return monthPositions();
687 }
688 
689 RecurrenceRule *Recurrence::setNewRecurrenceType( RecurrenceRule::PeriodType type, int freq )
690 {
691  if ( d->mRecurReadOnly || freq <= 0 ) {
692  return 0;
693  }
694 
695  qDeleteAll( d->mRRules );
696  d->mRRules.clear();
697  updated();
698  RecurrenceRule *rrule = defaultRRule( true );
699  if ( !rrule ) {
700  return 0;
701  }
702  rrule->setRecurrenceType( type );
703  rrule->setFrequency( freq );
704  rrule->setDuration( -1 );
705  return rrule;
706 }
707 
708 void Recurrence::setMinutely( int _rFreq )
709 {
710  if ( setNewRecurrenceType( RecurrenceRule::rMinutely, _rFreq ) ) {
711  updated();
712  }
713 }
714 
715 void Recurrence::setHourly( int _rFreq )
716 {
717  if ( setNewRecurrenceType( RecurrenceRule::rHourly, _rFreq ) ) {
718  updated();
719  }
720 }
721 
722 void Recurrence::setDaily( int _rFreq )
723 {
724  if ( setNewRecurrenceType( RecurrenceRule::rDaily, _rFreq ) ) {
725  updated();
726  }
727 }
728 
729 void Recurrence::setWeekly( int freq, int weekStart )
730 {
731  RecurrenceRule *rrule = setNewRecurrenceType( RecurrenceRule::rWeekly, freq );
732  if ( !rrule ) {
733  return;
734  }
735  rrule->setWeekStart( weekStart );
736  updated();
737 }
738 
739 void Recurrence::setWeekly( int freq, const QBitArray &days, int weekStart )
740 {
741  setWeekly( freq, weekStart );
742  addMonthlyPos( 0, days );
743 }
744 
745 void Recurrence::addWeeklyDays( const QBitArray &days )
746 {
747  addMonthlyPos( 0, days );
748 }
749 
750 void Recurrence::setMonthly( int freq )
751 {
752  if ( setNewRecurrenceType( RecurrenceRule::rMonthly, freq ) ) {
753  updated();
754  }
755 }
756 
757 void Recurrence::addMonthlyPos( short pos, const QBitArray &days )
758 {
759  // Allow 53 for yearly!
760  if ( d->mRecurReadOnly || pos > 53 || pos < -53 ) {
761  return;
762  }
763 
764  RecurrenceRule *rrule = defaultRRule( false );
765  if ( !rrule ) {
766  return;
767  }
768  bool changed = false;
769  QList<RecurrenceRule::WDayPos> positions = rrule->byDays();
770 
771  for ( int i = 0; i < 7; ++i ) {
772  if ( days.testBit( i ) ) {
773  RecurrenceRule::WDayPos p( pos, i + 1 );
774  if ( !positions.contains( p ) ) {
775  changed = true;
776  positions.append( p );
777  }
778  }
779  }
780  if ( changed ) {
781  rrule->setByDays( positions );
782  updated();
783  }
784 }
785 
786 void Recurrence::addMonthlyPos( short pos, ushort day )
787 {
788  // Allow 53 for yearly!
789  if ( d->mRecurReadOnly || pos > 53 || pos < -53 ) {
790  return;
791  }
792 
793  RecurrenceRule *rrule = defaultRRule( false );
794  if ( !rrule ) {
795  return;
796  }
797  QList<RecurrenceRule::WDayPos> positions = rrule->byDays();
798 
799  RecurrenceRule::WDayPos p( pos, day );
800  if ( !positions.contains( p ) ) {
801  positions.append( p );
802  rrule->setByDays( positions );
803  updated();
804  }
805 }
806 
807 void Recurrence::addMonthlyDate( short day )
808 {
809  if ( d->mRecurReadOnly || day > 31 || day < -31 ) {
810  return;
811  }
812 
813  RecurrenceRule *rrule = defaultRRule( true );
814  if ( !rrule ) {
815  return;
816  }
817 
818  QList<int> monthDays = rrule->byMonthDays();
819  if ( !monthDays.contains( day ) ) {
820  monthDays.append( day );
821  rrule->setByMonthDays( monthDays );
822  updated();
823  }
824 }
825 
826 void Recurrence::setYearly( int freq )
827 {
828  if ( setNewRecurrenceType( RecurrenceRule::rYearly, freq ) ) {
829  updated();
830  }
831 }
832 
833 // Daynumber within year
834 void Recurrence::addYearlyDay( int day )
835 {
836  RecurrenceRule *rrule = defaultRRule( false ); // It must already exist!
837  if ( !rrule ) {
838  return;
839  }
840 
841  QList<int> days = rrule->byYearDays();
842  if ( !days.contains( day ) ) {
843  days << day;
844  rrule->setByYearDays( days );
845  updated();
846  }
847 }
848 
849 // day part of date within year
850 void Recurrence::addYearlyDate( int day )
851 {
852  addMonthlyDate( day );
853 }
854 
855 // day part of date within year, given as position (n-th weekday)
856 void Recurrence::addYearlyPos( short pos, const QBitArray &days )
857 {
858  addMonthlyPos( pos, days );
859 }
860 
861 // month part of date within year
862 void Recurrence::addYearlyMonth( short month )
863 {
864  if ( d->mRecurReadOnly || month < 1 || month > 12 ) {
865  return;
866  }
867 
868  RecurrenceRule *rrule = defaultRRule( false );
869  if ( !rrule ) {
870  return;
871  }
872 
873  QList<int> months = rrule->byMonths();
874  if ( !months.contains(month) ) {
875  months << month;
876  rrule->setByMonths( months );
877  updated();
878  }
879 }
880 
881 TimeList Recurrence::recurTimesOn( const QDate &date, const KDateTime::Spec &timeSpec ) const
882 {
883 // kDebug() << "recurTimesOn(" << date << ")";
884  int i, end;
885  TimeList times;
886 
887  // The whole day is excepted
888  if ( d->mExDates.containsSorted( date ) ) {
889  return times;
890  }
891 
892  // EXRULE takes precedence over RDATE entries, so for all-day events,
893  // a matching excule also excludes the whole day automatically
894  if ( allDay() ) {
895  for ( i = 0, end = d->mExRules.count(); i < end; ++i ) {
896  if ( d->mExRules[i]->recursOn( date, timeSpec ) ) {
897  return times;
898  }
899  }
900  }
901 
902  KDateTime dt = startDateTime().toTimeSpec( timeSpec );
903  if ( dt.date() == date ) {
904  times << dt.time();
905  }
906 
907  bool foundDate = false;
908  for ( i = 0, end = d->mRDateTimes.count(); i < end; ++i ) {
909  dt = d->mRDateTimes[i].toTimeSpec( timeSpec );
910  if ( dt.date() == date ) {
911  times << dt.time();
912  foundDate = true;
913  } else if (foundDate) break; // <= Assume that the rdatetime list is sorted
914  }
915  for ( i = 0, end = d->mRRules.count(); i < end; ++i ) {
916  times += d->mRRules[i]->recurTimesOn( date, timeSpec );
917  }
918  times.sortUnique();
919 
920  foundDate = false;
921  TimeList extimes;
922  for ( i = 0, end = d->mExDateTimes.count(); i < end; ++i ) {
923  dt = d->mExDateTimes[i].toTimeSpec( timeSpec );
924  if ( dt.date() == date ) {
925  extimes << dt.time();
926  foundDate = true;
927  } else if (foundDate) break;
928  }
929  if ( !allDay() ) { // we have already checked all-day times above
930  for ( i = 0, end = d->mExRules.count(); i < end; ++i ) {
931  extimes += d->mExRules[i]->recurTimesOn( date, timeSpec );
932  }
933  }
934  extimes.sortUnique();
935 
936  int st = 0;
937  for ( i = 0, end = extimes.count(); i < end; ++i ) {
938  int j = times.removeSorted( extimes[i], st );
939  if ( j >= 0 ) {
940  st = j;
941  }
942  }
943  return times;
944 }
945 
946 DateTimeList Recurrence::timesInInterval( const KDateTime &start, const KDateTime &end ) const
947 {
948  int i, count;
949  DateTimeList times;
950  for ( i = 0, count = d->mRRules.count(); i < count; ++i ) {
951  times += d->mRRules[i]->timesInInterval( start, end );
952  }
953 
954  // add rdatetimes that fit in the interval
955  for ( i = 0, count = d->mRDateTimes.count(); i < count; ++i ) {
956  if ( d->mRDateTimes[i] >= start && d->mRDateTimes[i] <= end ) {
957  times += d->mRDateTimes[i];
958  }
959  }
960 
961  // add rdates that fit in the interval
962  KDateTime kdt( d->mStartDateTime );
963  for ( i = 0, count = d->mRDates.count(); i < count; ++i ) {
964  kdt.setDate( d->mRDates[i] );
965  if ( kdt >= start && kdt <= end ) {
966  times += kdt;
967  }
968  }
969 
970  // Recurrence::timesInInterval(...) doesn't explicitly add mStartDateTime to the list
971  // of times to be returned. It calls mRRules[i]->timesInInterval(...) which include
972  // mStartDateTime.
973  // So, If we have rdates/rdatetimes but don't have any rrule we must explicitly
974  // add mStartDateTime to the list, otherwise we won't see the first occurrence.
975  if ( ( !d->mRDates.isEmpty() || !d->mRDateTimes.isEmpty() ) &&
976  d->mRRules.isEmpty() &&
977  start <= d->mStartDateTime &&
978  end >= d->mStartDateTime ) {
979  times += d->mStartDateTime;
980  }
981 
982  times.sortUnique();
983 
984  // Remove excluded times
985  int idt = 0;
986  int enddt = times.count();
987  for ( i = 0, count = d->mExDates.count(); i < count && idt < enddt; ++i ) {
988  while ( idt < enddt && times[idt].date() < d->mExDates[i] ) ++idt;
989  while ( idt < enddt && times[idt].date() == d->mExDates[i] ) {
990  times.removeAt(idt);
991  --enddt;
992  }
993  }
994  DateTimeList extimes;
995  for ( i = 0, count = d->mExRules.count(); i < count; ++i ) {
996  extimes += d->mExRules[i]->timesInInterval( start, end );
997  }
998  extimes += d->mExDateTimes;
999  extimes.sortUnique();
1000 
1001  int st = 0;
1002  for ( i = 0, count = extimes.count(); i < count; ++i ) {
1003  int j = times.removeSorted( extimes[i], st );
1004  if ( j >= 0 ) {
1005  st = j;
1006  }
1007  }
1008 
1009  return times;
1010 }
1011 
1012 KDateTime Recurrence::getNextDateTime( const KDateTime &preDateTime ) const
1013 {
1014  KDateTime nextDT = preDateTime;
1015  // prevent infinite loops, e.g. when an exrule extinguishes an rrule (e.g.
1016  // the exrule is identical to the rrule). If an occurrence is found, break
1017  // out of the loop by returning that KDateTime
1018 // TODO_Recurrence: Is a loop counter of 1000 really okay? I mean for secondly
1019 // recurrence, an exdate might exclude more than 1000 intervals!
1020  int loop = 0;
1021  while ( loop < 1000 ) {
1022  // Outline of the algo:
1023  // 1) Find the next date/time after preDateTime when the event could recur
1024  // 1.0) Add the start date if it's after preDateTime
1025  // 1.1) Use the next occurrence from the explicit RDATE lists
1026  // 1.2) Add the next recurrence for each of the RRULEs
1027  // 2) Take the earliest recurrence of these = KDateTime nextDT
1028  // 3) If that date/time is not excluded, either explicitly by an EXDATE or
1029  // by an EXRULE, return nextDT as the next date/time of the recurrence
1030  // 4) If it's excluded, start all at 1), but starting at nextDT (instead
1031  // of preDateTime). Loop at most 1000 times.
1032  ++loop;
1033  // First, get the next recurrence from the RDate lists
1034  DateTimeList dates;
1035  if ( nextDT < startDateTime() ) {
1036  dates << startDateTime();
1037  }
1038 
1039  int end;
1040  // Assume that the rdatetime list is sorted
1041  int i = d->mRDateTimes.findGT( nextDT );
1042  if ( i >= 0 ) {
1043  dates << d->mRDateTimes[i];
1044  }
1045 
1046  KDateTime kdt( startDateTime() );
1047  for ( i = 0, end = d->mRDates.count(); i < end; ++i ) {
1048  kdt.setDate( d->mRDates[i] );
1049  if ( kdt > nextDT ) {
1050  dates << kdt;
1051  break;
1052  }
1053  }
1054 
1055  // Add the next occurrences from all RRULEs.
1056  for ( i = 0, end = d->mRRules.count(); i < end; ++i ) {
1057  KDateTime dt = d->mRRules[i]->getNextDate( nextDT );
1058  if ( dt.isValid() ) {
1059  dates << dt;
1060  }
1061  }
1062 
1063  // Take the first of these (all others can't be used later on)
1064  dates.sortUnique();
1065  if ( dates.isEmpty() ) {
1066  return KDateTime();
1067  }
1068  nextDT = dates.first();
1069 
1070  // Check if that date/time is excluded explicitly or by an exrule:
1071  if ( !d->mExDates.containsSorted( nextDT.date() ) &&
1072  !d->mExDateTimes.containsSorted( nextDT ) ) {
1073  bool allowed = true;
1074  for ( i = 0, end = d->mExRules.count(); i < end; ++i ) {
1075  allowed = allowed && !( d->mExRules[i]->recursAt( nextDT ) );
1076  }
1077  if ( allowed ) {
1078  return nextDT;
1079  }
1080  }
1081  }
1082 
1083  // Couldn't find a valid occurrences in 1000 loops, something is wrong!
1084  return KDateTime();
1085 }
1086 
1087 KDateTime Recurrence::getPreviousDateTime( const KDateTime &afterDateTime ) const
1088 {
1089  KDateTime prevDT = afterDateTime;
1090  // prevent infinite loops, e.g. when an exrule extinguishes an rrule (e.g.
1091  // the exrule is identical to the rrule). If an occurrence is found, break
1092  // out of the loop by returning that KDateTime
1093  int loop = 0;
1094  while ( loop < 1000 ) {
1095  // Outline of the algo:
1096  // 1) Find the next date/time after preDateTime when the event could recur
1097  // 1.1) Use the next occurrence from the explicit RDATE lists
1098  // 1.2) Add the next recurrence for each of the RRULEs
1099  // 2) Take the earliest recurrence of these = KDateTime nextDT
1100  // 3) If that date/time is not excluded, either explicitly by an EXDATE or
1101  // by an EXRULE, return nextDT as the next date/time of the recurrence
1102  // 4) If it's excluded, start all at 1), but starting at nextDT (instead
1103  // of preDateTime). Loop at most 1000 times.
1104  ++loop;
1105  // First, get the next recurrence from the RDate lists
1106  DateTimeList dates;
1107  if ( prevDT > startDateTime() ) {
1108  dates << startDateTime();
1109  }
1110 
1111  int i = d->mRDateTimes.findLT( prevDT );
1112  if ( i >= 0 ) {
1113  dates << d->mRDateTimes[i];
1114  }
1115 
1116  KDateTime kdt( startDateTime() );
1117  for ( i = d->mRDates.count(); --i >= 0; ) {
1118  kdt.setDate( d->mRDates[i] );
1119  if ( kdt < prevDT ) {
1120  dates << kdt;
1121  break;
1122  }
1123  }
1124 
1125  // Add the previous occurrences from all RRULEs.
1126  int end;
1127  for ( i = 0, end = d->mRRules.count(); i < end; ++i ) {
1128  KDateTime dt = d->mRRules[i]->getPreviousDate( prevDT );
1129  if ( dt.isValid() ) {
1130  dates << dt;
1131  }
1132  }
1133 
1134  // Take the last of these (all others can't be used later on)
1135  dates.sortUnique();
1136  if ( dates.isEmpty() ) {
1137  return KDateTime();
1138  }
1139  prevDT = dates.last();
1140 
1141  // Check if that date/time is excluded explicitly or by an exrule:
1142  if ( !d->mExDates.containsSorted( prevDT.date() ) &&
1143  !d->mExDateTimes.containsSorted( prevDT ) ) {
1144  bool allowed = true;
1145  for ( i = 0, end = d->mExRules.count(); i < end; ++i ) {
1146  allowed = allowed && !( d->mExRules[i]->recursAt( prevDT ) );
1147  }
1148  if ( allowed ) {
1149  return prevDT;
1150  }
1151  }
1152  }
1153 
1154  // Couldn't find a valid occurrences in 1000 loops, something is wrong!
1155  return KDateTime();
1156 }
1157 
1158 /***************************** PROTECTED FUNCTIONS ***************************/
1159 
1160 RecurrenceRule::List Recurrence::rRules() const
1161 {
1162  return d->mRRules;
1163 }
1164 
1165 void Recurrence::addRRule( RecurrenceRule *rrule )
1166 {
1167  if ( d->mRecurReadOnly || !rrule ) {
1168  return;
1169  }
1170 
1171  rrule->setAllDay( d->mAllDay );
1172  d->mRRules.append( rrule );
1173  rrule->addObserver( this );
1174  updated();
1175 }
1176 
1177 void Recurrence::removeRRule( RecurrenceRule *rrule )
1178 {
1179  if (d->mRecurReadOnly) {
1180  return;
1181  }
1182 
1183  d->mRRules.removeAll( rrule );
1184  rrule->removeObserver( this );
1185  updated();
1186 }
1187 
1188 void Recurrence::deleteRRule( RecurrenceRule *rrule )
1189 {
1190  if (d->mRecurReadOnly) {
1191  return;
1192  }
1193 
1194  d->mRRules.removeAll( rrule );
1195  delete rrule;
1196  updated();
1197 }
1198 
1199 RecurrenceRule::List Recurrence::exRules() const
1200 {
1201  return d->mExRules;
1202 }
1203 
1204 void Recurrence::addExRule( RecurrenceRule *exrule )
1205 {
1206  if ( d->mRecurReadOnly || !exrule ) {
1207  return;
1208  }
1209 
1210  exrule->setAllDay( d->mAllDay );
1211  d->mExRules.append( exrule );
1212  exrule->addObserver( this );
1213  updated();
1214 }
1215 
1216 void Recurrence::removeExRule( RecurrenceRule *exrule )
1217 {
1218  if ( d->mRecurReadOnly ) {
1219  return;
1220  }
1221 
1222  d->mExRules.removeAll( exrule );
1223  exrule->removeObserver( this );
1224  updated();
1225 }
1226 
1227 void Recurrence::deleteExRule( RecurrenceRule *exrule )
1228 {
1229  if ( d->mRecurReadOnly ) {
1230  return;
1231  }
1232 
1233  d->mExRules.removeAll( exrule );
1234  delete exrule;
1235  updated();
1236 }
1237 
1238 DateTimeList Recurrence::rDateTimes() const
1239 {
1240  return d->mRDateTimes;
1241 }
1242 
1243 void Recurrence::setRDateTimes( const DateTimeList &rdates )
1244 {
1245  if ( d->mRecurReadOnly ) {
1246  return;
1247  }
1248 
1249  d->mRDateTimes = rdates;
1250  d->mRDateTimes.sortUnique();
1251  updated();
1252 }
1253 
1254 void Recurrence::addRDateTime( const KDateTime &rdate )
1255 {
1256  if ( d->mRecurReadOnly ) {
1257  return;
1258  }
1259 
1260  d->mRDateTimes.insertSorted( rdate );
1261  updated();
1262 }
1263 
1264 DateList Recurrence::rDates() const
1265 {
1266  return d->mRDates;
1267 }
1268 
1269 void Recurrence::setRDates( const DateList &rdates )
1270 {
1271  if ( d->mRecurReadOnly ) {
1272  return;
1273  }
1274 
1275  d->mRDates = rdates;
1276  d->mRDates.sortUnique();
1277  updated();
1278 }
1279 
1280 void Recurrence::addRDate( const QDate &rdate )
1281 {
1282  if ( d->mRecurReadOnly ) {
1283  return;
1284  }
1285 
1286  d->mRDates.insertSorted( rdate );
1287  updated();
1288 }
1289 
1290 DateTimeList Recurrence::exDateTimes() const
1291 {
1292  return d->mExDateTimes;
1293 }
1294 
1295 void Recurrence::setExDateTimes( const DateTimeList &exdates )
1296 {
1297  if ( d->mRecurReadOnly ) {
1298  return;
1299  }
1300 
1301  d->mExDateTimes = exdates;
1302  d->mExDateTimes.sortUnique();
1303 }
1304 
1305 void Recurrence::addExDateTime( const KDateTime &exdate )
1306 {
1307  if ( d->mRecurReadOnly ) {
1308  return;
1309  }
1310 
1311  d->mExDateTimes.insertSorted( exdate );
1312  updated();
1313 }
1314 
1315 DateList Recurrence::exDates() const
1316 {
1317  return d->mExDates;
1318 }
1319 
1320 void Recurrence::setExDates( const DateList &exdates )
1321 {
1322  if ( d->mRecurReadOnly ) {
1323  return;
1324  }
1325 
1326  d->mExDates = exdates;
1327  d->mExDates.sortUnique();
1328  updated();
1329 }
1330 
1331 void Recurrence::addExDate( const QDate &exdate )
1332 {
1333  if ( d->mRecurReadOnly ) {
1334  return;
1335  }
1336 
1337  d->mExDates.insertSorted( exdate );
1338  updated();
1339 }
1340 
1341 void Recurrence::recurrenceChanged( RecurrenceRule * )
1342 {
1343  updated();
1344 }
1345 
1346 // %%%%%%%%%%%%%%%%%% end:Recurrencerule %%%%%%%%%%%%%%%%%%
1347 
1348 void Recurrence::dump() const
1349 {
1350  kDebug();
1351 
1352  int i;
1353  int count = d->mRRules.count();
1354  kDebug() << " -)" << count << "RRULEs:";
1355  for ( i = 0; i < count; ++i ) {
1356  kDebug() << " -) RecurrenceRule: ";
1357  d->mRRules[i]->dump();
1358  }
1359  count = d->mExRules.count();
1360  kDebug() << " -)" << count << "EXRULEs:";
1361  for ( i = 0; i < count; ++i ) {
1362  kDebug() << " -) ExceptionRule :";
1363  d->mExRules[i]->dump();
1364  }
1365 
1366  count = d->mRDates.count();
1367  kDebug() << endl << " -)" << count << "Recurrence Dates:";
1368  for ( i = 0; i < count; ++i ) {
1369  kDebug() << " " << d->mRDates[i];
1370  }
1371  count = d->mRDateTimes.count();
1372  kDebug() << endl << " -)" << count << "Recurrence Date/Times:";
1373  for ( i = 0; i < count; ++i ) {
1374  kDebug() << " " << d->mRDateTimes[i].dateTime();
1375  }
1376  count = d->mExDates.count();
1377  kDebug() << endl << " -)" << count << "Exceptions Dates:";
1378  for ( i = 0; i < count; ++i ) {
1379  kDebug() << " " << d->mExDates[i];
1380  }
1381  count = d->mExDateTimes.count();
1382  kDebug() << endl << " -)" << count << "Exception Date/Times:";
1383  for ( i = 0; i < count; ++i ) {
1384  kDebug() << " " << d->mExDateTimes[i].dateTime();
1385  }
1386 }
1387 
1388 Recurrence::RecurrenceObserver::~RecurrenceObserver()
1389 {
1390 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Sat Jan 5 2013 19:43:55 by doxygen 1.8.1.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KCalCore Library

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

kdepimlibs-4.9.5 API Reference

Skip menu "kdepimlibs-4.9.5 API Reference"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • 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