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
33using namespace KCal;
34
35// Maximum number of intervals to process
36const 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!
60long 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
74class 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
87TQString 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
103TQDate 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
119int 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
144int 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
152int 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
167RecurrenceRule::Constraint::Constraint( int wkst )
168{
169 weekstart = wkst;
170 clear();
171}
172
173RecurrenceRule::Constraint::Constraint( const TQDateTime &preDate, PeriodType type, int wkst )
174{
175 weekstart = wkst;
176 readDateTime( preDate, type );
177}
178
179void 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
193bool 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
238bool 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
247bool RecurrenceRule::Constraint::isConsistent( PeriodType /*period*/) const
248{
249 // TODO: Check for consistency, e.g. byyearday=3 and bymonth=10
250 return true;
251}
252
253TQDateTime 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
299DateTimeList 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
419bool 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
450bool 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
480RecurrenceRule::RecurrenceRule( )
481: mPeriod( rNone ), mFrequency( 0 ), mIsReadOnly( false ),
482 mFloating( false ),
483 mWeekStart(1)
484{
485}
486
487RecurrenceRule::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
513RecurrenceRule::~RecurrenceRule()
514{
515}
516
517bool 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
542void RecurrenceRule::addObserver( Observer *observer )
543{
544 if ( !mObservers.contains( observer ) )
545 mObservers.append( observer );
546}
547
548void RecurrenceRule::removeObserver( Observer *observer )
549{
550 if ( mObservers.contains( observer ) )
551 mObservers.remove( observer );
552}
553
554
555
556void RecurrenceRule::setRecurrenceType( PeriodType period )
557{
558 if ( isReadOnly() ) return;
559 mPeriod = period;
560 setDirty();
561}
562
563TQDateTime 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
581void 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
590{
591 if ( isReadOnly() ) return;
592 mDuration = duration;
593 setDirty();
594}
595
596void 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
621void 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
634void 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
648void RecurrenceRule::setBySeconds( const TQValueList<int> bySeconds )
649{
650 if ( isReadOnly() ) return;
651 mBySeconds = bySeconds;
652 setDirty();
653}
654
655void RecurrenceRule::setByMinutes( const TQValueList<int> byMinutes )
656{
657 if ( isReadOnly() ) return;
658 mByMinutes = byMinutes;
659 setDirty();
660}
661
662void RecurrenceRule::setByHours( const TQValueList<int> byHours )
663{
664 if ( isReadOnly() ) return;
665 mByHours = byHours;
666 setDirty();
667}
668
669
670void RecurrenceRule::setByDays( const TQValueList<WDayPos> byDays )
671{
672 if ( isReadOnly() ) return;
673 mByDays = byDays;
674 setDirty();
675}
676
677void RecurrenceRule::setByMonthDays( const TQValueList<int> byMonthDays )
678{
679 if ( isReadOnly() ) return;
680 mByMonthDays = byMonthDays;
681 setDirty();
682}
683
684void RecurrenceRule::setByYearDays( const TQValueList<int> byYearDays )
685{
686 if ( isReadOnly() ) return;
687 mByYearDays = byYearDays;
688 setDirty();
689}
690
691void RecurrenceRule::setByWeekNumbers( const TQValueList<int> byWeekNumbers )
692{
693 if ( isReadOnly() ) return;
694 mByWeekNumbers = byWeekNumbers;
695 setDirty();
696}
697
698void RecurrenceRule::setByMonths( const TQValueList<int> byMonths )
699{
700 if ( isReadOnly() ) return;
701 mByMonths = byMonths;
702 setDirty();
703}
704
705void RecurrenceRule::setBySetPos( const TQValueList<int> bySetPos )
706{
707 if ( isReadOnly() ) return;
708 mBySetPos = bySetPos;
709 setDirty();
710}
711
712void 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
777void 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
901bool RecurrenceRule::buildCache() const
902{
903kdDebug(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
939kdDebug(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
954bool 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
964bool 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
1095bool 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
1128TimeList 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
1144int 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
1164TQDateTime 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
1222TQDateTime 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
1273DateTimeList 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
1371RecurrenceRule::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
1433RecurrenceRule::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
1501bool 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
1531DateTimeList 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
1643void 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