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 }
This class represents a recurrence rule for a calendar incidence.
TQDateTime getPreviousDate(const TQDateTime &afterDateTime) const
Returns the date and time of the last previous recurrence, before the specified date/time.
TQDateTime endDt(bool *result=0) const
Returns the date and time of the last recurrence.
TimeList recurTimesOn(const TQDate &date) const
Returns a list of the times on the specified date at which the recurrence will occur.
void clear()
Turns off recurrence for the event.
void setDuration(int duration)
Sets the total number of times the event is to occur, including both the first and last.
int durationTo(const TQDateTime &) const
Returns the number of recurrences up to and including the date/time specified.
void dump() const
Debug output.
TQDateTime startDt() const
Return the start of the recurrence.
bool recursOn(const TQDate &qd) const
Returns true if the date specified is one on which the event will recur.
void setFrequency(int freq)
Sets the frequency of recurrence, in terms of the recurrence time period type.
void removeObserver(Observer *observer)
Removes an observer that was added with addObserver.
void setFloats(bool floats)
Sets whether the dtstart is a floating time (i.e.
void setEndDt(const TQDateTime &endDateTime)
Sets the date and time of the last recurrence.
bool recursAt(const TQDateTime &) const
Returns true if the date/time specified is one at which the event will recur.
uint frequency() const
Returns frequency of recurrence, in terms of the recurrence time period type.
int duration() const
Returns -1 if the event recurs infinitely, 0 if the end date is set, otherwise the total number of re...
bool dateMatchesRules(const TQDateTime &qdt) const
Returns true if the date matches the rules.
void addObserver(Observer *observer)
Installs an observer.
bool doesFloat() const
Returns whether the start date has no time associated.
PeriodType
enum for describing the frequency how an event recurs, if at all.
bool isReadOnly() const
Returns true if the recurrence is read-only, or false if it can be changed.
TQDateTime getNextDate(const TQDateTime &preDateTime) const
Returns the date and time of the next recurrence, after the specified date/time.
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.
void setStartDt(const TQDateTime &start)
Set start of recurrence, as a date and time.
Namespace KCal is for global classes, objects and/or functions in libkcal.
Definition: alarm.h:38