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

KCalCore Library

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

KDE's Doxygen guidelines are available online.

KCalCore Library

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

kdepimlibs-4.9.3 API Reference

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

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