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

KCalCore Library

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

Skip menu "kdepimlibs-4.11.3 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