1/*
2 This file is part of libkcal.
3
4 Copyright (c) 2005 Reinhold Kainhofer <reinhold@kainhofe.com>
5 Copyright (c) 2006-2008 David Jarvie <djarvie@kde.org>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21*/
22
23#include "recurrencerule.h"
24
25#include <kdebug.h>
26#include <kglobal.h>
27
28#include <QtCore/QDateTime>
29#include <QtCore/QList>
30#include <QtCore/QStringList>
31
32#include <limits.h>
33#include <math.h>
34
35using namespace KCal;
36
37// Maximum number of intervals to process
38const int LOOP_LIMIT = 10000;
39
40static QString dumpTime( const KDateTime &dt ); // for debugging
41
42/*=========================================================================
43= =
44= IMPORTANT CODING NOTE: =
45= =
46= Recurrence handling code is time critical, especially for sub-daily =
47= recurrences. For example, if getNextDate() is called repeatedly to =
48= check all consecutive occurrences over a few years, on a slow machine =
49= this could take many seconds to complete in the worst case. Simple =
50= sub-daily recurrences are optimised by use of mTimedRepetition. =
51= =
52==========================================================================*/
53
54/**************************************************************************
55 * DateHelper *
56 **************************************************************************/
57//@cond PRIVATE
58class DateHelper
59{
60 public:
61#ifndef NDEBUG
62 static QString dayName( short day );
63#endif
64 static QDate getNthWeek( int year, int weeknumber, short weekstart = 1 );
65 static int weekNumbersInYear( int year, short weekstart = 1 );
66 static int getWeekNumber( const QDate &date, short weekstart, int *year = 0 );
67 static int getWeekNumberNeg( const QDate &date, short weekstart, int *year = 0 );
68 // Convert to QDate, allowing for day < 0.
69 // month and day must be non-zero.
70 static QDate getDate( int year, int month, int day )
71 {
72 if ( day >= 0 ) {
73 return QDate( year, month, day );
74 } else {
75 if ( ++month > 12 ) {
76 month = 1;
77 ++year;
78 }
79 return QDate( year, month, 1 ).addDays( day );
80 }
81 }
82};
83
84#ifndef NDEBUG
85// TODO: Move to a general library / class, as we need the same in the iCal
86// generator and in the xcal format
87QString DateHelper::dayName( short day )
88{
89 switch ( day ) {
90 case 1:
91 return "MO";
92 case 2:
93 return "TU";
94 case 3:
95 return "WE";
96 case 4:
97 return "TH";
98 case 5:
99 return "FR";
100 case 6:
101 return "SA";
102 case 7:
103 return "SU";
104 default:
105 return "??";
106 }
107}
108#endif
109
110QDate DateHelper::getNthWeek( int year, int weeknumber, short weekstart )
111{
112 if ( weeknumber == 0 ) {
113 return QDate();
114 }
115
116 // Adjust this to the first day of week #1 of the year and add 7*weekno days.
117 QDate dt( year, 1, 4 ); // Week #1 is the week that contains Jan 4
118 int adjust = -( 7 + dt.dayOfWeek() - weekstart ) % 7;
119 if ( weeknumber > 0 ) {
120 dt = dt.addDays( 7 * (weeknumber-1) + adjust );
121 } else if ( weeknumber < 0 ) {
122 dt = dt.addYears( 1 );
123 dt = dt.addDays( 7 * weeknumber + adjust );
124 }
125 return dt;
126}
127
128int DateHelper::getWeekNumber( const QDate &date, short weekstart, int *year )
129{
130 int y = date.year();
131 QDate dt( y, 1, 4 ); // <= definitely in week #1
132 dt = dt.addDays( -( 7 + dt.dayOfWeek() - weekstart ) % 7 ); // begin of week #1
133
134 int daysto = dt.daysTo( date );
135 if ( daysto < 0 ) {
136 // in first week of year
137 --y;
138 dt = QDate( y, 1, 4 );
139 dt = dt.addDays( -( 7 + dt.dayOfWeek() - weekstart ) % 7 ); // begin of week #1
140 daysto = dt.daysTo( date );
141 } else if ( daysto > 355 ) {
142 // near the end of the year - check if it's next year
143 QDate dtn( y+1, 1, 4 ); // <= definitely first week of next year
144 dtn = dtn.addDays( -( 7 + dtn.dayOfWeek() - weekstart ) % 7 );
145 int dayston = dtn.daysTo( date );
146 if ( dayston >= 0 ) {
147 // in first week of next year;
148 ++y;
149 daysto = dayston;
150 }
151 }
152 if ( year ) {
153 *year = y;
154 }
155 return daysto / 7 + 1;
156}
157
158int DateHelper::weekNumbersInYear( int year, short weekstart )
159{
160 QDate dt( year, 1, weekstart );
161 QDate dt1( year + 1, 1, weekstart );
162 return dt.daysTo( dt1 ) / 7;
163}
164
165// Week number from the end of the year
166int DateHelper::getWeekNumberNeg( const QDate &date, short weekstart, int *year )
167{
168 int weekpos = getWeekNumber( date, weekstart, year );
169 return weekNumbersInYear( *year, weekstart ) - weekpos - 1;
170}
171//@endcond
172
173/**************************************************************************
174 * Constraint *
175 **************************************************************************/
176//@cond PRIVATE
177class Constraint
178{
179 public:
180 typedef QList<Constraint> List;
181
182 explicit Constraint( KDateTime::Spec, int wkst = 1 );
183 Constraint( const KDateTime &dt, RecurrenceRule::PeriodType type, int wkst );
184 void clear();
185 void setYear( int n )
186 {
187 year = n;
188 useCachedDt = false;
189 }
190 void setMonth( int n )
191 {
192 month = n;
193 useCachedDt = false;
194 }
195 void setDay( int n )
196 {
197 day = n;
198 useCachedDt = false;
199 }
200 void setHour( int n )
201 {
202 hour = n;
203 useCachedDt = false;
204 }
205 void setMinute( int n )
206 {
207 minute = n;
208 useCachedDt = false;
209 }
210 void setSecond( int n )
211 {
212 second = n;
213 useCachedDt = false;
214 }
215 void setWeekday( int n )
216 {
217 weekday = n;
218 useCachedDt = false;
219 }
220 void setWeekdaynr( int n )
221 {
222 weekdaynr = n;
223 useCachedDt = false;
224 }
225 void setWeeknumber( int n )
226 {
227 weeknumber = n;
228 useCachedDt = false;
229 }
230 void setYearday( int n )
231 {
232 yearday = n;
233 useCachedDt = false;
234 }
235 void setWeekstart( int n )
236 {
237 weekstart = n;
238 useCachedDt = false;
239 }
240 void setSecondOccurrence( int n )
241 {
242 secondOccurrence = n;
243 useCachedDt = false;
244 }
245
246 int year; // 0 means unspecified
247 int month; // 0 means unspecified
248 int day; // 0 means unspecified
249 int hour; // -1 means unspecified
250 int minute; // -1 means unspecified
251 int second; // -1 means unspecified
252 int weekday; // 0 means unspecified
253 int weekdaynr; // index of weekday in month/year (0=unspecified)
254 int weeknumber; // 0 means unspecified
255 int yearday; // 0 means unspecified
256 int weekstart; // first day of week (1=monday, 7=sunday, 0=unspec.)
257 KDateTime::Spec timespec; // time zone etc. to use
258 bool secondOccurrence; // the time is the second occurrence during daylight savings shift
259
260 bool readDateTime( const KDateTime &dt, RecurrenceRule::PeriodType type );
261 bool matches( const QDate &dt, RecurrenceRule::PeriodType type ) const;
262 bool matches( const KDateTime &dt, RecurrenceRule::PeriodType type ) const;
263 bool merge( const Constraint &interval );
264 bool isConsistent() const;
265 bool isConsistent( RecurrenceRule::PeriodType period ) const;
266 bool increase( RecurrenceRule::PeriodType type, int freq );
267 KDateTime intervalDateTime( RecurrenceRule::PeriodType type ) const;
268 QList<KDateTime> dateTimes( RecurrenceRule::PeriodType type ) const;
269 void appendDateTime( const QDate &date, const QTime &time, QList<KDateTime> &list ) const;
270 void dump() const;
271
272 private:
273 mutable bool useCachedDt;
274 mutable KDateTime cachedDt;
275};
276
277Constraint::Constraint( KDateTime::Spec spec, int wkst )
278 : weekstart( wkst ),
279 timespec( spec )
280{
281 clear();
282}
283
284Constraint::Constraint( const KDateTime &dt, RecurrenceRule::PeriodType type, int wkst )
285 : weekstart( wkst ),
286 timespec( dt.timeSpec() )
287{
288 clear();
289 readDateTime( dt, type );
290}
291
292void Constraint::clear()
293{
294 year = 0;
295 month = 0;
296 day = 0;
297 hour = -1;
298 minute = -1;
299 second = -1;
300 weekday = 0;
301 weekdaynr = 0;
302 weeknumber = 0;
303 yearday = 0;
304 secondOccurrence = false;
305 useCachedDt = false;
306}
307
308bool Constraint::matches( const QDate &dt, RecurrenceRule::PeriodType type ) const
309{
310 // If the event recurs in week 53 or 1, the day might not belong to the same
311 // year as the week it is in. E.g. Jan 1, 2005 is in week 53 of year 2004.
312 // So we can't simply check the year in that case!
313 if ( weeknumber == 0 ) {
314 if ( year > 0 && year != dt.year() ) {
315 return false;
316 }
317 } else {
318 int y;
319 if ( weeknumber > 0 &&
320 weeknumber != DateHelper::getWeekNumber( dt, weekstart, &y ) ) {
321 return false;
322 }
323 if ( weeknumber < 0 &&
324 weeknumber != DateHelper::getWeekNumberNeg( dt, weekstart, &y ) ) {
325 return false;
326 }
327 if ( year > 0 && year != y ) {
328 return false;
329 }
330 }
331
332 if ( month > 0 && month != dt.month() ) {
333 return false;
334 }
335 if ( day > 0 && day != dt.day() ) {
336 return false;
337 }
338 if ( day < 0 && dt.day() != ( dt.daysInMonth() + day + 1 ) ) {
339 return false;
340 }
341 if ( weekday > 0 ) {
342 if ( weekday != dt.dayOfWeek() ) {
343 return false;
344 }
345 if ( weekdaynr != 0 ) {
346 // If it's a yearly recurrence and a month is given, the position is
347 // still in the month, not in the year.
348 if ( ( type == RecurrenceRule::rMonthly ) ||
349 ( type == RecurrenceRule::rYearly && month > 0 ) ) {
350 // Monthly
351 if ( weekdaynr > 0 &&
352 weekdaynr != ( dt.day() - 1 ) / 7 + 1 ) {
353 return false;
354 }
355 if ( weekdaynr < 0 &&
356 weekdaynr != -( ( dt.daysInMonth() - dt.day() ) / 7 + 1 ) ) {
357 return false;
358 }
359 } else {
360 // Yearly
361 if ( weekdaynr > 0 &&
362 weekdaynr != ( dt.dayOfYear() - 1 ) / 7 + 1 ) {
363 return false;
364 }
365 if ( weekdaynr < 0 &&
366 weekdaynr != -( ( dt.daysInYear() - dt.dayOfYear() ) / 7 + 1 ) ) {
367 return false;
368 }
369 }
370 }
371 }
372 if ( yearday > 0 && yearday != dt.dayOfYear() ) {
373 return false;
374 }
375 if ( yearday < 0 && yearday != dt.daysInYear() - dt.dayOfYear() + 1 ) {
376 return false;
377 }
378 return true;
379}
380
381/* Check for a match with the specified date/time.
382 * The date/time's time specification must correspond with that of the start date/time.
383 */
384bool Constraint::matches( const KDateTime &dt, RecurrenceRule::PeriodType type ) const
385{
386 if ( ( hour >= 0 && ( hour != dt.time().hour() ||
387 secondOccurrence != dt.isSecondOccurrence() ) ) ||
388 ( minute >= 0 && minute != dt.time().minute() ) ||
389 ( second >= 0 && second != dt.time().second() ) ||
390 !matches( dt.date(), type ) ) {
391 return false;
392 }
393 return true;
394}
395
396bool Constraint::isConsistent( RecurrenceRule::PeriodType /*period*/) const
397{
398 // TODO: Check for consistency, e.g. byyearday=3 and bymonth=10
399 return true;
400}
401
402// Return a date/time set to the constraint values, but with those parts less
403// significant than the given period type set to 1 (for dates) or 0 (for times).
404KDateTime Constraint::intervalDateTime( RecurrenceRule::PeriodType type ) const
405{
406 if ( useCachedDt ) {
407 return cachedDt;
408 }
409 QDate d;
410 QTime t( 0, 0, 0 );
411 bool subdaily = true;
412 switch ( type ) {
413 case RecurrenceRule::rSecondly:
414 t.setHMS( hour, minute, second );
415 break;
416 case RecurrenceRule::rMinutely:
417 t.setHMS( hour, minute, 0 );
418 break;
419 case RecurrenceRule::rHourly:
420 t.setHMS( hour, 0, 0 );
421 break;
422 case RecurrenceRule::rDaily:
423 break;
424 case RecurrenceRule::rWeekly:
425 d = DateHelper::getNthWeek( year, weeknumber, weekstart );
426 subdaily = false;
427 break;
428 case RecurrenceRule::rMonthly:
429 d.setYMD( year, month, 1 );
430 subdaily = false;
431 break;
432 case RecurrenceRule::rYearly:
433 d.setYMD( year, 1, 1 );
434 subdaily = false;
435 break;
436 default:
437 break;
438 }
439 if ( subdaily ) {
440 d = DateHelper::getDate( year, (month>0)?month:1, day?day:1 );
441 }
442 cachedDt = KDateTime( d, t, timespec );
443 if ( secondOccurrence ) {
444 cachedDt.setSecondOccurrence( true );
445 }
446 useCachedDt = true;
447 return cachedDt;
448}
449
450bool Constraint::merge( const Constraint &interval )
451{
452#define mergeConstraint( name, cmparison ) \
453 if ( interval.name cmparison ) { \
454 if ( !( name cmparison ) ) { \
455 name = interval.name; \
456 } else if ( name != interval.name ) { \
457 return false;\
458 } \
459 }
460
461 useCachedDt = false;
462
463 mergeConstraint( year, > 0 );
464 mergeConstraint( month, > 0 );
465 mergeConstraint( day, != 0 );
466 mergeConstraint( hour, >= 0 );
467 mergeConstraint( minute, >= 0 );
468 mergeConstraint( second, >= 0 );
469
470 mergeConstraint( weekday, != 0 );
471 mergeConstraint( weekdaynr, != 0 );
472 mergeConstraint( weeknumber, != 0 );
473 mergeConstraint( yearday, != 0 );
474
475#undef mergeConstraint
476 return true;
477}
478
479// Y M D | H Mn S | WD #WD | WN | YD
480// required:
481// x | x x x | | |
482// 0) Trivial: Exact date given, maybe other restrictions
483// x x x | x x x | | |
484// 1) Easy case: no weekly restrictions -> at most a loop through possible dates
485// x + + | x x x | - - | - | -
486// 2) Year day is given -> date known
487// x | x x x | | | +
488// 3) week number is given -> loop through all days of that week. Further
489// restrictions will be applied in the end, when we check all dates for
490// consistency with the constraints
491// x | x x x | | + | (-)
492// 4) week day is specified ->
493// x | x x x | x ? | (-)| (-)
494// 5) All possiblecases have already been treated, so this must be an error!
495
496QList<KDateTime> Constraint::dateTimes( RecurrenceRule::PeriodType type ) const
497{
498 QList<KDateTime> result;
499 bool done = false;
500 if ( !isConsistent( type ) ) {
501 return result;
502 }
503
504 // TODO_Recurrence: Handle all-day
505 QTime tm( hour, minute, second );
506
507 if ( !done && day && month > 0 ) {
508 appendDateTime( DateHelper::getDate( year, month, day ), tm, result );
509 done = true;
510 }
511
512 if ( !done && weekday == 0 && weeknumber == 0 && yearday == 0 ) {
513 // Easy case: date is given, not restrictions by week or yearday
514 uint mstart = ( month > 0 ) ? month : 1;
515 uint mend = ( month <= 0 ) ? 12 : month;
516 for ( uint m = mstart; m <= mend; ++m ) {
517 uint dstart, dend;
518 if ( day > 0 ) {
519 dstart = dend = day;
520 } else if ( day < 0 ) {
521 QDate date( year, month, 1 );
522 dstart = dend = date.daysInMonth() + day + 1;
523 } else {
524 QDate date( year, month, 1 );
525 dstart = 1;
526 dend = date.daysInMonth();
527 }
528 uint d = dstart;
529 for ( QDate dt( year, m, dstart ); ; dt = dt.addDays( 1 ) ) {
530 appendDateTime( dt, tm, result );
531 if ( ++d > dend ) {
532 break;
533 }
534 }
535 }
536 done = true;
537 }
538
539 // Else: At least one of the week / yearday restrictions was given...
540 // If we have a yearday (and of course a year), we know the exact date
541 if ( !done && yearday != 0 ) {
542 // yearday < 0 means from end of year, so we'll need Jan 1 of the next year
543 QDate d( year + ( ( yearday > 0 ) ? 0 : 1 ), 1, 1 );
544 d = d.addDays( yearday - ( ( yearday > 0 ) ? 1 : 0 ) );
545 appendDateTime( d, tm, result );
546 done = true;
547 }
548
549 // Else: If we have a weeknumber, we have at most 7 possible dates, loop through them
550 if ( !done && weeknumber != 0 ) {
551 QDate wst( DateHelper::getNthWeek( year, weeknumber, weekstart ) );
552 if ( weekday != 0 ) {
553 wst = wst.addDays( ( 7 + weekday - weekstart ) % 7 );
554 appendDateTime( wst, tm, result );
555 } else {
556 for ( int i = 0; i < 7; ++i ) {
557 appendDateTime( wst, tm, result );
558 wst = wst.addDays( 1 );
559 }
560 }
561 done = true;
562 }
563
564 // weekday is given
565 if ( !done && weekday != 0 ) {
566 QDate dt( year, 1, 1 );
567 // If type == yearly and month is given, pos is still in month not year!
568 // TODO_Recurrence: Correct handling of n-th BYDAY...
569 int maxloop = 53;
570 bool inMonth = ( type == RecurrenceRule::rMonthly ) ||
571 ( type == RecurrenceRule::rYearly && month > 0 );
572 if ( inMonth && month > 0 ) {
573 dt = QDate( year, month, 1 );
574 maxloop = 5;
575 }
576 if ( weekdaynr < 0 ) {
577 // From end of period (month, year) => relative to begin of next period
578 if ( inMonth ) {
579 dt = dt.addMonths( 1 );
580 } else {
581 dt = dt.addYears( 1 );
582 }
583 }
584 int adj = ( 7 + weekday - dt.dayOfWeek() ) % 7;
585 dt = dt.addDays( adj ); // correct first weekday of the period
586
587 if ( weekdaynr > 0 ) {
588 dt = dt.addDays( ( weekdaynr - 1 ) * 7 );
589 appendDateTime( dt, tm, result );
590 } else if ( weekdaynr < 0 ) {
591 dt = dt.addDays( weekdaynr * 7 );
592 appendDateTime( dt, tm, result );
593 } else {
594 // loop through all possible weeks, non-matching will be filtered later
595 for ( int i = 0; i < maxloop; ++i ) {
596 appendDateTime( dt, tm, result );
597 dt = dt.addDays( 7 );
598 }
599 }
600 } // weekday != 0
601
602 // Only use those times that really match all other constraints, too
603 QList<KDateTime> valid;
604 for ( int i = 0, iend = result.count(); i < iend; ++i ) {
605 if ( matches( result[i], type ) ) {
606 valid.append( result[i] );
607 }
608 }
609 // Don't sort it here, would be unnecessary work. The results from all
610 // constraints will be merged to one big list of the interval. Sort that one!
611 return valid;
612}
613
614void Constraint::appendDateTime( const QDate &date, const QTime &time,
615 QList<KDateTime> &list ) const
616{
617 KDateTime dt( date, time, timespec );
618 if ( dt.isValid() ) {
619 if ( secondOccurrence ) {
620 dt.setSecondOccurrence( true );
621 }
622 list.append( dt );
623 }
624}
625
626bool Constraint::increase( RecurrenceRule::PeriodType type, int freq )
627{
628 // convert the first day of the interval to KDateTime
629 intervalDateTime( type );
630
631 // Now add the intervals
632 switch ( type ) {
633 case RecurrenceRule::rSecondly:
634 cachedDt = cachedDt.addSecs( freq );
635 break;
636 case RecurrenceRule::rMinutely:
637 cachedDt = cachedDt.addSecs( 60 * freq );
638 break;
639 case RecurrenceRule::rHourly:
640 cachedDt = cachedDt.addSecs( 3600 * freq );
641 break;
642 case RecurrenceRule::rDaily:
643 cachedDt = cachedDt.addDays( freq );
644 break;
645 case RecurrenceRule::rWeekly:
646 cachedDt = cachedDt.addDays( 7 * freq );
647 break;
648 case RecurrenceRule::rMonthly:
649 cachedDt = cachedDt.addMonths( freq );
650 break;
651 case RecurrenceRule::rYearly:
652 cachedDt = cachedDt.addYears( freq );
653 break;
654 default:
655 break;
656 }
657 // Convert back from KDateTime to the Constraint class
658 readDateTime( cachedDt, type );
659 useCachedDt = true; // readDateTime() resets this
660
661 return true;
662}
663
664// Set the constraint's value appropriate to 'type', to the value contained in a date/time.
665bool Constraint::readDateTime( const KDateTime &dt, RecurrenceRule::PeriodType type )
666{
667 switch ( type ) {
668 // Really fall through! Only weekly needs to be treated differently!
669 case RecurrenceRule::rSecondly:
670 second = dt.time().second();
671 case RecurrenceRule::rMinutely:
672 minute = dt.time().minute();
673 case RecurrenceRule::rHourly:
674 hour = dt.time().hour();
675 secondOccurrence = dt.isSecondOccurrence();
676 case RecurrenceRule::rDaily:
677 day = dt.date().day();
678 case RecurrenceRule::rMonthly:
679 month = dt.date().month();
680 case RecurrenceRule::rYearly:
681 year = dt.date().year();
682 break;
683 case RecurrenceRule::rWeekly:
684 // Determine start day of the current week, calculate the week number from that
685 weeknumber = DateHelper::getWeekNumber( dt.date(), weekstart, &year );
686 break;
687 default:
688 break;
689 }
690 useCachedDt = false;
691 return true;
692}
693//@endcond
694
695/**************************************************************************
696 * RecurrenceRule::Private *
697 **************************************************************************/
698
699//@cond PRIVATE
700class KCal::RecurrenceRule::Private
701{
702 public:
703 Private( RecurrenceRule *parent )
704 : mParent( parent ),
705 mPeriod( rNone ),
706 mFrequency( 0 ),
707 mWeekStart( 1 ),
708 mIsReadOnly( false ),
709 mAllDay( false )
710 {}
711
712 Private( RecurrenceRule *parent, const Private &p );
713
714 Private &operator=( const Private &other );
715 bool operator==( const Private &other ) const;
716 void clear();
717 void setDirty();
718 void buildConstraints();
719 bool buildCache() const;
720 Constraint getNextValidDateInterval( const KDateTime &preDate, PeriodType type ) const;
721 Constraint getPreviousValidDateInterval( const KDateTime &afterDate, PeriodType type ) const;
722 DateTimeList datesForInterval( const Constraint &interval, PeriodType type ) const;
723
724 RecurrenceRule *mParent;
725 QString mRRule; // RRULE string
726 PeriodType mPeriod;
727 KDateTime mDateStart; // start of recurrence (but mDateStart is not an occurrence
728 // unless it matches the rule)
729 uint mFrequency;
730 /** how often it recurs:
731 < 0 means no end date,
732 0 means an explicit end date,
733 positive values give the number of occurrences */
734 int mDuration;
735 KDateTime mDateEnd;
736
737 QList<int> mBySeconds; // values: second 0-59
738 QList<int> mByMinutes; // values: minute 0-59
739 QList<int> mByHours; // values: hour 0-23
740
741 QList<WDayPos> mByDays; // n-th weekday of the month or year
742 QList<int> mByMonthDays; // values: day -31 to -1 and 1-31
743 QList<int> mByYearDays; // values: day -366 to -1 and 1-366
744 QList<int> mByWeekNumbers; // values: week -53 to -1 and 1-53
745 QList<int> mByMonths; // values: month 1-12
746 QList<int> mBySetPos; // values: position -366 to -1 and 1-366
747 short mWeekStart; // first day of the week (1=Monday, 7=Sunday)
748
749 Constraint::List mConstraints;
750 QList<RuleObserver*> mObservers;
751
752 // Cache for duration
753 mutable DateTimeList mCachedDates;
754 mutable KDateTime mCachedDateEnd;
755 mutable KDateTime mCachedLastDate; // when mCachedDateEnd invalid, last date checked
756 mutable bool mCached;
757
758 bool mIsReadOnly;
759 bool mAllDay;
760 bool mNoByRules; // no BySeconds, ByMinutes, ... rules exist
761 uint mTimedRepetition; // repeats at a regular number of seconds interval, or 0
762};
763
764RecurrenceRule::Private::Private( RecurrenceRule *parent, const Private &p )
765 : mParent( parent ),
766 mRRule( p.mRRule ),
767 mPeriod( p.mPeriod ),
768 mDateStart( p.mDateStart ),
769 mFrequency( p.mFrequency ),
770 mDuration( p.mDuration ),
771 mDateEnd( p.mDateEnd ),
772
773 mBySeconds( p.mBySeconds ),
774 mByMinutes( p.mByMinutes ),
775 mByHours( p.mByHours ),
776 mByDays( p.mByDays ),
777 mByMonthDays( p.mByMonthDays ),
778 mByYearDays( p.mByYearDays ),
779 mByWeekNumbers( p.mByWeekNumbers ),
780 mByMonths( p.mByMonths ),
781 mBySetPos( p.mBySetPos ),
782 mWeekStart( p.mWeekStart ),
783
784 mIsReadOnly( p.mIsReadOnly ),
785 mAllDay( p.mAllDay )
786{
787 setDirty();
788}
789
790RecurrenceRule::Private &RecurrenceRule::Private::operator=( const Private &p )
791{
792 // check for self assignment
793 if ( &p == this ) {
794 return *this;
795 }
796
797 mRRule = p.mRRule;
798 mPeriod = p.mPeriod;
799 mDateStart = p.mDateStart;
800 mFrequency = p.mFrequency;
801 mDuration = p.mDuration;
802 mDateEnd = p.mDateEnd;
803
804 mBySeconds = p.mBySeconds;
805 mByMinutes = p.mByMinutes;
806 mByHours = p.mByHours;
807 mByDays = p.mByDays;
808 mByMonthDays = p.mByMonthDays;
809 mByYearDays = p.mByYearDays;
810 mByWeekNumbers = p.mByWeekNumbers;
811 mByMonths = p.mByMonths;
812 mBySetPos = p.mBySetPos;
813 mWeekStart = p.mWeekStart;
814
815 mIsReadOnly = p.mIsReadOnly;
816 mAllDay = p.mAllDay;
817
818 setDirty();
819
820 return *this;
821}
822
823bool RecurrenceRule::Private::operator==( const Private &r ) const
824{
825 return
826 mPeriod == r.mPeriod &&
827 mDateStart == r.mDateStart &&
828 mDuration == r.mDuration &&
829 mDateEnd == r.mDateEnd &&
830 mFrequency == r.mFrequency &&
831 mIsReadOnly == r.mIsReadOnly &&
832 mAllDay == r.mAllDay &&
833 mBySeconds == r.mBySeconds &&
834 mByMinutes == r.mByMinutes &&
835 mByHours == r.mByHours &&
836 mByDays == r.mByDays &&
837 mByMonthDays == r.mByMonthDays &&
838 mByYearDays == r.mByYearDays &&
839 mByWeekNumbers == r.mByWeekNumbers &&
840 mByMonths == r.mByMonths &&
841 mBySetPos == r.mBySetPos &&
842 mWeekStart == r.mWeekStart;
843}
844
845void RecurrenceRule::Private::clear()
846{
847 if ( mIsReadOnly ) {
848 return;
849 }
850 mPeriod = rNone;
851 mBySeconds.clear();
852 mByMinutes.clear();
853 mByHours.clear();
854 mByDays.clear();
855 mByMonthDays.clear();
856 mByYearDays.clear();
857 mByWeekNumbers.clear();
858 mByMonths.clear();
859 mBySetPos.clear();
860 mWeekStart = 1;
861
862 setDirty();
863}
864
865void RecurrenceRule::Private::setDirty()
866{
867 buildConstraints();
868 mCached = false;
869 mCachedDates.clear();
870 for ( int i = 0, iend = mObservers.count(); i < iend; ++i ) {
871 if ( mObservers[i] ) {
872 mObservers[i]->recurrenceChanged( mParent );
873 }
874 }
875}
876//@endcond
877
878/**************************************************************************
879 * RecurrenceRule *
880 **************************************************************************/
881
882RecurrenceRule::RecurrenceRule()
883 : d( new Private( this ) )
884{
885}
886
887RecurrenceRule::RecurrenceRule( const RecurrenceRule &r )
888 : d( new Private( this, *r.d ) )
889{
890}
891
892RecurrenceRule::~RecurrenceRule()
893{
894 delete d;
895}
896
897bool RecurrenceRule::operator==( const RecurrenceRule &r ) const
898{
899 return *d == *r.d;
900}
901
902RecurrenceRule &RecurrenceRule::operator=( const RecurrenceRule &r )
903{
904 // check for self assignment
905 if ( &r == this ) {
906 return *this;
907 }
908
909 *d = *r.d;
910
911 return *this;
912}
913
914void RecurrenceRule::addObserver( RuleObserver *observer )
915{
916 if ( !d->mObservers.contains( observer ) ) {
917 d->mObservers.append( observer );
918 }
919}
920
921void RecurrenceRule::removeObserver( RuleObserver *observer )
922{
923 if ( d->mObservers.contains( observer ) ) {
924 d->mObservers.removeAll( observer );
925 }
926}
927
928void RecurrenceRule::setRecurrenceType( PeriodType period )
929{
930 if ( isReadOnly() ) {
931 return;
932 }
933 d->mPeriod = period;
934 d->setDirty();
935}
936
937KDateTime RecurrenceRule::endDt( bool *result ) const
938{
939 if ( result ) {
940 *result = false;
941 }
942 if ( d->mPeriod == rNone ) {
943 return KDateTime();
944 }
945 if ( d->mDuration < 0 ) {
946 return KDateTime();
947 }
948 if ( d->mDuration == 0 ) {
949 if ( result ) {
950 *result = true;
951 }
952 return d->mDateEnd;
953 }
954
955 // N occurrences. Check if we have a full cache. If so, return the cached end date.
956 if ( !d->mCached ) {
957 // If not enough occurrences can be found (i.e. inconsistent constraints)
958 if ( !d->buildCache() ) {
959 return KDateTime();
960 }
961 }
962 if ( result ) {
963 *result = true;
964 }
965 return d->mCachedDateEnd;
966}
967
968void RecurrenceRule::setEndDt( const KDateTime &dateTime )
969{
970 if ( isReadOnly() ) {
971 return;
972 }
973 d->mDateEnd = dateTime;
974 d->mDuration = 0; // set to 0 because there is an end date/time
975 d->setDirty();
976}
977
978void RecurrenceRule::setDuration( int duration )
979{
980 if ( isReadOnly() ) {
981 return;
982 }
983 d->mDuration = duration;
984 d->setDirty();
985}
986
987void RecurrenceRule::setAllDay( bool allDay )
988{
989 if ( isReadOnly() ) {
990 return;
991 }
992 d->mAllDay = allDay;
993 d->setDirty();
994}
995
996void RecurrenceRule::clear()
997{
998 d->clear();
999}
1000
1001void RecurrenceRule::setDirty()
1002{
1003 d->setDirty();
1004}
1005
1006void RecurrenceRule::setStartDt( const KDateTime &start )
1007{
1008 if ( isReadOnly() ) {
1009 return;
1010 }
1011 d->mDateStart = start;
1012 d->setDirty();
1013}
1014
1015void RecurrenceRule::setFrequency( int freq )
1016{
1017 if ( isReadOnly() || freq <= 0 ) {
1018 return;
1019 }
1020 d->mFrequency = freq;
1021 d->setDirty();
1022}
1023
1024void RecurrenceRule::setBySeconds( const QList<int> bySeconds )
1025{
1026 if ( isReadOnly() ) {
1027 return;
1028 }
1029 d->mBySeconds = bySeconds;
1030 d->setDirty();
1031}
1032
1033void RecurrenceRule::setByMinutes( const QList<int> byMinutes )
1034{
1035 if ( isReadOnly() ) {
1036 return;
1037 }
1038 d->mByMinutes = byMinutes;
1039 d->setDirty();
1040}
1041
1042void RecurrenceRule::setByHours( const QList<int> byHours )
1043{
1044 if ( isReadOnly() ) {
1045 return;
1046 }
1047 d->mByHours = byHours;
1048 d->setDirty();
1049}
1050
1051void RecurrenceRule::setByDays( const QList<WDayPos> byDays )
1052{
1053 if ( isReadOnly() ) {
1054 return;
1055 }
1056 d->mByDays = byDays;
1057 d->setDirty();
1058}
1059
1060void RecurrenceRule::setByMonthDays( const QList<int> byMonthDays )
1061{
1062 if ( isReadOnly() ) {
1063 return;
1064 }
1065 d->mByMonthDays = byMonthDays;
1066 d->setDirty();
1067}
1068
1069void RecurrenceRule::setByYearDays( const QList<int> byYearDays )
1070{
1071 if ( isReadOnly() ) {
1072 return;
1073 }
1074 d->mByYearDays = byYearDays;
1075 d->setDirty();
1076}
1077
1078void RecurrenceRule::setByWeekNumbers( const QList<int> byWeekNumbers )
1079{
1080 if ( isReadOnly() ) {
1081 return;
1082 }
1083 d->mByWeekNumbers = byWeekNumbers;
1084 d->setDirty();
1085}
1086
1087void RecurrenceRule::setByMonths( const QList<int> byMonths )
1088{
1089 if ( isReadOnly() ) {
1090 return;
1091 }
1092 d->mByMonths = byMonths;
1093 d->setDirty();
1094}
1095
1096void RecurrenceRule::setBySetPos( const QList<int> bySetPos )
1097{
1098 if ( isReadOnly() ) {
1099 return;
1100 }
1101 d->mBySetPos = bySetPos;
1102 d->setDirty();
1103}
1104
1105void RecurrenceRule::setWeekStart( short weekStart )
1106{
1107 if ( isReadOnly() ) {
1108 return;
1109 }
1110 d->mWeekStart = weekStart;
1111 d->setDirty();
1112}
1113
1114void RecurrenceRule::shiftTimes( const KDateTime::Spec &oldSpec, const KDateTime::Spec &newSpec )
1115{
1116 d->mDateStart = d->mDateStart.toTimeSpec( oldSpec );
1117 d->mDateStart.setTimeSpec( newSpec );
1118 if ( d->mDuration == 0 ) {
1119 d->mDateEnd = d->mDateEnd.toTimeSpec( oldSpec );
1120 d->mDateEnd.setTimeSpec( newSpec );
1121 }
1122 d->setDirty();
1123}
1124
1125// Taken from recurrence.cpp
1126// int RecurrenceRule::maxIterations() const
1127// {
1128// /* Find the maximum number of iterations which may be needed to reach the
1129// * next actual occurrence of a monthly or yearly recurrence.
1130// * More than one iteration may be needed if, for example, it's the 29th February,
1131// * the 31st day of the month or the 5th Monday, and the month being checked is
1132// * February or a 30-day month.
1133// * The following recurrences may never occur:
1134// * - For rMonthlyDay: if the frequency is a whole number of years.
1135// * - For rMonthlyPos: if the frequency is an even whole number of years.
1136// * - For rYearlyDay, rYearlyMonth: if the frequeny is a multiple of 4 years.
1137// * - For rYearlyPos: if the frequency is an even number of years.
1138// * The maximum number of iterations needed, assuming that it does actually occur,
1139// * was found empirically.
1140// */
1141// switch (recurs) {
1142// case rMonthlyDay:
1143// return (rFreq % 12) ? 6 : 8;
1144//
1145// case rMonthlyPos:
1146// if (rFreq % 12 == 0) {
1147// // Some of these frequencies may never occur
1148// return (rFreq % 84 == 0) ? 364 // frequency = multiple of 7 years
1149// : (rFreq % 48 == 0) ? 7 // frequency = multiple of 4 years
1150// : (rFreq % 24 == 0) ? 14 : 28; // frequency = multiple of 2 or 1 year
1151// }
1152// // All other frequencies will occur sometime
1153// if (rFreq > 120)
1154// return 364; // frequencies of > 10 years will hit the date limit first
1155// switch (rFreq) {
1156// case 23: return 50;
1157// case 46: return 38;
1158// case 56: return 138;
1159// case 66: return 36;
1160// case 89: return 54;
1161// case 112: return 253;
1162// default: return 25; // most frequencies will need < 25 iterations
1163// }
1164//
1165// case rYearlyMonth:
1166// case rYearlyDay:
1167// return 8; // only 29th Feb or day 366 will need more than one iteration
1168//
1169// case rYearlyPos:
1170// if (rFreq % 7 == 0)
1171// return 364; // frequencies of a multiple of 7 years will hit the date limit first
1172// if (rFreq % 2 == 0) {
1173// // Some of these frequencies may never occur
1174// return (rFreq % 4 == 0) ? 7 : 14; // frequency = even number of years
1175// }
1176// return 28;
1177// }
1178// return 1;
1179// }
1180
1181//@cond PRIVATE
1182void RecurrenceRule::Private::buildConstraints()
1183{
1184 mTimedRepetition = 0;
1185 mNoByRules = mBySetPos.isEmpty();
1186 mConstraints.clear();
1187 Constraint con( mDateStart.timeSpec() );
1188 if ( mWeekStart > 0 ) {
1189 con.setWeekstart( mWeekStart );
1190 }
1191 mConstraints.append( con );
1192
1193 int c, cend;
1194 int i, iend;
1195 Constraint::List tmp;
1196
1197 #define intConstraint( list, setElement ) \
1198 if ( !list.isEmpty() ) { \
1199 mNoByRules = false; \
1200 iend = list.count(); \
1201 if ( iend == 1 ) { \
1202 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1203 mConstraints[c].setElement( list[0] ); \
1204 } \
1205 } else { \
1206 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1207 for ( i = 0; i < iend; ++i ) { \
1208 con = mConstraints[c]; \
1209 con.setElement( list[i] ); \
1210 tmp.append( con ); \
1211 } \
1212 } \
1213 mConstraints = tmp; \
1214 tmp.clear(); \
1215 } \
1216 }
1217
1218 intConstraint( mBySeconds, setSecond );
1219 intConstraint( mByMinutes, setMinute );
1220 intConstraint( mByHours, setHour );
1221 intConstraint( mByMonthDays, setDay );
1222 intConstraint( mByMonths, setMonth );
1223 intConstraint( mByYearDays, setYearday );
1224 intConstraint( mByWeekNumbers, setWeeknumber );
1225 #undef intConstraint
1226
1227 if ( !mByDays.isEmpty() ) {
1228 mNoByRules = false;
1229 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) {
1230 for ( i = 0, iend = mByDays.count(); i < iend; ++i ) {
1231 con = mConstraints[c];
1232 con.setWeekday( mByDays[i].day() );
1233 con.setWeekdaynr( mByDays[i].pos() );
1234 tmp.append( con );
1235 }
1236 }
1237 mConstraints = tmp;
1238 tmp.clear();
1239 }
1240
1241 #define fixConstraint( setElement, value ) \
1242 { \
1243 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1244 mConstraints[c].setElement( value ); \
1245 } \
1246 }
1247 // Now determine missing values from DTSTART. This can speed up things,
1248 // because we have more restrictions and save some loops.
1249
1250 // TODO: Does RFC 2445 intend to restrict the weekday in all cases of weekly?
1251 if ( mPeriod == rWeekly && mByDays.isEmpty() ) {
1252 fixConstraint( setWeekday, mDateStart.date().dayOfWeek() );
1253 }
1254
1255 // Really fall through in the cases, because all smaller time intervals are
1256 // constrained from dtstart
1257 switch ( mPeriod ) {
1258 case rYearly:
1259 if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
1260 mByYearDays.isEmpty() && mByMonths.isEmpty() ) {
1261 fixConstraint( setMonth, mDateStart.date().month() );
1262 }
1263 case rMonthly:
1264 if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
1265 mByYearDays.isEmpty() && mByMonthDays.isEmpty() ) {
1266 fixConstraint( setDay, mDateStart.date().day() );
1267 }
1268 case rWeekly:
1269 case rDaily:
1270 if ( mByHours.isEmpty() ) {
1271 fixConstraint( setHour, mDateStart.time().hour() );
1272 }
1273 case rHourly:
1274 if ( mByMinutes.isEmpty() ) {
1275 fixConstraint( setMinute, mDateStart.time().minute() );
1276 }
1277 case rMinutely:
1278 if ( mBySeconds.isEmpty() ) {
1279 fixConstraint( setSecond, mDateStart.time().second() );
1280 }
1281 case rSecondly:
1282 default:
1283 break;
1284 }
1285 #undef fixConstraint
1286
1287 if ( mNoByRules ) {
1288 switch ( mPeriod ) {
1289 case rHourly:
1290 mTimedRepetition = mFrequency * 3600;
1291 break;
1292 case rMinutely:
1293 mTimedRepetition = mFrequency * 60;
1294 break;
1295 case rSecondly:
1296 mTimedRepetition = mFrequency;
1297 break;
1298 default:
1299 break;
1300 }
1301 } else {
1302 for ( c = 0, cend = mConstraints.count(); c < cend; ) {
1303 if ( mConstraints[c].isConsistent( mPeriod ) ) {
1304 ++c;
1305 } else {
1306 mConstraints.removeAt( c );
1307 --cend;
1308 }
1309 }
1310 }
1311}
1312
1313// Build and cache a list of all occurrences.
1314// Only call buildCache() if mDuration > 0.
1315bool RecurrenceRule::Private::buildCache() const
1316{
1317 // Build the list of all occurrences of this event (we need that to determine
1318 // the end date!)
1319 Constraint interval( getNextValidDateInterval( mDateStart, mPeriod ) );
1320 QDateTime next;
1321
1322 DateTimeList dts = datesForInterval( interval, mPeriod );
1323 // Only use dates after the event has started (start date is only included
1324 // if it matches)
1325 int i = dts.findLT( mDateStart );
1326 if ( i >= 0 ) {
1327 dts.erase( dts.begin(), dts.begin() + i + 1 );
1328 }
1329
1330 int loopnr = 0;
1331 int dtnr = dts.count();
1332 // some validity checks to avoid infinite loops (i.e. if we have
1333 // done this loop already 10000 times, bail out )
1334 while ( loopnr < LOOP_LIMIT && dtnr < mDuration ) {
1335 interval.increase( mPeriod, mFrequency );
1336 // The returned date list is already sorted!
1337 dts += datesForInterval( interval, mPeriod );
1338 dtnr = dts.count();
1339 ++loopnr;
1340 }
1341 if ( dts.count() > mDuration ) {
1342 // we have picked up more occurrences than necessary, remove them
1343 dts.erase( dts.begin() + mDuration, dts.end() );
1344 }
1345 mCached = true;
1346 mCachedDates = dts;
1347
1348// it = dts.begin();
1349// while ( it != dts.end() ) {
1350// kDebug() << " -=>" << dumpTime(*it);
1351// ++it;
1352// }
1353 if ( int( dts.count() ) == mDuration ) {
1354 mCachedDateEnd = dts.last();
1355 return true;
1356 } else {
1357 // The cached date list is incomplete
1358 mCachedDateEnd = KDateTime();
1359 mCachedLastDate = interval.intervalDateTime( mPeriod );
1360 return false;
1361 }
1362}
1363//@endcond
1364
1365bool RecurrenceRule::dateMatchesRules( const KDateTime &kdt ) const
1366{
1367 KDateTime dt = kdt.toTimeSpec( d->mDateStart.timeSpec() );
1368 for ( int i = 0, iend = d->mConstraints.count(); i < iend; ++i ) {
1369 if ( d->mConstraints[i].matches( dt, recurrenceType() ) ) {
1370 return true;
1371 }
1372 }
1373 return false;
1374}
1375
1376bool RecurrenceRule::recursOn( const QDate &qd, const KDateTime::Spec &timeSpec ) const
1377{
1378 int i, iend;
1379 if ( allDay() ) {
1380 // It's a date-only rule, so it has no time specification.
1381 // Therefore ignore 'timeSpec'.
1382 if ( qd < d->mDateStart.date() ) {
1383 return false;
1384 }
1385 // Start date is only included if it really matches
1386 QDate endDate;
1387 if ( d->mDuration >= 0 ) {
1388 endDate = endDt().date();
1389 if ( qd > endDate ) {
1390 return false;
1391 }
1392 }
1393
1394 // The date must be in an appropriate interval (getNextValidDateInterval),
1395 // Plus it must match at least one of the constraints
1396 bool match = false;
1397 for ( i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i ) {
1398 match = d->mConstraints[i].matches( qd, recurrenceType() );
1399 }
1400 if ( !match ) {
1401 return false;
1402 }
1403
1404 KDateTime start( qd, QTime( 0, 0, 0 ), d->mDateStart.timeSpec() );
1405 Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
1406 // Constraint::matches is quite efficient, so first check if it can occur at
1407 // all before we calculate all actual dates.
1408 if ( !interval.matches( qd, recurrenceType() ) ) {
1409 return false;
1410 }
1411 // We really need to obtain the list of dates in this interval, since
1412 // otherwise BYSETPOS will not work (i.e. the date will match the interval,
1413 // but BYSETPOS selects only one of these matching dates!
1414 KDateTime end = start.addDays(1);
1415 do {
1416 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1417 for ( i = 0, iend = dts.count(); i < iend; ++i ) {
1418 if ( dts[i].date() >= qd ) {
1419 return dts[i].date() == qd;
1420 }
1421 }
1422 interval.increase( recurrenceType(), frequency() );
1423 } while ( interval.intervalDateTime( recurrenceType() ) < end );
1424 return false;
1425 }
1426
1427 // It's a date-time rule, so we need to take the time specification into account.
1428 KDateTime start( qd, QTime( 0, 0, 0 ), timeSpec );
1429 KDateTime end = start.addDays( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1430 start = start.toTimeSpec( d->mDateStart.timeSpec() );
1431 if ( end < d->mDateStart ) {
1432 return false;
1433 }
1434 if ( start < d->mDateStart ) {
1435 start = d->mDateStart;
1436 }
1437
1438 // Start date is only included if it really matches
1439 if ( d->mDuration >= 0 ) {
1440 KDateTime endRecur = endDt();
1441 if ( endRecur.isValid() ) {
1442 if ( start > endRecur ) {
1443 return false;
1444 }
1445 if ( end > endRecur ) {
1446 end = endRecur; // limit end-of-day time to end of recurrence rule
1447 }
1448 }
1449 }
1450
1451 if ( d->mTimedRepetition ) {
1452 // It's a simple sub-daily recurrence with no constraints
1453 int n = static_cast<int>( ( d->mDateStart.secsTo_long( start ) - 1 ) % d->mTimedRepetition );
1454 return start.addSecs( d->mTimedRepetition - n ) < end;
1455 }
1456
1457 // Find the start and end dates in the time spec for the rule
1458 QDate startDay = start.date();
1459 QDate endDay = end.addSecs( -1 ).date();
1460 int dayCount = startDay.daysTo( endDay ) + 1;
1461
1462 // The date must be in an appropriate interval (getNextValidDateInterval),
1463 // Plus it must match at least one of the constraints
1464 bool match = false;
1465 for ( i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i ) {
1466 match = d->mConstraints[i].matches( startDay, recurrenceType() );
1467 for ( int day = 1; day < dayCount && !match; ++day ) {
1468 match = d->mConstraints[i].matches( startDay.addDays( day ), recurrenceType() );
1469 }
1470 }
1471 if ( !match ) {
1472 return false;
1473 }
1474
1475 Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
1476 // Constraint::matches is quite efficient, so first check if it can occur at
1477 // all before we calculate all actual dates.
1478 match = false;
1479 Constraint intervalm = interval;
1480 do {
1481 match = intervalm.matches( startDay, recurrenceType() );
1482 for ( int day = 1; day < dayCount && !match; ++day ) {
1483 match = intervalm.matches( startDay.addDays( day ), recurrenceType() );
1484 }
1485 if ( match ) {
1486 break;
1487 }
1488 intervalm.increase( recurrenceType(), frequency() );
1489 } while ( intervalm.intervalDateTime( recurrenceType() ) < end );
1490 if ( !match ) {
1491 return false;
1492 }
1493
1494 // We really need to obtain the list of dates in this interval, since
1495 // otherwise BYSETPOS will not work (i.e. the date will match the interval,
1496 // but BYSETPOS selects only one of these matching dates!
1497 do {
1498 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1499 int i = dts.findGE( start );
1500 if ( i >= 0 ) {
1501 return dts[i] <= end;
1502 }
1503 interval.increase( recurrenceType(), frequency() );
1504 } while ( interval.intervalDateTime( recurrenceType() ) < end );
1505
1506 return false;
1507}
1508
1509bool RecurrenceRule::recursAt( const KDateTime &kdt ) const
1510{
1511 // Convert to the time spec used by this recurrence rule
1512 KDateTime dt( kdt.toTimeSpec( d->mDateStart.timeSpec() ) );
1513
1514 if ( allDay() ) {
1515 return recursOn( dt.date(), dt.timeSpec() );
1516 }
1517 if ( dt < d->mDateStart ) {
1518 return false;
1519 }
1520 // Start date is only included if it really matches
1521 if ( d->mDuration >= 0 && dt > endDt() ) {
1522 return false;
1523 }
1524
1525 if ( d->mTimedRepetition ) {
1526 // It's a simple sub-daily recurrence with no constraints
1527 return !( d->mDateStart.secsTo_long( dt ) % d->mTimedRepetition );
1528 }
1529
1530 // The date must be in an appropriate interval (getNextValidDateInterval),
1531 // Plus it must match at least one of the constraints
1532 if ( !dateMatchesRules( dt ) ) {
1533 return false;
1534 }
1535 // if it recurs every interval, speed things up...
1536// if ( d->mFrequency == 1 && d->mBySetPos.isEmpty() && d->mByDays.isEmpty() ) return true;
1537 Constraint interval( d->getNextValidDateInterval( dt, recurrenceType() ) );
1538 // TODO_Recurrence: Does this work with BySetPos???
1539 if ( interval.matches( dt, recurrenceType() ) ) {
1540 return true;
1541 }
1542 return false;
1543}
1544
1545TimeList RecurrenceRule::recurTimesOn( const QDate &date, const KDateTime::Spec &timeSpec ) const
1546{
1547 TimeList lst;
1548 if ( allDay() ) {
1549 return lst;
1550 }
1551 KDateTime start( date, QTime( 0, 0, 0 ), timeSpec );
1552 KDateTime end = start.addDays( 1 ).addSecs( -1 );
1553 DateTimeList dts = timesInInterval( start, end ); // returns between start and end inclusive
1554 for ( int i = 0, iend = dts.count(); i < iend; ++i ) {
1555 lst += dts[i].toTimeSpec( timeSpec ).time();
1556 }
1557 return lst;
1558}
1559
1560/** Returns the number of recurrences up to and including the date/time specified. */
1561int RecurrenceRule::durationTo( const KDateTime &dt ) const
1562{
1563 // Convert to the time spec used by this recurrence rule
1564 KDateTime toDate( dt.toTimeSpec( d->mDateStart.timeSpec() ) );
1565 // Easy cases:
1566 // either before start, or after all recurrences and we know their number
1567 if ( toDate < d->mDateStart ) {
1568 return 0;
1569 }
1570 // Start date is only included if it really matches
1571 if ( d->mDuration > 0 && toDate >= endDt() ) {
1572 return d->mDuration;
1573 }
1574
1575 if ( d->mTimedRepetition ) {
1576 // It's a simple sub-daily recurrence with no constraints
1577 return static_cast<int>( d->mDateStart.secsTo_long( toDate ) / d->mTimedRepetition );
1578 }
1579
1580 return timesInInterval( d->mDateStart, toDate ).count();
1581}
1582
1583int RecurrenceRule::durationTo( const QDate &date ) const
1584{
1585 return durationTo( KDateTime( date, QTime( 23, 59, 59 ), d->mDateStart.timeSpec() ) );
1586}
1587
1588KDateTime RecurrenceRule::getPreviousDate( const KDateTime &afterDate ) const
1589{
1590 // Convert to the time spec used by this recurrence rule
1591 KDateTime toDate( afterDate.toTimeSpec( d->mDateStart.timeSpec() ) );
1592
1593 // Invalid starting point, or beyond end of recurrence
1594 if ( !toDate.isValid() || toDate < d->mDateStart ) {
1595 return KDateTime();
1596 }
1597
1598 if ( d->mTimedRepetition ) {
1599 // It's a simple sub-daily recurrence with no constraints
1600 KDateTime prev = toDate;
1601 if ( d->mDuration >= 0 && endDt().isValid() && toDate > endDt() ) {
1602 prev = endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1603 }
1604 int n = static_cast<int>( ( d->mDateStart.secsTo_long( prev ) - 1 ) % d->mTimedRepetition );
1605 if ( n < 0 ) {
1606 return KDateTime(); // before recurrence start
1607 }
1608 prev = prev.addSecs( -n - 1 );
1609 return prev >= d->mDateStart ? prev : KDateTime();
1610 }
1611
1612 // If we have a cache (duration given), use that
1613 if ( d->mDuration > 0 ) {
1614 if ( !d->mCached ) {
1615 d->buildCache();
1616 }
1617 int i = d->mCachedDates.findLT( toDate );
1618 if ( i >= 0 ) {
1619 return d->mCachedDates[i];
1620 }
1621 return KDateTime();
1622 }
1623
1624 KDateTime prev = toDate;
1625 if ( d->mDuration >= 0 && endDt().isValid() && toDate > endDt() ) {
1626 prev = endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1627 }
1628
1629 Constraint interval( d->getPreviousValidDateInterval( prev, recurrenceType() ) );
1630 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1631 int i = dts.findLT( prev );
1632 if ( i >= 0 ) {
1633 return ( dts[i] >= d->mDateStart ) ? dts[i] : KDateTime();
1634 }
1635
1636 // Previous interval. As soon as we find an occurrence, we're done.
1637 while ( interval.intervalDateTime( recurrenceType() ) > d->mDateStart ) {
1638 interval.increase( recurrenceType(), -int( frequency() ) );
1639 // The returned date list is sorted
1640 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1641 // The list is sorted, so take the last one.
1642 if ( !dts.isEmpty() ) {
1643 prev = dts.last();
1644 if ( prev.isValid() && prev >= d->mDateStart ) {
1645 return prev;
1646 } else {
1647 return KDateTime();
1648 }
1649 }
1650 }
1651 return KDateTime();
1652}
1653
1654KDateTime RecurrenceRule::getNextDate( const KDateTime &preDate ) const
1655{
1656 // Convert to the time spec used by this recurrence rule
1657 KDateTime fromDate( preDate.toTimeSpec( d->mDateStart.timeSpec() ) );
1658 // Beyond end of recurrence
1659 if ( d->mDuration >= 0 && endDt().isValid() && fromDate >= endDt() ) {
1660 return KDateTime();
1661 }
1662
1663 // Start date is only included if it really matches
1664 if ( fromDate < d->mDateStart ) {
1665 fromDate = d->mDateStart.addSecs( -1 );
1666 }
1667
1668 if ( d->mTimedRepetition ) {
1669 // It's a simple sub-daily recurrence with no constraints
1670 int n = static_cast<int>( ( d->mDateStart.secsTo_long( fromDate ) + 1 ) % d->mTimedRepetition );
1671 KDateTime next = fromDate.addSecs( d->mTimedRepetition - n + 1 );
1672 return d->mDuration < 0 || !endDt().isValid() || next <= endDt() ? next : KDateTime();
1673 }
1674
1675 if ( d->mDuration > 0 ) {
1676 if ( !d->mCached ) {
1677 d->buildCache();
1678 }
1679 int i = d->mCachedDates.findGT( fromDate );
1680 if ( i >= 0 ) {
1681 return d->mCachedDates[i];
1682 }
1683 }
1684
1685 KDateTime end = endDt();
1686 Constraint interval( d->getNextValidDateInterval( fromDate, recurrenceType() ) );
1687 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1688 int i = dts.findGT( fromDate );
1689 if ( i >= 0 ) {
1690 return ( d->mDuration < 0 || dts[i] <= end ) ? dts[i] : KDateTime();
1691 }
1692 interval.increase( recurrenceType(), frequency() );
1693 if ( d->mDuration >= 0 && interval.intervalDateTime( recurrenceType() ) > end ) {
1694 return KDateTime();
1695 }
1696
1697 // Increase the interval. The first occurrence that we find is the result (if
1698 // if's before the end date).
1699 // TODO: some validity checks to avoid infinite loops for contradictory constraints
1700 int loop = 0;
1701 do {
1702 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1703 if ( dts.count() > 0 ) {
1704 KDateTime ret( dts[0] );
1705 if ( d->mDuration >= 0 && ret > end ) {
1706 return KDateTime();
1707 } else {
1708 return ret;
1709 }
1710 }
1711 interval.increase( recurrenceType(), frequency() );
1712 } while ( ++loop < LOOP_LIMIT &&
1713 ( d->mDuration < 0 || interval.intervalDateTime( recurrenceType() ) < end ) );
1714 return KDateTime();
1715}
1716
1717DateTimeList RecurrenceRule::timesInInterval( const KDateTime &dtStart,
1718 const KDateTime &dtEnd ) const
1719{
1720 KDateTime start = dtStart.toTimeSpec( d->mDateStart.timeSpec() );
1721 KDateTime end = dtEnd.toTimeSpec( d->mDateStart.timeSpec() );
1722 DateTimeList result;
1723 if ( end < d->mDateStart ) {
1724 return result; // before start of recurrence
1725 }
1726 KDateTime enddt = end;
1727 if ( d->mDuration >= 0 ) {
1728 KDateTime endRecur = endDt();
1729 if ( endRecur.isValid() ) {
1730 if ( start > endRecur ) {
1731 return result; // beyond end of recurrence
1732 }
1733 if ( end > endRecur ) {
1734 enddt = endRecur; // limit end time to end of recurrence rule
1735 }
1736 }
1737 }
1738
1739 if ( d->mTimedRepetition ) {
1740 // It's a simple sub-daily recurrence with no constraints
1741 int n = static_cast<int>( ( d->mDateStart.secsTo_long( start ) - 1 ) % d->mTimedRepetition );
1742 KDateTime dt = start.addSecs( d->mTimedRepetition - n );
1743 if ( dt < enddt ) {
1744 n = static_cast<int>( ( dt.secsTo_long( enddt ) - 1 ) / d->mTimedRepetition ) + 1;
1745 // limit n by a sane value else we can "explode".
1746 n = qMin( n, LOOP_LIMIT );
1747 for ( int i = 0; i < n; dt = dt.addSecs( d->mTimedRepetition ), ++i ) {
1748 result += dt;
1749 }
1750 }
1751 return result;
1752 }
1753
1754 KDateTime st = start;
1755 bool done = false;
1756 if ( d->mDuration > 0 ) {
1757 if ( !d->mCached ) {
1758 d->buildCache();
1759 }
1760 if ( d->mCachedDateEnd.isValid() && start > d->mCachedDateEnd ) {
1761 return result; // beyond end of recurrence
1762 }
1763 int i = d->mCachedDates.findGE( start );
1764 if ( i >= 0 ) {
1765 int iend = d->mCachedDates.findGT( enddt, i );
1766 if ( iend < 0 ) {
1767 iend = d->mCachedDates.count();
1768 } else {
1769 done = true;
1770 }
1771 while ( i < iend ) {
1772 result += d->mCachedDates[i++];
1773 }
1774 }
1775 if ( d->mCachedDateEnd.isValid() ) {
1776 done = true;
1777 } else if ( !result.isEmpty() ) {
1778 result += KDateTime(); // indicate that the returned list is incomplete
1779 done = true;
1780 }
1781 if ( done ) {
1782 return result;
1783 }
1784 // We don't have any result yet, but we reached the end of the incomplete cache
1785 st = d->mCachedLastDate.addSecs( 1 );
1786 }
1787
1788 Constraint interval( d->getNextValidDateInterval( st, recurrenceType() ) );
1789 int loop = 0;
1790 do {
1791 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1792 int i = 0;
1793 int iend = dts.count();
1794 if ( loop == 0 ) {
1795 i = dts.findGE( st );
1796 if ( i < 0 ) {
1797 i = iend;
1798 }
1799 }
1800 int j = dts.findGT( enddt, i );
1801 if ( j >= 0 ) {
1802 iend = j;
1803 loop = LOOP_LIMIT;
1804 }
1805 while ( i < iend ) {
1806 result += dts[i++];
1807 }
1808 // Increase the interval.
1809 interval.increase( recurrenceType(), frequency() );
1810 } while ( ++loop < LOOP_LIMIT &&
1811 interval.intervalDateTime( recurrenceType() ) < end );
1812 return result;
1813}
1814
1815//@cond PRIVATE
1816// Find the date/time of the occurrence at or before a date/time,
1817// for a given period type.
1818// Return a constraint whose value appropriate to 'type', is set to
1819// the value contained in the date/time.
1820Constraint RecurrenceRule::Private::getPreviousValidDateInterval( const KDateTime &dt,
1821 PeriodType type ) const
1822{
1823 long periods = 0;
1824 KDateTime start = mDateStart;
1825 KDateTime nextValid( start );
1826 int modifier = 1;
1827 KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
1828 // for super-daily recurrences, don't care about the time part
1829
1830 // Find the #intervals since the dtstart and round to the next multiple of
1831 // the frequency
1832 switch ( type ) {
1833 // Really fall through for sub-daily, since the calculations only differ
1834 // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
1835 case rHourly:
1836 modifier *= 60;
1837 case rMinutely:
1838 modifier *= 60;
1839 case rSecondly:
1840 periods = static_cast<int>( start.secsTo_long( toDate ) / modifier );
1841 // round it down to the next lower multiple of frequency:
1842 if ( mFrequency > 0 ) {
1843 periods = ( periods / mFrequency ) * mFrequency;
1844 }
1845 nextValid = start.addSecs( modifier * periods );
1846 break;
1847 case rWeekly:
1848 toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
1849 start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
1850 modifier *= 7;
1851 case rDaily:
1852 periods = start.daysTo( toDate ) / modifier;
1853 // round it down to the next lower multiple of frequency:
1854 if ( mFrequency > 0 ) {
1855 periods = ( periods / mFrequency ) * mFrequency;
1856 }
1857 nextValid = start.addDays( modifier * periods );
1858 break;
1859 case rMonthly:
1860 {
1861 periods = 12 * ( toDate.date().year() - start.date().year() ) +
1862 ( toDate.date().month() - start.date().month() );
1863 // round it down to the next lower multiple of frequency:
1864 if ( mFrequency > 0 ) {
1865 periods = ( periods / mFrequency ) * mFrequency;
1866 }
1867 // set the day to the first day of the month, so we don't have problems
1868 // with non-existent days like Feb 30 or April 31
1869 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
1870 nextValid.setDate( start.date().addMonths( periods ) );
1871 break; }
1872 case rYearly:
1873 periods = ( toDate.date().year() - start.date().year() );
1874 // round it down to the next lower multiple of frequency:
1875 if ( mFrequency > 0 ) {
1876 periods = ( periods / mFrequency ) * mFrequency;
1877 }
1878 nextValid.setDate( start.date().addYears( periods ) );
1879 break;
1880 default:
1881 break;
1882 }
1883
1884 return Constraint( nextValid, type, mWeekStart );
1885}
1886
1887// Find the date/time of the next occurrence at or after a date/time,
1888// for a given period type.
1889// Return a constraint whose value appropriate to 'type', is set to the
1890// value contained in the date/time.
1891Constraint RecurrenceRule::Private::getNextValidDateInterval( const KDateTime &dt,
1892 PeriodType type ) const
1893{
1894 // TODO: Simplify this!
1895 long periods = 0;
1896 KDateTime start = mDateStart;
1897 KDateTime nextValid( start );
1898 int modifier = 1;
1899 KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
1900 // for super-daily recurrences, don't care about the time part
1901
1902 // Find the #intervals since the dtstart and round to the next multiple of
1903 // the frequency
1904 switch ( type ) {
1905 // Really fall through for sub-daily, since the calculations only differ
1906 // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
1907 case rHourly:
1908 modifier *= 60;
1909 case rMinutely:
1910 modifier *= 60;
1911 case rSecondly:
1912 periods = static_cast<int>( start.secsTo_long( toDate ) / modifier );
1913 periods = qMax( 0L, periods );
1914 if ( periods > 0 && mFrequency > 0 ) {
1915 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1916 }
1917 nextValid = start.addSecs( modifier * periods );
1918 break;
1919 case rWeekly:
1920 // correct both start date and current date to start of week
1921 toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
1922 start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
1923 modifier *= 7;
1924 case rDaily:
1925 periods = start.daysTo( toDate ) / modifier;
1926 periods = qMax( 0L, periods );
1927 if ( periods > 0 && mFrequency > 0 ) {
1928 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1929 }
1930 nextValid = start.addDays( modifier * periods );
1931 break;
1932 case rMonthly:
1933 {
1934 periods = 12 * ( toDate.date().year() - start.date().year() ) +
1935 ( toDate.date().month() - start.date().month() );
1936 periods = qMax( 0L, periods );
1937 if ( periods > 0 && mFrequency > 0 ) {
1938 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1939 }
1940 // set the day to the first day of the month, so we don't have problems
1941 // with non-existent days like Feb 30 or April 31
1942 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
1943 nextValid.setDate( start.date().addMonths( periods ) );
1944 break;
1945 }
1946 case rYearly:
1947 periods = ( toDate.date().year() - start.date().year() );
1948 periods = qMax( 0L, periods );
1949 if ( periods > 0 && mFrequency > 0 ) {
1950 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1951 }
1952 nextValid.setDate( start.date().addYears( periods ) );
1953 break;
1954 default:
1955 break;
1956 }
1957
1958 return Constraint( nextValid, type, mWeekStart );
1959}
1960
1961DateTimeList RecurrenceRule::Private::datesForInterval( const Constraint &interval,
1962 PeriodType type ) const
1963{
1964 /* -) Loop through constraints,
1965 -) merge interval with each constraint
1966 -) if merged constraint is not consistent => ignore that constraint
1967 -) if complete => add that one date to the date list
1968 -) Loop through all missing fields => For each add the resulting
1969 */
1970 DateTimeList lst;
1971 for ( int i = 0, iend = mConstraints.count(); i < iend; ++i ) {
1972 Constraint merged( interval );
1973 if ( merged.merge( mConstraints[i] ) ) {
1974 // If the information is incomplete, we can't use this constraint
1975 if ( merged.year > 0 && merged.hour >= 0 && merged.minute >= 0 && merged.second >= 0 ) {
1976 // We have a valid constraint, so get all datetimes that match it andd
1977 // append it to all date/times of this interval
1978 QList<KDateTime> lstnew = merged.dateTimes( type );
1979 lst += lstnew;
1980 }
1981 }
1982 }
1983 // Sort it so we can apply the BySetPos. Also some logic relies on this being sorted
1984 lst.sortUnique();
1985
1986/*if ( lst.isEmpty() ) {
1987 kDebug() << " No Dates in Interval";
1988} else {
1989 kDebug() << " Dates:";
1990 for ( int i = 0, iend = lst.count(); i < iend; ++i ) {
1991 kDebug()<< " -)" << dumpTime(lst[i]);
1992 }
1993 kDebug() << " ---------------------";
1994}*/
1995 if ( !mBySetPos.isEmpty() ) {
1996 DateTimeList tmplst = lst;
1997 lst.clear();
1998 for ( int i = 0, iend = mBySetPos.count(); i < iend; ++i ) {
1999 int pos = mBySetPos[i];
2000 if ( pos > 0 ) {
2001 --pos;
2002 }
2003 if ( pos < 0 ) {
2004 pos += tmplst.count();
2005 }
2006 if ( pos >= 0 && pos < tmplst.count() ) {
2007 lst.append( tmplst[pos] );
2008 }
2009 }
2010 lst.sortUnique();
2011 }
2012
2013 return lst;
2014}
2015//@endcond
2016
2017void RecurrenceRule::dump() const
2018{
2019#ifndef NDEBUG
2020 kDebug();
2021 if ( !d->mRRule.isEmpty() ) {
2022 kDebug() << " RRULE=" << d->mRRule;
2023 }
2024 kDebug() << " Read-Only:" << isReadOnly();
2025
2026 kDebug() << " Period type:" << recurrenceType()
2027 << ", frequency:" << frequency();
2028 kDebug() << " #occurrences:" << duration();
2029 kDebug() << " start date:" << dumpTime( startDt() )
2030 << ", end date:" << dumpTime( endDt() );
2031
2032#define dumpByIntList(list,label) \
2033 if ( !list.isEmpty() ) {\
2034 QStringList lst;\
2035 for ( int i = 0, iend = list.count(); i < iend; ++i ) {\
2036 lst.append( QString::number( list[i] ) );\
2037 }\
2038 kDebug() << " " << label << lst.join( ", " );\
2039 }
2040 dumpByIntList( d->mBySeconds, "BySeconds: " );
2041 dumpByIntList( d->mByMinutes, "ByMinutes: " );
2042 dumpByIntList( d->mByHours, "ByHours: " );
2043 if ( !d->mByDays.isEmpty() ) {
2044 QStringList lst;
2045 for ( int i = 0, iend = d->mByDays.count(); i < iend; ++i ) {\
2046 lst.append( ( d->mByDays[i].pos() ? QString::number( d->mByDays[i].pos() ) : "" ) +
2047 DateHelper::dayName( d->mByDays[i].day() ) );
2048 }
2049 kDebug() << " ByDays: " << lst.join( ", " );
2050 }
2051 dumpByIntList( d->mByMonthDays, "ByMonthDays:" );
2052 dumpByIntList( d->mByYearDays, "ByYearDays: " );
2053 dumpByIntList( d->mByWeekNumbers, "ByWeekNr: " );
2054 dumpByIntList( d->mByMonths, "ByMonths: " );
2055 dumpByIntList( d->mBySetPos, "BySetPos: " );
2056 #undef dumpByIntList
2057
2058 kDebug() << " Week start:" << DateHelper::dayName( d->mWeekStart );
2059
2060 kDebug() << " Constraints:";
2061 // dump constraints
2062 for ( int i = 0, iend = d->mConstraints.count(); i < iend; ++i ) {
2063 d->mConstraints[i].dump();
2064 }
2065#endif
2066}
2067
2068//@cond PRIVATE
2069void Constraint::dump() const
2070{
2071 kDebug() << " ~> Y=" << year
2072 << ", M=" << month
2073 << ", D=" << day
2074 << ", H=" << hour
2075 << ", m=" << minute
2076 << ", S=" << second
2077 << ", wd=" << weekday
2078 << ",#wd=" << weekdaynr
2079 << ", #w=" << weeknumber
2080 << ", yd=" << yearday;
2081}
2082//@endcond
2083
2084QString dumpTime( const KDateTime &dt )
2085{
2086#ifndef NDEBUG
2087 if ( !dt.isValid() ) {
2088 return QString();
2089 }
2090 QString result;
2091 if ( dt.isDateOnly() ) {
2092 result = dt.toString( "%a %Y-%m-%d %:Z" );
2093 } else {
2094 result = dt.toString( "%a %Y-%m-%d %H:%M:%S %:Z" );
2095 if ( dt.isSecondOccurrence() ) {
2096 result += QLatin1String( " (2nd)" );
2097 }
2098 }
2099 if ( dt.timeSpec() == KDateTime::Spec::ClockTime() ) {
2100 result += QLatin1String( "Clock" );
2101 }
2102 return result;
2103#else
2104 Q_UNUSED( dt );
2105 return QString();
2106#endif
2107}
2108
2109KDateTime RecurrenceRule::startDt() const
2110{
2111 return d->mDateStart;
2112}
2113
2114RecurrenceRule::PeriodType RecurrenceRule::recurrenceType() const
2115{
2116 return d->mPeriod;
2117}
2118
2119uint RecurrenceRule::frequency() const
2120{
2121 return d->mFrequency;
2122}
2123
2124int RecurrenceRule::duration() const
2125{
2126 return d->mDuration;
2127}
2128
2129QString RecurrenceRule::rrule() const
2130{
2131 return d->mRRule;
2132}
2133
2134void RecurrenceRule::setRRule( const QString &rrule )
2135{
2136 d->mRRule = rrule;
2137}
2138
2139bool RecurrenceRule::isReadOnly() const
2140{
2141 return d->mIsReadOnly;
2142}
2143
2144void RecurrenceRule::setReadOnly( bool readOnly )
2145{
2146 d->mIsReadOnly = readOnly;
2147}
2148
2149bool RecurrenceRule::recurs() const
2150{
2151 return d->mPeriod != rNone;
2152}
2153
2154bool RecurrenceRule::allDay() const
2155{
2156 return d->mAllDay;
2157}
2158
2159const QList<int> &RecurrenceRule::bySeconds() const
2160{
2161 return d->mBySeconds;
2162}
2163
2164const QList<int> &RecurrenceRule::byMinutes() const
2165{
2166 return d->mByMinutes;
2167}
2168
2169const QList<int> &RecurrenceRule::byHours() const
2170{
2171 return d->mByHours;
2172}
2173
2174const QList<RecurrenceRule::WDayPos> &RecurrenceRule::byDays() const
2175{
2176 return d->mByDays;
2177}
2178
2179const QList<int> &RecurrenceRule::byMonthDays() const
2180{
2181 return d->mByMonthDays;
2182}
2183
2184const QList<int> &RecurrenceRule::byYearDays() const
2185{
2186 return d->mByYearDays;
2187}
2188
2189const QList<int> &RecurrenceRule::byWeekNumbers() const
2190{
2191 return d->mByWeekNumbers;
2192}
2193
2194const QList<int> &RecurrenceRule::byMonths() const
2195{
2196 return d->mByMonths;
2197}
2198
2199const QList<int> &RecurrenceRule::bySetPos() const
2200{
2201 return d->mBySetPos;
2202}
2203
2204short RecurrenceRule::weekStart() const
2205{
2206 return d->mWeekStart;
2207}
2208