22 #include "recurrencerule.h"
26 #include <QtCore/QStringList>
28 using namespace KCalCore;
31 const int LOOP_LIMIT = 10000;
33 static QString dumpTime(
const KDateTime &dt );
55 static QString dayName(
short day );
57 static QDate getNthWeek(
int year,
int weeknumber,
short weekstart = 1 );
58 static int weekNumbersInYear(
int year,
short weekstart = 1 );
59 static int getWeekNumber(
const QDate &date,
short weekstart,
int *year = 0 );
60 static int getWeekNumberNeg(
const QDate &date,
short weekstart,
int *year = 0 );
63 static QDate getDate(
int year,
int month,
int day )
66 return QDate( year, month, day );
72 return QDate( year, month, 1 ).addDays( day );
80 QString DateHelper::dayName(
short day )
103 QDate DateHelper::getNthWeek(
int year,
int weeknumber,
short weekstart )
105 if ( weeknumber == 0 ) {
110 QDate dt( year, 1, 4 );
111 int adjust = -( 7 + dt.dayOfWeek() - weekstart ) % 7;
112 if ( weeknumber > 0 ) {
113 dt = dt.addDays( 7 * (weeknumber-1) + adjust );
114 }
else if ( weeknumber < 0 ) {
115 dt = dt.addYears( 1 );
116 dt = dt.addDays( 7 * weeknumber + adjust );
121 int DateHelper::getWeekNumber(
const QDate &date,
short weekstart,
int *year )
125 dt = dt.addDays( -( 7 + dt.dayOfWeek() - weekstart ) % 7 );
127 int daysto = dt.daysTo( date );
131 dt = QDate( y, 1, 4 );
132 dt = dt.addDays( -( 7 + dt.dayOfWeek() - weekstart ) % 7 );
133 daysto = dt.daysTo( date );
134 }
else if ( daysto > 355 ) {
136 QDate dtn( y+1, 1, 4 );
137 dtn = dtn.addDays( -( 7 + dtn.dayOfWeek() - weekstart ) % 7 );
138 int dayston = dtn.daysTo( date );
139 if ( dayston >= 0 ) {
148 return daysto / 7 + 1;
151 int DateHelper::weekNumbersInYear(
int year,
short weekstart )
153 QDate dt( year, 1, weekstart );
154 QDate dt1( year + 1, 1, weekstart );
155 return dt.daysTo( dt1 ) / 7;
159 int DateHelper::getWeekNumberNeg(
const QDate &date,
short weekstart,
int *year )
161 int weekpos = getWeekNumber( date, weekstart, year );
162 return weekNumbersInYear( *year, weekstart ) - weekpos - 1;
172 return mDay == pos2.mDay && mPos == pos2.mPos;
177 return !operator==( pos2 );
187 typedef QList<Constraint> List;
189 explicit Constraint( KDateTime::Spec,
int wkst = 1 );
192 void setYear(
int n )
197 void setMonth(
int n )
207 void setHour(
int n )
212 void setMinute(
int n )
217 void setSecond(
int n )
222 void setWeekday(
int n )
227 void setWeekdaynr(
int n )
232 void setWeeknumber(
int n )
237 void setYearday(
int n )
242 void setWeekstart(
int n )
247 void setSecondOccurrence(
int n )
249 secondOccurrence = n;
264 KDateTime::Spec timespec;
265 bool secondOccurrence;
270 bool merge(
const Constraint &interval );
271 bool isConsistent()
const;
276 void appendDateTime(
const QDate &date,
const QTime &time, QList<KDateTime> &list )
const;
280 mutable bool useCachedDt;
281 mutable KDateTime cachedDt;
284 Constraint::Constraint( KDateTime::Spec spec,
int wkst )
293 timespec( dt.timeSpec() )
296 readDateTime( dt, type );
299 void Constraint::clear()
311 secondOccurrence =
false;
320 if ( weeknumber == 0 ) {
321 if ( year > 0 && year != dt.year() ) {
326 if ( weeknumber > 0 &&
327 weeknumber != DateHelper::getWeekNumber( dt, weekstart, &y ) ) {
330 if ( weeknumber < 0 &&
331 weeknumber != DateHelper::getWeekNumberNeg( dt, weekstart, &y ) ) {
334 if ( year > 0 && year != y ) {
339 if ( month > 0 && month != dt.month() ) {
342 if ( day > 0 && day != dt.day() ) {
345 if ( day < 0 && dt.day() != ( dt.daysInMonth() + day + 1 ) ) {
349 if ( weekday != dt.dayOfWeek() ) {
352 if ( weekdaynr != 0 ) {
355 if ( ( type == RecurrenceRule::rMonthly ) ||
356 ( type == RecurrenceRule::rYearly && month > 0 ) ) {
358 if ( weekdaynr > 0 &&
359 weekdaynr != ( dt.day() - 1 ) / 7 + 1 ) {
362 if ( weekdaynr < 0 &&
363 weekdaynr != -( ( dt.daysInMonth() - dt.day() ) / 7 + 1 ) ) {
368 if ( weekdaynr > 0 &&
369 weekdaynr != ( dt.dayOfYear() - 1 ) / 7 + 1 ) {
372 if ( weekdaynr < 0 &&
373 weekdaynr != -( ( dt.daysInYear() - dt.dayOfYear() ) / 7 + 1 ) ) {
379 if ( yearday > 0 && yearday != dt.dayOfYear() ) {
382 if ( yearday < 0 && yearday != dt.daysInYear() - dt.dayOfYear() + 1 ) {
393 if ( ( hour >= 0 && ( hour != dt.time().hour() ||
394 secondOccurrence != dt.isSecondOccurrence() ) ) ||
395 ( minute >= 0 && minute != dt.time().minute() ) ||
396 ( second >= 0 && second != dt.time().second() ) ||
397 !matches( dt.date(), type ) ) {
418 bool subdaily =
true;
420 case RecurrenceRule::rSecondly:
421 t.setHMS( hour, minute, second );
423 case RecurrenceRule::rMinutely:
424 t.setHMS( hour, minute, 0 );
426 case RecurrenceRule::rHourly:
427 t.setHMS( hour, 0, 0 );
429 case RecurrenceRule::rDaily:
431 case RecurrenceRule::rWeekly:
432 d = DateHelper::getNthWeek( year, weeknumber, weekstart );
435 case RecurrenceRule::rMonthly:
436 d.setYMD( year, month, 1 );
439 case RecurrenceRule::rYearly:
440 d.setYMD( year, 1, 1 );
447 d = DateHelper::getDate( year, (month>0)?month:1, day?day:1 );
449 cachedDt = KDateTime( d, t, timespec );
450 if ( secondOccurrence ) {
451 cachedDt.setSecondOccurrence(
true );
457 bool Constraint::merge(
const Constraint &interval )
459 #define mergeConstraint( name, cmparison ) \
460 if ( interval.name cmparison ) { \
461 if ( !( name cmparison ) ) { \
462 name = interval.name; \
463 } else if ( name != interval.name ) { \
470 mergeConstraint( year, > 0 );
471 mergeConstraint( month, > 0 );
472 mergeConstraint( day, != 0 );
473 mergeConstraint( hour, >= 0 );
474 mergeConstraint( minute, >= 0 );
475 mergeConstraint( second, >= 0 );
477 mergeConstraint( weekday, != 0 );
478 mergeConstraint( weekdaynr, != 0 );
479 mergeConstraint( weeknumber, != 0 );
480 mergeConstraint( yearday, != 0 );
482 #undef mergeConstraint
505 QList<KDateTime> result;
507 if ( !isConsistent( type ) ) {
512 QTime tm( hour, minute, second );
514 if ( !done && day && month > 0 ) {
515 appendDateTime( DateHelper::getDate( year, month, day ), tm, result );
519 if ( !done && weekday == 0 && weeknumber == 0 && yearday == 0 ) {
521 uint mstart = ( month > 0 ) ? month : 1;
522 uint mend = ( month <= 0 ) ? 12 : month;
523 for ( uint m = mstart; m <= mend; ++m ) {
527 }
else if ( day < 0 ) {
528 QDate date( year, month, 1 );
529 dstart = dend = date.daysInMonth() + day + 1;
531 QDate date( year, month, 1 );
533 dend = date.daysInMonth();
536 for ( QDate dt( year, m, dstart ); ; dt = dt.addDays( 1 ) ) {
537 appendDateTime( dt, tm, result );
548 if ( !done && yearday != 0 ) {
550 QDate d( year + ( ( yearday > 0 ) ? 0 : 1 ), 1, 1 );
551 d = d.addDays( yearday - ( ( yearday > 0 ) ? 1 : 0 ) );
552 appendDateTime( d, tm, result );
557 if ( !done && weeknumber != 0 ) {
558 QDate wst( DateHelper::getNthWeek( year, weeknumber, weekstart ) );
559 if ( weekday != 0 ) {
560 wst = wst.addDays( ( 7 + weekday - weekstart ) % 7 );
561 appendDateTime( wst, tm, result );
563 for (
int i = 0; i < 7; ++i ) {
564 appendDateTime( wst, tm, result );
565 wst = wst.addDays( 1 );
572 if ( !done && weekday != 0 ) {
573 QDate dt( year, 1, 1 );
577 bool inMonth = ( type == RecurrenceRule::rMonthly ) ||
578 ( type == RecurrenceRule::rYearly && month > 0 );
579 if ( inMonth && month > 0 ) {
580 dt = QDate( year, month, 1 );
583 if ( weekdaynr < 0 ) {
586 dt = dt.addMonths( 1 );
588 dt = dt.addYears( 1 );
591 int adj = ( 7 + weekday - dt.dayOfWeek() ) % 7;
592 dt = dt.addDays( adj );
594 if ( weekdaynr > 0 ) {
595 dt = dt.addDays( ( weekdaynr - 1 ) * 7 );
596 appendDateTime( dt, tm, result );
597 }
else if ( weekdaynr < 0 ) {
598 dt = dt.addDays( weekdaynr * 7 );
599 appendDateTime( dt, tm, result );
602 for (
int i = 0; i < maxloop; ++i ) {
603 appendDateTime( dt, tm, result );
604 dt = dt.addDays( 7 );
610 QList<KDateTime> valid;
611 for (
int i = 0, iend = result.count(); i < iend; ++i ) {
612 if ( matches( result[i], type ) ) {
613 valid.append( result[i] );
621 void Constraint::appendDateTime(
const QDate &date,
const QTime &time,
622 QList<KDateTime> &list )
const
624 KDateTime dt( date, time, timespec );
625 if ( dt.isValid() ) {
626 if ( secondOccurrence ) {
627 dt.setSecondOccurrence(
true );
636 intervalDateTime( type );
640 case RecurrenceRule::rSecondly:
641 cachedDt = cachedDt.addSecs( freq );
643 case RecurrenceRule::rMinutely:
644 cachedDt = cachedDt.addSecs( 60 * freq );
646 case RecurrenceRule::rHourly:
647 cachedDt = cachedDt.addSecs( 3600 * freq );
649 case RecurrenceRule::rDaily:
650 cachedDt = cachedDt.addDays( freq );
652 case RecurrenceRule::rWeekly:
653 cachedDt = cachedDt.addDays( 7 * freq );
655 case RecurrenceRule::rMonthly:
656 cachedDt = cachedDt.addMonths( freq );
658 case RecurrenceRule::rYearly:
659 cachedDt = cachedDt.addYears( freq );
665 readDateTime( cachedDt, type );
676 case RecurrenceRule::rSecondly:
677 second = dt.time().second();
678 case RecurrenceRule::rMinutely:
679 minute = dt.time().minute();
680 case RecurrenceRule::rHourly:
681 hour = dt.time().hour();
682 secondOccurrence = dt.isSecondOccurrence();
683 case RecurrenceRule::rDaily:
684 day = dt.date().day();
685 case RecurrenceRule::rMonthly:
686 month = dt.date().month();
687 case RecurrenceRule::rYearly:
688 year = dt.date().year();
690 case RecurrenceRule::rWeekly:
692 weeknumber = DateHelper::getWeekNumber( dt.date(), weekstart, &year );
707 class KCalCore::RecurrenceRule::Private
716 mIsReadOnly( false ),
724 Private &operator=(
const Private &other );
725 bool operator==(
const Private &other )
const;
728 void buildConstraints();
729 bool buildCache()
const;
730 Constraint getNextValidDateInterval(
const KDateTime &preDate, PeriodType type )
const;
731 Constraint getPreviousValidDateInterval(
const KDateTime &afterDate, PeriodType type )
const;
732 DateTimeList datesForInterval(
const Constraint &interval, PeriodType type )
const;
737 KDateTime mDateStart;
747 QList<int> mBySeconds;
748 QList<int> mByMinutes;
751 QList<WDayPos> mByDays;
752 QList<int> mByMonthDays;
753 QList<int> mByYearDays;
754 QList<int> mByWeekNumbers;
755 QList<int> mByMonths;
756 QList<int> mBySetPos;
759 Constraint::List mConstraints;
760 QList<RuleObserver*> mObservers;
764 mutable KDateTime mCachedDateEnd;
765 mutable KDateTime mCachedLastDate;
766 mutable bool mCached;
771 uint mTimedRepetition;
774 RecurrenceRule::Private::Private(
RecurrenceRule *parent,
const Private &p )
777 mPeriod( p.mPeriod ),
778 mDateStart( p.mDateStart ),
779 mFrequency( p.mFrequency ),
780 mDuration( p.mDuration ),
781 mDateEnd( p.mDateEnd ),
783 mBySeconds( p.mBySeconds ),
784 mByMinutes( p.mByMinutes ),
785 mByHours( p.mByHours ),
786 mByDays( p.mByDays ),
787 mByMonthDays( p.mByMonthDays ),
788 mByYearDays( p.mByYearDays ),
789 mByWeekNumbers( p.mByWeekNumbers ),
790 mByMonths( p.mByMonths ),
791 mBySetPos( p.mBySetPos ),
792 mWeekStart( p.mWeekStart ),
794 mIsReadOnly( p.mIsReadOnly ),
795 mAllDay( p.mAllDay ),
796 mNoByRules( p.mNoByRules )
801 RecurrenceRule::Private &RecurrenceRule::Private::operator=(
const Private &p )
810 mDateStart = p.mDateStart;
811 mFrequency = p.mFrequency;
812 mDuration = p.mDuration;
813 mDateEnd = p.mDateEnd;
815 mBySeconds = p.mBySeconds;
816 mByMinutes = p.mByMinutes;
817 mByHours = p.mByHours;
819 mByMonthDays = p.mByMonthDays;
820 mByYearDays = p.mByYearDays;
821 mByWeekNumbers = p.mByWeekNumbers;
822 mByMonths = p.mByMonths;
823 mBySetPos = p.mBySetPos;
824 mWeekStart = p.mWeekStart;
826 mIsReadOnly = p.mIsReadOnly;
828 mNoByRules = p.mNoByRules;
835 bool RecurrenceRule::Private::operator==(
const Private &r )
const
838 mPeriod == r.mPeriod &&
839 ( ( mDateStart == r.mDateStart ) ||
840 ( !mDateStart.isValid() && !r.mDateStart.isValid() ) ) &&
841 mDuration == r.mDuration &&
842 ( ( mDateEnd == r.mDateEnd ) ||
843 ( !mDateEnd.isValid() && !r.mDateEnd.isValid() ) ) &&
844 mFrequency == r.mFrequency &&
845 mIsReadOnly == r.mIsReadOnly &&
846 mAllDay == r.mAllDay &&
847 mBySeconds == r.mBySeconds &&
848 mByMinutes == r.mByMinutes &&
849 mByHours == r.mByHours &&
850 mByDays == r.mByDays &&
851 mByMonthDays == r.mByMonthDays &&
852 mByYearDays == r.mByYearDays &&
853 mByWeekNumbers == r.mByWeekNumbers &&
854 mByMonths == r.mByMonths &&
855 mBySetPos == r.mBySetPos &&
856 mWeekStart == r.mWeekStart &&
857 mNoByRules == r.mNoByRules;
860 void RecurrenceRule::Private::clear()
870 mByMonthDays.clear();
872 mByWeekNumbers.clear();
881 void RecurrenceRule::Private::setDirty()
885 mCachedDates.clear();
886 for (
int i = 0, iend = mObservers.count(); i < iend; ++i ) {
887 if ( mObservers[i] ) {
888 mObservers[i]->recurrenceChanged( mParent );
898 RecurrenceRule::RecurrenceRule()
899 : d( new Private( this ) )
904 : d( new Private( this, *r.d ) )
908 RecurrenceRule::~RecurrenceRule()
932 if ( !d->mObservers.contains( observer ) ) {
933 d->mObservers.append( observer );
939 if ( d->mObservers.contains( observer ) ) {
940 d->mObservers.removeAll( observer );
944 void RecurrenceRule::setRecurrenceType( PeriodType period )
958 if ( d->mPeriod == rNone ) {
961 if ( d->mDuration < 0 ) {
964 if ( d->mDuration == 0 ) {
974 if ( !d->buildCache() ) {
981 return d->mCachedDateEnd;
989 d->mDateEnd = dateTime;
1017 void RecurrenceRule::setDirty()
1027 d->mDateStart = start;
1036 d->mFrequency = freq;
1040 void RecurrenceRule::setBySeconds(
const QList<int> &bySeconds )
1045 d->mBySeconds = bySeconds;
1049 void RecurrenceRule::setByMinutes(
const QList<int> &byMinutes )
1054 d->mByMinutes = byMinutes;
1058 void RecurrenceRule::setByHours(
const QList<int> &byHours )
1063 d->mByHours = byHours;
1067 void RecurrenceRule::setByDays(
const QList<WDayPos> &byDays )
1072 d->mByDays = byDays;
1076 void RecurrenceRule::setByMonthDays(
const QList<int> &byMonthDays )
1081 d->mByMonthDays = byMonthDays;
1085 void RecurrenceRule::setByYearDays(
const QList<int> &byYearDays )
1090 d->mByYearDays = byYearDays;
1094 void RecurrenceRule::setByWeekNumbers(
const QList<int> &byWeekNumbers )
1099 d->mByWeekNumbers = byWeekNumbers;
1103 void RecurrenceRule::setByMonths(
const QList<int> &byMonths )
1108 d->mByMonths = byMonths;
1112 void RecurrenceRule::setBySetPos(
const QList<int> &bySetPos )
1117 d->mBySetPos = bySetPos;
1121 void RecurrenceRule::setWeekStart(
short weekStart )
1126 d->mWeekStart = weekStart;
1132 d->mDateStart = d->mDateStart.toTimeSpec( oldSpec );
1133 d->mDateStart.setTimeSpec( newSpec );
1134 if ( d->mDuration == 0 ) {
1135 d->mDateEnd = d->mDateEnd.toTimeSpec( oldSpec );
1136 d->mDateEnd.setTimeSpec( newSpec );
1198 void RecurrenceRule::Private::buildConstraints()
1200 mTimedRepetition = 0;
1201 mNoByRules = mBySetPos.isEmpty();
1202 mConstraints.clear();
1203 Constraint con( mDateStart.timeSpec() );
1204 if ( mWeekStart > 0 ) {
1205 con.setWeekstart( mWeekStart );
1207 mConstraints.append( con );
1211 Constraint::List tmp;
1213 #define intConstraint( list, setElement ) \
1214 if ( !list.isEmpty() ) { \
1215 mNoByRules = false; \
1216 iend = list.count(); \
1217 if ( iend == 1 ) { \
1218 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1219 mConstraints[c].setElement( list[0] ); \
1222 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1223 for ( i = 0; i < iend; ++i ) { \
1224 con = mConstraints[c]; \
1225 con.setElement( list[i] ); \
1226 tmp.append( con ); \
1229 mConstraints = tmp; \
1234 intConstraint( mBySeconds, setSecond );
1235 intConstraint( mByMinutes, setMinute );
1236 intConstraint( mByHours, setHour );
1237 intConstraint( mByMonthDays, setDay );
1238 intConstraint( mByMonths, setMonth );
1239 intConstraint( mByYearDays, setYearday );
1240 intConstraint( mByWeekNumbers, setWeeknumber );
1241 #undef intConstraint
1243 if ( !mByDays.isEmpty() ) {
1245 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) {
1246 for ( i = 0, iend = mByDays.count(); i < iend; ++i ) {
1247 con = mConstraints[c];
1248 con.setWeekday( mByDays[i].day() );
1249 con.setWeekdaynr( mByDays[i].pos() );
1257 #define fixConstraint( setElement, value ) \
1259 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1260 mConstraints[c].setElement( value ); \
1267 if ( mPeriod == rWeekly && mByDays.isEmpty() ) {
1268 fixConstraint( setWeekday, mDateStart.date().dayOfWeek() );
1273 switch ( mPeriod ) {
1275 if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
1276 mByYearDays.isEmpty() && mByMonths.isEmpty() ) {
1277 fixConstraint( setMonth, mDateStart.date().month() );
1280 if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
1281 mByYearDays.isEmpty() && mByMonthDays.isEmpty() ) {
1282 fixConstraint( setDay, mDateStart.date().day() );
1286 if ( mByHours.isEmpty() ) {
1287 fixConstraint( setHour, mDateStart.time().hour() );
1290 if ( mByMinutes.isEmpty() ) {
1291 fixConstraint( setMinute, mDateStart.time().minute() );
1294 if ( mBySeconds.isEmpty() ) {
1295 fixConstraint( setSecond, mDateStart.time().second() );
1301 #undef fixConstraint
1304 switch ( mPeriod ) {
1306 mTimedRepetition = mFrequency * 3600;
1309 mTimedRepetition = mFrequency * 60;
1312 mTimedRepetition = mFrequency;
1318 for ( c = 0, cend = mConstraints.count(); c < cend; ) {
1319 if ( mConstraints[c].isConsistent( mPeriod ) ) {
1322 mConstraints.removeAt( c );
1331 bool RecurrenceRule::Private::buildCache()
const
1335 Constraint interval( getNextValidDateInterval( mDateStart, mPeriod ) );
1338 DateTimeList dts = datesForInterval( interval, mPeriod );
1341 int i = dts.
findLT( mDateStart );
1343 dts.erase( dts.begin(), dts.begin() + i + 1 );
1347 int dtnr = dts.count();
1350 while ( loopnr < LOOP_LIMIT && dtnr < mDuration ) {
1351 interval.increase( mPeriod, mFrequency );
1353 dts += datesForInterval( interval, mPeriod );
1357 if ( dts.count() > mDuration ) {
1359 dts.erase( dts.begin() + mDuration, dts.end() );
1369 if (
int( dts.count() ) == mDuration ) {
1370 mCachedDateEnd = dts.last();
1374 mCachedDateEnd = KDateTime();
1375 mCachedLastDate = interval.intervalDateTime( mPeriod );
1383 KDateTime dt = kdt.toTimeSpec( d->mDateStart.timeSpec() );
1384 for (
int i = 0, iend = d->mConstraints.count(); i < iend; ++i ) {
1385 if ( d->mConstraints[i].matches( dt, recurrenceType() ) ) {
1396 if ( !qd.isValid() ) {
1404 if ( qd < d->mDateStart.date() ) {
1409 if ( d->mDuration >= 0 ) {
1410 endDate =
endDt().date();
1411 if ( qd > endDate ) {
1419 for ( i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i ) {
1420 match = d->mConstraints[i].matches( qd, recurrenceType() );
1426 KDateTime start( qd, QTime( 0, 0, 0 ), d->mDateStart.timeSpec() );
1427 Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
1430 if ( !interval.matches( qd, recurrenceType() ) ) {
1436 KDateTime end = start.addDays( 1 );
1438 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1439 for ( i = 0, iend = dts.count(); i < iend; ++i ) {
1440 if ( dts[i].date() >= qd ) {
1441 return dts[i].date() == qd;
1444 interval.increase( recurrenceType(),
frequency() );
1445 }
while ( interval.intervalDateTime( recurrenceType() ) < end );
1450 KDateTime start( qd, QTime( 0, 0, 0 ), timeSpec );
1451 KDateTime end = start.addDays( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1452 start = start.toTimeSpec( d->mDateStart.timeSpec() );
1453 if ( end < d->mDateStart ) {
1456 if ( start < d->mDateStart ) {
1457 start = d->mDateStart;
1461 if ( d->mDuration >= 0 ) {
1462 KDateTime endRecur =
endDt();
1463 if ( endRecur.isValid() ) {
1464 if ( start > endRecur ) {
1467 if ( end > endRecur ) {
1473 if ( d->mTimedRepetition ) {
1475 int n =
static_cast<int>( ( d->mDateStart.secsTo_long( start ) - 1 ) % d->mTimedRepetition );
1476 return start.addSecs( d->mTimedRepetition - n ) < end;
1480 QDate startDay = start.date();
1481 QDate endDay = end.addSecs( -1 ).date();
1482 int dayCount = startDay.daysTo( endDay ) + 1;
1487 for ( i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i ) {
1488 match = d->mConstraints[i].matches( startDay, recurrenceType() );
1489 for (
int day = 1; day < dayCount && !match; ++day ) {
1490 match = d->mConstraints[i].matches( startDay.addDays( day ), recurrenceType() );
1497 Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
1501 Constraint intervalm = interval;
1503 match = intervalm.matches( startDay, recurrenceType() );
1504 for (
int day = 1; day < dayCount && !match; ++day ) {
1505 match = intervalm.matches( startDay.addDays( day ), recurrenceType() );
1510 intervalm.increase( recurrenceType(),
frequency() );
1511 }
while ( intervalm.intervalDateTime( recurrenceType() ) < end );
1520 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1521 int i = dts.
findGE( start );
1523 return dts[i] <= end;
1525 interval.increase( recurrenceType(),
frequency() );
1526 }
while ( interval.intervalDateTime( recurrenceType() ) < end );
1534 KDateTime dt( kdt.toTimeSpec( d->mDateStart.timeSpec() ) );
1537 return recursOn( dt.date(), dt.timeSpec() );
1539 if ( dt < d->mDateStart ) {
1543 if ( d->mDuration >= 0 && dt >
endDt() ) {
1547 if ( d->mTimedRepetition ) {
1549 return !( d->mDateStart.secsTo_long( dt ) % d->mTimedRepetition );
1559 Constraint interval( d->getNextValidDateInterval( dt, recurrenceType() ) );
1561 if ( interval.matches( dt, recurrenceType() ) ) {
1573 KDateTime start( date, QTime( 0, 0, 0 ), timeSpec );
1574 KDateTime end = start.addDays( 1 ).addSecs( -1 );
1576 for (
int i = 0, iend = dts.count(); i < iend; ++i ) {
1577 lst += dts[i].toTimeSpec( timeSpec ).time();
1586 KDateTime toDate( dt.toTimeSpec( d->mDateStart.timeSpec() ) );
1589 if ( toDate < d->mDateStart ) {
1593 if ( d->mDuration > 0 && toDate >=
endDt() ) {
1594 return d->mDuration;
1597 if ( d->mTimedRepetition ) {
1599 return static_cast<int>( d->mDateStart.secsTo_long( toDate ) / d->mTimedRepetition );
1607 return durationTo( KDateTime( date, QTime( 23, 59, 59 ), d->mDateStart.timeSpec() ) );
1613 KDateTime toDate( afterDate.toTimeSpec( d->mDateStart.timeSpec() ) );
1616 if ( !toDate.isValid() || toDate < d->mDateStart ) {
1620 if ( d->mTimedRepetition ) {
1622 KDateTime prev = toDate;
1623 if ( d->mDuration >= 0 &&
endDt().isValid() && toDate >
endDt() ) {
1624 prev =
endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1626 int n =
static_cast<int>( ( d->mDateStart.secsTo_long( prev ) - 1 ) % d->mTimedRepetition );
1630 prev = prev.addSecs( -n - 1 );
1631 return prev >= d->mDateStart ? prev : KDateTime();
1635 if ( d->mDuration > 0 ) {
1636 if ( !d->mCached ) {
1639 int i = d->mCachedDates.findLT( toDate );
1641 return d->mCachedDates[i];
1646 KDateTime prev = toDate;
1647 if ( d->mDuration >= 0 &&
endDt().isValid() && toDate >
endDt() ) {
1648 prev =
endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1651 Constraint interval( d->getPreviousValidDateInterval( prev, recurrenceType() ) );
1652 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1653 int i = dts.
findLT( prev );
1655 return ( dts[i] >= d->mDateStart ) ? dts[i] : KDateTime();
1659 while ( interval.intervalDateTime( recurrenceType() ) > d->mDateStart ) {
1660 interval.increase( recurrenceType(), -
int(
frequency() ) );
1662 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1664 if ( !dts.isEmpty() ) {
1666 if ( prev.isValid() && prev >= d->mDateStart ) {
1679 KDateTime fromDate( preDate.toTimeSpec( d->mDateStart.timeSpec() ) );
1681 if ( d->mDuration >= 0 &&
endDt().isValid() && fromDate >=
endDt() ) {
1686 if ( fromDate < d->mDateStart ) {
1687 fromDate = d->mDateStart.addSecs( -1 );
1690 if ( d->mTimedRepetition ) {
1692 int n =
static_cast<int>( ( d->mDateStart.secsTo_long( fromDate ) + 1 ) % d->mTimedRepetition );
1693 KDateTime next = fromDate.addSecs( d->mTimedRepetition - n + 1 );
1694 return d->mDuration < 0 || !
endDt().isValid() || next <=
endDt() ? next : KDateTime();
1697 if ( d->mDuration > 0 ) {
1698 if ( !d->mCached ) {
1701 int i = d->mCachedDates.findGT( fromDate );
1703 return d->mCachedDates[i];
1707 KDateTime end =
endDt();
1708 Constraint interval( d->getNextValidDateInterval( fromDate, recurrenceType() ) );
1709 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1710 int i = dts.
findGT( fromDate );
1712 return ( d->mDuration < 0 || dts[i] <= end ) ? dts[i] : KDateTime();
1714 interval.increase( recurrenceType(),
frequency() );
1715 if ( d->mDuration >= 0 && interval.intervalDateTime( recurrenceType() ) > end ) {
1724 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1725 if ( dts.count() > 0 ) {
1726 KDateTime ret( dts[0] );
1727 if ( d->mDuration >= 0 && ret > end ) {
1733 interval.increase( recurrenceType(),
frequency() );
1734 }
while ( ++loop < LOOP_LIMIT &&
1735 ( d->mDuration < 0 || interval.intervalDateTime( recurrenceType() ) < end ) );
1740 const KDateTime &dtEnd )
const
1742 KDateTime start = dtStart.toTimeSpec( d->mDateStart.timeSpec() );
1743 KDateTime end = dtEnd.toTimeSpec( d->mDateStart.timeSpec() );
1745 if ( end < d->mDateStart ) {
1748 KDateTime enddt = end;
1749 if ( d->mDuration >= 0 ) {
1750 KDateTime endRecur =
endDt();
1751 if ( endRecur.isValid() ) {
1752 if ( start > endRecur ) {
1755 if ( end > endRecur ) {
1761 if ( d->mTimedRepetition ) {
1763 int n =
static_cast<int>( ( d->mDateStart.secsTo_long( start ) - 1 ) % d->mTimedRepetition );
1764 KDateTime dt = start.addSecs( d->mTimedRepetition - n );
1766 n =
static_cast<int>( ( dt.secsTo_long( enddt ) - 1 ) / d->mTimedRepetition ) + 1;
1768 n = qMin( n, LOOP_LIMIT );
1769 for (
int i = 0; i < n; dt = dt.addSecs( d->mTimedRepetition ), ++i ) {
1776 KDateTime st = start;
1778 if ( d->mDuration > 0 ) {
1779 if ( !d->mCached ) {
1782 if ( d->mCachedDateEnd.isValid() && start > d->mCachedDateEnd ) {
1785 int i = d->mCachedDates.
findGE( start );
1787 int iend = d->mCachedDates.findGT( enddt, i );
1789 iend = d->mCachedDates.count();
1793 while ( i < iend ) {
1794 result += d->mCachedDates[i++];
1797 if ( d->mCachedDateEnd.isValid() ) {
1799 }
else if ( !result.isEmpty() ) {
1800 result += KDateTime();
1807 st = d->mCachedLastDate.addSecs( 1 );
1810 Constraint interval( d->getNextValidDateInterval( st, recurrenceType() ) );
1813 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1815 int iend = dts.count();
1822 int j = dts.
findGT( enddt, i );
1827 while ( i < iend ) {
1831 interval.increase( recurrenceType(),
frequency() );
1832 }
while ( ++loop < LOOP_LIMIT &&
1833 interval.intervalDateTime( recurrenceType() ) < end );
1842 Constraint RecurrenceRule::Private::getPreviousValidDateInterval(
const KDateTime &dt,
1843 PeriodType type )
const
1846 KDateTime start = mDateStart;
1847 KDateTime nextValid( start );
1849 KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
1862 periods =
static_cast<int>( start.secsTo_long( toDate ) / modifier );
1864 if ( mFrequency > 0 ) {
1865 periods = ( periods / mFrequency ) * mFrequency;
1867 nextValid = start.addSecs( modifier * periods );
1870 toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
1871 start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
1874 periods = start.daysTo( toDate ) / modifier;
1876 if ( mFrequency > 0 ) {
1877 periods = ( periods / mFrequency ) * mFrequency;
1879 nextValid = start.addDays( modifier * periods );
1883 periods = 12 * ( toDate.date().year() - start.date().year() ) +
1884 ( toDate.date().month() - start.date().month() );
1886 if ( mFrequency > 0 ) {
1887 periods = ( periods / mFrequency ) * mFrequency;
1891 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
1892 nextValid.setDate( start.date().addMonths( periods ) );
1895 periods = ( toDate.date().year() - start.date().year() );
1897 if ( mFrequency > 0 ) {
1898 periods = ( periods / mFrequency ) * mFrequency;
1900 nextValid.setDate( start.date().addYears( periods ) );
1906 return Constraint( nextValid, type, mWeekStart );
1913 Constraint RecurrenceRule::Private::getNextValidDateInterval(
const KDateTime &dt,
1914 PeriodType type )
const
1918 KDateTime start = mDateStart;
1919 KDateTime nextValid( start );
1921 KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
1934 periods =
static_cast<int>( start.secsTo_long( toDate ) / modifier );
1935 periods = qMax( 0L, periods );
1936 if ( periods > 0 && mFrequency > 0 ) {
1937 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1939 nextValid = start.addSecs( modifier * periods );
1943 toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
1944 start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
1947 periods = start.daysTo( toDate ) / modifier;
1948 periods = qMax( 0L, periods );
1949 if ( periods > 0 && mFrequency > 0 ) {
1950 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1952 nextValid = start.addDays( modifier * periods );
1956 periods = 12 * ( toDate.date().year() - start.date().year() ) +
1957 ( toDate.date().month() - start.date().month() );
1958 periods = qMax( 0L, periods );
1959 if ( periods > 0 && mFrequency > 0 ) {
1960 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1964 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
1965 nextValid.setDate( start.date().addMonths( periods ) );
1969 periods = ( toDate.date().year() - start.date().year() );
1970 periods = qMax( 0L, periods );
1971 if ( periods > 0 && mFrequency > 0 ) {
1972 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1974 nextValid.setDate( start.date().addYears( periods ) );
1980 return Constraint( nextValid, type, mWeekStart );
1983 DateTimeList RecurrenceRule::Private::datesForInterval(
const Constraint &interval,
1984 PeriodType type )
const
1993 for (
int i = 0, iend = mConstraints.count(); i < iend; ++i ) {
1994 Constraint merged( interval );
1995 if ( merged.merge( mConstraints[i] ) ) {
1997 if ( merged.year > 0 && merged.hour >= 0 && merged.minute >= 0 && merged.second >= 0 ) {
2000 QList<KDateTime> lstnew = merged.dateTimes( type );
2017 if ( !mBySetPos.isEmpty() ) {
2020 for (
int i = 0, iend = mBySetPos.count(); i < iend; ++i ) {
2021 int pos = mBySetPos[i];
2026 pos += tmplst.count();
2028 if ( pos >= 0 && pos < tmplst.count() ) {
2029 lst.append( tmplst[pos] );
2043 if ( !d->mRRule.isEmpty() ) {
2044 kDebug() <<
" RRULE=" << d->mRRule;
2048 kDebug() <<
" Period type:" << int( recurrenceType() ) <<
", frequency:" <<
frequency();
2049 kDebug() <<
" #occurrences:" <<
duration();
2050 kDebug() <<
" start date:" << dumpTime(
startDt() )
2051 <<
", end date:" << dumpTime(
endDt() );
2053 #define dumpByIntList(list,label) \
2054 if ( !list.isEmpty() ) {\
2056 for ( int i = 0, iend = list.count(); i < iend; ++i ) {\
2057 lst.append( QString::number( list[i] ) );\
2059 kDebug() << " " << label << lst.join( ", " );\
2061 dumpByIntList( d->mBySeconds,
"BySeconds: " );
2062 dumpByIntList( d->mByMinutes,
"ByMinutes: " );
2063 dumpByIntList( d->mByHours,
"ByHours: " );
2064 if ( !d->mByDays.isEmpty() ) {
2066 for (
int i = 0, iend = d->mByDays.count(); i < iend; ++i ) {\
2067 lst.append( ( d->mByDays[i].pos() ? QString::number( d->mByDays[i].pos() ) :
"" ) +
2068 DateHelper::dayName( d->mByDays[i].day() ) );
2070 kDebug() <<
" ByDays: " << lst.join(
", " );
2072 dumpByIntList( d->mByMonthDays,
"ByMonthDays:" );
2073 dumpByIntList( d->mByYearDays,
"ByYearDays: " );
2074 dumpByIntList( d->mByWeekNumbers,
"ByWeekNr: " );
2075 dumpByIntList( d->mByMonths,
"ByMonths: " );
2076 dumpByIntList( d->mBySetPos,
"BySetPos: " );
2077 #undef dumpByIntList
2079 kDebug() <<
" Week start:" << DateHelper::dayName( d->mWeekStart );
2081 kDebug() <<
" Constraints:";
2083 for (
int i = 0, iend = d->mConstraints.count(); i < iend; ++i ) {
2084 d->mConstraints[i].dump();
2090 void Constraint::dump()
const
2092 kDebug() <<
" ~> Y=" << year
2098 <<
", wd=" << weekday
2099 <<
",#wd=" << weekdaynr
2100 <<
", #w=" << weeknumber
2101 <<
", yd=" << yearday;
2105 QString dumpTime(
const KDateTime &dt )
2108 if ( !dt.isValid() ) {
2112 if ( dt.isDateOnly() ) {
2113 result = dt.toString(
"%a %Y-%m-%d %:Z" );
2115 result = dt.toString(
"%a %Y-%m-%d %H:%M:%S %:Z" );
2116 if ( dt.isSecondOccurrence() ) {
2117 result += QLatin1String(
" (2nd)" );
2120 if ( dt.timeSpec() == KDateTime::Spec::ClockTime() ) {
2121 result += QLatin1String(
"Clock" );
2132 return d->mDateStart;
2142 return d->mFrequency;
2147 return d->mDuration;
2150 QString RecurrenceRule::rrule()
const
2162 return d->mIsReadOnly;
2167 d->mIsReadOnly = readOnly;
2172 return d->mPeriod != rNone;
2180 const QList<int> &RecurrenceRule::bySeconds()
const
2182 return d->mBySeconds;
2185 const QList<int> &RecurrenceRule::byMinutes()
const
2187 return d->mByMinutes;
2190 const QList<int> &RecurrenceRule::byHours()
const
2195 const QList<RecurrenceRule::WDayPos> &RecurrenceRule::byDays()
const
2200 const QList<int> &RecurrenceRule::byMonthDays()
const
2202 return d->mByMonthDays;
2205 const QList<int> &RecurrenceRule::byYearDays()
const
2207 return d->mByYearDays;
2210 const QList<int> &RecurrenceRule::byWeekNumbers()
const
2212 return d->mByWeekNumbers;
2215 const QList<int> &RecurrenceRule::byMonths()
const
2217 return d->mByMonths;
2220 const QList<int> &RecurrenceRule::bySetPos()
const
2222 return d->mBySetPos;
2225 short RecurrenceRule::weekStart()
const
2227 return d->mWeekStart;
2230 RecurrenceRule::RuleObserver::~RuleObserver()
2234 RecurrenceRule::WDayPos::WDayPos(
int ps,
short dy )
2235 : mDay( dy ), mPos( ps )
2239 void RecurrenceRule::WDayPos::setDay(
short dy )
2244 short RecurrenceRule::WDayPos::day()
const
2249 void RecurrenceRule::WDayPos::setPos(
int ps )
2254 int RecurrenceRule::WDayPos::pos()
const