libkcal

recurrencerule.cpp
1 /*
2  This file is part of libkcal.
3 
4  Copyright (c) 2005 Reinhold Kainhofer <reinhold@kainhofe.com>
5 
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 <tdeglobal.h>
27 #include <tqdatetime.h>
28 #include <tqstringlist.h>
29 
30 #include <limits.h>
31 #include <math.h>
32 
33 using namespace KCal;
34 
35 // Maximum number of intervals to process
36 const int LOOP_LIMIT = 10000;
37 
38 // FIXME: If TQt is ever changed so that TQDateTime:::addSecs takes into account
39 // DST shifts, we need to use our own addSecs method, too, since we
40 // need to caalculate things in UTC!
60 long long ownSecsTo( const TQDateTime &dt1, const TQDateTime &dt2 )
61 {
62  long long res = static_cast<long long>( dt1.date().daysTo( dt2.date() ) ) * 24*3600;
63  res += dt1.time().secsTo( dt2.time() );
64  return res;
65 }
66 
67 
68 
69 /**************************************************************************
70  * DateHelper *
71  **************************************************************************/
72 
73 
74 class DateHelper {
75  public:
76 #ifndef NDEBUG
77  static TQString dayName( short day );
78 #endif
79  static TQDate getNthWeek( int year, int weeknumber, short weekstart = 1 );
80  static int weekNumbersInYear( int year, short weekstart = 1 );
81  static int getWeekNumber( const TQDate &date, short weekstart, int *year = 0 );
82  static int getWeekNumberNeg( const TQDate &date, short weekstart, int *year = 0 );
83 };
84 
85 
86 #ifndef NDEBUG
87 TQString DateHelper::dayName( short day )
88 {
89  switch ( day ) {
90  case 1: return "MO"; break;
91  case 2: return "TU"; break;
92  case 3: return "WE"; break;
93  case 4: return "TH"; break;
94  case 5: return "FR"; break;
95  case 6: return "SA"; break;
96  case 7: return "SU"; break;
97  default: return "??";
98  }
99 }
100 #endif
101 
102 
103 TQDate DateHelper::getNthWeek( int year, int weeknumber, short weekstart )
104 {
105  if ( weeknumber == 0 ) return TQDate();
106  // Adjust this to the first day of week #1 of the year and add 7*weekno days.
107  TQDate dt( year, 1, 4 ); // Week #1 is the week that contains Jan 4
108  int adjust = -(7 + dt.dayOfWeek() - weekstart) % 7;
109  if ( weeknumber > 0 ) {
110  dt = dt.addDays( 7 * (weeknumber-1) + adjust );
111  } else if ( weeknumber < 0 ) {
112  dt = dt.addYears( 1 );
113  dt = dt.addDays( 7 * weeknumber + adjust );
114  }
115  return dt;
116 }
117 
118 
119 int DateHelper::getWeekNumber( const TQDate &date, short weekstart, int *year )
120 {
121 // kdDebug(5800) << "Getting week number for " << date << " with weekstart="<<weekstart<<endl;
122  if ( year ) *year = date.year();
123  TQDate dt( date.year(), 1, 4 ); // <= definitely in week #1
124  dt = dt.addDays( -(7 + dt.dayOfWeek() - weekstart) % 7 ); // begin of week #1
125  TQDate dtn( date.year()+1, 1, 4 ); // <= definitely first week of next year
126  dtn = dtn.addDays( -(7 + dtn.dayOfWeek() - weekstart) % 7 );
127 
128  int daysto = dt.daysTo( date );
129  int dayston = dtn.daysTo( date );
130  if ( daysto < 0 ) {
131  if ( year ) *year = date.year()-1;
132  dt = TQDate( date.year()-1, 1, 4 );
133  dt = dt.addDays( -(7 + dt.dayOfWeek() - weekstart) % 7 ); // begin of week #1
134  daysto = dt.daysTo( date );
135  } else if ( dayston >= 0 ) {
136  // in first week of next year;
137  if ( year ) *year = date.year() + 1;
138  dt = dtn;
139  daysto = dayston;
140  }
141  return daysto / 7 + 1;
142 }
143 
144 int DateHelper::weekNumbersInYear( int year, short weekstart )
145 {
146  TQDate dt( year, 1, weekstart );
147  TQDate dt1( year + 1, 1, weekstart );
148  return dt.daysTo( dt1 ) / 7;
149 }
150 
151 // Week number from the end of the year
152 int DateHelper::getWeekNumberNeg( const TQDate &date, short weekstart, int *year )
153 {
154  int weekpos = getWeekNumber( date, weekstart, year );
155  return weekNumbersInYear( *year, weekstart ) - weekpos - 1;
156 }
157 
158 
159 
160 
161 
162 /**************************************************************************
163  * RecurrenceRule::Constraint *
164  **************************************************************************/
165 
166 
167 RecurrenceRule::Constraint::Constraint( int wkst )
168 {
169  weekstart = wkst;
170  clear();
171 }
172 
173 RecurrenceRule::Constraint::Constraint( const TQDateTime &preDate, PeriodType type, int wkst )
174 {
175  weekstart = wkst;
176  readDateTime( preDate, type );
177 }
178 
179 void RecurrenceRule::Constraint::clear()
180 {
181  year = 0;
182  month = 0;
183  day = 0;
184  hour = -1;
185  minute = -1;
186  second = -1;
187  weekday = 0;
188  weekdaynr = 0;
189  weeknumber = 0;
190  yearday = 0;
191 }
192 
193 bool RecurrenceRule::Constraint::matches( const TQDate &dt, RecurrenceRule::PeriodType type ) const
194 {
195  // If the event recurs in week 53 or 1, the day might not belong to the same
196  // year as the week it is in. E.g. Jan 1, 2005 is in week 53 of year 2004.
197  // So we can't simply check the year in that case!
198  if ( weeknumber == 0 ) {
199  if ( year > 0 && year != dt.year() ) return false;
200  } else {
201  int y;
202  if ( weeknumber > 0 &&
203  weeknumber != DateHelper::getWeekNumber( dt, weekstart, &y ) ) return false;
204  if ( weeknumber < 0 &&
205  weeknumber != DateHelper::getWeekNumberNeg( dt, weekstart, &y ) ) return false;
206  if ( year > 0 && year != y ) return false;
207  }
208 
209  if ( month > 0 && month != dt.month() ) return false;
210  if ( day > 0 && day != dt.day() ) return false;
211  if ( day < 0 && dt.day() != (dt.daysInMonth() + day + 1 ) ) return false;
212  if ( weekday > 0 ) {
213  if ( weekday != dt.dayOfWeek() ) return false;
214  if ( weekdaynr != 0 ) {
215  // If it's a yearly recurrence and a month is given, the position is
216  // still in the month, not in the year.
217  bool inMonth = (type == rMonthly) || ( type == rYearly && month > 0 );
218  // Monthly
219  if ( weekdaynr > 0 && inMonth &&
220  weekdaynr != (dt.day() - 1)/7 + 1 ) return false;
221  if ( weekdaynr < 0 && inMonth &&
222  weekdaynr != -((dt.daysInMonth() - dt.day() )/7 + 1 ) )
223  return false;
224  // Yearly
225  if ( weekdaynr > 0 && !inMonth &&
226  weekdaynr != (dt.dayOfYear() - 1)/7 + 1 ) return false;
227  if ( weekdaynr < 0 && !inMonth &&
228  weekdaynr != -((dt.daysInYear() - dt.dayOfYear() )/7 + 1 ) )
229  return false;
230  }
231  }
232  if ( yearday > 0 && yearday != dt.dayOfYear() ) return false;
233  if ( yearday < 0 && yearday != dt.daysInYear() - dt.dayOfYear() + 1 )
234  return false;
235  return true;
236 }
237 
238 bool RecurrenceRule::Constraint::matches( const TQDateTime &dt, RecurrenceRule::PeriodType type ) const
239 {
240  if ( !matches( dt.date(), type ) ) return false;
241  if ( hour >= 0 && hour != dt.time().hour() ) return false;
242  if ( minute >= 0 && minute != dt.time().minute() ) return false;
243  if ( second >= 0 && second != dt.time().second() ) return false;
244  return true;
245 }
246 
247 bool RecurrenceRule::Constraint::isConsistent( PeriodType /*period*/) const
248 {
249  // TODO: Check for consistency, e.g. byyearday=3 and bymonth=10
250  return true;
251 }
252 
253 TQDateTime RecurrenceRule::Constraint::intervalDateTime( RecurrenceRule::PeriodType type ) const
254 {
255  TQDateTime dt;
256  dt.setTime( TQTime( 0, 0, 0 ) );
257  dt.setDate( TQDate( year, (month>0)?month:1, (day>0)?day:1 ) );
258  if ( day < 0 )
259  dt = dt.addDays( dt.date().daysInMonth() + day );
260  switch ( type ) {
261  case rSecondly:
262  dt.setTime( TQTime( hour, minute, second ) ); break;
263  case rMinutely:
264  dt.setTime( TQTime( hour, minute, 1 ) ); break;
265  case rHourly:
266  dt.setTime( TQTime( hour, 1, 1 ) ); break;
267  case rDaily:
268  break;
269  case rWeekly:
270  dt = DateHelper::getNthWeek( year, weeknumber, weekstart ); break;
271  case rMonthly:
272  dt.setDate( TQDate( year, month, 1 ) ); break;
273  case rYearly:
274  dt.setDate( TQDate( year, 1, 1 ) ); break;
275  default:
276  break;
277  }
278  return dt;
279 }
280 
281 
282 // Y M D | H Mn S | WD #WD | WN | YD
283 // required:
284 // x | x x x | | |
285 // 0) Trivial: Exact date given, maybe other restrictions
286 // x x x | x x x | | |
287 // 1) Easy case: no weekly restrictions -> at most a loop through possible dates
288 // x + + | x x x | - - | - | -
289 // 2) Year day is given -> date known
290 // x | x x x | | | +
291 // 3) week number is given -> loop through all days of that week. Further
292 // restrictions will be applied in the end, when we check all dates for
293 // consistency with the constraints
294 // x | x x x | | + | (-)
295 // 4) week day is specified ->
296 // x | x x x | x ? | (-)| (-)
297 // 5) All possiblecases have already been treated, so this must be an error!
298 
299 DateTimeList RecurrenceRule::Constraint::dateTimes( RecurrenceRule::PeriodType type ) const
300 {
301 // kdDebug(5800) << " RecurrenceRule::Constraint::dateTimes: " << endl;
302  DateTimeList result;
303  bool done = false;
304  // TODO_Recurrence: Handle floating
305  TQTime tm( hour, minute, second );
306  if ( !isConsistent( type ) ) return result;
307 
308  if ( !done && day > 0 && month > 0 ) {
309  TQDateTime dt( TQDate( year, month, day ), tm );
310  if ( dt.isValid() ) result.append( dt );
311  done = true;
312  }
313  if ( !done && day < 0 && month > 0 ) {
314  TQDateTime dt( TQDate( year, month, 1 ), tm );
315  dt = dt.addDays( dt.date().daysInMonth() + day );
316  if ( dt.isValid() ) result.append( dt );
317  done = true;
318  }
319 
320 
321  if ( !done && weekday == 0 && weeknumber == 0 && yearday == 0 ) {
322  // Easy case: date is given, not restrictions by week or yearday
323  uint mstart = (month>0) ? month : 1;
324  uint mend = (month <= 0) ? 12 : month;
325  for ( uint m = mstart; m <= mend; ++m ) {
326  uint dstart, dend;
327  if ( day > 0 ) {
328  dstart = dend = day;
329  } else if ( day < 0 ) {
330  TQDate date( year, month, 1 );
331  dstart = dend = date.daysInMonth() + day + 1;
332  } else {
333  TQDate date( year, month, 1 );
334  dstart = 1;
335  dend = date.daysInMonth();
336  }
337  for ( uint d = dstart; d <= dend; ++d ) {
338  TQDateTime dt( TQDate( year, m, d ), tm );
339  if ( dt.isValid() ) result.append( dt );
340  }
341  }
342  done = true;
343  }
344 
345  // Else: At least one of the week / yearday restrictions was given...
346  // If we have a yearday (and of course a year), we know the exact date
347  if ( !done && yearday != 0 ) {
348  // yearday < 0 means from end of year, so we'll need Jan 1 of the next year
349  TQDate d( year + ((yearday>0)?0:1), 1, 1 );
350  d = d.addDays( yearday - ((yearday>0)?1:0) );
351  result.append( TQDateTime( d, tm ) );
352  done = true;
353  }
354 
355  // Else: If we have a weeknumber, we have at most 7 possible dates, loop through them
356  if ( !done && weeknumber != 0 ) {
357  TQDate wst( DateHelper::getNthWeek( year, weeknumber, weekstart ) );
358  if ( weekday != 0 ) {
359  wst = wst.addDays( (7 + weekday - weekstart ) % 7 );
360  result.append( TQDateTime( wst, tm ) );
361  } else {
362  for ( int i = 0; i < 7; ++i ) {
363  result.append( TQDateTime( wst, tm ) );
364  wst = wst.addDays( 1 );
365  }
366  }
367  done = true;
368  }
369 
370  // weekday is given
371  if ( !done && weekday != 0 ) {
372  TQDate dt( year, 1, 1 );
373  // If type == yearly and month is given, pos is still in month not year!
374  // TODO_Recurrence: Correct handling of n-th BYDAY...
375  int maxloop = 53;
376  bool inMonth = ( type == rMonthly) || ( type == rYearly && month > 0 );
377  if ( inMonth && month > 0 ) {
378  dt = TQDate( year, month, 1 );
379  maxloop = 5;
380  }
381  if ( weekdaynr < 0 ) {
382  // From end of period (month, year) => relative to begin of next period
383  if ( inMonth )
384  dt = dt.addMonths( 1 );
385  else
386  dt = dt.addYears( 1 );
387  }
388  int adj = ( 7 + weekday - dt.dayOfWeek() ) % 7;
389  dt = dt.addDays( adj ); // correct first weekday of the period
390 
391  if ( weekdaynr > 0 ) {
392  dt = dt.addDays( ( weekdaynr - 1 ) * 7 );
393  result.append( TQDateTime( dt, tm ) );
394  } else if ( weekdaynr < 0 ) {
395  dt = dt.addDays( weekdaynr * 7 );
396  result.append( TQDateTime( dt, tm ) );
397  } else {
398  // loop through all possible weeks, non-matching will be filtered later
399  for ( int i = 0; i < maxloop; ++i ) {
400  result.append( TQDateTime( dt, tm ) );
401  dt = dt.addDays( 7 );
402  }
403  }
404  } // weekday != 0
405 
406 
407  // Only use those times that really match all other constraints, too
408  DateTimeList valid;
409  DateTimeList::Iterator it;
410  for ( it = result.begin(); it != result.end(); ++it ) {
411  if ( matches( *it, type ) ) valid.append( *it );
412  }
413  // Don't sort it here, would be unnecessary work. The results from all
414  // constraints will be merged to one big list of the interval. Sort that one!
415  return valid;
416 }
417 
418 
419 bool RecurrenceRule::Constraint::increase( RecurrenceRule::PeriodType type, int freq )
420 {
421  // convert the first day of the interval to TQDateTime
422  // Sub-daily types need to be converted to UTC to correctly handle DST shifts
423  TQDateTime dt( intervalDateTime( type ) );
424 
425  // Now add the intervals
426  switch ( type ) {
427  case rSecondly:
428  dt = dt.addSecs( freq ); break;
429  case rMinutely:
430  dt = dt.addSecs( 60*freq ); break;
431  case rHourly:
432  dt = dt.addSecs( 3600 * freq ); break;
433  case rDaily:
434  dt = dt.addDays( freq ); break;
435  case rWeekly:
436  dt = dt.addDays( 7*freq ); break;
437  case rMonthly:
438  dt = dt.addMonths( freq ); break;
439  case rYearly:
440  dt = dt.addYears( freq ); break;
441  default:
442  break;
443  }
444  // Convert back from TQDateTime to the Constraint class
445  readDateTime( dt, type );
446 
447  return true;
448 }
449 
450 bool RecurrenceRule::Constraint::readDateTime( const TQDateTime &preDate, PeriodType type )
451 {
452  clear();
453  switch ( type ) {
454  // Really fall through! Only weekly needs to be treated differentely!
455  case rSecondly:
456  second = preDate.time().second();
457  case rMinutely:
458  minute = preDate.time().minute();
459  case rHourly:
460  hour = preDate.time().hour();
461  case rDaily:
462  day = preDate.date().day();
463  case rMonthly:
464  month = preDate.date().month();
465  case rYearly:
466  year = preDate.date().year();
467  break;
468 
469  case rWeekly:
470  // Determine start day of the current week, calculate the week number from that
471  weeknumber = DateHelper::getWeekNumber( preDate.date(), weekstart, &year );
472  break;
473  default:
474  break;
475  }
476  return true;
477 }
478 
479 
480 RecurrenceRule::RecurrenceRule( )
481 : mPeriod( rNone ), mFrequency( 0 ), mIsReadOnly( false ),
482  mFloating( false ),
483  mWeekStart(1)
484 {
485 }
486 
487 RecurrenceRule::RecurrenceRule( const RecurrenceRule &r )
488 {
489  mRRule = r.mRRule;
490  mPeriod = r.mPeriod;
491  mDateStart = r.mDateStart;
492  mDuration = r.mDuration;
493  mDateEnd = r.mDateEnd;
494  mFrequency = r.mFrequency;
495 
496  mIsReadOnly = r.mIsReadOnly;
497  mFloating = r.mFloating;
498 
499  mBySeconds = r.mBySeconds;
500  mByMinutes = r.mByMinutes;
501  mByHours = r.mByHours;
502  mByDays = r.mByDays;
503  mByMonthDays = r.mByMonthDays;
504  mByYearDays = r.mByYearDays;
505  mByWeekNumbers = r.mByWeekNumbers;
506  mByMonths = r.mByMonths;
507  mBySetPos = r.mBySetPos;
508  mWeekStart = r.mWeekStart;
509 
510  setDirty();
511 }
512 
513 RecurrenceRule::~RecurrenceRule()
514 {
515 }
516 
517 bool RecurrenceRule::operator==( const RecurrenceRule& r ) const
518 {
519  if ( mPeriod != r.mPeriod ) return false;
520  if ( mDateStart != r.mDateStart ) return false;
521  if ( mDuration != r.mDuration ) return false;
522  if ( mDateEnd != r.mDateEnd ) return false;
523  if ( mFrequency != r.mFrequency ) return false;
524 
525  if ( mIsReadOnly != r.mIsReadOnly ) return false;
526  if ( mFloating != r.mFloating ) return false;
527 
528  if ( mBySeconds != r.mBySeconds ) return false;
529  if ( mByMinutes != r.mByMinutes ) return false;
530  if ( mByHours != r.mByHours ) return false;
531  if ( mByDays != r.mByDays ) return false;
532  if ( mByMonthDays != r.mByMonthDays ) return false;
533  if ( mByYearDays != r.mByYearDays ) return false;
534  if ( mByWeekNumbers != r.mByWeekNumbers ) return false;
535  if ( mByMonths != r.mByMonths ) return false;
536  if ( mBySetPos != r.mBySetPos ) return false;
537  if ( mWeekStart != r.mWeekStart ) return false;
538 
539  return true;
540 }
541 
542 void RecurrenceRule::addObserver( Observer *observer )
543 {
544  if ( !mObservers.contains( observer ) )
545  mObservers.append( observer );
546 }
547 
548 void RecurrenceRule::removeObserver( Observer *observer )
549 {
550  if ( mObservers.contains( observer ) )
551  mObservers.remove( observer );
552 }
553 
554 
555 
556 void RecurrenceRule::setRecurrenceType( PeriodType period )
557 {
558  if ( isReadOnly() ) return;
559  mPeriod = period;
560  setDirty();
561 }
562 
563 TQDateTime RecurrenceRule::endDt( bool *result ) const
564 {
565  if ( result ) *result = false;
566  if ( mPeriod == rNone ) return TQDateTime();
567  if ( mDuration < 0 ) return TQDateTime();
568  if ( mDuration == 0 ) {
569  if ( result ) *result = true;
570  return mDateEnd;
571  }
572  // N occurrences. Check if we have a full cache. If so, return the cached end date.
573  if ( ! mCached ) {
574  // If not enough occurrences can be found (i.e. inconsistent constraints)
575  if ( !buildCache() ) return TQDateTime();
576  }
577  if ( result ) *result = true;
578  return mCachedDateEnd;
579 }
580 
581 void RecurrenceRule::setEndDt( const TQDateTime &dateTime )
582 {
583  if ( isReadOnly() ) return;
584  mDateEnd = dateTime;
585  mDuration = 0; // set to 0 because there is an end date/time
586  setDirty();
587 }
588 
589 void RecurrenceRule::setDuration(int duration)
590 {
591  if ( isReadOnly() ) return;
592  mDuration = duration;
593  setDirty();
594 }
595 
596 void RecurrenceRule::setFloats( bool floats )
597 {
598  if ( isReadOnly() ) return;
599  mFloating = floats;
600  setDirty();
601 }
602 
604 {
605  if ( isReadOnly() ) return;
606  mPeriod = rNone;
607  mBySeconds.clear();
608  mByMinutes.clear();
609  mByHours.clear();
610  mByDays.clear();
611  mByMonthDays.clear();
612  mByYearDays.clear();
613  mByWeekNumbers.clear();
614  mByMonths.clear();
615  mBySetPos.clear();
616  mWeekStart = 1;
617 
618  setDirty();
619 }
620 
621 void RecurrenceRule::setDirty()
622 {
623  mConstraints.clear();
624  buildConstraints();
625  mDirty = true;
626  mCached = false;
627  mCachedDates.clear();
628  for ( TQValueList<Observer*>::ConstIterator it = mObservers.begin();
629  it != mObservers.end(); ++it ) {
630  if ( (*it) ) (*it)->recurrenceChanged( this );
631  }
632 }
633 
634 void RecurrenceRule::setStartDt( const TQDateTime &start )
635 {
636  if ( isReadOnly() ) return;
637  mDateStart = start;
638  setDirty();
639 }
640 
642 {
643  if ( isReadOnly() || freq <= 0 ) return;
644  mFrequency = freq;
645  setDirty();
646 }
647 
648 void RecurrenceRule::setBySeconds( const TQValueList<int> bySeconds )
649 {
650  if ( isReadOnly() ) return;
651  mBySeconds = bySeconds;
652  setDirty();
653 }
654 
655 void RecurrenceRule::setByMinutes( const TQValueList<int> byMinutes )
656 {
657  if ( isReadOnly() ) return;
658  mByMinutes = byMinutes;
659  setDirty();
660 }
661 
662 void RecurrenceRule::setByHours( const TQValueList<int> byHours )
663 {
664  if ( isReadOnly() ) return;
665  mByHours = byHours;
666  setDirty();
667 }
668 
669 
670 void RecurrenceRule::setByDays( const TQValueList<WDayPos> byDays )
671 {
672  if ( isReadOnly() ) return;
673  mByDays = byDays;
674  setDirty();
675 }
676 
677 void RecurrenceRule::setByMonthDays( const TQValueList<int> byMonthDays )
678 {
679  if ( isReadOnly() ) return;
680  mByMonthDays = byMonthDays;
681  setDirty();
682 }
683 
684 void RecurrenceRule::setByYearDays( const TQValueList<int> byYearDays )
685 {
686  if ( isReadOnly() ) return;
687  mByYearDays = byYearDays;
688  setDirty();
689 }
690 
691 void RecurrenceRule::setByWeekNumbers( const TQValueList<int> byWeekNumbers )
692 {
693  if ( isReadOnly() ) return;
694  mByWeekNumbers = byWeekNumbers;
695  setDirty();
696 }
697 
698 void RecurrenceRule::setByMonths( const TQValueList<int> byMonths )
699 {
700  if ( isReadOnly() ) return;
701  mByMonths = byMonths;
702  setDirty();
703 }
704 
705 void RecurrenceRule::setBySetPos( const TQValueList<int> bySetPos )
706 {
707  if ( isReadOnly() ) return;
708  mBySetPos = bySetPos;
709  setDirty();
710 }
711 
712 void RecurrenceRule::setWeekStart( short weekStart )
713 {
714  if ( isReadOnly() ) return;
715  mWeekStart = weekStart;
716  setDirty();
717 }
718 
719 
720 
721 // Taken from recurrence.cpp
722 // int RecurrenceRule::maxIterations() const
723 // {
724 // /* Find the maximum number of iterations which may be needed to reach the
725 // * next actual occurrence of a monthly or yearly recurrence.
726 // * More than one iteration may be needed if, for example, it's the 29th February,
727 // * the 31st day of the month or the 5th Monday, and the month being checked is
728 // * February or a 30-day month.
729 // * The following recurrences may never occur:
730 // * - For rMonthlyDay: if the frequency is a whole number of years.
731 // * - For rMonthlyPos: if the frequency is an even whole number of years.
732 // * - For rYearlyDay, rYearlyMonth: if the frequeny is a multiple of 4 years.
733 // * - For rYearlyPos: if the frequency is an even number of years.
734 // * The maximum number of iterations needed, assuming that it does actually occur,
735 // * was found empirically.
736 // */
737 // switch (recurs) {
738 // case rMonthlyDay:
739 // return (rFreq % 12) ? 6 : 8;
740 //
741 // case rMonthlyPos:
742 // if (rFreq % 12 == 0) {
743 // // Some of these frequencies may never occur
744 // return (rFreq % 84 == 0) ? 364 // frequency = multiple of 7 years
745 // : (rFreq % 48 == 0) ? 7 // frequency = multiple of 4 years
746 // : (rFreq % 24 == 0) ? 14 : 28; // frequency = multiple of 2 or 1 year
747 // }
748 // // All other frequencies will occur sometime
749 // if (rFreq > 120)
750 // return 364; // frequencies of > 10 years will hit the date limit first
751 // switch (rFreq) {
752 // case 23: return 50;
753 // case 46: return 38;
754 // case 56: return 138;
755 // case 66: return 36;
756 // case 89: return 54;
757 // case 112: return 253;
758 // default: return 25; // most frequencies will need < 25 iterations
759 // }
760 //
761 // case rYearlyMonth:
762 // case rYearlyDay:
763 // return 8; // only 29th Feb or day 366 will need more than one iteration
764 //
765 // case rYearlyPos:
766 // if (rFreq % 7 == 0)
767 // return 364; // frequencies of a multiple of 7 years will hit the date limit first
768 // if (rFreq % 2 == 0) {
769 // // Some of these frequencies may never occur
770 // return (rFreq % 4 == 0) ? 7 : 14; // frequency = even number of years
771 // }
772 // return 28;
773 // }
774 // return 1;
775 // }
776 
777 void RecurrenceRule::buildConstraints()
778 {
779  mTimedRepetition = 0;
780  mNoByRules = mBySetPos.isEmpty();
781  mConstraints.clear();
782  Constraint con;
783  if ( mWeekStart > 0 ) con.weekstart = mWeekStart;
784  mConstraints.append( con );
785 
786  Constraint::List tmp;
787  Constraint::List::const_iterator it;
788  TQValueList<int>::const_iterator intit;
789 
790  #define intConstraint( list, element ) \
791  if ( !list.isEmpty() ) { \
792  mNoByRules = false; \
793  for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) { \
794  for ( intit = list.constBegin(); intit != list.constEnd(); ++intit ) { \
795  con = (*it); \
796  con.element = (*intit); \
797  tmp.append( con ); \
798  } \
799  } \
800  mConstraints = tmp; \
801  tmp.clear(); \
802  }
803 
804  intConstraint( mBySeconds, second );
805  intConstraint( mByMinutes, minute );
806  intConstraint( mByHours, hour );
807  intConstraint( mByMonthDays, day );
808  intConstraint( mByMonths, month );
809  intConstraint( mByYearDays, yearday );
810  intConstraint( mByWeekNumbers, weeknumber );
811  #undef intConstraint
812 
813  if ( !mByDays.isEmpty() ) {
814  mNoByRules = false;
815  for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) {
816  TQValueList<WDayPos>::const_iterator dayit;
817  for ( dayit = mByDays.constBegin(); dayit != mByDays.constEnd(); ++dayit ) {
818  con = (*it);
819  con.weekday = (*dayit).day();
820  con.weekdaynr = (*dayit).pos();
821  tmp.append( con );
822  }
823  }
824  mConstraints = tmp;
825  tmp.clear();
826  }
827 
828  #define fixConstraint( element, value ) \
829  { \
830  tmp.clear(); \
831  for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) { \
832  con = (*it); con.element = value; tmp.append( con ); \
833  } \
834  mConstraints = tmp; \
835  }
836  // Now determine missing values from DTSTART. This can speed up things,
837  // because we have more restrictions and save some loops.
838 
839  // TODO: Does RFC 2445 intend to restrict the weekday in all cases of weekly?
840  if ( mPeriod == rWeekly && mByDays.isEmpty() ) {
841  fixConstraint( weekday, mDateStart.date().dayOfWeek() );
842  }
843 
844  // Really fall through in the cases, because all smaller time intervals are
845  // constrained from dtstart
846  switch ( mPeriod ) {
847  case rYearly:
848  if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() && mByYearDays.isEmpty() && mByMonths.isEmpty() ) {
849  fixConstraint( month, mDateStart.date().month() );
850  }
851  case rMonthly:
852  if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() && mByYearDays.isEmpty() && mByMonthDays.isEmpty() ) {
853  fixConstraint( day, mDateStart.date().day() );
854  }
855 
856  case rWeekly:
857  case rDaily:
858  if ( mByHours.isEmpty() ) {
859  fixConstraint( hour, mDateStart.time().hour() );
860  }
861  case rHourly:
862  if ( mByMinutes.isEmpty() ) {
863  fixConstraint( minute, mDateStart.time().minute() );
864  }
865  case rMinutely:
866  if ( mBySeconds.isEmpty() ) {
867  fixConstraint( second, mDateStart.time().second() );
868  }
869  case rSecondly:
870  default:
871  break;
872  }
873  #undef fixConstraint
874 
875  if ( mNoByRules ) {
876  switch ( mPeriod ) {
877  case rHourly:
878  mTimedRepetition = mFrequency * 3600;
879  break;
880  case rMinutely:
881  mTimedRepetition = mFrequency * 60;
882  break;
883  case rSecondly:
884  mTimedRepetition = mFrequency;
885  break;
886  default:
887  break;
888  }
889  } else {
890  Constraint::List::Iterator conit = mConstraints.begin();
891  while ( conit != mConstraints.end() ) {
892  if ( (*conit).isConsistent( mPeriod ) ) {
893  ++conit;
894  } else {
895  conit = mConstraints.remove( conit );
896  }
897  }
898  }
899 }
900 
901 bool RecurrenceRule::buildCache() const
902 {
903 kdDebug(5800) << " RecurrenceRule::buildCache: " << endl;
904  // Build the list of all occurrences of this event (we need that to determine
905  // the end date!)
906  Constraint interval( getNextValidDateInterval( startDt(), recurrenceType() ) );
907  TQDateTime next;
908 
909  DateTimeList dts = datesForInterval( interval, recurrenceType() );
910  DateTimeList::Iterator it = dts.begin();
911  // Only use dates after the event has started (start date is only included
912  // if it matches)
913  while ( it != dts.end() ) {
914  if ( (*it) < startDt() ) it = dts.remove( it );
915  else ++it;
916  }
917 // dts.prepend( startDt() ); // the start date is always the first occurrence
918 
919 
920  int loopnr = 0;
921  int dtnr = dts.count();
922  // some validity checks to avoid infinite loops (i.e. if we have
923  // done this loop already 10000 times and found no occurrence, bail out )
924  while ( loopnr < 10000 && dtnr < mDuration ) {
925  interval.increase( recurrenceType(), frequency() );
926  // The returned date list is already sorted!
927  dts += datesForInterval( interval, recurrenceType() );
928  dtnr = dts.count();
929  ++loopnr;
930  }
931  if ( int(dts.count()) > mDuration ) {
932  // we have picked up more occurrences than necessary, remove them
933  it = dts.at( mDuration );
934  while ( it != dts.end() ) it = dts.remove( it );
935  }
936  mCached = true;
937  mCachedDates = dts;
938 
939 kdDebug(5800) << " Finished Building Cache, cache has " << dts.count() << " entries:" << endl;
940 // it = dts.begin();
941 // while ( it != dts.end() ) {
942 // kdDebug(5800) << " -=> " << (*it) << endl;
943 // ++it;
944 // }
945  if ( int(dts.count()) == mDuration ) {
946  mCachedDateEnd = dts.last();
947  return true;
948  } else {
949  mCachedDateEnd = TQDateTime();
950  return false;
951  }
952 }
953 
954 bool RecurrenceRule::dateMatchesRules( const TQDateTime &qdt ) const
955 {
956  bool match = false;
957  for ( Constraint::List::ConstIterator it = mConstraints.begin();
958  it!=mConstraints.end(); ++it ) {
959  match = match || ( (*it).matches( qdt, recurrenceType() ) );
960  }
961  return match;
962 }
963 
964 bool RecurrenceRule::recursOn( const TQDate &qd ) const
965 {
966  int i, iend;
967  if ( doesFloat() ) {
968  // It's a date-only rule, so it has no time specification.
969  if ( qd < mDateStart.date() ) {
970  return false;
971  }
972  // Start date is only included if it really matches
973  TQDate endDate;
974  if ( mDuration >= 0 ) {
975  endDate = endDt().date();
976  if ( qd > endDate ) {
977  return false;
978  }
979  }
980 
981  // The date must be in an appropriate interval (getNextValidDateInterval),
982  // Plus it must match at least one of the constraints
983  bool match = false;
984  for ( i = 0, iend = mConstraints.count(); i < iend && !match; ++i ) {
985  match = mConstraints[i].matches( qd, recurrenceType() );
986  }
987  if ( !match ) {
988  return false;
989  }
990 
991  TQDateTime start( qd, TQTime( 0, 0, 0 ) );
992  Constraint interval( getNextValidDateInterval( start, recurrenceType() ) );
993  // Constraint::matches is quite efficient, so first check if it can occur at
994  // all before we calculate all actual dates.
995  if ( !interval.matches( qd, recurrenceType() ) ) {
996  return false;
997  }
998  // We really need to obtain the list of dates in this interval, since
999  // otherwise BYSETPOS will not work (i.e. the date will match the interval,
1000  // but BYSETPOS selects only one of these matching dates!
1001  TQDateTime end = start.addDays(1);
1002  do {
1003  DateTimeList dts = datesForInterval( interval, recurrenceType() );
1004  for ( i = 0, iend = dts.count(); i < iend; ++i ) {
1005  if ( dts[i].date() >= qd ) {
1006  return dts[i].date() == qd;
1007  }
1008  }
1009  interval.increase( recurrenceType(), frequency() );
1010  } while ( interval.intervalDateTime( recurrenceType() ) < end );
1011  return false;
1012  }
1013 
1014  // It's a date-time rule, so we need to take the time specification into account.
1015  TQDateTime start( qd, TQTime( 0, 0, 0 ) );
1016  TQDateTime end = start.addDays( 1 );
1017  if ( end < mDateStart ) {
1018  return false;
1019  }
1020  if ( start < mDateStart ) {
1021  start = mDateStart;
1022  }
1023 
1024  // Start date is only included if it really matches
1025  if ( mDuration >= 0 ) {
1026  TQDateTime endRecur = endDt();
1027  if ( endRecur.isValid() ) {
1028  if ( start > endRecur ) {
1029  return false;
1030  }
1031  if ( end > endRecur ) {
1032  end = endRecur; // limit end-of-day time to end of recurrence rule
1033  }
1034  }
1035  }
1036 
1037  if ( mTimedRepetition ) {
1038  // It's a simple sub-daily recurrence with no constraints
1039  int n = static_cast<int>( ( mDateStart.secsTo( start ) - 1 ) % mTimedRepetition );
1040  return start.addSecs( mTimedRepetition - n ) < end;
1041  }
1042 
1043  // Find the start and end dates in the time spec for the rule
1044  TQDate startDay = start.date();
1045  TQDate endDay = end.addSecs( -1 ).date();
1046  int dayCount = startDay.daysTo( endDay ) + 1;
1047 
1048  // The date must be in an appropriate interval (getNextValidDateInterval),
1049  // Plus it must match at least one of the constraints
1050  bool match = false;
1051  for ( i = 0, iend = mConstraints.count(); i < iend && !match; ++i ) {
1052  match = mConstraints[i].matches( startDay, recurrenceType() );
1053  for ( int day = 1; day < dayCount && !match; ++day ) {
1054  match = mConstraints[i].matches( startDay.addDays( day ), recurrenceType() );
1055  }
1056  }
1057  if ( !match ) {
1058  return false;
1059  }
1060 
1061  Constraint interval( getNextValidDateInterval( start, recurrenceType() ) );
1062  // Constraint::matches is quite efficient, so first check if it can occur at
1063  // all before we calculate all actual dates.
1064  match = false;
1065  Constraint intervalm = interval;
1066  do {
1067  match = intervalm.matches( startDay, recurrenceType() );
1068  for ( int day = 1; day < dayCount && !match; ++day ) {
1069  match = intervalm.matches( startDay.addDays( day ), recurrenceType() );
1070  }
1071  if ( match ) {
1072  break;
1073  }
1074  intervalm.increase( recurrenceType(), frequency() );
1075  } while ( intervalm.intervalDateTime( recurrenceType() ) < end );
1076  if ( !match ) {
1077  return false;
1078  }
1079 
1080  // We really need to obtain the list of dates in this interval, since
1081  // otherwise BYSETPOS will not work (i.e. the date will match the interval,
1082  // but BYSETPOS selects only one of these matching dates!
1083  do {
1084  DateTimeList dts = datesForInterval( interval, recurrenceType() );
1085  int i = findGE( dts, start, 0 );
1086  if ( i >= 0 ) {
1087  return dts[i] < end;
1088  }
1089  interval.increase( recurrenceType(), frequency() );
1090  } while ( interval.intervalDateTime( recurrenceType() ) < end );
1091 
1092  return false;
1093 }
1094 
1095 bool RecurrenceRule::recursAt( const TQDateTime &dt ) const
1096 {
1097  if ( doesFloat() ) {
1098  return recursOn( dt.date() );
1099  }
1100  if ( dt < mDateStart ) {
1101  return false;
1102  }
1103  // Start date is only included if it really matches
1104  if ( mDuration >= 0 && dt > endDt() ) {
1105  return false;
1106  }
1107 
1108  if ( mTimedRepetition ) {
1109  // It's a simple sub-daily recurrence with no constraints
1110  return !( mDateStart.secsTo( dt ) % mTimedRepetition );
1111  }
1112 
1113  // The date must be in an appropriate interval (getNextValidDateInterval),
1114  // Plus it must match at least one of the constraints
1115  if ( !dateMatchesRules( dt ) ) {
1116  return false;
1117  }
1118  // if it recurs every interval, speed things up...
1119 // if ( d->mFrequency == 1 && d->mBySetPos.isEmpty() && d->mByDays.isEmpty() ) return true;
1120  Constraint interval( getNextValidDateInterval( dt, recurrenceType() ) );
1121  // TODO_Recurrence: Does this work with BySetPos???
1122  if ( interval.matches( dt, recurrenceType() ) ) {
1123  return true;
1124  }
1125  return false;
1126 }
1127 
1128 TimeList RecurrenceRule::recurTimesOn( const TQDate &date ) const
1129 {
1130  TimeList lst;
1131  if ( doesFloat() ) {
1132  return lst;
1133  }
1134  TQDateTime start( date, TQTime( 0, 0, 0 ) );
1135  TQDateTime end = start.addDays( 1 ).addSecs( -1 );
1136  DateTimeList dts = timesInInterval( start, end ); // returns between start and end inclusive
1137  for ( int i = 0, iend = dts.count(); i < iend; ++i ) {
1138  lst += dts[i].time();
1139  }
1140  return lst;
1141 }
1142 
1144 int RecurrenceRule::durationTo( const TQDateTime &dt ) const
1145 {
1146 // kdDebug(5800) << " RecurrenceRule::durationTo: " << dt << endl;
1147  // Easy cases: either before start, or after all recurrences and we know
1148  // their number
1149  if ( dt < startDt() ) return 0;
1150  // Start date is only included if it really matches
1151 // if ( dt == startDt() ) return 1;
1152  if ( mDuration > 0 && dt >= endDt() ) return mDuration;
1153 
1154  TQDateTime next( startDt() );
1155  int found = 0;
1156  while ( next.isValid() && next <= dt ) {
1157  ++found;
1158  next = getNextDate( next );
1159  }
1160  return found;
1161 }
1162 
1163 
1164 TQDateTime RecurrenceRule::getPreviousDate( const TQDateTime& afterDate ) const
1165 {
1166 // kdDebug(5800) << " RecurrenceRule::getPreviousDate: " << afterDate << endl;
1167  // Beyond end of recurrence
1168  if ( afterDate < startDt() )
1169  return TQDateTime();
1170 
1171  // If we have a cache (duration given), use that
1172  TQDateTime prev;
1173  if ( mDuration > 0 ) {
1174  if ( !mCached ) buildCache();
1175  DateTimeList::ConstIterator it = mCachedDates.begin();
1176  while ( it != mCachedDates.end() && (*it) < afterDate ) {
1177  prev = *it;
1178  ++it;
1179  }
1180  if ( prev.isValid() && prev < afterDate ) return prev;
1181  else return TQDateTime();
1182  }
1183 
1184 // kdDebug(5800) << " getNext date after " << preDate << endl;
1185  prev = afterDate;
1186  if ( mDuration >= 0 && endDt().isValid() && afterDate > endDt() )
1187  prev = endDt().addSecs( 1 );
1188 
1189  Constraint interval( getPreviousValidDateInterval( prev, recurrenceType() ) );
1190 // kdDebug(5800) << "Previous Valid Date Interval for date " << prev << ": " << endl;
1191 // interval.dump();
1192  DateTimeList dts = datesForInterval( interval, recurrenceType() );
1193  DateTimeList::Iterator dtit = dts.end();
1194  if ( dtit != dts.begin() ) {
1195  do {
1196  --dtit;
1197  } while ( dtit != dts.begin() && (*dtit) >= prev );
1198  if ( (*dtit) < prev ) {
1199  if ( (*dtit) >= startDt() ) return (*dtit);
1200  else return TQDateTime();
1201  }
1202  }
1203 
1204  // Previous interval. As soon as we find an occurrence, we're done.
1205  while ( interval.intervalDateTime( recurrenceType() ) > startDt() ) {
1206  interval.increase( recurrenceType(), -frequency() );
1207 // kdDebug(5800) << "Decreased interval: " << endl;
1208 // interval.dump();
1209  // The returned date list is sorted
1210  DateTimeList dts = datesForInterval( interval, recurrenceType() );
1211  // The list is sorted, so take the last one.
1212  if ( dts.count() > 0 ) {
1213  prev = dts.last();
1214  if ( prev.isValid() && prev >= startDt() ) return prev;
1215  else return TQDateTime();
1216  }
1217  }
1218  return TQDateTime();
1219 }
1220 
1221 
1222 TQDateTime RecurrenceRule::getNextDate( const TQDateTime &preDate ) const
1223 {
1224 // kdDebug(5800) << " RecurrenceRule::getNextDate: " << preDate << endl;
1225  // Beyond end of recurrence
1226  if ( mDuration >= 0 && endDt().isValid() && preDate >= endDt() )
1227  return TQDateTime();
1228 
1229  // Start date is only included if it really matches
1230  TQDateTime adjustedPreDate;
1231  if ( preDate < startDt() )
1232  adjustedPreDate = startDt().addSecs( -1 );
1233  else
1234  adjustedPreDate = preDate;
1235 
1236  if ( mDuration > 0 ) {
1237  if ( !mCached ) buildCache();
1238  DateTimeList::ConstIterator it = mCachedDates.begin();
1239  while ( it != mCachedDates.end() && (*it) <= adjustedPreDate ) ++it;
1240  if ( it != mCachedDates.end() ) {
1241 // kdDebug(5800) << " getNext date after " << adjustedPreDate << ", cached date: " << *it << endl;
1242  return (*it);
1243  }
1244  }
1245 
1246 // kdDebug(5800) << " getNext date after " << adjustedPreDate << endl;
1247  Constraint interval( getNextValidDateInterval( adjustedPreDate, recurrenceType() ) );
1248  DateTimeList dts = datesForInterval( interval, recurrenceType() );
1249  DateTimeList::Iterator dtit = dts.begin();
1250  while ( dtit != dts.end() && (*dtit) <= adjustedPreDate ) ++dtit;
1251  if ( dtit != dts.end() ) {
1252  if ( mDuration >= 0 && (*dtit) > endDt() ) return TQDateTime();
1253  else return (*dtit);
1254  }
1255 
1256  // Increase the interval. The first occurrence that we find is the result (if
1257  // if's before the end date).
1258  // TODO: some validity checks to avoid infinite loops for contradictory constraints
1259  int loopnr = 0;
1260  while ( loopnr < 10000 ) {
1261  interval.increase( recurrenceType(), frequency() );
1262  DateTimeList dts = datesForInterval( interval, recurrenceType() );
1263  if ( dts.count() > 0 ) {
1264  TQDateTime ret( dts.first() );
1265  if ( mDuration >= 0 && ret > endDt() ) return TQDateTime();
1266  else return ret;
1267  }
1268  ++loopnr;
1269  }
1270  return TQDateTime();
1271 }
1272 
1273 DateTimeList RecurrenceRule::timesInInterval( const TQDateTime &dtStart,
1274  const TQDateTime &dtEnd ) const
1275 {
1276  TQDateTime start = dtStart;
1277  TQDateTime end = dtEnd;
1278  DateTimeList result;
1279  if ( end < mDateStart ) {
1280  return result; // before start of recurrence
1281  }
1282  TQDateTime enddt = end;
1283  if ( mDuration >= 0 ) {
1284  TQDateTime endRecur = endDt();
1285  if ( endRecur.isValid() ) {
1286  if ( start > endRecur ) {
1287  return result; // beyond end of recurrence
1288  }
1289  if ( end > endRecur ) {
1290  enddt = endRecur; // limit end time to end of recurrence rule
1291  }
1292  }
1293  }
1294 
1295  if ( mTimedRepetition ) {
1296  // It's a simple sub-daily recurrence with no constraints
1297  int n = static_cast<int>( ( mDateStart.secsTo( start ) - 1 ) % mTimedRepetition );
1298  TQDateTime dt = start.addSecs( mTimedRepetition - n );
1299  if ( dt < enddt ) {
1300  n = static_cast<int>( ( dt.secsTo( enddt ) - 1 ) / mTimedRepetition ) + 1;
1301  // limit n by a sane value else we can "explode".
1302  n = TQMIN( n, LOOP_LIMIT );
1303  for ( int i = 0; i < n; dt = dt.addSecs( mTimedRepetition ), ++i ) {
1304  result += dt;
1305  }
1306  }
1307  return result;
1308  }
1309 
1310  TQDateTime st = start;
1311  bool done = false;
1312  if ( mDuration > 0 ) {
1313  if ( !mCached ) {
1314  buildCache();
1315  }
1316  if ( mCachedDateEnd.isValid() && start > mCachedDateEnd ) {
1317  return result; // beyond end of recurrence
1318  }
1319  int i = findGE( mCachedDates, start, 0 );
1320  if ( i >= 0 ) {
1321  int iend = findGT( mCachedDates, enddt, i );
1322  if ( iend < 0 ) {
1323  iend = mCachedDates.count();
1324  } else {
1325  done = true;
1326  }
1327  while ( i < iend ) {
1328  result += mCachedDates[i++];
1329  }
1330  }
1331  if ( mCachedDateEnd.isValid() ) {
1332  done = true;
1333  } else if ( !result.isEmpty() ) {
1334  result += TQDateTime(); // indicate that the returned list is incomplete
1335  done = true;
1336  }
1337  if ( done ) {
1338  return result;
1339  }
1340  // We don't have any result yet, but we reached the end of the incomplete cache
1341  st = mCachedLastDate.addSecs( 1 );
1342  }
1343 
1344  Constraint interval( getNextValidDateInterval( st, recurrenceType() ) );
1345  int loop = 0;
1346  do {
1347  DateTimeList dts = datesForInterval( interval, recurrenceType() );
1348  int i = 0;
1349  int iend = dts.count();
1350  if ( loop == 0 ) {
1351  i = findGE( dts, st, 0 );
1352  if ( i < 0 ) {
1353  i = iend;
1354  }
1355  }
1356  int j = findGT( dts, enddt, i );
1357  if ( j >= 0 ) {
1358  iend = j;
1359  loop = LOOP_LIMIT;
1360  }
1361  while ( i < iend ) {
1362  result += dts[i++];
1363  }
1364  // Increase the interval.
1365  interval.increase( recurrenceType(), frequency() );
1366  } while ( ++loop < LOOP_LIMIT &&
1367  interval.intervalDateTime( recurrenceType() ) < end );
1368  return result;
1369 }
1370 
1371 RecurrenceRule::Constraint RecurrenceRule::getPreviousValidDateInterval( const TQDateTime &preDate, PeriodType type ) const
1372 {
1373 // kdDebug(5800) << " (o) getPreviousValidDateInterval after " << preDate << ", type=" << type << endl;
1374  long periods = 0;
1375  TQDateTime nextValid = startDt();
1376  TQDateTime start = startDt();
1377  int modifier = 1;
1378  TQDateTime toDate( preDate );
1379  // for super-daily recurrences, don't care about the time part
1380 
1381  // Find the #intervals since the dtstart and round to the next multiple of
1382  // the frequency
1383  // FIXME: All sub-daily periods need to convert to UTC, do the calculations
1384  // in UTC, then convert back to the local time zone. Otherwise,
1385  // recurrences across DST changes will be determined wrongly
1386  switch ( type ) {
1387  // Really fall through for sub-daily, since the calculations only differ
1388  // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
1389  case rHourly: modifier *= 60;
1390  case rMinutely: modifier *= 60;
1391  case rSecondly:
1392  periods = ownSecsTo( start, toDate ) / modifier;
1393  // round it down to the next lower multiple of frequency():
1394  periods = ( periods / frequency() ) * frequency();
1395  nextValid = start.addSecs( modifier * periods );
1396  break;
1397 
1398  case rWeekly:
1399  toDate = toDate.addDays( -(7 + toDate.date().dayOfWeek() - mWeekStart) % 7 );
1400  start = start.addDays( -(7 + start.date().dayOfWeek() - mWeekStart) % 7 );
1401  modifier *= 7;
1402  case rDaily:
1403  periods = start.daysTo( toDate ) / modifier;
1404  // round it down to the next lower multiple of frequency():
1405  periods = ( periods / frequency() ) * frequency();
1406  nextValid = start.addDays( modifier * periods );
1407  break;
1408 
1409  case rMonthly: {
1410  periods = 12*( toDate.date().year() - start.date().year() ) +
1411  ( toDate.date().month() - start.date().month() );
1412  // round it down to the next lower multiple of frequency():
1413  periods = ( periods / frequency() ) * frequency();
1414  // set the day to the first day of the month, so we don't have problems
1415  // with non-existent days like Feb 30 or April 31
1416  start.setDate( TQDate( start.date().year(), start.date().month(), 1 ) );
1417  nextValid.setDate( start.date().addMonths( periods ) );
1418  break; }
1419  case rYearly:
1420  periods = ( toDate.date().year() - start.date().year() );
1421  // round it down to the next lower multiple of frequency():
1422  periods = ( periods / frequency() ) * frequency();
1423  nextValid.setDate( start.date().addYears( periods ) );
1424  break;
1425  default:
1426  break;
1427  }
1428 // kdDebug(5800) << " ~~~> date in previous interval is: : " << nextValid << endl;
1429 
1430  return Constraint( nextValid, type, mWeekStart );
1431 }
1432 
1433 RecurrenceRule::Constraint RecurrenceRule::getNextValidDateInterval( const TQDateTime &preDate, PeriodType type ) const
1434 {
1435  // TODO: Simplify this!
1436  kdDebug(5800) << " (o) getNextValidDateInterval after " << preDate << ", type=" << type << endl;
1437  long periods = 0;
1438  TQDateTime start = startDt();
1439  TQDateTime nextValid( start );
1440  int modifier = 1;
1441  TQDateTime toDate( preDate );
1442  // for super-daily recurrences, don't care about the time part
1443 
1444  // Find the #intervals since the dtstart and round to the next multiple of
1445  // the frequency
1446  // FIXME: All sub-daily periods need to convert to UTC, do the calculations
1447  // in UTC, then convert back to the local time zone. Otherwise,
1448  // recurrences across DST changes will be determined wrongly
1449  switch ( type ) {
1450  // Really fall through for sub-daily, since the calculations only differ
1451  // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
1452  case rHourly: modifier *= 60;
1453  case rMinutely: modifier *= 60;
1454  case rSecondly:
1455  periods = ownSecsTo( start, toDate ) / modifier;
1456  periods = TQMAX( 0, periods);
1457  if ( periods > 0 )
1458  periods += ( frequency() - 1 - ( (periods - 1) % frequency() ) );
1459  nextValid = start.addSecs( modifier * periods );
1460  break;
1461 
1462  case rWeekly:
1463  // correct both start date and current date to start of week
1464  toDate = toDate.addDays( -(7 + toDate.date().dayOfWeek() - mWeekStart) % 7 );
1465  start = start.addDays( -(7 + start.date().dayOfWeek() - mWeekStart) % 7 );
1466  modifier *= 7;
1467  case rDaily:
1468  periods = start.daysTo( toDate ) / modifier;
1469  periods = TQMAX( 0, periods);
1470  if ( periods > 0 )
1471  periods += (frequency() - 1 - ( (periods - 1) % frequency() ) );
1472  nextValid = start.addDays( modifier * periods );
1473  break;
1474 
1475  case rMonthly: {
1476  periods = 12*( toDate.date().year() - start.date().year() ) +
1477  ( toDate.date().month() - start.date().month() );
1478  periods = TQMAX( 0, periods);
1479  if ( periods > 0 )
1480  periods += (frequency() - 1 - ( (periods - 1) % frequency() ) );
1481  // set the day to the first day of the month, so we don't have problems
1482  // with non-existent days like Feb 30 or April 31
1483  start.setDate( TQDate( start.date().year(), start.date().month(), 1 ) );
1484  nextValid.setDate( start.date().addMonths( periods ) );
1485  break; }
1486  case rYearly:
1487  periods = ( toDate.date().year() - start.date().year() );
1488  periods = TQMAX( 0, periods);
1489  if ( periods > 0 )
1490  periods += ( frequency() - 1 - ( (periods - 1) % frequency() ) );
1491  nextValid.setDate( start.date().addYears( periods ) );
1492  break;
1493  default:
1494  break;
1495  }
1496 // kdDebug(5800) << " ~~~> date in next interval is: : " << nextValid << endl;
1497 
1498  return Constraint( nextValid, type, mWeekStart );
1499 }
1500 
1501 bool RecurrenceRule::mergeIntervalConstraint( Constraint *merged,
1502  const Constraint &conit, const Constraint &interval ) const
1503 {
1504  Constraint result( interval );
1505 
1506 #define mergeConstraint( name, cmparison ) \
1507  if ( conit.name cmparison ) { \
1508  if ( !(result.name cmparison) || result.name == conit.name ) { \
1509  result.name = conit.name; \
1510  } else return false;\
1511  }
1512 
1513  mergeConstraint( year, > 0 );
1514  mergeConstraint( month, > 0 );
1515  mergeConstraint( day, != 0 );
1516  mergeConstraint( hour, >= 0 );
1517  mergeConstraint( minute, >= 0 );
1518  mergeConstraint( second, >= 0 );
1519 
1520  mergeConstraint( weekday, != 0 );
1521  mergeConstraint( weekdaynr, != 0 );
1522  mergeConstraint( weeknumber, != 0 );
1523  mergeConstraint( yearday, != 0 );
1524 
1525  #undef mergeConstraint
1526  if ( merged ) *merged = result;
1527  return true;
1528 }
1529 
1530 
1531 DateTimeList RecurrenceRule::datesForInterval( const Constraint &interval, PeriodType type ) const
1532 {
1533  /* -) Loop through constraints,
1534  -) merge interval with each constraint
1535  -) if merged constraint is not consistent => ignore that constraint
1536  -) if complete => add that one date to the date list
1537  -) Loop through all missing fields => For each add the resulting
1538  */
1539 // kdDebug(5800) << " RecurrenceRule::datesForInterval: " << endl;
1540 // interval.dump();
1541  DateTimeList lst;
1542  Constraint::List::ConstIterator conit = mConstraints.begin();
1543  for ( ; conit != mConstraints.end(); ++conit ) {
1544  Constraint merged;
1545  bool mergeok = mergeIntervalConstraint( &merged, *conit, interval );
1546  // If the information is incomplete, we can't use this constraint
1547  if ( merged.year <= 0 || merged.hour < 0 || merged.minute < 0 || merged.second < 0 )
1548  mergeok = false;
1549  if ( mergeok ) {
1550 // kdDebug(5800) << " -) merged constraint: " << endl;
1551 // merged.dump();
1552  // We have a valid constraint, so get all datetimes that match it andd
1553  // append it to all date/times of this interval
1554  DateTimeList lstnew = merged.dateTimes( type );
1555  lst += lstnew;
1556  }
1557  }
1558  // Sort it so we can apply the BySetPos. Also some logic relies on this being sorted
1559  qSortUnique( lst );
1560 
1561 
1562 /*if ( lst.isEmpty() ) {
1563  kdDebug(5800) << " No Dates in Interval " << endl;
1564 } else {
1565  kdDebug(5800) << " Dates: " << endl;
1566  for ( DateTimeList::Iterator it = lst.begin(); it != lst.end(); ++it ) {
1567  kdDebug(5800)<< " -) " << (*it).toString() << endl;
1568  }
1569  kdDebug(5800) << " ---------------------" << endl;
1570 }*/
1571  if ( !mBySetPos.isEmpty() ) {
1572  DateTimeList tmplst = lst;
1573  lst.clear();
1574  TQValueList<int>::ConstIterator it;
1575  for ( it = mBySetPos.begin(); it != mBySetPos.end(); ++it ) {
1576  int pos = *it;
1577  if ( pos > 0 ) --pos;
1578  if ( pos < 0 ) pos += tmplst.count();
1579  if ( pos >= 0 && uint(pos) < tmplst.count() ) {
1580  lst.append( tmplst[pos] );
1581  }
1582  }
1583  qSortUnique( lst );
1584  }
1585 
1586  return lst;
1587 }
1588 
1589 
1591 {
1592 #ifndef NDEBUG
1593  kdDebug(5800) << "RecurrenceRule::dump():" << endl;
1594  if ( !mRRule.isEmpty() )
1595  kdDebug(5800) << " RRULE=" << mRRule << endl;
1596  kdDebug(5800) << " Read-Only: " << isReadOnly() <<
1597  ", dirty: " << mDirty << endl;
1598 
1599  kdDebug(5800) << " Period type: " << recurrenceType() << ", frequency: " << frequency() << endl;
1600  kdDebug(5800) << " #occurrences: " << duration() << endl;
1601  kdDebug(5800) << " start date: " << startDt() <<", end date: " << endDt() << endl;
1602 
1603 
1604 #define dumpByIntList(list,label) \
1605  if ( !list.isEmpty() ) {\
1606  TQStringList lst;\
1607  for ( TQValueList<int>::ConstIterator it = list.begin();\
1608  it != list.end(); ++it ) {\
1609  lst.append( TQString::number( *it ) );\
1610  }\
1611  kdDebug(5800) << " " << label << lst.join(", ") << endl;\
1612  }
1613  dumpByIntList( mBySeconds, "BySeconds: " );
1614  dumpByIntList( mByMinutes, "ByMinutes: " );
1615  dumpByIntList( mByHours, "ByHours: " );
1616  if ( !mByDays.isEmpty() ) {
1617  TQStringList lst;
1618  for ( TQValueList<WDayPos>::ConstIterator it = mByDays.begin();
1619  it != mByDays.end(); ++it ) {
1620  lst.append( ( ((*it).pos()!=0) ? TQString::number( (*it).pos() ) : "" ) +
1621  DateHelper::dayName( (*it).day() ) );
1622  }
1623  kdDebug(5800) << " ByDays: " << lst.join(", ") << endl;
1624  }
1625  dumpByIntList( mByMonthDays, "ByMonthDays:" );
1626  dumpByIntList( mByYearDays, "ByYearDays: " );
1627  dumpByIntList( mByWeekNumbers,"ByWeekNr: " );
1628  dumpByIntList( mByMonths, "ByMonths: " );
1629  dumpByIntList( mBySetPos, "BySetPos: " );
1630  #undef dumpByIntList
1631 
1632  kdDebug(5800) << " Week start: " << DateHelper::dayName( mWeekStart ) << endl;
1633 
1634  kdDebug(5800) << " Constraints:" << endl;
1635  // dump constraints
1636  for ( Constraint::List::ConstIterator it = mConstraints.begin();
1637  it!=mConstraints.end(); ++it ) {
1638  (*it).dump();
1639  }
1640 #endif
1641 }
1642 
1643 void RecurrenceRule::Constraint::dump() const
1644 {
1645  kdDebug(5800) << " ~> Y="<<year<<", M="<<month<<", D="<<day<<", H="<<hour<<", m="<<minute<<", S="<<second<<", wd="<<weekday<<",#wd="<<weekdaynr<<", #w="<<weeknumber<<", yd="<<yearday<<endl;
1646 }
void dump() const
Debug output.
TQDateTime getPreviousDate(const TQDateTime &afterDateTime) const
Returns the date and time of the last previous recurrence, before the specified date/time.
bool dateMatchesRules(const TQDateTime &qdt) const
Returns true if the date matches the rules.
void setFloats(bool floats)
Sets whether the dtstart is a floating time (i.e.
int durationTo(const TQDateTime &) const
Returns the number of recurrences up to and including the date/time specified.
int duration() const
Returns -1 if the event recurs infinitely, 0 if the end date is set, otherwise the total number of re...
bool recursAt(const TQDateTime &) const
Returns true if the date/time specified is one at which the event will recur.
PeriodType
enum for describing the frequency how an event recurs, if at all.
DateTimeList timesInInterval(const TQDateTime &start, const TQDateTime &end) const
Returns a list of all the times at which the recurrence will occur between two specified times.
Definition: alarm.h:38
TimeList recurTimesOn(const TQDate &date) const
Returns a list of the times on the specified date at which the recurrence will occur.
bool doesFloat() const
Returns whether the start date has no time associated.
TQDateTime endDt(bool *result=0) const
Returns the date and time of the last recurrence.
void clear()
Turns off recurrence for the event.
void setStartDt(const TQDateTime &start)
Set start of recurrence, as a date and time.
uint frequency() const
Returns frequency of recurrence, in terms of the recurrence time period type.
TQDateTime getNextDate(const TQDateTime &preDateTime) const
Returns the date and time of the next recurrence, after the specified date/time.
void setFrequency(int freq)
Sets the frequency of recurrence, in terms of the recurrence time period type.
bool recursOn(const TQDate &qd) const
Returns true if the date specified is one on which the event will recur.
bool isReadOnly() const
Returns true if the recurrence is read-only, or false if it can be changed.
void setDuration(int duration)
Sets the total number of times the event is to occur, including both the first and last.
void setEndDt(const TQDateTime &endDateTime)
Sets the date and time of the last recurrence.
This class represents a recurrence rule for a calendar incidence.
void removeObserver(Observer *observer)
Removes an observer that was added with addObserver.
TQDateTime startDt() const
Return the start of the recurrence.
void addObserver(Observer *observer)
Installs an observer.