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

KCal Library

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

KDE's Doxygen guidelines are available online.

KCal Library

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

kdepimlibs API Reference

Skip menu "kdepimlibs API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kldap
  • kmbox
  • kmime
  • kpimidentities
  • kpimtextedit
  • kresources
  • ktnef
  • kxmlrpcclient
  • microblog

Search



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