• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.10.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 ) {
914  break; // <= Assume that the rdatetime list is sorted
915  }
916  }
917  for ( i = 0, end = d->mRRules.count(); i < end; ++i ) {
918  times += d->mRRules[i]->recurTimesOn( date, timeSpec );
919  }
920  times.sortUnique();
921 
922  foundDate = false;
923  TimeList extimes;
924  for ( i = 0, end = d->mExDateTimes.count(); i < end; ++i ) {
925  dt = d->mExDateTimes[i].toTimeSpec( timeSpec );
926  if ( dt.date() == date ) {
927  extimes << dt.time();
928  foundDate = true;
929  } else if ( foundDate ) {
930  break;
931  }
932  }
933  if ( !allDay() ) { // we have already checked all-day times above
934  for ( i = 0, end = d->mExRules.count(); i < end; ++i ) {
935  extimes += d->mExRules[i]->recurTimesOn( date, timeSpec );
936  }
937  }
938  extimes.sortUnique();
939 
940  int st = 0;
941  for ( i = 0, end = extimes.count(); i < end; ++i ) {
942  int j = times.removeSorted( extimes[i], st );
943  if ( j >= 0 ) {
944  st = j;
945  }
946  }
947  return times;
948 }
949 
950 DateTimeList Recurrence::timesInInterval( const KDateTime &start, const KDateTime &end ) const
951 {
952  int i, count;
953  DateTimeList times;
954  for ( i = 0, count = d->mRRules.count(); i < count; ++i ) {
955  times += d->mRRules[i]->timesInInterval( start, end );
956  }
957 
958  // add rdatetimes that fit in the interval
959  for ( i = 0, count = d->mRDateTimes.count(); i < count; ++i ) {
960  if ( d->mRDateTimes[i] >= start && d->mRDateTimes[i] <= end ) {
961  times += d->mRDateTimes[i];
962  }
963  }
964 
965  // add rdates that fit in the interval
966  KDateTime kdt( d->mStartDateTime );
967  for ( i = 0, count = d->mRDates.count(); i < count; ++i ) {
968  kdt.setDate( d->mRDates[i] );
969  if ( kdt >= start && kdt <= end ) {
970  times += kdt;
971  }
972  }
973 
974  // Recurrence::timesInInterval(...) doesn't explicitly add mStartDateTime to the list
975  // of times to be returned. It calls mRRules[i]->timesInInterval(...) which include
976  // mStartDateTime.
977  // So, If we have rdates/rdatetimes but don't have any rrule we must explicitly
978  // add mStartDateTime to the list, otherwise we won't see the first occurrence.
979  if ( ( !d->mRDates.isEmpty() || !d->mRDateTimes.isEmpty() ) &&
980  d->mRRules.isEmpty() &&
981  start <= d->mStartDateTime &&
982  end >= d->mStartDateTime ) {
983  times += d->mStartDateTime;
984  }
985 
986  times.sortUnique();
987 
988  // Remove excluded times
989  int idt = 0;
990  int enddt = times.count();
991  for ( i = 0, count = d->mExDates.count(); i < count && idt < enddt; ++i ) {
992  while ( idt < enddt && times[idt].date() < d->mExDates[i] ) {
993  ++idt;
994  }
995  while ( idt < enddt && times[idt].date() == d->mExDates[i] ) {
996  times.removeAt( idt );
997  --enddt;
998  }
999  }
1000  DateTimeList extimes;
1001  for ( i = 0, count = d->mExRules.count(); i < count; ++i ) {
1002  extimes += d->mExRules[i]->timesInInterval( start, end );
1003  }
1004  extimes += d->mExDateTimes;
1005  extimes.sortUnique();
1006 
1007  int st = 0;
1008  for ( i = 0, count = extimes.count(); i < count; ++i ) {
1009  int j = times.removeSorted( extimes[i], st );
1010  if ( j >= 0 ) {
1011  st = j;
1012  }
1013  }
1014 
1015  return times;
1016 }
1017 
1018 KDateTime Recurrence::getNextDateTime( const KDateTime &preDateTime ) const
1019 {
1020  KDateTime nextDT = preDateTime;
1021  // prevent infinite loops, e.g. when an exrule extinguishes an rrule (e.g.
1022  // the exrule is identical to the rrule). If an occurrence is found, break
1023  // out of the loop by returning that KDateTime
1024 // TODO_Recurrence: Is a loop counter of 1000 really okay? I mean for secondly
1025 // recurrence, an exdate might exclude more than 1000 intervals!
1026  int loop = 0;
1027  while ( loop < 1000 ) {
1028  // Outline of the algo:
1029  // 1) Find the next date/time after preDateTime when the event could recur
1030  // 1.0) Add the start date if it's after preDateTime
1031  // 1.1) Use the next occurrence from the explicit RDATE lists
1032  // 1.2) Add the next recurrence for each of the RRULEs
1033  // 2) Take the earliest recurrence of these = KDateTime nextDT
1034  // 3) If that date/time is not excluded, either explicitly by an EXDATE or
1035  // by an EXRULE, return nextDT as the next date/time of the recurrence
1036  // 4) If it's excluded, start all at 1), but starting at nextDT (instead
1037  // of preDateTime). Loop at most 1000 times.
1038  ++loop;
1039  // First, get the next recurrence from the RDate lists
1040  DateTimeList dates;
1041  if ( nextDT < startDateTime() ) {
1042  dates << startDateTime();
1043  }
1044 
1045  int end;
1046  // Assume that the rdatetime list is sorted
1047  int i = d->mRDateTimes.findGT( nextDT );
1048  if ( i >= 0 ) {
1049  dates << d->mRDateTimes[i];
1050  }
1051 
1052  KDateTime kdt( startDateTime() );
1053  for ( i = 0, end = d->mRDates.count(); i < end; ++i ) {
1054  kdt.setDate( d->mRDates[i] );
1055  if ( kdt > nextDT ) {
1056  dates << kdt;
1057  break;
1058  }
1059  }
1060 
1061  // Add the next occurrences from all RRULEs.
1062  for ( i = 0, end = d->mRRules.count(); i < end; ++i ) {
1063  KDateTime dt = d->mRRules[i]->getNextDate( nextDT );
1064  if ( dt.isValid() ) {
1065  dates << dt;
1066  }
1067  }
1068 
1069  // Take the first of these (all others can't be used later on)
1070  dates.sortUnique();
1071  if ( dates.isEmpty() ) {
1072  return KDateTime();
1073  }
1074  nextDT = dates.first();
1075 
1076  // Check if that date/time is excluded explicitly or by an exrule:
1077  if ( !d->mExDates.containsSorted( nextDT.date() ) &&
1078  !d->mExDateTimes.containsSorted( nextDT ) ) {
1079  bool allowed = true;
1080  for ( i = 0, end = d->mExRules.count(); i < end; ++i ) {
1081  allowed = allowed && !( d->mExRules[i]->recursAt( nextDT ) );
1082  }
1083  if ( allowed ) {
1084  return nextDT;
1085  }
1086  }
1087  }
1088 
1089  // Couldn't find a valid occurrences in 1000 loops, something is wrong!
1090  return KDateTime();
1091 }
1092 
1093 KDateTime Recurrence::getPreviousDateTime( const KDateTime &afterDateTime ) const
1094 {
1095  KDateTime prevDT = afterDateTime;
1096  // prevent infinite loops, e.g. when an exrule extinguishes an rrule (e.g.
1097  // the exrule is identical to the rrule). If an occurrence is found, break
1098  // out of the loop by returning that KDateTime
1099  int loop = 0;
1100  while ( loop < 1000 ) {
1101  // Outline of the algo:
1102  // 1) Find the next date/time after preDateTime when the event could recur
1103  // 1.1) Use the next occurrence from the explicit RDATE lists
1104  // 1.2) Add the next recurrence for each of the RRULEs
1105  // 2) Take the earliest recurrence of these = KDateTime nextDT
1106  // 3) If that date/time is not excluded, either explicitly by an EXDATE or
1107  // by an EXRULE, return nextDT as the next date/time of the recurrence
1108  // 4) If it's excluded, start all at 1), but starting at nextDT (instead
1109  // of preDateTime). Loop at most 1000 times.
1110  ++loop;
1111  // First, get the next recurrence from the RDate lists
1112  DateTimeList dates;
1113  if ( prevDT > startDateTime() ) {
1114  dates << startDateTime();
1115  }
1116 
1117  int i = d->mRDateTimes.findLT( prevDT );
1118  if ( i >= 0 ) {
1119  dates << d->mRDateTimes[i];
1120  }
1121 
1122  KDateTime kdt( startDateTime() );
1123  for ( i = d->mRDates.count(); --i >= 0; ) {
1124  kdt.setDate( d->mRDates[i] );
1125  if ( kdt < prevDT ) {
1126  dates << kdt;
1127  break;
1128  }
1129  }
1130 
1131  // Add the previous occurrences from all RRULEs.
1132  int end;
1133  for ( i = 0, end = d->mRRules.count(); i < end; ++i ) {
1134  KDateTime dt = d->mRRules[i]->getPreviousDate( prevDT );
1135  if ( dt.isValid() ) {
1136  dates << dt;
1137  }
1138  }
1139 
1140  // Take the last of these (all others can't be used later on)
1141  dates.sortUnique();
1142  if ( dates.isEmpty() ) {
1143  return KDateTime();
1144  }
1145  prevDT = dates.last();
1146 
1147  // Check if that date/time is excluded explicitly or by an exrule:
1148  if ( !d->mExDates.containsSorted( prevDT.date() ) &&
1149  !d->mExDateTimes.containsSorted( prevDT ) ) {
1150  bool allowed = true;
1151  for ( i = 0, end = d->mExRules.count(); i < end; ++i ) {
1152  allowed = allowed && !( d->mExRules[i]->recursAt( prevDT ) );
1153  }
1154  if ( allowed ) {
1155  return prevDT;
1156  }
1157  }
1158  }
1159 
1160  // Couldn't find a valid occurrences in 1000 loops, something is wrong!
1161  return KDateTime();
1162 }
1163 
1164 /***************************** PROTECTED FUNCTIONS ***************************/
1165 
1166 RecurrenceRule::List Recurrence::rRules() const
1167 {
1168  return d->mRRules;
1169 }
1170 
1171 void Recurrence::addRRule( RecurrenceRule *rrule )
1172 {
1173  if ( d->mRecurReadOnly || !rrule ) {
1174  return;
1175  }
1176 
1177  rrule->setAllDay( d->mAllDay );
1178  d->mRRules.append( rrule );
1179  rrule->addObserver( this );
1180  updated();
1181 }
1182 
1183 void Recurrence::removeRRule( RecurrenceRule *rrule )
1184 {
1185  if (d->mRecurReadOnly) {
1186  return;
1187  }
1188 
1189  d->mRRules.removeAll( rrule );
1190  rrule->removeObserver( this );
1191  updated();
1192 }
1193 
1194 void Recurrence::deleteRRule( RecurrenceRule *rrule )
1195 {
1196  if (d->mRecurReadOnly) {
1197  return;
1198  }
1199 
1200  d->mRRules.removeAll( rrule );
1201  delete rrule;
1202  updated();
1203 }
1204 
1205 RecurrenceRule::List Recurrence::exRules() const
1206 {
1207  return d->mExRules;
1208 }
1209 
1210 void Recurrence::addExRule( RecurrenceRule *exrule )
1211 {
1212  if ( d->mRecurReadOnly || !exrule ) {
1213  return;
1214  }
1215 
1216  exrule->setAllDay( d->mAllDay );
1217  d->mExRules.append( exrule );
1218  exrule->addObserver( this );
1219  updated();
1220 }
1221 
1222 void Recurrence::removeExRule( RecurrenceRule *exrule )
1223 {
1224  if ( d->mRecurReadOnly ) {
1225  return;
1226  }
1227 
1228  d->mExRules.removeAll( exrule );
1229  exrule->removeObserver( this );
1230  updated();
1231 }
1232 
1233 void Recurrence::deleteExRule( RecurrenceRule *exrule )
1234 {
1235  if ( d->mRecurReadOnly ) {
1236  return;
1237  }
1238 
1239  d->mExRules.removeAll( exrule );
1240  delete exrule;
1241  updated();
1242 }
1243 
1244 DateTimeList Recurrence::rDateTimes() const
1245 {
1246  return d->mRDateTimes;
1247 }
1248 
1249 void Recurrence::setRDateTimes( const DateTimeList &rdates )
1250 {
1251  if ( d->mRecurReadOnly ) {
1252  return;
1253  }
1254 
1255  d->mRDateTimes = rdates;
1256  d->mRDateTimes.sortUnique();
1257  updated();
1258 }
1259 
1260 void Recurrence::addRDateTime( const KDateTime &rdate )
1261 {
1262  if ( d->mRecurReadOnly ) {
1263  return;
1264  }
1265 
1266  d->mRDateTimes.insertSorted( rdate );
1267  updated();
1268 }
1269 
1270 DateList Recurrence::rDates() const
1271 {
1272  return d->mRDates;
1273 }
1274 
1275 void Recurrence::setRDates( const DateList &rdates )
1276 {
1277  if ( d->mRecurReadOnly ) {
1278  return;
1279  }
1280 
1281  d->mRDates = rdates;
1282  d->mRDates.sortUnique();
1283  updated();
1284 }
1285 
1286 void Recurrence::addRDate( const QDate &rdate )
1287 {
1288  if ( d->mRecurReadOnly ) {
1289  return;
1290  }
1291 
1292  d->mRDates.insertSorted( rdate );
1293  updated();
1294 }
1295 
1296 DateTimeList Recurrence::exDateTimes() const
1297 {
1298  return d->mExDateTimes;
1299 }
1300 
1301 void Recurrence::setExDateTimes( const DateTimeList &exdates )
1302 {
1303  if ( d->mRecurReadOnly ) {
1304  return;
1305  }
1306 
1307  d->mExDateTimes = exdates;
1308  d->mExDateTimes.sortUnique();
1309 }
1310 
1311 void Recurrence::addExDateTime( const KDateTime &exdate )
1312 {
1313  if ( d->mRecurReadOnly ) {
1314  return;
1315  }
1316 
1317  d->mExDateTimes.insertSorted( exdate );
1318  updated();
1319 }
1320 
1321 DateList Recurrence::exDates() const
1322 {
1323  return d->mExDates;
1324 }
1325 
1326 void Recurrence::setExDates( const DateList &exdates )
1327 {
1328  if ( d->mRecurReadOnly ) {
1329  return;
1330  }
1331 
1332  d->mExDates = exdates;
1333  d->mExDates.sortUnique();
1334  updated();
1335 }
1336 
1337 void Recurrence::addExDate( const QDate &exdate )
1338 {
1339  if ( d->mRecurReadOnly ) {
1340  return;
1341  }
1342 
1343  d->mExDates.insertSorted( exdate );
1344  updated();
1345 }
1346 
1347 void Recurrence::recurrenceChanged( RecurrenceRule * )
1348 {
1349  updated();
1350 }
1351 
1352 // %%%%%%%%%%%%%%%%%% end:Recurrencerule %%%%%%%%%%%%%%%%%%
1353 
1354 void Recurrence::dump() const
1355 {
1356  kDebug();
1357 
1358  int i;
1359  int count = d->mRRules.count();
1360  kDebug() << " -)" << count << "RRULEs:";
1361  for ( i = 0; i < count; ++i ) {
1362  kDebug() << " -) RecurrenceRule: ";
1363  d->mRRules[i]->dump();
1364  }
1365  count = d->mExRules.count();
1366  kDebug() << " -)" << count << "EXRULEs:";
1367  for ( i = 0; i < count; ++i ) {
1368  kDebug() << " -) ExceptionRule :";
1369  d->mExRules[i]->dump();
1370  }
1371 
1372  count = d->mRDates.count();
1373  kDebug() << endl << " -)" << count << "Recurrence Dates:";
1374  for ( i = 0; i < count; ++i ) {
1375  kDebug() << " " << d->mRDates[i];
1376  }
1377  count = d->mRDateTimes.count();
1378  kDebug() << endl << " -)" << count << "Recurrence Date/Times:";
1379  for ( i = 0; i < count; ++i ) {
1380  kDebug() << " " << d->mRDateTimes[i].dateTime();
1381  }
1382  count = d->mExDates.count();
1383  kDebug() << endl << " -)" << count << "Exceptions Dates:";
1384  for ( i = 0; i < count; ++i ) {
1385  kDebug() << " " << d->mExDates[i];
1386  }
1387  count = d->mExDateTimes.count();
1388  kDebug() << endl << " -)" << count << "Exception Date/Times:";
1389  for ( i = 0; i < count; ++i ) {
1390  kDebug() << " " << d->mExDateTimes[i].dateTime();
1391  }
1392 }
1393 
1394 Recurrence::RecurrenceObserver::~RecurrenceObserver()
1395 {
1396 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Sat Jul 13 2013 01:24:52 by doxygen 1.8.3.1 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.10.5 API Reference

Skip menu "kdepimlibs-4.10.5 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