• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.10.5 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 
1396  if ( !qd.isValid() ) {
1397  // There can't be recurrences on invalid dates
1398  return false;
1399  }
1400 
1401  if ( allDay() ) {
1402  // It's a date-only rule, so it has no time specification.
1403  // Therefore ignore 'timeSpec'.
1404  if ( qd < d->mDateStart.date() ) {
1405  return false;
1406  }
1407  // Start date is only included if it really matches
1408  QDate endDate;
1409  if ( d->mDuration >= 0 ) {
1410  endDate = endDt().date();
1411  if ( qd > endDate ) {
1412  return false;
1413  }
1414  }
1415 
1416  // The date must be in an appropriate interval (getNextValidDateInterval),
1417  // Plus it must match at least one of the constraints
1418  bool match = false;
1419  for ( i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i ) {
1420  match = d->mConstraints[i].matches( qd, recurrenceType() );
1421  }
1422  if ( !match ) {
1423  return false;
1424  }
1425 
1426  KDateTime start( qd, QTime( 0, 0, 0 ), d->mDateStart.timeSpec() );
1427  Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
1428  // Constraint::matches is quite efficient, so first check if it can occur at
1429  // all before we calculate all actual dates.
1430  if ( !interval.matches( qd, recurrenceType() ) ) {
1431  return false;
1432  }
1433  // We really need to obtain the list of dates in this interval, since
1434  // otherwise BYSETPOS will not work (i.e. the date will match the interval,
1435  // but BYSETPOS selects only one of these matching dates!
1436  KDateTime end = start.addDays( 1 );
1437  do {
1438  DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1439  for ( i = 0, iend = dts.count(); i < iend; ++i ) {
1440  if ( dts[i].date() >= qd ) {
1441  return dts[i].date() == qd;
1442  }
1443  }
1444  interval.increase( recurrenceType(), frequency() );
1445  } while ( interval.intervalDateTime( recurrenceType() ) < end );
1446  return false;
1447  }
1448 
1449  // It's a date-time rule, so we need to take the time specification into account.
1450  KDateTime start( qd, QTime( 0, 0, 0 ), timeSpec );
1451  KDateTime end = start.addDays( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1452  start = start.toTimeSpec( d->mDateStart.timeSpec() );
1453  if ( end < d->mDateStart ) {
1454  return false;
1455  }
1456  if ( start < d->mDateStart ) {
1457  start = d->mDateStart;
1458  }
1459 
1460  // Start date is only included if it really matches
1461  if ( d->mDuration >= 0 ) {
1462  KDateTime endRecur = endDt();
1463  if ( endRecur.isValid() ) {
1464  if ( start > endRecur ) {
1465  return false;
1466  }
1467  if ( end > endRecur ) {
1468  end = endRecur; // limit end-of-day time to end of recurrence rule
1469  }
1470  }
1471  }
1472 
1473  if ( d->mTimedRepetition ) {
1474  // It's a simple sub-daily recurrence with no constraints
1475  int n = static_cast<int>( ( d->mDateStart.secsTo_long( start ) - 1 ) % d->mTimedRepetition );
1476  return start.addSecs( d->mTimedRepetition - n ) < end;
1477  }
1478 
1479  // Find the start and end dates in the time spec for the rule
1480  QDate startDay = start.date();
1481  QDate endDay = end.addSecs( -1 ).date();
1482  int dayCount = startDay.daysTo( endDay ) + 1;
1483 
1484  // The date must be in an appropriate interval (getNextValidDateInterval),
1485  // Plus it must match at least one of the constraints
1486  bool match = false;
1487  for ( i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i ) {
1488  match = d->mConstraints[i].matches( startDay, recurrenceType() );
1489  for ( int day = 1; day < dayCount && !match; ++day ) {
1490  match = d->mConstraints[i].matches( startDay.addDays( day ), recurrenceType() );
1491  }
1492  }
1493  if ( !match ) {
1494  return false;
1495  }
1496 
1497  Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
1498  // Constraint::matches is quite efficient, so first check if it can occur at
1499  // all before we calculate all actual dates.
1500  match = false;
1501  Constraint intervalm = interval;
1502  do {
1503  match = intervalm.matches( startDay, recurrenceType() );
1504  for ( int day = 1; day < dayCount && !match; ++day ) {
1505  match = intervalm.matches( startDay.addDays( day ), recurrenceType() );
1506  }
1507  if ( match ) {
1508  break;
1509  }
1510  intervalm.increase( recurrenceType(), frequency() );
1511  } while ( intervalm.intervalDateTime( recurrenceType() ) < end );
1512  if ( !match ) {
1513  return false;
1514  }
1515 
1516  // We really need to obtain the list of dates in this interval, since
1517  // otherwise BYSETPOS will not work (i.e. the date will match the interval,
1518  // but BYSETPOS selects only one of these matching dates!
1519  do {
1520  DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1521  int i = dts.findGE( start );
1522  if ( i >= 0 ) {
1523  return dts[i] <= end;
1524  }
1525  interval.increase( recurrenceType(), frequency() );
1526  } while ( interval.intervalDateTime( recurrenceType() ) < end );
1527 
1528  return false;
1529 }
1530 
1531 bool RecurrenceRule::recursAt( const KDateTime &kdt ) const
1532 {
1533  // Convert to the time spec used by this recurrence rule
1534  KDateTime dt( kdt.toTimeSpec( d->mDateStart.timeSpec() ) );
1535 
1536  if ( allDay() ) {
1537  return recursOn( dt.date(), dt.timeSpec() );
1538  }
1539  if ( dt < d->mDateStart ) {
1540  return false;
1541  }
1542  // Start date is only included if it really matches
1543  if ( d->mDuration >= 0 && dt > endDt() ) {
1544  return false;
1545  }
1546 
1547  if ( d->mTimedRepetition ) {
1548  // It's a simple sub-daily recurrence with no constraints
1549  return !( d->mDateStart.secsTo_long( dt ) % d->mTimedRepetition );
1550  }
1551 
1552  // The date must be in an appropriate interval (getNextValidDateInterval),
1553  // Plus it must match at least one of the constraints
1554  if ( !dateMatchesRules( dt ) ) {
1555  return false;
1556  }
1557  // if it recurs every interval, speed things up...
1558 // if ( d->mFrequency == 1 && d->mBySetPos.isEmpty() && d->mByDays.isEmpty() ) return true;
1559  Constraint interval( d->getNextValidDateInterval( dt, recurrenceType() ) );
1560  // TODO_Recurrence: Does this work with BySetPos???
1561  if ( interval.matches( dt, recurrenceType() ) ) {
1562  return true;
1563  }
1564  return false;
1565 }
1566 
1567 TimeList RecurrenceRule::recurTimesOn( const QDate &date, const KDateTime::Spec &timeSpec ) const
1568 {
1569  TimeList lst;
1570  if ( allDay() ) {
1571  return lst;
1572  }
1573  KDateTime start( date, QTime( 0, 0, 0 ), timeSpec );
1574  KDateTime end = start.addDays( 1 ).addSecs( -1 );
1575  DateTimeList dts = timesInInterval( start, end ); // returns between start and end inclusive
1576  for ( int i = 0, iend = dts.count(); i < iend; ++i ) {
1577  lst += dts[i].toTimeSpec( timeSpec ).time();
1578  }
1579  return lst;
1580 }
1581 
1583 int RecurrenceRule::durationTo( const KDateTime &dt ) const
1584 {
1585  // Convert to the time spec used by this recurrence rule
1586  KDateTime toDate( dt.toTimeSpec( d->mDateStart.timeSpec() ) );
1587  // Easy cases:
1588  // either before start, or after all recurrences and we know their number
1589  if ( toDate < d->mDateStart ) {
1590  return 0;
1591  }
1592  // Start date is only included if it really matches
1593  if ( d->mDuration > 0 && toDate >= endDt() ) {
1594  return d->mDuration;
1595  }
1596 
1597  if ( d->mTimedRepetition ) {
1598  // It's a simple sub-daily recurrence with no constraints
1599  return static_cast<int>( d->mDateStart.secsTo_long( toDate ) / d->mTimedRepetition );
1600  }
1601 
1602  return timesInInterval( d->mDateStart, toDate ).count();
1603 }
1604 
1605 int RecurrenceRule::durationTo( const QDate &date ) const
1606 {
1607  return durationTo( KDateTime( date, QTime( 23, 59, 59 ), d->mDateStart.timeSpec() ) );
1608 }
1609 
1610 KDateTime RecurrenceRule::getPreviousDate( const KDateTime &afterDate ) const
1611 {
1612  // Convert to the time spec used by this recurrence rule
1613  KDateTime toDate( afterDate.toTimeSpec( d->mDateStart.timeSpec() ) );
1614 
1615  // Invalid starting point, or beyond end of recurrence
1616  if ( !toDate.isValid() || toDate < d->mDateStart ) {
1617  return KDateTime();
1618  }
1619 
1620  if ( d->mTimedRepetition ) {
1621  // It's a simple sub-daily recurrence with no constraints
1622  KDateTime prev = toDate;
1623  if ( d->mDuration >= 0 && endDt().isValid() && toDate > endDt() ) {
1624  prev = endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1625  }
1626  int n = static_cast<int>( ( d->mDateStart.secsTo_long( prev ) - 1 ) % d->mTimedRepetition );
1627  if ( n < 0 ) {
1628  return KDateTime(); // before recurrence start
1629  }
1630  prev = prev.addSecs( -n - 1 );
1631  return prev >= d->mDateStart ? prev : KDateTime();
1632  }
1633 
1634  // If we have a cache (duration given), use that
1635  if ( d->mDuration > 0 ) {
1636  if ( !d->mCached ) {
1637  d->buildCache();
1638  }
1639  int i = d->mCachedDates.findLT( toDate );
1640  if ( i >= 0 ) {
1641  return d->mCachedDates[i];
1642  }
1643  return KDateTime();
1644  }
1645 
1646  KDateTime prev = toDate;
1647  if ( d->mDuration >= 0 && endDt().isValid() && toDate > endDt() ) {
1648  prev = endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1649  }
1650 
1651  Constraint interval( d->getPreviousValidDateInterval( prev, recurrenceType() ) );
1652  DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1653  int i = dts.findLT( prev );
1654  if ( i >= 0 ) {
1655  return ( dts[i] >= d->mDateStart ) ? dts[i] : KDateTime();
1656  }
1657 
1658  // Previous interval. As soon as we find an occurrence, we're done.
1659  while ( interval.intervalDateTime( recurrenceType() ) > d->mDateStart ) {
1660  interval.increase( recurrenceType(), -int( frequency() ) );
1661  // The returned date list is sorted
1662  DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1663  // The list is sorted, so take the last one.
1664  if ( !dts.isEmpty() ) {
1665  prev = dts.last();
1666  if ( prev.isValid() && prev >= d->mDateStart ) {
1667  return prev;
1668  } else {
1669  return KDateTime();
1670  }
1671  }
1672  }
1673  return KDateTime();
1674 }
1675 
1676 KDateTime RecurrenceRule::getNextDate( const KDateTime &preDate ) const
1677 {
1678  // Convert to the time spec used by this recurrence rule
1679  KDateTime fromDate( preDate.toTimeSpec( d->mDateStart.timeSpec() ) );
1680  // Beyond end of recurrence
1681  if ( d->mDuration >= 0 && endDt().isValid() && fromDate >= endDt() ) {
1682  return KDateTime();
1683  }
1684 
1685  // Start date is only included if it really matches
1686  if ( fromDate < d->mDateStart ) {
1687  fromDate = d->mDateStart.addSecs( -1 );
1688  }
1689 
1690  if ( d->mTimedRepetition ) {
1691  // It's a simple sub-daily recurrence with no constraints
1692  int n = static_cast<int>( ( d->mDateStart.secsTo_long( fromDate ) + 1 ) % d->mTimedRepetition );
1693  KDateTime next = fromDate.addSecs( d->mTimedRepetition - n + 1 );
1694  return d->mDuration < 0 || !endDt().isValid() || next <= endDt() ? next : KDateTime();
1695  }
1696 
1697  if ( d->mDuration > 0 ) {
1698  if ( !d->mCached ) {
1699  d->buildCache();
1700  }
1701  int i = d->mCachedDates.findGT( fromDate );
1702  if ( i >= 0 ) {
1703  return d->mCachedDates[i];
1704  }
1705  }
1706 
1707  KDateTime end = endDt();
1708  Constraint interval( d->getNextValidDateInterval( fromDate, recurrenceType() ) );
1709  DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1710  int i = dts.findGT( fromDate );
1711  if ( i >= 0 ) {
1712  return ( d->mDuration < 0 || dts[i] <= end ) ? dts[i] : KDateTime();
1713  }
1714  interval.increase( recurrenceType(), frequency() );
1715  if ( d->mDuration >= 0 && interval.intervalDateTime( recurrenceType() ) > end ) {
1716  return KDateTime();
1717  }
1718 
1719  // Increase the interval. The first occurrence that we find is the result (if
1720  // if's before the end date).
1721  // TODO: some validity checks to avoid infinite loops for contradictory constraints
1722  int loop = 0;
1723  do {
1724  DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1725  if ( dts.count() > 0 ) {
1726  KDateTime ret( dts[0] );
1727  if ( d->mDuration >= 0 && ret > end ) {
1728  return KDateTime();
1729  } else {
1730  return ret;
1731  }
1732  }
1733  interval.increase( recurrenceType(), frequency() );
1734  } while ( ++loop < LOOP_LIMIT &&
1735  ( d->mDuration < 0 || interval.intervalDateTime( recurrenceType() ) < end ) );
1736  return KDateTime();
1737 }
1738 
1739 DateTimeList RecurrenceRule::timesInInterval( const KDateTime &dtStart,
1740  const KDateTime &dtEnd ) const
1741 {
1742  KDateTime start = dtStart.toTimeSpec( d->mDateStart.timeSpec() );
1743  KDateTime end = dtEnd.toTimeSpec( d->mDateStart.timeSpec() );
1744  DateTimeList result;
1745  if ( end < d->mDateStart ) {
1746  return result; // before start of recurrence
1747  }
1748  KDateTime enddt = end;
1749  if ( d->mDuration >= 0 ) {
1750  KDateTime endRecur = endDt();
1751  if ( endRecur.isValid() ) {
1752  if ( start > endRecur ) {
1753  return result; // beyond end of recurrence
1754  }
1755  if ( end > endRecur ) {
1756  enddt = endRecur; // limit end time to end of recurrence rule
1757  }
1758  }
1759  }
1760 
1761  if ( d->mTimedRepetition ) {
1762  // It's a simple sub-daily recurrence with no constraints
1763  int n = static_cast<int>( ( d->mDateStart.secsTo_long( start ) - 1 ) % d->mTimedRepetition );
1764  KDateTime dt = start.addSecs( d->mTimedRepetition - n );
1765  if ( dt < enddt ) {
1766  n = static_cast<int>( ( dt.secsTo_long( enddt ) - 1 ) / d->mTimedRepetition ) + 1;
1767  // limit n by a sane value else we can "explode".
1768  n = qMin( n, LOOP_LIMIT );
1769  for ( int i = 0; i < n; dt = dt.addSecs( d->mTimedRepetition ), ++i ) {
1770  result += dt;
1771  }
1772  }
1773  return result;
1774  }
1775 
1776  KDateTime st = start;
1777  bool done = false;
1778  if ( d->mDuration > 0 ) {
1779  if ( !d->mCached ) {
1780  d->buildCache();
1781  }
1782  if ( d->mCachedDateEnd.isValid() && start > d->mCachedDateEnd ) {
1783  return result; // beyond end of recurrence
1784  }
1785  int i = d->mCachedDates.findGE( start );
1786  if ( i >= 0 ) {
1787  int iend = d->mCachedDates.findGT( enddt, i );
1788  if ( iend < 0 ) {
1789  iend = d->mCachedDates.count();
1790  } else {
1791  done = true;
1792  }
1793  while ( i < iend ) {
1794  result += d->mCachedDates[i++];
1795  }
1796  }
1797  if ( d->mCachedDateEnd.isValid() ) {
1798  done = true;
1799  } else if ( !result.isEmpty() ) {
1800  result += KDateTime(); // indicate that the returned list is incomplete
1801  done = true;
1802  }
1803  if ( done ) {
1804  return result;
1805  }
1806  // We don't have any result yet, but we reached the end of the incomplete cache
1807  st = d->mCachedLastDate.addSecs( 1 );
1808  }
1809 
1810  Constraint interval( d->getNextValidDateInterval( st, recurrenceType() ) );
1811  int loop = 0;
1812  do {
1813  DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1814  int i = 0;
1815  int iend = dts.count();
1816  if ( loop == 0 ) {
1817  i = dts.findGE( st );
1818  if ( i < 0 ) {
1819  i = iend;
1820  }
1821  }
1822  int j = dts.findGT( enddt, i );
1823  if ( j >= 0 ) {
1824  iend = j;
1825  loop = LOOP_LIMIT;
1826  }
1827  while ( i < iend ) {
1828  result += dts[i++];
1829  }
1830  // Increase the interval.
1831  interval.increase( recurrenceType(), frequency() );
1832  } while ( ++loop < LOOP_LIMIT &&
1833  interval.intervalDateTime( recurrenceType() ) < end );
1834  return result;
1835 }
1836 
1837 //@cond PRIVATE
1838 // Find the date/time of the occurrence at or before a date/time,
1839 // for a given period type.
1840 // Return a constraint whose value appropriate to 'type', is set to
1841 // the value contained in the date/time.
1842 Constraint RecurrenceRule::Private::getPreviousValidDateInterval( const KDateTime &dt,
1843  PeriodType type ) const
1844 {
1845  long periods = 0;
1846  KDateTime start = mDateStart;
1847  KDateTime nextValid( start );
1848  int modifier = 1;
1849  KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
1850  // for super-daily recurrences, don't care about the time part
1851 
1852  // Find the #intervals since the dtstart and round to the next multiple of
1853  // the frequency
1854  switch ( type ) {
1855  // Really fall through for sub-daily, since the calculations only differ
1856  // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
1857  case rHourly:
1858  modifier *= 60;
1859  case rMinutely:
1860  modifier *= 60;
1861  case rSecondly:
1862  periods = static_cast<int>( start.secsTo_long( toDate ) / modifier );
1863  // round it down to the next lower multiple of frequency:
1864  if ( mFrequency > 0 ) {
1865  periods = ( periods / mFrequency ) * mFrequency;
1866  }
1867  nextValid = start.addSecs( modifier * periods );
1868  break;
1869  case rWeekly:
1870  toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
1871  start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
1872  modifier *= 7;
1873  case rDaily:
1874  periods = start.daysTo( toDate ) / modifier;
1875  // round it down to the next lower multiple of frequency:
1876  if ( mFrequency > 0 ) {
1877  periods = ( periods / mFrequency ) * mFrequency;
1878  }
1879  nextValid = start.addDays( modifier * periods );
1880  break;
1881  case rMonthly:
1882  {
1883  periods = 12 * ( toDate.date().year() - start.date().year() ) +
1884  ( toDate.date().month() - start.date().month() );
1885  // round it down to the next lower multiple of frequency:
1886  if ( mFrequency > 0 ) {
1887  periods = ( periods / mFrequency ) * mFrequency;
1888  }
1889  // set the day to the first day of the month, so we don't have problems
1890  // with non-existent days like Feb 30 or April 31
1891  start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
1892  nextValid.setDate( start.date().addMonths( periods ) );
1893  break; }
1894  case rYearly:
1895  periods = ( toDate.date().year() - start.date().year() );
1896  // round it down to the next lower multiple of frequency:
1897  if ( mFrequency > 0 ) {
1898  periods = ( periods / mFrequency ) * mFrequency;
1899  }
1900  nextValid.setDate( start.date().addYears( periods ) );
1901  break;
1902  default:
1903  break;
1904  }
1905 
1906  return Constraint( nextValid, type, mWeekStart );
1907 }
1908 
1909 // Find the date/time of the next occurrence at or after a date/time,
1910 // for a given period type.
1911 // Return a constraint whose value appropriate to 'type', is set to the
1912 // value contained in the date/time.
1913 Constraint RecurrenceRule::Private::getNextValidDateInterval( const KDateTime &dt,
1914  PeriodType type ) const
1915 {
1916  // TODO: Simplify this!
1917  long periods = 0;
1918  KDateTime start = mDateStart;
1919  KDateTime nextValid( start );
1920  int modifier = 1;
1921  KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
1922  // for super-daily recurrences, don't care about the time part
1923 
1924  // Find the #intervals since the dtstart and round to the next multiple of
1925  // the frequency
1926  switch ( type ) {
1927  // Really fall through for sub-daily, since the calculations only differ
1928  // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
1929  case rHourly:
1930  modifier *= 60;
1931  case rMinutely:
1932  modifier *= 60;
1933  case rSecondly:
1934  periods = static_cast<int>( start.secsTo_long( toDate ) / modifier );
1935  periods = qMax( 0L, periods );
1936  if ( periods > 0 && mFrequency > 0 ) {
1937  periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1938  }
1939  nextValid = start.addSecs( modifier * periods );
1940  break;
1941  case rWeekly:
1942  // correct both start date and current date to start of week
1943  toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
1944  start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
1945  modifier *= 7;
1946  case rDaily:
1947  periods = start.daysTo( toDate ) / modifier;
1948  periods = qMax( 0L, periods );
1949  if ( periods > 0 && mFrequency > 0 ) {
1950  periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1951  }
1952  nextValid = start.addDays( modifier * periods );
1953  break;
1954  case rMonthly:
1955  {
1956  periods = 12 * ( toDate.date().year() - start.date().year() ) +
1957  ( toDate.date().month() - start.date().month() );
1958  periods = qMax( 0L, periods );
1959  if ( periods > 0 && mFrequency > 0 ) {
1960  periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1961  }
1962  // set the day to the first day of the month, so we don't have problems
1963  // with non-existent days like Feb 30 or April 31
1964  start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
1965  nextValid.setDate( start.date().addMonths( periods ) );
1966  break;
1967  }
1968  case rYearly:
1969  periods = ( toDate.date().year() - start.date().year() );
1970  periods = qMax( 0L, periods );
1971  if ( periods > 0 && mFrequency > 0 ) {
1972  periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1973  }
1974  nextValid.setDate( start.date().addYears( periods ) );
1975  break;
1976  default:
1977  break;
1978  }
1979 
1980  return Constraint( nextValid, type, mWeekStart );
1981 }
1982 
1983 DateTimeList RecurrenceRule::Private::datesForInterval( const Constraint &interval,
1984  PeriodType type ) const
1985 {
1986  /* -) Loop through constraints,
1987  -) merge interval with each constraint
1988  -) if merged constraint is not consistent => ignore that constraint
1989  -) if complete => add that one date to the date list
1990  -) Loop through all missing fields => For each add the resulting
1991  */
1992  DateTimeList lst;
1993  for ( int i = 0, iend = mConstraints.count(); i < iend; ++i ) {
1994  Constraint merged( interval );
1995  if ( merged.merge( mConstraints[i] ) ) {
1996  // If the information is incomplete, we can't use this constraint
1997  if ( merged.year > 0 && merged.hour >= 0 && merged.minute >= 0 && merged.second >= 0 ) {
1998  // We have a valid constraint, so get all datetimes that match it andd
1999  // append it to all date/times of this interval
2000  QList<KDateTime> lstnew = merged.dateTimes( type );
2001  lst += lstnew;
2002  }
2003  }
2004  }
2005  // Sort it so we can apply the BySetPos. Also some logic relies on this being sorted
2006  lst.sortUnique();
2007 
2008 /*if ( lst.isEmpty() ) {
2009  kDebug() << " No Dates in Interval";
2010 } else {
2011  kDebug() << " Dates:";
2012  for ( int i = 0, iend = lst.count(); i < iend; ++i ) {
2013  kDebug()<< " -)" << dumpTime(lst[i]);
2014  }
2015  kDebug() << " ---------------------";
2016 }*/
2017  if ( !mBySetPos.isEmpty() ) {
2018  DateTimeList tmplst = lst;
2019  lst.clear();
2020  for ( int i = 0, iend = mBySetPos.count(); i < iend; ++i ) {
2021  int pos = mBySetPos[i];
2022  if ( pos > 0 ) {
2023  --pos;
2024  }
2025  if ( pos < 0 ) {
2026  pos += tmplst.count();
2027  }
2028  if ( pos >= 0 && pos < tmplst.count() ) {
2029  lst.append( tmplst[pos] );
2030  }
2031  }
2032  lst.sortUnique();
2033  }
2034 
2035  return lst;
2036 }
2037 //@endcond
2038 
2039 void RecurrenceRule::dump() const
2040 {
2041 #ifndef NDEBUG
2042  kDebug();
2043  if ( !d->mRRule.isEmpty() ) {
2044  kDebug() << " RRULE=" << d->mRRule;
2045  }
2046  kDebug() << " Read-Only:" << isReadOnly();
2047 
2048  kDebug() << " Period type:" << int( recurrenceType() ) << ", frequency:" << frequency();
2049  kDebug() << " #occurrences:" << duration();
2050  kDebug() << " start date:" << dumpTime( startDt() )
2051  << ", end date:" << dumpTime( endDt() );
2052 
2053 #define dumpByIntList(list,label) \
2054  if ( !list.isEmpty() ) {\
2055  QStringList lst;\
2056  for ( int i = 0, iend = list.count(); i < iend; ++i ) {\
2057  lst.append( QString::number( list[i] ) );\
2058  }\
2059  kDebug() << " " << label << lst.join( ", " );\
2060  }
2061  dumpByIntList( d->mBySeconds, "BySeconds: " );
2062  dumpByIntList( d->mByMinutes, "ByMinutes: " );
2063  dumpByIntList( d->mByHours, "ByHours: " );
2064  if ( !d->mByDays.isEmpty() ) {
2065  QStringList lst;
2066  for ( int i = 0, iend = d->mByDays.count(); i < iend; ++i ) {\
2067  lst.append( ( d->mByDays[i].pos() ? QString::number( d->mByDays[i].pos() ) : "" ) +
2068  DateHelper::dayName( d->mByDays[i].day() ) );
2069  }
2070  kDebug() << " ByDays: " << lst.join( ", " );
2071  }
2072  dumpByIntList( d->mByMonthDays, "ByMonthDays:" );
2073  dumpByIntList( d->mByYearDays, "ByYearDays: " );
2074  dumpByIntList( d->mByWeekNumbers, "ByWeekNr: " );
2075  dumpByIntList( d->mByMonths, "ByMonths: " );
2076  dumpByIntList( d->mBySetPos, "BySetPos: " );
2077  #undef dumpByIntList
2078 
2079  kDebug() << " Week start:" << DateHelper::dayName( d->mWeekStart ); //krazy:exclude=kdebug
2080 
2081  kDebug() << " Constraints:";
2082  // dump constraints
2083  for ( int i = 0, iend = d->mConstraints.count(); i < iend; ++i ) {
2084  d->mConstraints[i].dump();
2085  }
2086 #endif
2087 }
2088 
2089 //@cond PRIVATE
2090 void Constraint::dump() const
2091 {
2092  kDebug() << " ~> Y=" << year
2093  << ", M=" << month
2094  << ", D=" << day
2095  << ", H=" << hour
2096  << ", m=" << minute
2097  << ", S=" << second
2098  << ", wd=" << weekday
2099  << ",#wd=" << weekdaynr
2100  << ", #w=" << weeknumber
2101  << ", yd=" << yearday;
2102 }
2103 //@endcond
2104 
2105 QString dumpTime( const KDateTime &dt )
2106 {
2107 #ifndef NDEBUG
2108  if ( !dt.isValid() ) {
2109  return QString();
2110  }
2111  QString result;
2112  if ( dt.isDateOnly() ) {
2113  result = dt.toString( "%a %Y-%m-%d %:Z" );
2114  } else {
2115  result = dt.toString( "%a %Y-%m-%d %H:%M:%S %:Z" );
2116  if ( dt.isSecondOccurrence() ) {
2117  result += QLatin1String( " (2nd)" );
2118  }
2119  }
2120  if ( dt.timeSpec() == KDateTime::Spec::ClockTime() ) {
2121  result += QLatin1String( "Clock" );
2122  }
2123  return result;
2124 #else
2125  Q_UNUSED( dt );
2126  return QString();
2127 #endif
2128 }
2129 
2130 KDateTime RecurrenceRule::startDt() const
2131 {
2132  return d->mDateStart;
2133 }
2134 
2135 RecurrenceRule::PeriodType RecurrenceRule::recurrenceType() const
2136 {
2137  return d->mPeriod;
2138 }
2139 
2140 uint RecurrenceRule::frequency() const
2141 {
2142  return d->mFrequency;
2143 }
2144 
2145 int RecurrenceRule::duration() const
2146 {
2147  return d->mDuration;
2148 }
2149 
2150 QString RecurrenceRule::rrule() const
2151 {
2152  return d->mRRule;
2153 }
2154 
2155 void RecurrenceRule::setRRule( const QString &rrule )
2156 {
2157  d->mRRule = rrule;
2158 }
2159 
2160 bool RecurrenceRule::isReadOnly() const
2161 {
2162  return d->mIsReadOnly;
2163 }
2164 
2165 void RecurrenceRule::setReadOnly( bool readOnly )
2166 {
2167  d->mIsReadOnly = readOnly;
2168 }
2169 
2170 bool RecurrenceRule::recurs() const
2171 {
2172  return d->mPeriod != rNone;
2173 }
2174 
2175 bool RecurrenceRule::allDay() const
2176 {
2177  return d->mAllDay;
2178 }
2179 
2180 const QList<int> &RecurrenceRule::bySeconds() const
2181 {
2182  return d->mBySeconds;
2183 }
2184 
2185 const QList<int> &RecurrenceRule::byMinutes() const
2186 {
2187  return d->mByMinutes;
2188 }
2189 
2190 const QList<int> &RecurrenceRule::byHours() const
2191 {
2192  return d->mByHours;
2193 }
2194 
2195 const QList<RecurrenceRule::WDayPos> &RecurrenceRule::byDays() const
2196 {
2197  return d->mByDays;
2198 }
2199 
2200 const QList<int> &RecurrenceRule::byMonthDays() const
2201 {
2202  return d->mByMonthDays;
2203 }
2204 
2205 const QList<int> &RecurrenceRule::byYearDays() const
2206 {
2207  return d->mByYearDays;
2208 }
2209 
2210 const QList<int> &RecurrenceRule::byWeekNumbers() const
2211 {
2212  return d->mByWeekNumbers;
2213 }
2214 
2215 const QList<int> &RecurrenceRule::byMonths() const
2216 {
2217  return d->mByMonths;
2218 }
2219 
2220 const QList<int> &RecurrenceRule::bySetPos() const
2221 {
2222  return d->mBySetPos;
2223 }
2224 
2225 short RecurrenceRule::weekStart() const
2226 {
2227  return d->mWeekStart;
2228 }
2229 
2230 RecurrenceRule::RuleObserver::~RuleObserver()
2231 {
2232 }
2233 
2234 RecurrenceRule::WDayPos::WDayPos( int ps, short dy )
2235  : mDay( dy ), mPos( ps )
2236 {
2237 }
2238 
2239 void RecurrenceRule::WDayPos::setDay( short dy )
2240 {
2241  mDay = dy;
2242 }
2243 
2244 short RecurrenceRule::WDayPos::day() const
2245 {
2246  return mDay;
2247 }
2248 
2249 void RecurrenceRule::WDayPos::setPos( int ps )
2250 {
2251  mPos = ps;
2252 }
2253 
2254 int RecurrenceRule::WDayPos::pos() const
2255 {
2256  return mPos;
2257 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Sat Jul 13 2013 01:24:52 by doxygen 1.8.3.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KCalCore Library

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

kdepimlibs-4.10.5 API Reference

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

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