summaryrefslogtreecommitdiffstats
path: root/libkcal/recurrencerule.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libkcal/recurrencerule.cpp')
-rw-r--r--libkcal/recurrencerule.cpp1428
1 files changed, 1428 insertions, 0 deletions
diff --git a/libkcal/recurrencerule.cpp b/libkcal/recurrencerule.cpp
new file mode 100644
index 000000000..ef6f9ccd9
--- /dev/null
+++ b/libkcal/recurrencerule.cpp
@@ -0,0 +1,1428 @@
+/*
+ This file is part of libkcal.
+
+ Copyright (c) 2005 Reinhold Kainhofer <reinhold@kainhofe.com>
+
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "recurrencerule.h"
+
+#include <kdebug.h>
+#include <kglobal.h>
+#include <qdatetime.h>
+#include <qstringlist.h>
+
+#include <limits.h>
+#include <math.h>
+
+using namespace KCal;
+
+
+// FIXME: If Qt is ever changed so that QDateTime:::addSecs takes into account
+// DST shifts, we need to use our own addSecs method, too, since we
+// need to caalculate things in UTC!
+/** Workaround for broken QDateTime::secsTo (at least in Qt 3.3). While
+ QDateTime::secsTo does take time zones into account, QDateTime::addSecs
+ does not, so we have a problem:
+ QDateTime d1(QDate(2005, 10, 30), QTime(1, 30, 0) );
+ QDateTime d2(QDate(2005, 10, 30), QTime(3, 30, 0) );
+
+ kdDebug(5800) << "d1=" << d1 << ", d2=" << d2 << endl;
+ kdDebug(5800) << "d1.secsTo(d2)=" << d1.secsTo(d2) << endl;
+ kdDebug(5800) << "d1.addSecs(d1.secsTo(d2))=" << d1.addSecs(d1.secsTo(d2)) << endl;
+ This code generates the following output:
+ libkcal: d1=Son Okt 30 01:30:00 2005, d2=Son Okt 30 03:30:00 2005
+ libkcal: d1.secsTo(d2)=10800
+ libkcal: d1.addSecs(d1.secsTo(d2))=Son Okt 30 04:30:00 2005
+ Notice that secsTo counts the hour between 2:00 and 3:00 twice, while
+ adddSecs doesn't and so has one additional hour. This basically makes it
+ impossible to use QDateTime for *any* calculations, in local time zone as
+ well as in UTC. Since we don't want to use
+ time zones anyway, but do all secondsly/minutely/hourly calculations in UTC,
+ we simply use our own secsTo, which ignores all time zone shifts. */
+long long ownSecsTo( const QDateTime &dt1, const QDateTime &dt2 )
+{
+ long long res = static_cast<long long>( dt1.date().daysTo( dt2.date() ) ) * 24*3600;
+ res += dt1.time().secsTo( dt2.time() );
+ return res;
+}
+
+
+
+/**************************************************************************
+ * DateHelper *
+ **************************************************************************/
+
+
+class DateHelper {
+ public:
+#ifndef NDEBUG
+ static QString dayName( short day );
+#endif
+ static QDate getNthWeek( int year, int weeknumber, short weekstart = 1 );
+ static int weekNumbersInYear( int year, short weekstart = 1 );
+ static int getWeekNumber( const QDate &date, short weekstart, int *year = 0 );
+ static int getWeekNumberNeg( const QDate &date, short weekstart, int *year = 0 );
+};
+
+
+#ifndef NDEBUG
+QString DateHelper::dayName( short day )
+{
+ switch ( day ) {
+ case 1: return "MO"; break;
+ case 2: return "TU"; break;
+ case 3: return "WE"; break;
+ case 4: return "TH"; break;
+ case 5: return "FR"; break;
+ case 6: return "SA"; break;
+ case 7: return "SU"; break;
+ default: return "??";
+ }
+}
+#endif
+
+
+QDate DateHelper::getNthWeek( int year, int weeknumber, short weekstart )
+{
+ if ( weeknumber == 0 ) return QDate();
+ // Adjust this to the first day of week #1 of the year and add 7*weekno days.
+ QDate dt( year, 1, 4 ); // Week #1 is the week that contains Jan 4
+ int adjust = -(7 + dt.dayOfWeek() - weekstart) % 7;
+ if ( weeknumber > 0 ) {
+ dt = dt.addDays( 7 * (weeknumber-1) + adjust );
+ } else if ( weeknumber < 0 ) {
+ dt = dt.addYears( 1 );
+ dt = dt.addDays( 7 * weeknumber + adjust );
+ }
+ return dt;
+}
+
+
+int DateHelper::getWeekNumber( const QDate &date, short weekstart, int *year )
+{
+// kdDebug(5800) << "Getting week number for " << date << " with weekstart="<<weekstart<<endl;
+ if ( year ) *year = date.year();
+ QDate dt( date.year(), 1, 4 ); // <= definitely in week #1
+ dt = dt.addDays( -(7 + dt.dayOfWeek() - weekstart) % 7 ); // begin of week #1
+ QDate dtn( date.year()+1, 1, 4 ); // <= definitely first week of next year
+ dtn = dtn.addDays( -(7 + dtn.dayOfWeek() - weekstart) % 7 );
+
+ int daysto = dt.daysTo( date );
+ int dayston = dtn.daysTo( date );
+ if ( daysto < 0 ) {
+ if ( year ) *year = date.year()-1;
+ dt = QDate( date.year()-1, 1, 4 );
+ dt = dt.addDays( -(7 + dt.dayOfWeek() - weekstart) % 7 ); // begin of week #1
+ daysto = dt.daysTo( date );
+ } else if ( dayston >= 0 ) {
+ // in first week of next year;
+ if ( year ) *year = date.year() + 1;
+ dt = dtn;
+ daysto = dayston;
+ }
+ return daysto / 7 + 1;
+}
+
+int DateHelper::weekNumbersInYear( int year, short weekstart )
+{
+ QDate dt( year, 1, weekstart );
+ QDate dt1( year + 1, 1, weekstart );
+ return dt.daysTo( dt1 ) / 7;
+}
+
+// Week number from the end of the year
+int DateHelper::getWeekNumberNeg( const QDate &date, short weekstart, int *year )
+{
+ int weekpos = getWeekNumber( date, weekstart, year );
+ return weekNumbersInYear( *year, weekstart ) - weekpos - 1;
+}
+
+
+
+
+
+/**************************************************************************
+ * RecurrenceRule::Constraint *
+ **************************************************************************/
+
+
+RecurrenceRule::Constraint::Constraint( int wkst )
+{
+ weekstart = wkst;
+ clear();
+}
+
+RecurrenceRule::Constraint::Constraint( const QDateTime &preDate, PeriodType type, int wkst )
+{
+ weekstart = wkst;
+ readDateTime( preDate, type );
+}
+
+void RecurrenceRule::Constraint::clear()
+{
+ year = 0;
+ month = 0;
+ day = 0;
+ hour = -1;
+ minute = -1;
+ second = -1;
+ weekday = 0;
+ weekdaynr = 0;
+ weeknumber = 0;
+ yearday = 0;
+}
+
+bool RecurrenceRule::Constraint::matches( const QDate &dt, RecurrenceRule::PeriodType type ) const
+{
+ // If the event recurs in week 53 or 1, the day might not belong to the same
+ // year as the week it is in. E.g. Jan 1, 2005 is in week 53 of year 2004.
+ // So we can't simply check the year in that case!
+ if ( weeknumber == 0 ) {
+ if ( year > 0 && year != dt.year() ) return false;
+ } else {
+ int y;
+ if ( weeknumber > 0 &&
+ weeknumber != DateHelper::getWeekNumber( dt, weekstart, &y ) ) return false;
+ if ( weeknumber < 0 &&
+ weeknumber != DateHelper::getWeekNumberNeg( dt, weekstart, &y ) ) return false;
+ if ( year > 0 && year != y ) return false;
+ }
+
+ if ( month > 0 && month != dt.month() ) return false;
+ if ( day > 0 && day != dt.day() ) return false;
+ if ( day < 0 && dt.day() != (dt.daysInMonth() + day + 1 ) ) return false;
+ if ( weekday > 0 ) {
+ if ( weekday != dt.dayOfWeek() ) return false;
+ if ( weekdaynr != 0 ) {
+ // If it's a yearly recurrence and a month is given, the position is
+ // still in the month, not in the year.
+ bool inMonth = (type == rMonthly) || ( type == rYearly && month > 0 );
+ // Monthly
+ if ( weekdaynr > 0 && inMonth &&
+ weekdaynr != (dt.day() - 1)/7 + 1 ) return false;
+ if ( weekdaynr < 0 && inMonth &&
+ weekdaynr != -((dt.daysInMonth() - dt.day() )/7 + 1 ) )
+ return false;
+ // Yearly
+ if ( weekdaynr > 0 && !inMonth &&
+ weekdaynr != (dt.dayOfYear() - 1)/7 + 1 ) return false;
+ if ( weekdaynr < 0 && !inMonth &&
+ weekdaynr != -((dt.daysInYear() - dt.dayOfYear() )/7 + 1 ) )
+ return false;
+ }
+ }
+ if ( yearday > 0 && yearday != dt.dayOfYear() ) return false;
+ if ( yearday < 0 && yearday != dt.daysInYear() - dt.dayOfYear() + 1 )
+ return false;
+ return true;
+}
+
+bool RecurrenceRule::Constraint::matches( const QDateTime &dt, RecurrenceRule::PeriodType type ) const
+{
+ if ( !matches( dt.date(), type ) ) return false;
+ if ( hour >= 0 && hour != dt.time().hour() ) return false;
+ if ( minute >= 0 && minute != dt.time().minute() ) return false;
+ if ( second >= 0 && second != dt.time().second() ) return false;
+ return true;
+}
+
+bool RecurrenceRule::Constraint::isConsistent( PeriodType /*period*/) const
+{
+ // TODO: Check for consistency, e.g. byyearday=3 and bymonth=10
+ return true;
+}
+
+QDateTime RecurrenceRule::Constraint::intervalDateTime( RecurrenceRule::PeriodType type ) const
+{
+ QDateTime dt;
+ dt.setTime( QTime( 0, 0, 0 ) );
+ dt.setDate( QDate( year, (month>0)?month:1, (day>0)?day:1 ) );
+ if ( day < 0 )
+ dt = dt.addDays( dt.date().daysInMonth() + day );
+ switch ( type ) {
+ case rSecondly:
+ dt.setTime( QTime( hour, minute, second ) ); break;
+ case rMinutely:
+ dt.setTime( QTime( hour, minute, 1 ) ); break;
+ case rHourly:
+ dt.setTime( QTime( hour, 1, 1 ) ); break;
+ case rDaily:
+ break;
+ case rWeekly:
+ dt = DateHelper::getNthWeek( year, weeknumber, weekstart ); break;
+ case rMonthly:
+ dt.setDate( QDate( year, month, 1 ) ); break;
+ case rYearly:
+ dt.setDate( QDate( year, 1, 1 ) ); break;
+ default:
+ break;
+ }
+ return dt;
+}
+
+
+// Y M D | H Mn S | WD #WD | WN | YD
+// required:
+// x | x x x | | |
+// 0) Trivial: Exact date given, maybe other restrictions
+// x x x | x x x | | |
+// 1) Easy case: no weekly restrictions -> at most a loop through possible dates
+// x + + | x x x | - - | - | -
+// 2) Year day is given -> date known
+// x | x x x | | | +
+// 3) week number is given -> loop through all days of that week. Further
+// restrictions will be applied in the end, when we check all dates for
+// consistency with the constraints
+// x | x x x | | + | (-)
+// 4) week day is specified ->
+// x | x x x | x ? | (-)| (-)
+// 5) All possiblecases have already been treated, so this must be an error!
+
+DateTimeList RecurrenceRule::Constraint::dateTimes( RecurrenceRule::PeriodType type ) const
+{
+// kdDebug(5800) << " RecurrenceRule::Constraint::dateTimes: " << endl;
+ DateTimeList result;
+ bool done = false;
+ // TODO_Recurrence: Handle floating
+ QTime tm( hour, minute, second );
+ if ( !isConsistent( type ) ) return result;
+
+ if ( !done && day > 0 && month > 0 ) {
+ QDateTime dt( QDate( year, month, day ), tm );
+ if ( dt.isValid() ) result.append( dt );
+ done = true;
+ }
+ if ( !done && day < 0 && month > 0 ) {
+ QDateTime dt( QDate( year, month, 1 ), tm );
+ dt = dt.addDays( dt.date().daysInMonth() + day );
+ if ( dt.isValid() ) result.append( dt );
+ done = true;
+ }
+
+
+ if ( !done && weekday == 0 && weeknumber == 0 && yearday == 0 ) {
+ // Easy case: date is given, not restrictions by week or yearday
+ uint mstart = (month>0) ? month : 1;
+ uint mend = (month <= 0) ? 12 : month;
+ for ( uint m = mstart; m <= mend; ++m ) {
+ uint dstart, dend;
+ if ( day > 0 ) {
+ dstart = dend = day;
+ } else if ( day < 0 ) {
+ QDate date( year, month, 1 );
+ dstart = dend = date.daysInMonth() + day + 1;
+ } else {
+ QDate date( year, month, 1 );
+ dstart = 1;
+ dend = date.daysInMonth();
+ }
+ for ( uint d = dstart; d <= dend; ++d ) {
+ QDateTime dt( QDate( year, m, d ), tm );
+ if ( dt.isValid() ) result.append( dt );
+ }
+ }
+ done = true;
+ }
+
+ // Else: At least one of the week / yearday restrictions was given...
+ // If we have a yearday (and of course a year), we know the exact date
+ if ( !done && yearday != 0 ) {
+ // yearday < 0 means from end of year, so we'll need Jan 1 of the next year
+ QDate d( year + ((yearday>0)?0:1), 1, 1 );
+ d = d.addDays( yearday - ((yearday>0)?1:0) );
+ result.append( QDateTime( d, tm ) );
+ done = true;
+ }
+
+ // Else: If we have a weeknumber, we have at most 7 possible dates, loop through them
+ if ( !done && weeknumber != 0 ) {
+ QDate wst( DateHelper::getNthWeek( year, weeknumber, weekstart ) );
+ if ( weekday != 0 ) {
+ wst = wst.addDays( (7 + weekday - weekstart ) % 7 );
+ result.append( QDateTime( wst, tm ) );
+ } else {
+ for ( int i = 0; i < 7; ++i ) {
+ result.append( QDateTime( wst, tm ) );
+ wst = wst.addDays( 1 );
+ }
+ }
+ done = true;
+ }
+
+ // weekday is given
+ if ( !done && weekday != 0 ) {
+ QDate dt( year, 1, 1 );
+ // If type == yearly and month is given, pos is still in month not year!
+ // TODO_Recurrence: Correct handling of n-th BYDAY...
+ int maxloop = 53;
+ bool inMonth = ( type == rMonthly) || ( type == rYearly && month > 0 );
+ if ( inMonth && month > 0 ) {
+ dt = QDate( year, month, 1 );
+ maxloop = 5;
+ }
+ if ( weekdaynr < 0 ) {
+ // From end of period (month, year) => relative to begin of next period
+ if ( inMonth )
+ dt = dt.addMonths( 1 );
+ else
+ dt = dt.addYears( 1 );
+ }
+ int adj = ( 7 + weekday - dt.dayOfWeek() ) % 7;
+ dt = dt.addDays( adj ); // correct first weekday of the period
+
+ if ( weekdaynr > 0 ) {
+ dt = dt.addDays( ( weekdaynr - 1 ) * 7 );
+ result.append( QDateTime( dt, tm ) );
+ } else if ( weekdaynr < 0 ) {
+ dt = dt.addDays( weekdaynr * 7 );
+ result.append( QDateTime( dt, tm ) );
+ } else {
+ // loop through all possible weeks, non-matching will be filtered later
+ for ( int i = 0; i < maxloop; ++i ) {
+ result.append( QDateTime( dt, tm ) );
+ dt = dt.addDays( 7 );
+ }
+ }
+ } // weekday != 0
+
+
+ // Only use those times that really match all other constraints, too
+ DateTimeList valid;
+ DateTimeList::Iterator it;
+ for ( it = result.begin(); it != result.end(); ++it ) {
+ if ( matches( *it, type ) ) valid.append( *it );
+ }
+ // Don't sort it here, would be unnecessary work. The results from all
+ // constraints will be merged to one big list of the interval. Sort that one!
+ return valid;
+}
+
+
+bool RecurrenceRule::Constraint::increase( RecurrenceRule::PeriodType type, int freq )
+{
+ // convert the first day of the interval to QDateTime
+ // Sub-daily types need to be converted to UTC to correctly handle DST shifts
+ QDateTime dt( intervalDateTime( type ) );
+
+ // Now add the intervals
+ switch ( type ) {
+ case rSecondly:
+ dt = dt.addSecs( freq ); break;
+ case rMinutely:
+ dt = dt.addSecs( 60*freq ); break;
+ case rHourly:
+ dt = dt.addSecs( 3600 * freq ); break;
+ case rDaily:
+ dt = dt.addDays( freq ); break;
+ case rWeekly:
+ dt = dt.addDays( 7*freq ); break;
+ case rMonthly:
+ dt = dt.addMonths( freq ); break;
+ case rYearly:
+ dt = dt.addYears( freq ); break;
+ default:
+ break;
+ }
+ // Convert back from QDateTime to the Constraint class
+ readDateTime( dt, type );
+
+ return true;
+}
+
+bool RecurrenceRule::Constraint::readDateTime( const QDateTime &preDate, PeriodType type )
+{
+ clear();
+ switch ( type ) {
+ // Really fall through! Only weekly needs to be treated differentely!
+ case rSecondly:
+ second = preDate.time().second();
+ case rMinutely:
+ minute = preDate.time().minute();
+ case rHourly:
+ hour = preDate.time().hour();
+ case rDaily:
+ day = preDate.date().day();
+ case rMonthly:
+ month = preDate.date().month();
+ case rYearly:
+ year = preDate.date().year();
+ break;
+
+ case rWeekly:
+ // Determine start day of the current week, calculate the week number from that
+ weeknumber = DateHelper::getWeekNumber( preDate.date(), weekstart, &year );
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+
+RecurrenceRule::RecurrenceRule( )
+: mPeriod( rNone ), mFrequency( 0 ), mIsReadOnly( false ),
+ mFloating( false ),
+ mWeekStart(1)
+{
+}
+
+RecurrenceRule::RecurrenceRule( const RecurrenceRule &r )
+{
+ mRRule = r.mRRule;
+ mPeriod = r.mPeriod;
+ mDateStart = r.mDateStart;
+ mDuration = r.mDuration;
+ mDateEnd = r.mDateEnd;
+ mFrequency = r.mFrequency;
+
+ mIsReadOnly = r.mIsReadOnly;
+ mFloating = r.mFloating;
+
+ mBySeconds = r.mBySeconds;
+ mByMinutes = r.mByMinutes;
+ mByHours = r.mByHours;
+ mByDays = r.mByDays;
+ mByMonthDays = r.mByMonthDays;
+ mByYearDays = r.mByYearDays;
+ mByWeekNumbers = r.mByWeekNumbers;
+ mByMonths = r.mByMonths;
+ mBySetPos = r.mBySetPos;
+ mWeekStart = r.mWeekStart;
+
+ setDirty();
+}
+
+RecurrenceRule::~RecurrenceRule()
+{
+}
+
+bool RecurrenceRule::operator==( const RecurrenceRule& r ) const
+{
+ if ( mPeriod != r.mPeriod ) return false;
+ if ( mDateStart != r.mDateStart ) return false;
+ if ( mDuration != r.mDuration ) return false;
+ if ( mDateEnd != r.mDateEnd ) return false;
+ if ( mFrequency != r.mFrequency ) return false;
+
+ if ( mIsReadOnly != r.mIsReadOnly ) return false;
+ if ( mFloating != r.mFloating ) return false;
+
+ if ( mBySeconds != r.mBySeconds ) return false;
+ if ( mByMinutes != r.mByMinutes ) return false;
+ if ( mByHours != r.mByHours ) return false;
+ if ( mByDays != r.mByDays ) return false;
+ if ( mByMonthDays != r.mByMonthDays ) return false;
+ if ( mByYearDays != r.mByYearDays ) return false;
+ if ( mByWeekNumbers != r.mByWeekNumbers ) return false;
+ if ( mByMonths != r.mByMonths ) return false;
+ if ( mBySetPos != r.mBySetPos ) return false;
+ if ( mWeekStart != r.mWeekStart ) return false;
+
+ return true;
+}
+
+void RecurrenceRule::addObserver( Observer *observer )
+{
+ if ( !mObservers.contains( observer ) )
+ mObservers.append( observer );
+}
+
+void RecurrenceRule::removeObserver( Observer *observer )
+{
+ if ( mObservers.contains( observer ) )
+ mObservers.remove( observer );
+}
+
+
+
+void RecurrenceRule::setRecurrenceType( PeriodType period )
+{
+ if ( isReadOnly() ) return;
+ mPeriod = period;
+ setDirty();
+}
+
+QDateTime RecurrenceRule::endDt( bool *result ) const
+{
+ if ( result ) *result = false;
+ if ( mPeriod == rNone ) return QDateTime();
+ if ( mDuration < 0 ) return QDateTime();
+ if ( mDuration == 0 ) {
+ if ( result ) *result = true;
+ return mDateEnd;
+ }
+ // N occurrences. Check if we have a full cache. If so, return the cached end date.
+ if ( ! mCached ) {
+ // If not enough occurrences can be found (i.e. inconsistent constraints)
+ if ( !buildCache() ) return QDateTime();
+ }
+ if ( result ) *result = true;
+ return mCachedDateEnd;
+}
+
+void RecurrenceRule::setEndDt( const QDateTime &dateTime )
+{
+ if ( isReadOnly() ) return;
+ mDateEnd = dateTime;
+ mDuration = 0; // set to 0 because there is an end date/time
+ setDirty();
+}
+
+void RecurrenceRule::setDuration(int duration)
+{
+ if ( isReadOnly() ) return;
+ mDuration = duration;
+ setDirty();
+}
+
+void RecurrenceRule::setFloats( bool floats )
+{
+ if ( isReadOnly() ) return;
+ mFloating = floats;
+ setDirty();
+}
+
+void RecurrenceRule::clear()
+{
+ if ( isReadOnly() ) return;
+ mPeriod = rNone;
+ mBySeconds.clear();
+ mByMinutes.clear();
+ mByHours.clear();
+ mByDays.clear();
+ mByMonthDays.clear();
+ mByYearDays.clear();
+ mByWeekNumbers.clear();
+ mByMonths.clear();
+ mBySetPos.clear();
+ mWeekStart = 1;
+
+ setDirty();
+}
+
+void RecurrenceRule::setDirty()
+{
+ mConstraints.clear();
+ buildConstraints();
+ mDirty = true;
+ mCached = false;
+ mCachedDates.clear();
+ for ( QValueList<Observer*>::ConstIterator it = mObservers.begin();
+ it != mObservers.end(); ++it ) {
+ if ( (*it) ) (*it)->recurrenceChanged( this );
+ }
+}
+
+void RecurrenceRule::setStartDt( const QDateTime &start )
+{
+ if ( isReadOnly() ) return;
+ mDateStart = start;
+ setDirty();
+}
+
+void RecurrenceRule::setFrequency(int freq)
+{
+ if ( isReadOnly() || freq <= 0 ) return;
+ mFrequency = freq;
+ setDirty();
+}
+
+void RecurrenceRule::setBySeconds( const QValueList<int> bySeconds )
+{
+ if ( isReadOnly() ) return;
+ mBySeconds = bySeconds;
+ setDirty();
+}
+
+void RecurrenceRule::setByMinutes( const QValueList<int> byMinutes )
+{
+ if ( isReadOnly() ) return;
+ mByMinutes = byMinutes;
+ setDirty();
+}
+
+void RecurrenceRule::setByHours( const QValueList<int> byHours )
+{
+ if ( isReadOnly() ) return;
+ mByHours = byHours;
+ setDirty();
+}
+
+
+void RecurrenceRule::setByDays( const QValueList<WDayPos> byDays )
+{
+ if ( isReadOnly() ) return;
+ mByDays = byDays;
+ setDirty();
+}
+
+void RecurrenceRule::setByMonthDays( const QValueList<int> byMonthDays )
+{
+ if ( isReadOnly() ) return;
+ mByMonthDays = byMonthDays;
+ setDirty();
+}
+
+void RecurrenceRule::setByYearDays( const QValueList<int> byYearDays )
+{
+ if ( isReadOnly() ) return;
+ mByYearDays = byYearDays;
+ setDirty();
+}
+
+void RecurrenceRule::setByWeekNumbers( const QValueList<int> byWeekNumbers )
+{
+ if ( isReadOnly() ) return;
+ mByWeekNumbers = byWeekNumbers;
+ setDirty();
+}
+
+void RecurrenceRule::setByMonths( const QValueList<int> byMonths )
+{
+ if ( isReadOnly() ) return;
+ mByMonths = byMonths;
+ setDirty();
+}
+
+void RecurrenceRule::setBySetPos( const QValueList<int> bySetPos )
+{
+ if ( isReadOnly() ) return;
+ mBySetPos = bySetPos;
+ setDirty();
+}
+
+void RecurrenceRule::setWeekStart( short weekStart )
+{
+ if ( isReadOnly() ) return;
+ mWeekStart = weekStart;
+ setDirty();
+}
+
+
+
+// Taken from recurrence.cpp
+// int RecurrenceRule::maxIterations() const
+// {
+// /* Find the maximum number of iterations which may be needed to reach the
+// * next actual occurrence of a monthly or yearly recurrence.
+// * More than one iteration may be needed if, for example, it's the 29th February,
+// * the 31st day of the month or the 5th Monday, and the month being checked is
+// * February or a 30-day month.
+// * The following recurrences may never occur:
+// * - For rMonthlyDay: if the frequency is a whole number of years.
+// * - For rMonthlyPos: if the frequency is an even whole number of years.
+// * - For rYearlyDay, rYearlyMonth: if the frequeny is a multiple of 4 years.
+// * - For rYearlyPos: if the frequency is an even number of years.
+// * The maximum number of iterations needed, assuming that it does actually occur,
+// * was found empirically.
+// */
+// switch (recurs) {
+// case rMonthlyDay:
+// return (rFreq % 12) ? 6 : 8;
+//
+// case rMonthlyPos:
+// if (rFreq % 12 == 0) {
+// // Some of these frequencies may never occur
+// return (rFreq % 84 == 0) ? 364 // frequency = multiple of 7 years
+// : (rFreq % 48 == 0) ? 7 // frequency = multiple of 4 years
+// : (rFreq % 24 == 0) ? 14 : 28; // frequency = multiple of 2 or 1 year
+// }
+// // All other frequencies will occur sometime
+// if (rFreq > 120)
+// return 364; // frequencies of > 10 years will hit the date limit first
+// switch (rFreq) {
+// case 23: return 50;
+// case 46: return 38;
+// case 56: return 138;
+// case 66: return 36;
+// case 89: return 54;
+// case 112: return 253;
+// default: return 25; // most frequencies will need < 25 iterations
+// }
+//
+// case rYearlyMonth:
+// case rYearlyDay:
+// return 8; // only 29th Feb or day 366 will need more than one iteration
+//
+// case rYearlyPos:
+// if (rFreq % 7 == 0)
+// return 364; // frequencies of a multiple of 7 years will hit the date limit first
+// if (rFreq % 2 == 0) {
+// // Some of these frequencies may never occur
+// return (rFreq % 4 == 0) ? 7 : 14; // frequency = even number of years
+// }
+// return 28;
+// }
+// return 1;
+// }
+
+void RecurrenceRule::buildConstraints()
+{
+ mConstraints.clear();
+ Constraint con;
+ if ( mWeekStart > 0 ) con.weekstart = mWeekStart;
+ mConstraints.append( con );
+
+ Constraint::List tmp;
+ Constraint::List::const_iterator it;
+ QValueList<int>::const_iterator intit;
+
+ #define intConstraint( list, element ) \
+ if ( !list.isEmpty() ) { \
+ for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) { \
+ for ( intit = list.constBegin(); intit != list.constEnd(); ++intit ) { \
+ con = (*it); \
+ con.element = (*intit); \
+ tmp.append( con ); \
+ } \
+ } \
+ mConstraints = tmp; \
+ tmp.clear(); \
+ }
+
+ intConstraint( mBySeconds, second );
+ intConstraint( mByMinutes, minute );
+ intConstraint( mByHours, hour );
+ intConstraint( mByMonthDays, day );
+ intConstraint( mByMonths, month );
+ intConstraint( mByYearDays, yearday );
+ intConstraint( mByWeekNumbers, weeknumber );
+ #undef intConstraint
+
+ if ( !mByDays.isEmpty() ) {
+ for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) {
+ QValueList<WDayPos>::const_iterator dayit;
+ for ( dayit = mByDays.constBegin(); dayit != mByDays.constEnd(); ++dayit ) {
+ con = (*it);
+ con.weekday = (*dayit).day();
+ con.weekdaynr = (*dayit).pos();
+ tmp.append( con );
+ }
+ }
+ mConstraints = tmp;
+ tmp.clear();
+ }
+
+ #define fixConstraint( element, value ) \
+ { \
+ tmp.clear(); \
+ for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) { \
+ con = (*it); con.element = value; tmp.append( con ); \
+ } \
+ mConstraints = tmp; \
+ }
+ // Now determine missing values from DTSTART. This can speed up things,
+ // because we have more restrictions and save some loops.
+
+ // TODO: Does RFC 2445 intend to restrict the weekday in all cases of weekly?
+ if ( mPeriod == rWeekly && mByDays.isEmpty() ) {
+ fixConstraint( weekday, mDateStart.date().dayOfWeek() );
+ }
+
+ // Really fall through in the cases, because all smaller time intervals are
+ // constrained from dtstart
+ switch ( mPeriod ) {
+ case rYearly:
+ if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() && mByYearDays.isEmpty() && mByMonths.isEmpty() ) {
+ fixConstraint( month, mDateStart.date().month() );
+ }
+ case rMonthly:
+ if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() && mByYearDays.isEmpty() && mByMonthDays.isEmpty() ) {
+ fixConstraint( day, mDateStart.date().day() );
+ }
+
+ case rWeekly:
+ case rDaily:
+ if ( mByHours.isEmpty() ) {
+ fixConstraint( hour, mDateStart.time().hour() );
+ }
+ case rHourly:
+ if ( mByMinutes.isEmpty() ) {
+ fixConstraint( minute, mDateStart.time().minute() );
+ }
+ case rMinutely:
+ if ( mBySeconds.isEmpty() ) {
+ fixConstraint( second, mDateStart.time().second() );
+ }
+ case rSecondly:
+ default:
+ break;
+ }
+ #undef fixConstraint
+
+ Constraint::List::Iterator conit = mConstraints.begin();
+ while ( conit != mConstraints.end() ) {
+ if ( (*conit).isConsistent( mPeriod ) ) {
+ ++conit;
+ } else {
+ conit = mConstraints.remove( conit );
+ }
+ }
+}
+
+bool RecurrenceRule::buildCache() const
+{
+kdDebug(5800) << " RecurrenceRule::buildCache: " << endl;
+ // Build the list of all occurrences of this event (we need that to determine
+ // the end date!)
+ Constraint interval( getNextValidDateInterval( startDt(), recurrenceType() ) );
+ QDateTime next;
+
+ DateTimeList dts = datesForInterval( interval, recurrenceType() );
+ DateTimeList::Iterator it = dts.begin();
+ // Only use dates after the event has started (start date is only included
+ // if it matches)
+ while ( it != dts.end() ) {
+ if ( (*it) < startDt() ) it = dts.remove( it );
+ else ++it;
+ }
+// dts.prepend( startDt() ); // the start date is always the first occurrence
+
+
+ int loopnr = 0;
+ int dtnr = dts.count();
+ // some validity checks to avoid infinite loops (i.e. if we have
+ // done this loop already 10000 times and found no occurrence, bail out )
+ while ( loopnr < 10000 && dtnr < mDuration ) {
+ interval.increase( recurrenceType(), frequency() );
+ // The returned date list is already sorted!
+ dts += datesForInterval( interval, recurrenceType() );
+ dtnr = dts.count();
+ ++loopnr;
+ }
+ if ( int(dts.count()) > mDuration ) {
+ // we have picked up more occurrences than necessary, remove them
+ it = dts.at( mDuration );
+ while ( it != dts.end() ) it = dts.remove( it );
+ }
+ mCached = true;
+ mCachedDates = dts;
+
+kdDebug(5800) << " Finished Building Cache, cache has " << dts.count() << " entries:" << endl;
+// it = dts.begin();
+// while ( it != dts.end() ) {
+// kdDebug(5800) << " -=> " << (*it) << endl;
+// ++it;
+// }
+ if ( int(dts.count()) == mDuration ) {
+ mCachedDateEnd = dts.last();
+ return true;
+ } else {
+ mCachedDateEnd = QDateTime();
+ return false;
+ }
+}
+
+bool RecurrenceRule::dateMatchesRules( const QDateTime &qdt ) const
+{
+ bool match = false;
+ for ( Constraint::List::ConstIterator it = mConstraints.begin();
+ it!=mConstraints.end(); ++it ) {
+ match = match || ( (*it).matches( qdt, recurrenceType() ) );
+ }
+ return match;
+}
+
+bool RecurrenceRule::recursOn( const QDate &qd ) const
+{
+// kdDebug(5800) << " RecurrenceRule::recursOn: " << qd << endl;
+ if ( qd < startDt().date() ) return false;
+ // Start date is only included if it really matches
+// if ( qd == startDt().date() ) return true;
+ if ( mDuration >= 0 && qd > endDt().date() ) return false;
+
+ // The date must be in an appropriate interval (getNextValidDateInterval),
+ // Plus it must match at least one of the constraints
+ bool match = false;
+ for ( Constraint::List::ConstIterator it = mConstraints.begin();
+ it!=mConstraints.end(); ++it ) {
+ match = match || ( (*it).matches( qd, recurrenceType() ) );
+ }
+ if ( !match ) return false;
+ QDateTime tmp( qd, QTime( 0, 0, 0 ) );
+ Constraint interval( getNextValidDateInterval( tmp, recurrenceType() ) );
+ // Constraint::matches is quite efficient, so first check if it can occur at
+ // all before we calculate all actual dates.
+ if ( !interval.matches( qd, recurrenceType() ) ) return false;
+ // We really need to obtain the list of dates in this interval, since
+ // otherwise BYSETPOS will not work (i.e. the date will match the interval,
+ // but BYSETPOS selects only one of these matching dates!
+ DateTimeList times = datesForInterval( interval, recurrenceType() );
+ DateTimeList::ConstIterator it = times.begin();
+ while ( ( it != times.end() ) && ( (*it).date() < qd ) )
+ ++it;
+ if ( it != times.end() ) {
+ // If we are beyond the end...
+ if ( mDuration >= 0 && (*it) > endDt() )
+ return false;
+ if ( (*it).date() == qd )
+ return true;
+ }
+ return false;
+}
+
+
+bool RecurrenceRule::recursAt( const QDateTime &qd ) const
+{
+// kdDebug(5800) << " RecurrenceRule::recursAt: " << qd << endl;
+ if ( doesFloat() ) return recursOn( qd.date() );
+ if ( qd < startDt() ) return false;
+ // Start date is only included if it really matches
+// if ( qd == startDt() ) return true;
+ if ( mDuration >= 0 && qd > endDt() ) return false;
+
+ // The date must be in an appropriate interval (getNextValidDateInterval),
+ // Plus it must match at least one of the constraints
+ bool match = dateMatchesRules( qd );
+ if ( !match ) return false;
+ // if it recurs every interval, speed things up...
+// if ( mFrequency == 1 && mBySetPos.isEmpty() && mByDays.isEmpty() ) return true;
+ Constraint interval( getNextValidDateInterval( qd, recurrenceType() ) );
+ // TODO_Recurrence: Does this work with BySetPos???
+ if ( interval.matches( qd, recurrenceType() ) ) return true;
+
+ return false;
+}
+
+
+TimeList RecurrenceRule::recurTimesOn( const QDate &date ) const
+{
+// kdDebug(5800) << " RecurrenceRule::recurTimesOn: " << date << endl;
+ TimeList lst;
+ if ( !recursOn( date ) ) return lst;
+
+ if ( doesFloat() ) return lst;
+
+ QDateTime dt( date, QTime( 0, 0, 0 ) );
+ bool valid = dt.isValid() && ( dt.date() == date );
+ while ( valid ) {
+ // TODO: Add a flag so that the date is never increased!
+ dt = getNextDate( dt );
+ valid = dt.isValid() && ( dt.date() == date );
+ if ( valid ) lst.append( dt.time() );
+ }
+ return lst;
+}
+
+/** Returns the number of recurrences up to and including the date/time specified. */
+int RecurrenceRule::durationTo( const QDateTime &dt ) const
+{
+// kdDebug(5800) << " RecurrenceRule::durationTo: " << dt << endl;
+ // Easy cases: either before start, or after all recurrences and we know
+ // their number
+ if ( dt < startDt() ) return 0;
+ // Start date is only included if it really matches
+// if ( dt == startDt() ) return 1;
+ if ( mDuration > 0 && dt >= endDt() ) return mDuration;
+
+ QDateTime next( startDt() );
+ int found = 0;
+ while ( next.isValid() && next <= dt ) {
+ ++found;
+ next = getNextDate( next );
+ }
+ return found;
+}
+
+
+QDateTime RecurrenceRule::getPreviousDate( const QDateTime& afterDate ) const
+{
+// kdDebug(5800) << " RecurrenceRule::getPreviousDate: " << afterDate << endl;
+ // Beyond end of recurrence
+ if ( afterDate < startDt() )
+ return QDateTime();
+
+ // If we have a cache (duration given), use that
+ QDateTime prev;
+ if ( mDuration > 0 ) {
+ if ( !mCached ) buildCache();
+ DateTimeList::ConstIterator it = mCachedDates.begin();
+ while ( it != mCachedDates.end() && (*it) < afterDate ) {
+ prev = *it;
+ ++it;
+ }
+ if ( prev.isValid() && prev < afterDate ) return prev;
+ else return QDateTime();
+ }
+
+// kdDebug(5800) << " getNext date after " << preDate << endl;
+ prev = afterDate;
+ if ( mDuration >= 0 && endDt().isValid() && afterDate > endDt() )
+ prev = endDt().addSecs( 1 );
+
+ Constraint interval( getPreviousValidDateInterval( prev, recurrenceType() ) );
+// kdDebug(5800) << "Previous Valid Date Interval for date " << prev << ": " << endl;
+// interval.dump();
+ DateTimeList dts = datesForInterval( interval, recurrenceType() );
+ DateTimeList::Iterator dtit = dts.end();
+ if ( dtit != dts.begin() ) {
+ do {
+ --dtit;
+ } while ( dtit != dts.begin() && (*dtit) >= prev );
+ if ( (*dtit) < prev ) {
+ if ( (*dtit) >= startDt() ) return (*dtit);
+ else return QDateTime();
+ }
+ }
+
+ // Previous interval. As soon as we find an occurrence, we're done.
+ while ( interval.intervalDateTime( recurrenceType() ) > startDt() ) {
+ interval.increase( recurrenceType(), -frequency() );
+// kdDebug(5800) << "Decreased interval: " << endl;
+// interval.dump();
+ // The returned date list is sorted
+ DateTimeList dts = datesForInterval( interval, recurrenceType() );
+ // The list is sorted, so take the last one.
+ if ( dts.count() > 0 ) {
+ prev = dts.last();
+ if ( prev.isValid() && prev >= startDt() ) return prev;
+ else return QDateTime();
+ }
+ }
+ return QDateTime();
+}
+
+
+QDateTime RecurrenceRule::getNextDate( const QDateTime &preDate ) const
+{
+// kdDebug(5800) << " RecurrenceRule::getNextDate: " << preDate << endl;
+ // Beyond end of recurrence
+ if ( mDuration >= 0 && endDt().isValid() && preDate >= endDt() )
+ return QDateTime();
+
+ // Start date is only included if it really matches
+ QDateTime adjustedPreDate;
+ if ( preDate < startDt() )
+ adjustedPreDate = startDt().addSecs( -1 );
+ else
+ adjustedPreDate = preDate;
+
+ if ( mDuration > 0 ) {
+ if ( !mCached ) buildCache();
+ DateTimeList::ConstIterator it = mCachedDates.begin();
+ while ( it != mCachedDates.end() && (*it) <= adjustedPreDate ) ++it;
+ if ( it != mCachedDates.end() ) {
+// kdDebug(5800) << " getNext date after " << adjustedPreDate << ", cached date: " << *it << endl;
+ return (*it);
+ }
+ }
+
+// kdDebug(5800) << " getNext date after " << adjustedPreDate << endl;
+ Constraint interval( getNextValidDateInterval( adjustedPreDate, recurrenceType() ) );
+ DateTimeList dts = datesForInterval( interval, recurrenceType() );
+ DateTimeList::Iterator dtit = dts.begin();
+ while ( dtit != dts.end() && (*dtit) <= adjustedPreDate ) ++dtit;
+ if ( dtit != dts.end() ) {
+ if ( mDuration >= 0 && (*dtit) > endDt() ) return QDateTime();
+ else return (*dtit);
+ }
+
+ // Increase the interval. The first occurrence that we find is the result (if
+ // if's before the end date).
+ // TODO: some validity checks to avoid infinite loops for contradictory constraints
+ int loopnr = 0;
+ while ( loopnr < 10000 ) {
+ interval.increase( recurrenceType(), frequency() );
+ DateTimeList dts = datesForInterval( interval, recurrenceType() );
+ if ( dts.count() > 0 ) {
+ QDateTime ret( dts.first() );
+ if ( mDuration >= 0 && ret > endDt() ) return QDateTime();
+ else return ret;
+ }
+ ++loopnr;
+ }
+ return QDateTime();
+}
+
+RecurrenceRule::Constraint RecurrenceRule::getPreviousValidDateInterval( const QDateTime &preDate, PeriodType type ) const
+{
+// kdDebug(5800) << " (o) getPreviousValidDateInterval after " << preDate << ", type=" << type << endl;
+ long periods = 0;
+ QDateTime nextValid = startDt();
+ QDateTime start = startDt();
+ int modifier = 1;
+ QDateTime toDate( preDate );
+ // for super-daily recurrences, don't care about the time part
+
+ // Find the #intervals since the dtstart and round to the next multiple of
+ // the frequency
+ // FIXME: All sub-daily periods need to convert to UTC, do the calculations
+ // in UTC, then convert back to the local time zone. Otherwise,
+ // recurrences across DST changes will be determined wrongly
+ switch ( type ) {
+ // Really fall through for sub-daily, since the calculations only differ
+ // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
+ case rHourly: modifier *= 60;
+ case rMinutely: modifier *= 60;
+ case rSecondly:
+ periods = ownSecsTo( start, toDate ) / modifier;
+ // round it down to the next lower multiple of frequency():
+ periods = ( periods / frequency() ) * frequency();
+ nextValid = start.addSecs( modifier * periods );
+ break;
+
+ case rWeekly:
+ toDate = toDate.addDays( -(7 + toDate.date().dayOfWeek() - mWeekStart) % 7 );
+ start = start.addDays( -(7 + start.date().dayOfWeek() - mWeekStart) % 7 );
+ modifier *= 7;
+ case rDaily:
+ periods = start.daysTo( toDate ) / modifier;
+ // round it down to the next lower multiple of frequency():
+ periods = ( periods / frequency() ) * frequency();
+ nextValid = start.addDays( modifier * periods );
+ break;
+
+ case rMonthly: {
+ periods = 12*( toDate.date().year() - start.date().year() ) +
+ ( toDate.date().month() - start.date().month() );
+ // round it down to the next lower multiple of frequency():
+ periods = ( periods / frequency() ) * frequency();
+ // set the day to the first day of the month, so we don't have problems
+ // with non-existent days like Feb 30 or April 31
+ start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
+ nextValid.setDate( start.date().addMonths( periods ) );
+ break; }
+ case rYearly:
+ periods = ( toDate.date().year() - start.date().year() );
+ // round it down to the next lower multiple of frequency():
+ periods = ( periods / frequency() ) * frequency();
+ nextValid.setDate( start.date().addYears( periods ) );
+ break;
+ default:
+ break;
+ }
+// kdDebug(5800) << " ~~~> date in previous interval is: : " << nextValid << endl;
+
+ return Constraint( nextValid, type, mWeekStart );
+}
+
+RecurrenceRule::Constraint RecurrenceRule::getNextValidDateInterval( const QDateTime &preDate, PeriodType type ) const
+{
+ // TODO: Simplify this!
+ kdDebug(5800) << " (o) getNextValidDateInterval after " << preDate << ", type=" << type << endl;
+ long periods = 0;
+ QDateTime start = startDt();
+ QDateTime nextValid( start );
+ int modifier = 1;
+ QDateTime toDate( preDate );
+ // for super-daily recurrences, don't care about the time part
+
+ // Find the #intervals since the dtstart and round to the next multiple of
+ // the frequency
+ // FIXME: All sub-daily periods need to convert to UTC, do the calculations
+ // in UTC, then convert back to the local time zone. Otherwise,
+ // recurrences across DST changes will be determined wrongly
+ switch ( type ) {
+ // Really fall through for sub-daily, since the calculations only differ
+ // by the factor 60 and 60*60! Same for weekly and daily (factor 7)
+ case rHourly: modifier *= 60;
+ case rMinutely: modifier *= 60;
+ case rSecondly:
+ periods = ownSecsTo( start, toDate ) / modifier;
+ periods = QMAX( 0, periods);
+ if ( periods > 0 )
+ periods += ( frequency() - 1 - ( (periods - 1) % frequency() ) );
+ nextValid = start.addSecs( modifier * periods );
+ break;
+
+ case rWeekly:
+ // correct both start date and current date to start of week
+ toDate = toDate.addDays( -(7 + toDate.date().dayOfWeek() - mWeekStart) % 7 );
+ start = start.addDays( -(7 + start.date().dayOfWeek() - mWeekStart) % 7 );
+ modifier *= 7;
+ case rDaily:
+ periods = start.daysTo( toDate ) / modifier;
+ periods = QMAX( 0, periods);
+ if ( periods > 0 )
+ periods += (frequency() - 1 - ( (periods - 1) % frequency() ) );
+ nextValid = start.addDays( modifier * periods );
+ break;
+
+ case rMonthly: {
+ periods = 12*( toDate.date().year() - start.date().year() ) +
+ ( toDate.date().month() - start.date().month() );
+ periods = QMAX( 0, periods);
+ if ( periods > 0 )
+ periods += (frequency() - 1 - ( (periods - 1) % frequency() ) );
+ // set the day to the first day of the month, so we don't have problems
+ // with non-existent days like Feb 30 or April 31
+ start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
+ nextValid.setDate( start.date().addMonths( periods ) );
+ break; }
+ case rYearly:
+ periods = ( toDate.date().year() - start.date().year() );
+ periods = QMAX( 0, periods);
+ if ( periods > 0 )
+ periods += ( frequency() - 1 - ( (periods - 1) % frequency() ) );
+ nextValid.setDate( start.date().addYears( periods ) );
+ break;
+ default:
+ break;
+ }
+// kdDebug(5800) << " ~~~> date in next interval is: : " << nextValid << endl;
+
+ return Constraint( nextValid, type, mWeekStart );
+}
+
+bool RecurrenceRule::mergeIntervalConstraint( Constraint *merged,
+ const Constraint &conit, const Constraint &interval ) const
+{
+ Constraint result( interval );
+
+#define mergeConstraint( name, cmparison ) \
+ if ( conit.name cmparison ) { \
+ if ( !(result.name cmparison) || result.name == conit.name ) { \
+ result.name = conit.name; \
+ } else return false;\
+ }
+
+ mergeConstraint( year, > 0 );
+ mergeConstraint( month, > 0 );
+ mergeConstraint( day, != 0 );
+ mergeConstraint( hour, >= 0 );
+ mergeConstraint( minute, >= 0 );
+ mergeConstraint( second, >= 0 );
+
+ mergeConstraint( weekday, != 0 );
+ mergeConstraint( weekdaynr, != 0 );
+ mergeConstraint( weeknumber, != 0 );
+ mergeConstraint( yearday, != 0 );
+
+ #undef mergeConstraint
+ if ( merged ) *merged = result;
+ return true;
+}
+
+
+DateTimeList RecurrenceRule::datesForInterval( const Constraint &interval, PeriodType type ) const
+{
+ /* -) Loop through constraints,
+ -) merge interval with each constraint
+ -) if merged constraint is not consistent => ignore that constraint
+ -) if complete => add that one date to the date list
+ -) Loop through all missing fields => For each add the resulting
+ */
+// kdDebug(5800) << " RecurrenceRule::datesForInterval: " << endl;
+// interval.dump();
+ DateTimeList lst;
+ Constraint::List::ConstIterator conit = mConstraints.begin();
+ for ( ; conit != mConstraints.end(); ++conit ) {
+ Constraint merged;
+ bool mergeok = mergeIntervalConstraint( &merged, *conit, interval );
+ // If the information is incomplete, we can't use this constraint
+ if ( merged.year <= 0 || merged.hour < 0 || merged.minute < 0 || merged.second < 0 )
+ mergeok = false;
+ if ( mergeok ) {
+// kdDebug(5800) << " -) merged constraint: " << endl;
+// merged.dump();
+ // We have a valid constraint, so get all datetimes that match it andd
+ // append it to all date/times of this interval
+ DateTimeList lstnew = merged.dateTimes( type );
+ lst += lstnew;
+ }
+ }
+ // Sort it so we can apply the BySetPos. Also some logic relies on this being sorted
+ qSortUnique( lst );
+
+
+/*if ( lst.isEmpty() ) {
+ kdDebug(5800) << " No Dates in Interval " << endl;
+} else {
+ kdDebug(5800) << " Dates: " << endl;
+ for ( DateTimeList::Iterator it = lst.begin(); it != lst.end(); ++it ) {
+ kdDebug(5800)<< " -) " << (*it).toString() << endl;
+ }
+ kdDebug(5800) << " ---------------------" << endl;
+}*/
+ if ( !mBySetPos.isEmpty() ) {
+ DateTimeList tmplst = lst;
+ lst.clear();
+ QValueList<int>::ConstIterator it;
+ for ( it = mBySetPos.begin(); it != mBySetPos.end(); ++it ) {
+ int pos = *it;
+ if ( pos > 0 ) --pos;
+ if ( pos < 0 ) pos += tmplst.count();
+ if ( pos >= 0 && uint(pos) < tmplst.count() ) {
+ lst.append( tmplst[pos] );
+ }
+ }
+ qSortUnique( lst );
+ }
+
+ return lst;
+}
+
+
+void RecurrenceRule::dump() const
+{
+#ifndef NDEBUG
+ kdDebug(5800) << "RecurrenceRule::dump():" << endl;
+ if ( !mRRule.isEmpty() )
+ kdDebug(5800) << " RRULE=" << mRRule << endl;
+ kdDebug(5800) << " Read-Only: " << isReadOnly() <<
+ ", dirty: " << mDirty << endl;
+
+ kdDebug(5800) << " Period type: " << recurrenceType() << ", frequency: " << frequency() << endl;
+ kdDebug(5800) << " #occurrences: " << duration() << endl;
+ kdDebug(5800) << " start date: " << startDt() <<", end date: " << endDt() << endl;
+
+
+#define dumpByIntList(list,label) \
+ if ( !list.isEmpty() ) {\
+ QStringList lst;\
+ for ( QValueList<int>::ConstIterator it = list.begin();\
+ it != list.end(); ++it ) {\
+ lst.append( QString::number( *it ) );\
+ }\
+ kdDebug(5800) << " " << label << lst.join(", ") << endl;\
+ }
+ dumpByIntList( mBySeconds, "BySeconds: " );
+ dumpByIntList( mByMinutes, "ByMinutes: " );
+ dumpByIntList( mByHours, "ByHours: " );
+ if ( !mByDays.isEmpty() ) {
+ QStringList lst;
+ for ( QValueList<WDayPos>::ConstIterator it = mByDays.begin();
+ it != mByDays.end(); ++it ) {
+ lst.append( ( ((*it).pos()!=0) ? QString::number( (*it).pos() ) : "" ) +
+ DateHelper::dayName( (*it).day() ) );
+ }
+ kdDebug(5800) << " ByDays: " << lst.join(", ") << endl;
+ }
+ dumpByIntList( mByMonthDays, "ByMonthDays:" );
+ dumpByIntList( mByYearDays, "ByYearDays: " );
+ dumpByIntList( mByWeekNumbers,"ByWeekNr: " );
+ dumpByIntList( mByMonths, "ByMonths: " );
+ dumpByIntList( mBySetPos, "BySetPos: " );
+ #undef dumpByIntList
+
+ kdDebug(5800) << " Week start: " << DateHelper::dayName( mWeekStart ) << endl;
+
+ kdDebug(5800) << " Constraints:" << endl;
+ // dump constraints
+ for ( Constraint::List::ConstIterator it = mConstraints.begin();
+ it!=mConstraints.end(); ++it ) {
+ (*it).dump();
+ }
+#endif
+}
+
+void RecurrenceRule::Constraint::dump() const
+{
+ kdDebug(5800) << " ~> Y="<<year<<", M="<<month<<", D="<<day<<", H="<<hour<<", m="<<minute<<", S="<<second<<", wd="<<weekday<<",#wd="<<weekdaynr<<", #w="<<weeknumber<<", yd="<<yearday<<endl;
+}