1/*
2 This file is part of libkcal.
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
25#include "recurrence.h"
26#include "recurrencerule.h"
27
28#include <kglobal.h>
29#include <klocalizedstring.h>
30#include <kdebug.h>
31
32#include <QtCore/QList>
33#include <QtCore/QBitArray>
34
35#include <limits.h>
36
37using namespace KCal;
38
39//@cond PRIVATE
40class KCal::Recurrence::Private
41{
42 public:
43 Private()
44 : mCachedType( rMax ),
45 mAllDay( false ),
46 mRecurReadOnly( false )
47 {
48 mExRules.setAutoDelete( true );
49 mRRules.setAutoDelete( true );
50 }
51
52 Private( const Private &p )
53 : mRDateTimes( p.mRDateTimes ),
54 mRDates( p.mRDates ),
55 mExDateTimes( p.mExDateTimes ),
56 mExDates( p.mExDates ),
57 mStartDateTime( p.mStartDateTime ),
58 mCachedType( p.mCachedType ),
59 mAllDay( p.mAllDay ),
60 mRecurReadOnly( p.mRecurReadOnly )
61 {
62 mExRules.setAutoDelete( true );
63 mRRules.setAutoDelete( true );
64 }
65
66 bool operator==( const Private &p ) const;
67
68 RecurrenceRule::List mExRules;
69 RecurrenceRule::List mRRules;
70 DateTimeList mRDateTimes;
71 DateList mRDates;
72 DateTimeList mExDateTimes;
73 DateList mExDates;
74 KDateTime mStartDateTime; // date/time of first recurrence
75 QList<RecurrenceObserver*> mObservers;
76
77 // Cache the type of the recurrence with the old system (e.g. MonthlyPos)
78 mutable ushort mCachedType;
79
80 bool mAllDay; // the recurrence has no time, just a date
81 bool mRecurReadOnly;
82};
83
84bool Recurrence::Private::operator==( const Recurrence::Private &p ) const
85{
86 if ( mStartDateTime != p.mStartDateTime ||
87 mAllDay != p.mAllDay ||
88 mRecurReadOnly != p.mRecurReadOnly ||
89 mExDates != p.mExDates ||
90 mExDateTimes != p.mExDateTimes ||
91 mRDates != p.mRDates ||
92 mRDateTimes != p.mRDateTimes ) {
93 return false;
94 }
95
96// Compare the rrules, exrules! Assume they have the same order... This only
97// matters if we have more than one rule (which shouldn't be the default anyway)
98 int i;
99 int end = mRRules.count();
100 if ( end != p.mRRules.count() ) {
101 return false;
102 }
103 for ( i = 0; i < end; ++i ) {
104 if ( *mRRules[i] != *p.mRRules[i] ) {
105 return false;
106 }
107 }
108 end = mExRules.count();
109 if ( end != p.mExRules.count() ) {
110 return false;
111 }
112 for ( i = 0; i < end; ++i ) {
113 if ( *mExRules[i] != *p.mExRules[i] ) {
114 return false;
115 }
116 }
117 return true;
118}
119//@endcond
120
121Recurrence::Recurrence()
122 : d( new KCal::Recurrence::Private() )
123{
124}
125
126Recurrence::Recurrence( const Recurrence &r )
127 : RecurrenceRule::RuleObserver(),
128 d( new KCal::Recurrence::Private( *r.d ) )
129{
130 int i, end;
131 for ( i = 0, end = r.d->mRRules.count(); i < end; ++i ) {
132 RecurrenceRule *rule = new RecurrenceRule( *r.d->mRRules[i] );
133 d->mRRules.append( rule );
134 rule->addObserver( this );
135 }
136 for ( i = 0, end = r.d->mExRules.count(); i < end; ++i ) {
137 RecurrenceRule *rule = new RecurrenceRule( *r.d->mExRules[i] );
138 d->mExRules.append( rule );
139 rule->addObserver( this );
140 }
141}
142
143Recurrence::~Recurrence()
144{
145 delete d;
146}
147
148bool Recurrence::operator==( const Recurrence &r2 ) const
149{
150 return *d == *r2.d;
151}
152
153Recurrence &Recurrence::operator=( const Recurrence &other )
154{
155 if ( &other == this ) // Check for self assignment
156 return *this;
157
158 *d = *other.d;
159 return *this;
160}
161
162void Recurrence::addObserver( RecurrenceObserver *observer )
163{
164 if ( !d->mObservers.contains( observer ) ) {
165 d->mObservers.append( observer );
166 }
167}
168
169void Recurrence::removeObserver( RecurrenceObserver *observer )
170{
171 if ( d->mObservers.contains( observer ) ) {
172 d->mObservers.removeAll( observer );
173 }
174}
175
176KDateTime Recurrence::startDateTime() const
177{
178 return d->mStartDateTime;
179}
180
181bool Recurrence::allDay() const
182{
183 return d->mAllDay;
184}
185
186void Recurrence::setAllDay( bool allDay )
187{
188 if ( d->mRecurReadOnly || allDay == d->mAllDay ) {
189 return;
190 }
191
192 d->mAllDay = allDay;
193 for ( int i = 0, end = d->mRRules.count(); i < end; ++i ) {
194 d->mRRules[i]->setAllDay( allDay );
195 }
196 for ( int i = 0, end = d->mExRules.count(); i < end; ++i ) {
197 d->mExRules[i]->setAllDay( allDay );
198 }
199 updated();
200}
201
202RecurrenceRule *Recurrence::defaultRRule( bool create ) const
203{
204 if ( d->mRRules.isEmpty() ) {
205 if ( !create || d->mRecurReadOnly ) {
206 return 0;
207 }
208 RecurrenceRule *rrule = new RecurrenceRule();
209 rrule->setStartDt( startDateTime() );
210 const_cast<KCal::Recurrence*>(this)->addRRule( rrule );
211 return rrule;
212 } else {
213 return d->mRRules[0];
214 }
215}
216
217RecurrenceRule *Recurrence::defaultRRuleConst() const
218{
219 return d->mRRules.isEmpty() ? 0 : d->mRRules[0];
220}
221
222void Recurrence::updated()
223{
224 // recurrenceType() re-calculates the type if it's rMax
225 d->mCachedType = rMax;
226 for ( int i = 0, end = d->mObservers.count(); i < end; ++i ) {
227 if ( d->mObservers[i] ) {
228 d->mObservers[i]->recurrenceUpdated( this );
229 }
230 }
231}
232
233bool Recurrence::recurs() const
234{
235 return !d->mRRules.isEmpty() || !d->mRDates.isEmpty() || !d->mRDateTimes.isEmpty();
236}
237
238ushort Recurrence::recurrenceType() const
239{
240 if ( d->mCachedType == rMax ) {
241 d->mCachedType = recurrenceType( defaultRRuleConst() );
242 }
243 return d->mCachedType;
244}
245
246ushort Recurrence::recurrenceType( const RecurrenceRule *rrule )
247{
248 if ( !rrule ) {
249 return rNone;
250 }
251 RecurrenceRule::PeriodType type = rrule->recurrenceType();
252
253 // BYSETPOS, BYWEEKNUMBER and BYSECOND were not supported in old versions
254 if ( !rrule->bySetPos().isEmpty() ||
255 !rrule->bySeconds().isEmpty() ||
256 !rrule->byWeekNumbers().isEmpty() ) {
257 return rOther;
258 }
259
260 // It wasn't possible to set BYMINUTES, BYHOUR etc. by the old code. So if
261 // it's set, it's none of the old types
262 if ( !rrule->byMinutes().isEmpty() || !rrule->byHours().isEmpty() ) {
263 return rOther;
264 }
265
266 // Possible combinations were:
267 // BYDAY: with WEEKLY, MONTHLY, YEARLY
268 // BYMONTHDAY: with MONTHLY, YEARLY
269 // BYMONTH: with YEARLY
270 // BYYEARDAY: with YEARLY
271 if ( ( !rrule->byYearDays().isEmpty() && type != RecurrenceRule::rYearly ) ||
272 ( !rrule->byMonths().isEmpty() && type != RecurrenceRule::rYearly ) ) {
273 return rOther;
274 }
275 if ( !rrule->byDays().isEmpty() ) {
276 if ( type != RecurrenceRule::rYearly &&
277 type != RecurrenceRule::rMonthly &&
278 type != RecurrenceRule::rWeekly ) {
279 return rOther;
280 }
281 }
282
283 switch ( type ) {
284 case RecurrenceRule::rNone:
285 return rNone;
286 case RecurrenceRule::rMinutely:
287 return rMinutely;
288 case RecurrenceRule::rHourly:
289 return rHourly;
290 case RecurrenceRule::rDaily:
291 return rDaily;
292 case RecurrenceRule::rWeekly:
293 return rWeekly;
294 case RecurrenceRule::rMonthly:
295 {
296 if ( rrule->byDays().isEmpty() ) {
297 return rMonthlyDay;
298 } else if ( rrule->byMonthDays().isEmpty() ) {
299 return rMonthlyPos;
300 } else {
301 return rOther; // both position and date specified
302 }
303 }
304 case RecurrenceRule::rYearly:
305 {
306 // Possible combinations:
307 // rYearlyMonth: [BYMONTH &] BYMONTHDAY
308 // rYearlyDay: BYYEARDAY
309 // rYearlyPos: [BYMONTH &] BYDAY
310 if ( !rrule->byDays().isEmpty() ) {
311 // can only by rYearlyPos
312 if ( rrule->byMonthDays().isEmpty() && rrule->byYearDays().isEmpty() ) {
313 return rYearlyPos;
314 } else {
315 return rOther;
316 }
317 } else if ( !rrule->byYearDays().isEmpty() ) {
318 // Can only be rYearlyDay
319 if ( rrule->byMonths().isEmpty() && rrule->byMonthDays().isEmpty() ) {
320 return rYearlyDay;
321 } else {
322 return rOther;
323 }
324 } else {
325 return rYearlyMonth;
326 }
327 break;
328 }
329 default: return rOther;
330 }
331 return rOther;
332}
333
334bool Recurrence::recursOn( const QDate &qd, const KDateTime::Spec &timeSpec ) const
335{
336 // Don't waste time if date is before the start of the recurrence
337 if ( KDateTime( qd, QTime( 23, 59, 59 ), timeSpec ) < d->mStartDateTime ) {
338 return false;
339 }
340
341 // First handle dates. Exrules override
342 if ( d->mExDates.containsSorted( qd ) ) {
343 return false;
344 }
345
346 int i, end;
347 TimeList tms;
348 // For all-day events a matching exrule excludes the whole day
349 // since exclusions take precedence over inclusions, we know it can't occur on that day.
350 if ( allDay() ) {
351 for ( i = 0, end = d->mExRules.count(); i < end; ++i ) {
352 if ( d->mExRules[i]->recursOn( qd, timeSpec ) ) {
353 return false;
354 }
355 }
356 }
357
358 if ( d->mRDates.containsSorted( qd ) ) {
359 return true;
360 }
361
362 // Check if it might recur today at all.
363 bool recurs = ( startDate() == qd );
364 for ( i = 0, end = d->mRDateTimes.count(); i < end && !recurs; ++i ) {
365 recurs = ( d->mRDateTimes[i].toTimeSpec( timeSpec ).date() == qd );
366 }
367 for ( i = 0, end = d->mRRules.count(); i < end && !recurs; ++i ) {
368 recurs = d->mRRules[i]->recursOn( qd, timeSpec );
369 }
370 // If the event wouldn't recur at all, simply return false, don't check ex*
371 if ( !recurs ) {
372 return false;
373 }
374
375 // Check if there are any times for this day excluded, either by exdate or exrule:
376 bool exon = false;
377 for ( i = 0, end = d->mExDateTimes.count(); i < end && !exon; ++i ) {
378 exon = ( d->mExDateTimes[i].toTimeSpec( timeSpec ).date() == qd );
379 }
380 if ( !allDay() ) { // we have already checked all-day times above
381 for ( i = 0, end = d->mExRules.count(); i < end && !exon; ++i ) {
382 exon = d->mExRules[i]->recursOn( qd, timeSpec );
383 }
384 }
385
386 if ( !exon ) {
387 // Simple case, nothing on that day excluded, return the value from before
388 return recurs;
389 } else {
390 // Harder part: I don't think there is any way other than to calculate the
391 // whole list of items for that day.
392//TODO: consider whether it would be more efficient to call
393// Rule::recurTimesOn() instead of Rule::recursOn() from the start
394 TimeList timesForDay( recurTimesOn( qd, timeSpec ) );
395 return !timesForDay.isEmpty();
396 }
397}
398
399bool Recurrence::recursAt( const KDateTime &dt ) const
400{
401 // Convert to recurrence's time zone for date comparisons, and for more efficient time comparisons
402 KDateTime dtrecur = dt.toTimeSpec( d->mStartDateTime.timeSpec() );
403
404 // if it's excluded anyway, don't bother to check if it recurs at all.
405 if ( d->mExDateTimes.containsSorted( dtrecur ) ||
406 d->mExDates.containsSorted( dtrecur.date() ) ) {
407 return false;
408 }
409 int i, end;
410 for ( i = 0, end = d->mExRules.count(); i < end; ++i ) {
411 if ( d->mExRules[i]->recursAt( dtrecur ) ) {
412 return false;
413 }
414 }
415
416 // Check explicit recurrences, then rrules.
417 if ( startDateTime() == dtrecur || d->mRDateTimes.containsSorted( dtrecur ) ) {
418 return true;
419 }
420 for ( i = 0, end = d->mRRules.count(); i < end; ++i ) {
421 if ( d->mRRules[i]->recursAt( dtrecur ) ) {
422 return true;
423 }
424 }
425
426 return false;
427}
428
429/** Calculates the cumulative end of the whole recurrence (rdates and rrules).
430 If any rrule is infinite, or the recurrence doesn't have any rrules or
431 rdates, an invalid date is returned. */
432KDateTime Recurrence::endDateTime() const
433{
434 DateTimeList dts;
435 dts << startDateTime();
436 if ( !d->mRDates.isEmpty() ) {
437 dts << KDateTime( d->mRDates.last(), QTime( 0, 0, 0 ), d->mStartDateTime.timeSpec() );
438 }
439 if ( !d->mRDateTimes.isEmpty() ) {
440 dts << d->mRDateTimes.last();
441 }
442 for ( int i = 0, end = d->mRRules.count(); i < end; ++i ) {
443 KDateTime rl( d->mRRules[i]->endDt() );
444 // if any of the rules is infinite, the whole recurrence is
445 if ( !rl.isValid() ) {
446 return KDateTime();
447 }
448 dts << rl;
449 }
450 dts.sortUnique();
451 return dts.isEmpty() ? KDateTime() : dts.last();
452}
453
454/** Calculates the cumulative end of the whole recurrence (rdates and rrules).
455 If any rrule is infinite, or the recurrence doesn't have any rrules or
456 rdates, an invalid date is returned. */
457QDate Recurrence::endDate() const
458{
459 KDateTime end( endDateTime() );
460 return end.isValid() ? end.date() : QDate();
461}
462
463void Recurrence::setEndDate( const QDate &date )
464{
465 KDateTime dt( date, d->mStartDateTime.time(), d->mStartDateTime.timeSpec() );
466 if ( allDay() ) {
467 dt.setTime( QTime( 23, 59, 59 ) );
468 }
469 setEndDateTime( dt );
470}
471
472void Recurrence::setEndDateTime( const KDateTime &dateTime )
473{
474 if ( d->mRecurReadOnly ) {
475 return;
476 }
477 RecurrenceRule *rrule = defaultRRule( true );
478 if ( !rrule ) {
479 return;
480 }
481 rrule->setEndDt( dateTime );
482 updated();
483}
484
485int Recurrence::duration() const
486{
487 RecurrenceRule *rrule = defaultRRuleConst();
488 return rrule ? rrule->duration() : 0;
489}
490
491int Recurrence::durationTo( const KDateTime &datetime ) const
492{
493 // Emulate old behavior: This is just an interface to the first rule!
494 RecurrenceRule *rrule = defaultRRuleConst();
495 return rrule ? rrule->durationTo( datetime ) : 0;
496}
497
498int Recurrence::durationTo( const QDate &date ) const
499{
500 return durationTo( KDateTime( date, QTime( 23, 59, 59 ), d->mStartDateTime.timeSpec() ) );
501}
502
503void Recurrence::setDuration( int duration )
504{
505 if ( d->mRecurReadOnly ) {
506 return;
507 }
508
509 RecurrenceRule *rrule = defaultRRule( true );
510 if ( !rrule ) {
511 return;
512 }
513 rrule->setDuration( duration );
514 updated();
515}
516
517void Recurrence::shiftTimes( const KDateTime::Spec &oldSpec, const KDateTime::Spec &newSpec )
518{
519 if ( d->mRecurReadOnly ) {
520 return;
521 }
522
523 d->mStartDateTime = d->mStartDateTime.toTimeSpec( oldSpec );
524 d->mStartDateTime.setTimeSpec( newSpec );
525
526 int i, end;
527 for ( i = 0, end = d->mRDateTimes.count(); i < end; ++i ) {
528 d->mRDateTimes[i] = d->mRDateTimes[i].toTimeSpec( oldSpec );
529 d->mRDateTimes[i].setTimeSpec( newSpec );
530 }
531 for ( i = 0, end = d->mExDateTimes.count(); i < end; ++i ) {
532 d->mExDateTimes[i] = d->mExDateTimes[i].toTimeSpec( oldSpec );
533 d->mExDateTimes[i].setTimeSpec( newSpec );
534 }
535 for ( i = 0, end = d->mRRules.count(); i < end; ++i ) {
536 d->mRRules[i]->shiftTimes( oldSpec, newSpec );
537 }
538 for ( i = 0, end = d->mExRules.count(); i < end; ++i ) {
539 d->mExRules[i]->shiftTimes( oldSpec, newSpec );
540 }
541}
542
543void Recurrence::unsetRecurs()
544{
545 if ( d->mRecurReadOnly ) {
546 return;
547 }
548 d->mRRules.clear();
549 updated();
550}
551
552void Recurrence::clear()
553{
554 if ( d->mRecurReadOnly ) {
555 return;
556 }
557 d->mRRules.clearAll();
558 d->mExRules.clearAll();
559 d->mRDates.clear();
560 d->mRDateTimes.clear();
561 d->mExDates.clear();
562 d->mExDateTimes.clear();
563 d->mCachedType = rMax;
564 updated();
565}
566
567void Recurrence::setRecurReadOnly( bool readOnly )
568{
569 d->mRecurReadOnly = readOnly;
570}
571
572bool Recurrence::recurReadOnly() const
573{
574 return d->mRecurReadOnly;
575}
576
577QDate Recurrence::startDate() const
578{
579 return d->mStartDateTime.date();
580}
581
582void Recurrence::setStartDateTime( const KDateTime &start )
583{
584 if ( d->mRecurReadOnly ) {
585 return;
586 }
587 d->mStartDateTime = start;
588 setAllDay( start.isDateOnly() ); // set all RRULEs and EXRULEs
589
590 int i, end;
591 for ( i = 0, end = d->mRRules.count(); i < end; ++i ) {
592 d->mRRules[i]->setStartDt( start );
593 }
594 for ( i = 0, end = d->mExRules.count(); i < end; ++i ) {
595 d->mExRules[i]->setStartDt( start );
596 }
597 updated();
598}
599
600int Recurrence::frequency() const
601{
602 RecurrenceRule *rrule = defaultRRuleConst();
603 return rrule ? rrule->frequency() : 0;
604}
605
606// Emulate the old behaviour. Make this methods just an interface to the
607// first rrule
608void Recurrence::setFrequency( int freq )
609{
610 if ( d->mRecurReadOnly || freq <= 0 ) {
611 return;
612 }
613
614 RecurrenceRule *rrule = defaultRRule( true );
615 if ( rrule ) {
616 rrule->setFrequency( freq );
617 }
618 updated();
619}
620
621// WEEKLY
622
623int Recurrence::weekStart() const
624{
625 RecurrenceRule *rrule = defaultRRuleConst();
626 return rrule ? rrule->weekStart() : 1;
627}
628
629// Emulate the old behavior
630QBitArray Recurrence::days() const
631{
632 QBitArray days( 7 );
633 days.fill( 0 );
634 RecurrenceRule *rrule = defaultRRuleConst();
635 if ( rrule ) {
636 QList<RecurrenceRule::WDayPos> bydays = rrule->byDays();
637 for ( int i = 0; i < bydays.size(); ++i ) {
638 if ( bydays.at(i).pos() == 0 ) {
639 days.setBit( bydays.at( i ).day() - 1 );
640 }
641 }
642 }
643 return days;
644}
645
646// MONTHLY
647
648// Emulate the old behavior
649QList<int> Recurrence::monthDays() const
650{
651 RecurrenceRule *rrule = defaultRRuleConst();
652 if ( rrule ) {
653 return rrule->byMonthDays();
654 } else {
655 return QList<int>();
656 }
657}
658
659// Emulate the old behavior
660QList<RecurrenceRule::WDayPos> Recurrence::monthPositions() const
661{
662 RecurrenceRule *rrule = defaultRRuleConst();
663 return rrule ? rrule->byDays() : QList<RecurrenceRule::WDayPos>();
664}
665
666// YEARLY
667
668QList<int> Recurrence::yearDays() const
669{
670 RecurrenceRule *rrule = defaultRRuleConst();
671 return rrule ? rrule->byYearDays() : QList<int>();
672}
673
674QList<int> Recurrence::yearDates() const
675{
676 return monthDays();
677}
678
679QList<int> Recurrence::yearMonths() const
680{
681 RecurrenceRule *rrule = defaultRRuleConst();
682 return rrule ? rrule->byMonths() : QList<int>();
683}
684
685QList<RecurrenceRule::WDayPos> Recurrence::yearPositions() const
686{
687 return monthPositions();
688}
689
690RecurrenceRule *Recurrence::setNewRecurrenceType( RecurrenceRule::PeriodType type, int freq )
691{
692 if ( d->mRecurReadOnly || freq <= 0 ) {
693 return 0;
694 }
695
696 d->mRRules.clearAll();
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
708void Recurrence::setMinutely( int _rFreq )
709{
710 if ( setNewRecurrenceType( RecurrenceRule::rMinutely, _rFreq ) ) {
711 updated();
712 }
713}
714
715void Recurrence::setHourly( int _rFreq )
716{
717 if ( setNewRecurrenceType( RecurrenceRule::rHourly, _rFreq ) ) {
718 updated();
719 }
720}
721
722void Recurrence::setDaily( int _rFreq )
723{
724 if ( setNewRecurrenceType( RecurrenceRule::rDaily, _rFreq ) ) {
725 updated();
726 }
727}
728
729void 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
739void Recurrence::setWeekly( int freq, const QBitArray &days, int weekStart )
740{
741 setWeekly( freq, weekStart );
742 addMonthlyPos( 0, days );
743}
744
745void Recurrence::addWeeklyDays( const QBitArray &days )
746{
747 addMonthlyPos( 0, days );
748}
749
750void Recurrence::setMonthly( int freq )
751{
752 if ( setNewRecurrenceType( RecurrenceRule::rMonthly, freq ) ) {
753 updated();
754 }
755}
756
757void 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
786void 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
807void 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
826void Recurrence::setYearly( int freq )
827{
828 if ( setNewRecurrenceType( RecurrenceRule::rYearly, freq ) ) {
829 updated();
830 }
831}
832
833// Daynumber within year
834void 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
850void Recurrence::addYearlyDate( int day )
851{
852 addMonthlyDate( day );
853}
854
855// day part of date within year, given as position (n-th weekday)
856void Recurrence::addYearlyPos( short pos, const QBitArray &days )
857{
858 addMonthlyPos( pos, days );
859}
860
861// month part of date within year
862void 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
881TimeList 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
946DateTimeList 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
1012KDateTime 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
1087KDateTime 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
1160RecurrenceRule::List Recurrence::rRules() const
1161{
1162 return d->mRRules;
1163}
1164
1165void 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
1177void 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
1188void 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
1199RecurrenceRule::List Recurrence::exRules() const
1200{
1201 return d->mExRules;
1202}
1203
1204void 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
1216void 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
1227void 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
1238DateTimeList Recurrence::rDateTimes() const
1239{
1240 return d->mRDateTimes;
1241}
1242
1243void 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
1254void Recurrence::addRDateTime( const KDateTime &rdate )
1255{
1256 if ( d->mRecurReadOnly ) {
1257 return;
1258 }
1259
1260 d->mRDateTimes.insertSorted( rdate );
1261 updated();
1262}
1263
1264DateList Recurrence::rDates() const
1265{
1266 return d->mRDates;
1267}
1268
1269void 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
1280void Recurrence::addRDate( const QDate &rdate )
1281{
1282 if ( d->mRecurReadOnly ) {
1283 return;
1284 }
1285
1286 d->mRDates.insertSorted( rdate );
1287 updated();
1288}
1289
1290DateTimeList Recurrence::exDateTimes() const
1291{
1292 return d->mExDateTimes;
1293}
1294
1295void Recurrence::setExDateTimes( const DateTimeList &exdates )
1296{
1297 if ( d->mRecurReadOnly ) {
1298 return;
1299 }
1300
1301 d->mExDateTimes = exdates;
1302 d->mExDateTimes.sortUnique();
1303}
1304
1305void Recurrence::addExDateTime( const KDateTime &exdate )
1306{
1307 if ( d->mRecurReadOnly ) {
1308 return;
1309 }
1310
1311 d->mExDateTimes.insertSorted( exdate );
1312 updated();
1313}
1314
1315DateList Recurrence::exDates() const
1316{
1317 return d->mExDates;
1318}
1319
1320void 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
1331void Recurrence::addExDate( const QDate &exdate )
1332{
1333 if ( d->mRecurReadOnly ) {
1334 return;
1335 }
1336
1337 d->mExDates.insertSorted( exdate );
1338 updated();
1339}
1340
1341void Recurrence::recurrenceChanged( RecurrenceRule * )
1342{
1343 updated();
1344}
1345
1346// %%%%%%%%%%%%%%%%%% end:Recurrencerule %%%%%%%%%%%%%%%%%%
1347
1348void 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