/* This file is part of libkcal. Copyright (c) 2005 Reinhold Kainhofer 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 #include #include #include #include #include using namespace KCal; // Maximum number of intervals to process const int LOOP_LIMIT = 10000; // FIXME: If TQt is ever changed so that TQDateTime:::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 TQDateTime::secsTo (at least in TQt 3.3). While TQDateTime::secsTo does take time zones into account, TQDateTime::addSecs does not, so we have a problem: TQDateTime d1(TQDate(2005, 10, 30), TQTime(1, 30, 0) ); TQDateTime d2(TQDate(2005, 10, 30), TQTime(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 TQDateTime 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 TQDateTime &dt1, const TQDateTime &dt2 ) { long long res = static_cast( dt1.date().daysTo( dt2.date() ) ) * 24*3600; res += dt1.time().secsTo( dt2.time() ); return res; } /************************************************************************** * DateHelper * **************************************************************************/ class DateHelper { public: #ifndef NDEBUG static TQString dayName( short day ); #endif static TQDate getNthWeek( int year, int weeknumber, short weekstart = 1 ); static int weekNumbersInYear( int year, short weekstart = 1 ); static int getWeekNumber( const TQDate &date, short weekstart, int *year = 0 ); static int getWeekNumberNeg( const TQDate &date, short weekstart, int *year = 0 ); }; #ifndef NDEBUG TQString 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 TQDate DateHelper::getNthWeek( int year, int weeknumber, short weekstart ) { if ( weeknumber == 0 ) return TQDate(); // Adjust this to the first day of week #1 of the year and add 7*weekno days. TQDate 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 TQDate &date, short weekstart, int *year ) { // kdDebug(5800) << "Getting week number for " << date << " with weekstart="<= 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 ) { TQDate dt( year, 1, weekstart ); TQDate dt1( year + 1, 1, weekstart ); return dt.daysTo( dt1 ) / 7; } // Week number from the end of the year int DateHelper::getWeekNumberNeg( const TQDate &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 TQDateTime &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 TQDate &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 TQDateTime &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; } TQDateTime RecurrenceRule::Constraint::intervalDateTime( RecurrenceRule::PeriodType type ) const { TQDateTime dt; dt.setTime( TQTime( 0, 0, 0 ) ); dt.setDate( TQDate( 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( TQTime( hour, minute, second ) ); break; case rMinutely: dt.setTime( TQTime( hour, minute, 1 ) ); break; case rHourly: dt.setTime( TQTime( hour, 1, 1 ) ); break; case rDaily: break; case rWeekly: dt = DateHelper::getNthWeek( year, weeknumber, weekstart ); break; case rMonthly: dt.setDate( TQDate( year, month, 1 ) ); break; case rYearly: dt.setDate( TQDate( 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 TQTime tm( hour, minute, second ); if ( !isConsistent( type ) ) return result; if ( !done && day > 0 && month > 0 ) { TQDateTime dt( TQDate( year, month, day ), tm ); if ( dt.isValid() ) result.append( dt ); done = true; } if ( !done && day < 0 && month > 0 ) { TQDateTime dt( TQDate( 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 ) { TQDate date( year, month, 1 ); dstart = dend = date.daysInMonth() + day + 1; } else { TQDate date( year, month, 1 ); dstart = 1; dend = date.daysInMonth(); } for ( uint d = dstart; d <= dend; ++d ) { TQDateTime dt( TQDate( 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 TQDate d( year + ((yearday>0)?0:1), 1, 1 ); d = d.addDays( yearday - ((yearday>0)?1:0) ); result.append( TQDateTime( d, tm ) ); done = true; } // Else: If we have a weeknumber, we have at most 7 possible dates, loop through them if ( !done && weeknumber != 0 ) { TQDate wst( DateHelper::getNthWeek( year, weeknumber, weekstart ) ); if ( weekday != 0 ) { wst = wst.addDays( (7 + weekday - weekstart ) % 7 ); result.append( TQDateTime( wst, tm ) ); } else { for ( int i = 0; i < 7; ++i ) { result.append( TQDateTime( wst, tm ) ); wst = wst.addDays( 1 ); } } done = true; } // weekday is given if ( !done && weekday != 0 ) { TQDate 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 = TQDate( 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( TQDateTime( dt, tm ) ); } else if ( weekdaynr < 0 ) { dt = dt.addDays( weekdaynr * 7 ); result.append( TQDateTime( dt, tm ) ); } else { // loop through all possible weeks, non-matching will be filtered later for ( int i = 0; i < maxloop; ++i ) { result.append( TQDateTime( 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 TQDateTime // Sub-daily types need to be converted to UTC to correctly handle DST shifts TQDateTime 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 TQDateTime to the Constraint class readDateTime( dt, type ); return true; } bool RecurrenceRule::Constraint::readDateTime( const TQDateTime &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(); } TQDateTime RecurrenceRule::endDt( bool *result ) const { if ( result ) *result = false; if ( mPeriod == rNone ) return TQDateTime(); if ( mDuration < 0 ) return TQDateTime(); 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 TQDateTime(); } if ( result ) *result = true; return mCachedDateEnd; } void RecurrenceRule::setEndDt( const TQDateTime &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 ( TQValueList::ConstIterator it = mObservers.begin(); it != mObservers.end(); ++it ) { if ( (*it) ) (*it)->recurrenceChanged( this ); } } void RecurrenceRule::setStartDt( const TQDateTime &start ) { if ( isReadOnly() ) return; mDateStart = start; setDirty(); } void RecurrenceRule::setFrequency(int freq) { if ( isReadOnly() || freq <= 0 ) return; mFrequency = freq; setDirty(); } void RecurrenceRule::setBySeconds( const TQValueList bySeconds ) { if ( isReadOnly() ) return; mBySeconds = bySeconds; setDirty(); } void RecurrenceRule::setByMinutes( const TQValueList byMinutes ) { if ( isReadOnly() ) return; mByMinutes = byMinutes; setDirty(); } void RecurrenceRule::setByHours( const TQValueList byHours ) { if ( isReadOnly() ) return; mByHours = byHours; setDirty(); } void RecurrenceRule::setByDays( const TQValueList byDays ) { if ( isReadOnly() ) return; mByDays = byDays; setDirty(); } void RecurrenceRule::setByMonthDays( const TQValueList byMonthDays ) { if ( isReadOnly() ) return; mByMonthDays = byMonthDays; setDirty(); } void RecurrenceRule::setByYearDays( const TQValueList byYearDays ) { if ( isReadOnly() ) return; mByYearDays = byYearDays; setDirty(); } void RecurrenceRule::setByWeekNumbers( const TQValueList byWeekNumbers ) { if ( isReadOnly() ) return; mByWeekNumbers = byWeekNumbers; setDirty(); } void RecurrenceRule::setByMonths( const TQValueList byMonths ) { if ( isReadOnly() ) return; mByMonths = byMonths; setDirty(); } void RecurrenceRule::setBySetPos( const TQValueList 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() { mTimedRepetition = 0; mNoByRules = mBySetPos.isEmpty(); mConstraints.clear(); Constraint con; if ( mWeekStart > 0 ) con.weekstart = mWeekStart; mConstraints.append( con ); Constraint::List tmp; Constraint::List::const_iterator it; TQValueList::const_iterator intit; #define intConstraint( list, element ) \ if ( !list.isEmpty() ) { \ mNoByRules = false; \ 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() ) { mNoByRules = false; for ( it = mConstraints.constBegin(); it != mConstraints.constEnd(); ++it ) { TQValueList::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 if ( mNoByRules ) { switch ( mPeriod ) { case rHourly: mTimedRepetition = mFrequency * 3600; break; case rMinutely: mTimedRepetition = mFrequency * 60; break; case rSecondly: mTimedRepetition = mFrequency; break; default: break; } } else { 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() ) ); TQDateTime 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 = TQDateTime(); return false; } } bool RecurrenceRule::dateMatchesRules( const TQDateTime &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 TQDate &qd ) const { int i, iend; if ( doesFloat() ) { // It's a date-only rule, so it has no time specification. if ( qd < mDateStart.date() ) { return false; } // Start date is only included if it really matches TQDate endDate; if ( mDuration >= 0 ) { endDate = endDt().date(); if ( qd > endDate ) { 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 ( i = 0, iend = mConstraints.count(); i < iend && !match; ++i ) { match = mConstraints[i].matches( qd, recurrenceType() ); } if ( !match ) { return false; } TQDateTime start( qd, TQTime( 0, 0, 0 ) ); Constraint interval( getNextValidDateInterval( start, 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! TQDateTime end = start.addDays(1); do { DateTimeList dts = datesForInterval( interval, recurrenceType() ); for ( i = 0, iend = dts.count(); i < iend; ++i ) { if ( dts[i].date() >= qd ) { return dts[i].date() == qd; } } interval.increase( recurrenceType(), frequency() ); } while ( interval.intervalDateTime( recurrenceType() ) < end ); return false; } // It's a date-time rule, so we need to take the time specification into account. TQDateTime start( qd, TQTime( 0, 0, 0 ) ); TQDateTime end = start.addDays( 1 ); if ( end < mDateStart ) { return false; } if ( start < mDateStart ) { start = mDateStart; } // Start date is only included if it really matches if ( mDuration >= 0 ) { TQDateTime endRecur = endDt(); if ( endRecur.isValid() ) { if ( start > endRecur ) { return false; } if ( end > endRecur ) { end = endRecur; // limit end-of-day time to end of recurrence rule } } } if ( mTimedRepetition ) { // It's a simple sub-daily recurrence with no constraints int n = static_cast( ( mDateStart.secsTo( start ) - 1 ) % mTimedRepetition ); return start.addSecs( mTimedRepetition - n ) < end; } // Find the start and end dates in the time spec for the rule TQDate startDay = start.date(); TQDate endDay = end.addSecs( -1 ).date(); int dayCount = startDay.daysTo( endDay ) + 1; // The date must be in an appropriate interval (getNextValidDateInterval), // Plus it must match at least one of the constraints bool match = false; for ( i = 0, iend = mConstraints.count(); i < iend && !match; ++i ) { match = mConstraints[i].matches( startDay, recurrenceType() ); for ( int day = 1; day < dayCount && !match; ++day ) { match = mConstraints[i].matches( startDay.addDays( day ), recurrenceType() ); } } if ( !match ) { return false; } Constraint interval( getNextValidDateInterval( start, recurrenceType() ) ); // Constraint::matches is quite efficient, so first check if it can occur at // all before we calculate all actual dates. match = false; Constraint intervalm = interval; do { match = intervalm.matches( startDay, recurrenceType() ); for ( int day = 1; day < dayCount && !match; ++day ) { match = intervalm.matches( startDay.addDays( day ), recurrenceType() ); } if ( match ) { break; } intervalm.increase( recurrenceType(), frequency() ); } while ( intervalm.intervalDateTime( recurrenceType() ) < end ); if ( !match ) { 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! do { DateTimeList dts = datesForInterval( interval, recurrenceType() ); int i = findGE( dts, start, 0 ); if ( i >= 0 ) { return dts[i] < end; } interval.increase( recurrenceType(), frequency() ); } while ( interval.intervalDateTime( recurrenceType() ) < end ); return false; } bool RecurrenceRule::recursAt( const TQDateTime &dt ) const { if ( doesFloat() ) { return recursOn( dt.date() ); } if ( dt < mDateStart ) { return false; } // Start date is only included if it really matches if ( mDuration >= 0 && dt > endDt() ) { return false; } if ( mTimedRepetition ) { // It's a simple sub-daily recurrence with no constraints return !( mDateStart.secsTo( dt ) % mTimedRepetition ); } // The date must be in an appropriate interval (getNextValidDateInterval), // Plus it must match at least one of the constraints if ( !dateMatchesRules( dt ) ) { return false; } // if it recurs every interval, speed things up... // if ( d->mFrequency == 1 && d->mBySetPos.isEmpty() && d->mByDays.isEmpty() ) return true; Constraint interval( getNextValidDateInterval( dt, recurrenceType() ) ); // TODO_Recurrence: Does this work with BySetPos??? if ( interval.matches( dt, recurrenceType() ) ) { return true; } return false; } TimeList RecurrenceRule::recurTimesOn( const TQDate &date ) const { TimeList lst; if ( doesFloat() ) { return lst; } TQDateTime start( date, TQTime( 0, 0, 0 ) ); TQDateTime end = start.addDays( 1 ).addSecs( -1 ); DateTimeList dts = timesInInterval( start, end ); // returns between start and end inclusive for ( int i = 0, iend = dts.count(); i < iend; ++i ) { lst += dts[i].time(); } return lst; } /** Returns the number of recurrences up to and including the date/time specified. */ int RecurrenceRule::durationTo( const TQDateTime &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; TQDateTime next( startDt() ); int found = 0; while ( next.isValid() && next <= dt ) { ++found; next = getNextDate( next ); } return found; } TQDateTime RecurrenceRule::getPreviousDate( const TQDateTime& afterDate ) const { // kdDebug(5800) << " RecurrenceRule::getPreviousDate: " << afterDate << endl; // Beyond end of recurrence if ( afterDate < startDt() ) return TQDateTime(); // If we have a cache (duration given), use that TQDateTime 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 TQDateTime(); } // 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 TQDateTime(); } } // 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 TQDateTime(); } } return TQDateTime(); } TQDateTime RecurrenceRule::getNextDate( const TQDateTime &preDate ) const { // kdDebug(5800) << " RecurrenceRule::getNextDate: " << preDate << endl; // Beyond end of recurrence if ( mDuration >= 0 && endDt().isValid() && preDate >= endDt() ) return TQDateTime(); // Start date is only included if it really matches TQDateTime 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 TQDateTime(); 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 ) { TQDateTime ret( dts.first() ); if ( mDuration >= 0 && ret > endDt() ) return TQDateTime(); else return ret; } ++loopnr; } return TQDateTime(); } DateTimeList RecurrenceRule::timesInInterval( const TQDateTime &dtStart, const TQDateTime &dtEnd ) const { TQDateTime start = dtStart; TQDateTime end = dtEnd; DateTimeList result; if ( end < mDateStart ) { return result; // before start of recurrence } TQDateTime enddt = end; if ( mDuration >= 0 ) { TQDateTime endRecur = endDt(); if ( endRecur.isValid() ) { if ( start > endRecur ) { return result; // beyond end of recurrence } if ( end > endRecur ) { enddt = endRecur; // limit end time to end of recurrence rule } } } if ( mTimedRepetition ) { // It's a simple sub-daily recurrence with no constraints int n = static_cast( ( mDateStart.secsTo( start ) - 1 ) % mTimedRepetition ); TQDateTime dt = start.addSecs( mTimedRepetition - n ); if ( dt < enddt ) { n = static_cast( ( dt.secsTo( enddt ) - 1 ) / mTimedRepetition ) + 1; // limit n by a sane value else we can "explode". n = TQMIN( n, LOOP_LIMIT ); for ( int i = 0; i < n; dt = dt.addSecs( mTimedRepetition ), ++i ) { result += dt; } } return result; } TQDateTime st = start; bool done = false; if ( mDuration > 0 ) { if ( !mCached ) { buildCache(); } if ( mCachedDateEnd.isValid() && start > mCachedDateEnd ) { return result; // beyond end of recurrence } int i = findGE( mCachedDates, start, 0 ); if ( i >= 0 ) { int iend = findGT( mCachedDates, enddt, i ); if ( iend < 0 ) { iend = mCachedDates.count(); } else { done = true; } while ( i < iend ) { result += mCachedDates[i++]; } } if ( mCachedDateEnd.isValid() ) { done = true; } else if ( !result.isEmpty() ) { result += TQDateTime(); // indicate that the returned list is incomplete done = true; } if ( done ) { return result; } // We don't have any result yet, but we reached the end of the incomplete cache st = mCachedLastDate.addSecs( 1 ); } Constraint interval( getNextValidDateInterval( st, recurrenceType() ) ); int loop = 0; do { DateTimeList dts = datesForInterval( interval, recurrenceType() ); int i = 0; int iend = dts.count(); if ( loop == 0 ) { i = findGE( dts, st, 0 ); if ( i < 0 ) { i = iend; } } int j = findGT( dts, enddt, i ); if ( j >= 0 ) { iend = j; loop = LOOP_LIMIT; } while ( i < iend ) { result += dts[i++]; } // Increase the interval. interval.increase( recurrenceType(), frequency() ); } while ( ++loop < LOOP_LIMIT && interval.intervalDateTime( recurrenceType() ) < end ); return result; } RecurrenceRule::Constraint RecurrenceRule::getPreviousValidDateInterval( const TQDateTime &preDate, PeriodType type ) const { // kdDebug(5800) << " (o) getPreviousValidDateInterval after " << preDate << ", type=" << type << endl; long periods = 0; TQDateTime nextValid = startDt(); TQDateTime start = startDt(); int modifier = 1; TQDateTime 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( TQDate( 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 TQDateTime &preDate, PeriodType type ) const { // TODO: Simplify this! kdDebug(5800) << " (o) getNextValidDateInterval after " << preDate << ", type=" << type << endl; long periods = 0; TQDateTime start = startDt(); TQDateTime nextValid( start ); int modifier = 1; TQDateTime 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 = TQMAX( 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 = TQMAX( 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 = TQMAX( 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( TQDate( start.date().year(), start.date().month(), 1 ) ); nextValid.setDate( start.date().addMonths( periods ) ); break; } case rYearly: periods = ( toDate.date().year() - start.date().year() ); periods = TQMAX( 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(); TQValueList::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() ) {\ TQStringList lst;\ for ( TQValueList::ConstIterator it = list.begin();\ it != list.end(); ++it ) {\ lst.append( TQString::number( *it ) );\ }\ kdDebug(5800) << " " << label << lst.join(", ") << endl;\ } dumpByIntList( mBySeconds, "BySeconds: " ); dumpByIntList( mByMinutes, "ByMinutes: " ); dumpByIntList( mByHours, "ByHours: " ); if ( !mByDays.isEmpty() ) { TQStringList lst; for ( TQValueList::ConstIterator it = mByDays.begin(); it != mByDays.end(); ++it ) { lst.append( ( ((*it).pos()!=0) ? TQString::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="<