22 #include "recurrencerule.h"
26 #include <QtCore/QStringList>
27 #include <QtCore/QTime>
29 using namespace KCalCore;
32 const int LOOP_LIMIT = 10000;
34 static QString dumpTime(
const KDateTime &dt );
56 static QString dayName(
short day );
58 static QDate getNthWeek(
int year,
int weeknumber,
short weekstart = 1 );
59 static int weekNumbersInYear(
int year,
short weekstart = 1 );
60 static int getWeekNumber(
const QDate &date,
short weekstart,
int *year = 0 );
61 static int getWeekNumberNeg(
const QDate &date,
short weekstart,
int *year = 0 );
64 static QDate getDate(
int year,
int month,
int day )
67 return QDate( year, month, day );
73 return QDate( year, month, 1 ).addDays( day );
81 QString DateHelper::dayName(
short day )
104 QDate DateHelper::getNthWeek(
int year,
int weeknumber,
short weekstart )
106 if ( weeknumber == 0 ) {
111 QDate dt( year, 1, 4 );
112 int adjust = -( 7 + dt.dayOfWeek() - weekstart ) % 7;
113 if ( weeknumber > 0 ) {
114 dt = dt.addDays( 7 * (weeknumber-1) + adjust );
115 }
else if ( weeknumber < 0 ) {
116 dt = dt.addYears( 1 );
117 dt = dt.addDays( 7 * weeknumber + adjust );
122 int DateHelper::getWeekNumber(
const QDate &date,
short weekstart,
int *year )
126 dt = dt.addDays( -( 7 + dt.dayOfWeek() - weekstart ) % 7 );
128 int daysto = dt.daysTo( date );
132 dt = QDate( y, 1, 4 );
133 dt = dt.addDays( -( 7 + dt.dayOfWeek() - weekstart ) % 7 );
134 daysto = dt.daysTo( date );
135 }
else if ( daysto > 355 ) {
137 QDate dtn( y+1, 1, 4 );
138 dtn = dtn.addDays( -( 7 + dtn.dayOfWeek() - weekstart ) % 7 );
139 int dayston = dtn.daysTo( date );
140 if ( dayston >= 0 ) {
149 return daysto / 7 + 1;
152 int DateHelper::weekNumbersInYear(
int year,
short weekstart )
154 QDate dt( year, 1, weekstart );
155 QDate dt1( year + 1, 1, weekstart );
156 return dt.daysTo( dt1 ) / 7;
160 int DateHelper::getWeekNumberNeg(
const QDate &date,
short weekstart,
int *year )
162 int weekpos = getWeekNumber( date, weekstart, year );
163 return weekNumbersInYear( *year, weekstart ) - weekpos - 1;
173 return mDay == pos2.mDay && mPos == pos2.mPos;
178 return !operator==( pos2 );
188 typedef QList<Constraint> List;
190 explicit Constraint( KDateTime::Spec,
int wkst = 1 );
193 void setYear(
int n )
198 void setMonth(
int n )
208 void setHour(
int n )
213 void setMinute(
int n )
218 void setSecond(
int n )
223 void setWeekday(
int n )
228 void setWeekdaynr(
int n )
233 void setWeeknumber(
int n )
238 void setYearday(
int n )
243 void setWeekstart(
int n )
248 void setSecondOccurrence(
int n )
250 secondOccurrence = n;
265 KDateTime::Spec timespec;
266 bool secondOccurrence;
271 bool merge(
const Constraint &interval );
272 bool isConsistent()
const;
277 void appendDateTime(
const QDate &date,
const QTime &time, QList<KDateTime> &list )
const;
281 mutable bool useCachedDt;
282 mutable KDateTime cachedDt;
285 Constraint::Constraint( KDateTime::Spec spec,
int wkst )
294 timespec( dt.timeSpec() )
297 readDateTime( dt, type );
300 void Constraint::clear()
312 secondOccurrence =
false;
321 if ( weeknumber == 0 ) {
322 if ( year > 0 && year != dt.year() ) {
327 if ( weeknumber > 0 &&
328 weeknumber != DateHelper::getWeekNumber( dt, weekstart, &y ) ) {
331 if ( weeknumber < 0 &&
332 weeknumber != DateHelper::getWeekNumberNeg( dt, weekstart, &y ) ) {
335 if ( year > 0 && year != y ) {
340 if ( month > 0 && month != dt.month() ) {
343 if ( day > 0 && day != dt.day() ) {
346 if ( day < 0 && dt.day() != ( dt.daysInMonth() + day + 1 ) ) {
350 if ( weekday != dt.dayOfWeek() ) {
353 if ( weekdaynr != 0 ) {
356 if ( ( type == RecurrenceRule::rMonthly ) ||
357 ( type == RecurrenceRule::rYearly && month > 0 ) ) {
359 if ( weekdaynr > 0 &&
360 weekdaynr != ( dt.day() - 1 ) / 7 + 1 ) {
363 if ( weekdaynr < 0 &&
364 weekdaynr != -( ( dt.daysInMonth() - dt.day() ) / 7 + 1 ) ) {
369 if ( weekdaynr > 0 &&
370 weekdaynr != ( dt.dayOfYear() - 1 ) / 7 + 1 ) {
373 if ( weekdaynr < 0 &&
374 weekdaynr != -( ( dt.daysInYear() - dt.dayOfYear() ) / 7 + 1 ) ) {
380 if ( yearday > 0 && yearday != dt.dayOfYear() ) {
383 if ( yearday < 0 && yearday != dt.daysInYear() - dt.dayOfYear() + 1 ) {
394 if ( ( hour >= 0 && ( hour != dt.time().hour() ||
395 secondOccurrence != dt.isSecondOccurrence() ) ) ||
396 ( minute >= 0 && minute != dt.time().minute() ) ||
397 ( second >= 0 && second != dt.time().second() ) ||
398 !matches( dt.date(), type ) ) {
419 bool subdaily =
true;
421 case RecurrenceRule::rSecondly:
422 t.setHMS( hour, minute, second );
424 case RecurrenceRule::rMinutely:
425 t.setHMS( hour, minute, 0 );
427 case RecurrenceRule::rHourly:
428 t.setHMS( hour, 0, 0 );
430 case RecurrenceRule::rDaily:
432 case RecurrenceRule::rWeekly:
433 d = DateHelper::getNthWeek( year, weeknumber, weekstart );
436 case RecurrenceRule::rMonthly:
437 d.setYMD( year, month, 1 );
440 case RecurrenceRule::rYearly:
441 d.setYMD( year, 1, 1 );
448 d = DateHelper::getDate( year, (month>0)?month:1, day?day:1 );
450 cachedDt = KDateTime( d, t, timespec );
451 if ( secondOccurrence ) {
452 cachedDt.setSecondOccurrence(
true );
458 bool Constraint::merge(
const Constraint &interval )
460 #define mergeConstraint( name, cmparison ) \
461 if ( interval.name cmparison ) { \
462 if ( !( name cmparison ) ) { \
463 name = interval.name; \
464 } else if ( name != interval.name ) { \
471 mergeConstraint( year, > 0 );
472 mergeConstraint( month, > 0 );
473 mergeConstraint( day, != 0 );
474 mergeConstraint( hour, >= 0 );
475 mergeConstraint( minute, >= 0 );
476 mergeConstraint( second, >= 0 );
478 mergeConstraint( weekday, != 0 );
479 mergeConstraint( weekdaynr, != 0 );
480 mergeConstraint( weeknumber, != 0 );
481 mergeConstraint( yearday, != 0 );
483 #undef mergeConstraint
506 QList<KDateTime> result;
508 if ( !isConsistent( type ) ) {
513 QTime tm( hour, minute, second );
515 if ( !done && day && month > 0 ) {
516 appendDateTime( DateHelper::getDate( year, month, day ), tm, result );
520 if ( !done && weekday == 0 && weeknumber == 0 && yearday == 0 ) {
522 uint mstart = ( month > 0 ) ? month : 1;
523 uint mend = ( month <= 0 ) ? 12 : month;
524 for ( uint m = mstart; m <= mend; ++m ) {
528 }
else if ( day < 0 ) {
529 QDate date( year, month, 1 );
530 dstart = dend = date.daysInMonth() + day + 1;
532 QDate date( year, month, 1 );
534 dend = date.daysInMonth();
537 for ( QDate dt( year, m, dstart ); ; dt = dt.addDays( 1 ) ) {
538 appendDateTime( dt, tm, result );
549 if ( !done && yearday != 0 ) {
551 QDate d( year + ( ( yearday > 0 ) ? 0 : 1 ), 1, 1 );
552 d = d.addDays( yearday - ( ( yearday > 0 ) ? 1 : 0 ) );
553 appendDateTime( d, tm, result );
558 if ( !done && weeknumber != 0 ) {
559 QDate wst( DateHelper::getNthWeek( year, weeknumber, weekstart ) );
560 if ( weekday != 0 ) {
561 wst = wst.addDays( ( 7 + weekday - weekstart ) % 7 );
562 appendDateTime( wst, tm, result );
564 for (
int i = 0; i < 7; ++i ) {
565 appendDateTime( wst, tm, result );
566 wst = wst.addDays( 1 );
573 if ( !done && weekday != 0 ) {
574 QDate dt( year, 1, 1 );
578 bool inMonth = ( type == RecurrenceRule::rMonthly ) ||
579 ( type == RecurrenceRule::rYearly && month > 0 );
580 if ( inMonth && month > 0 ) {
581 dt = QDate( year, month, 1 );
584 if ( weekdaynr < 0 ) {
587 dt = dt.addMonths( 1 );
589 dt = dt.addYears( 1 );
592 int adj = ( 7 + weekday - dt.dayOfWeek() ) % 7;
593 dt = dt.addDays( adj );
595 if ( weekdaynr > 0 ) {
596 dt = dt.addDays( ( weekdaynr - 1 ) * 7 );
597 appendDateTime( dt, tm, result );
598 }
else if ( weekdaynr < 0 ) {
599 dt = dt.addDays( weekdaynr * 7 );
600 appendDateTime( dt, tm, result );
603 for (
int i = 0; i < maxloop; ++i ) {
604 appendDateTime( dt, tm, result );
605 dt = dt.addDays( 7 );
611 QList<KDateTime> valid;
612 for (
int i = 0, iend = result.count(); i < iend; ++i ) {
613 if ( matches( result[i], type ) ) {
614 valid.append( result[i] );
622 void Constraint::appendDateTime(
const QDate &date,
const QTime &time,
623 QList<KDateTime> &list )
const
625 KDateTime dt( date, time, timespec );
626 if ( dt.isValid() ) {
627 if ( secondOccurrence ) {
628 dt.setSecondOccurrence(
true );
637 intervalDateTime( type );
641 case RecurrenceRule::rSecondly:
642 cachedDt = cachedDt.addSecs( freq );
644 case RecurrenceRule::rMinutely:
645 cachedDt = cachedDt.addSecs( 60 * freq );
647 case RecurrenceRule::rHourly:
648 cachedDt = cachedDt.addSecs( 3600 * freq );
650 case RecurrenceRule::rDaily:
651 cachedDt = cachedDt.addDays( freq );
653 case RecurrenceRule::rWeekly:
654 cachedDt = cachedDt.addDays( 7 * freq );
656 case RecurrenceRule::rMonthly:
657 cachedDt = cachedDt.addMonths( freq );
659 case RecurrenceRule::rYearly:
660 cachedDt = cachedDt.addYears( freq );
666 readDateTime( cachedDt, type );
677 case RecurrenceRule::rSecondly:
678 second = dt.time().second();
679 case RecurrenceRule::rMinutely:
680 minute = dt.time().minute();
681 case RecurrenceRule::rHourly:
682 hour = dt.time().hour();
683 secondOccurrence = dt.isSecondOccurrence();
684 case RecurrenceRule::rDaily:
685 day = dt.date().day();
686 case RecurrenceRule::rMonthly:
687 month = dt.date().month();
688 case RecurrenceRule::rYearly:
689 year = dt.date().year();
691 case RecurrenceRule::rWeekly:
693 weeknumber = DateHelper::getWeekNumber( dt.date(), weekstart, &year );
708 class KCalCore::RecurrenceRule::Private
717 mIsReadOnly( false ),
725 Private &operator=(
const Private &other );
726 bool operator==(
const Private &other )
const;
729 void buildConstraints();
730 bool buildCache()
const;
731 Constraint getNextValidDateInterval(
const KDateTime &preDate, PeriodType type )
const;
732 Constraint getPreviousValidDateInterval(
const KDateTime &afterDate, PeriodType type )
const;
733 DateTimeList datesForInterval(
const Constraint &interval, PeriodType type )
const;
738 KDateTime mDateStart;
748 QList<int> mBySeconds;
749 QList<int> mByMinutes;
752 QList<WDayPos> mByDays;
753 QList<int> mByMonthDays;
754 QList<int> mByYearDays;
755 QList<int> mByWeekNumbers;
756 QList<int> mByMonths;
757 QList<int> mBySetPos;
760 Constraint::List mConstraints;
761 QList<RuleObserver*> mObservers;
765 mutable KDateTime mCachedDateEnd;
766 mutable KDateTime mCachedLastDate;
767 mutable bool mCached;
772 uint mTimedRepetition;
775 RecurrenceRule::Private::Private(
RecurrenceRule *parent,
const Private &p )
778 mPeriod( p.mPeriod ),
779 mDateStart( p.mDateStart ),
780 mFrequency( p.mFrequency ),
781 mDuration( p.mDuration ),
782 mDateEnd( p.mDateEnd ),
784 mBySeconds( p.mBySeconds ),
785 mByMinutes( p.mByMinutes ),
786 mByHours( p.mByHours ),
787 mByDays( p.mByDays ),
788 mByMonthDays( p.mByMonthDays ),
789 mByYearDays( p.mByYearDays ),
790 mByWeekNumbers( p.mByWeekNumbers ),
791 mByMonths( p.mByMonths ),
792 mBySetPos( p.mBySetPos ),
793 mWeekStart( p.mWeekStart ),
795 mIsReadOnly( p.mIsReadOnly ),
796 mAllDay( p.mAllDay ),
797 mNoByRules( p.mNoByRules )
802 RecurrenceRule::Private &RecurrenceRule::Private::operator=(
const Private &p )
811 mDateStart = p.mDateStart;
812 mFrequency = p.mFrequency;
813 mDuration = p.mDuration;
814 mDateEnd = p.mDateEnd;
816 mBySeconds = p.mBySeconds;
817 mByMinutes = p.mByMinutes;
818 mByHours = p.mByHours;
820 mByMonthDays = p.mByMonthDays;
821 mByYearDays = p.mByYearDays;
822 mByWeekNumbers = p.mByWeekNumbers;
823 mByMonths = p.mByMonths;
824 mBySetPos = p.mBySetPos;
825 mWeekStart = p.mWeekStart;
827 mIsReadOnly = p.mIsReadOnly;
829 mNoByRules = p.mNoByRules;
836 bool RecurrenceRule::Private::operator==(
const Private &r )
const
839 mPeriod == r.mPeriod &&
840 ( ( mDateStart == r.mDateStart ) ||
841 ( !mDateStart.isValid() && !r.mDateStart.isValid() ) ) &&
842 mDuration == r.mDuration &&
843 ( ( mDateEnd == r.mDateEnd ) ||
844 ( !mDateEnd.isValid() && !r.mDateEnd.isValid() ) ) &&
845 mFrequency == r.mFrequency &&
846 mIsReadOnly == r.mIsReadOnly &&
847 mAllDay == r.mAllDay &&
848 mBySeconds == r.mBySeconds &&
849 mByMinutes == r.mByMinutes &&
850 mByHours == r.mByHours &&
851 mByDays == r.mByDays &&
852 mByMonthDays == r.mByMonthDays &&
853 mByYearDays == r.mByYearDays &&
854 mByWeekNumbers == r.mByWeekNumbers &&
855 mByMonths == r.mByMonths &&
856 mBySetPos == r.mBySetPos &&
857 mWeekStart == r.mWeekStart &&
858 mNoByRules == r.mNoByRules;
861 void RecurrenceRule::Private::clear()
871 mByMonthDays.clear();
873 mByWeekNumbers.clear();
882 void RecurrenceRule::Private::setDirty()
886 mCachedDates.clear();
887 for (
int i = 0, iend = mObservers.count(); i < iend; ++i ) {
888 if ( mObservers[i] ) {
889 mObservers[i]->recurrenceChanged( mParent );
900 : d( new Private( this ) )
905 : d( new Private( this, *r.d ) )
909 RecurrenceRule::~RecurrenceRule()
933 if ( !d->mObservers.contains( observer ) ) {
934 d->mObservers.append( observer );
940 if ( d->mObservers.contains( observer ) ) {
941 d->mObservers.removeAll( observer );
945 void RecurrenceRule::setRecurrenceType( PeriodType period )
959 if ( d->mPeriod == rNone ) {
962 if ( d->mDuration < 0 ) {
965 if ( d->mDuration == 0 ) {
975 if ( !d->buildCache() ) {
982 return d->mCachedDateEnd;
990 d->mDateEnd = dateTime;
1018 void RecurrenceRule::setDirty()
1028 d->mDateStart = start;
1037 d->mFrequency = freq;
1041 void RecurrenceRule::setBySeconds(
const QList<int> &bySeconds )
1046 d->mBySeconds = bySeconds;
1050 void RecurrenceRule::setByMinutes(
const QList<int> &byMinutes )
1055 d->mByMinutes = byMinutes;
1059 void RecurrenceRule::setByHours(
const QList<int> &byHours )
1064 d->mByHours = byHours;
1068 void RecurrenceRule::setByDays(
const QList<WDayPos> &byDays )
1073 d->mByDays = byDays;
1077 void RecurrenceRule::setByMonthDays(
const QList<int> &byMonthDays )
1082 d->mByMonthDays = byMonthDays;
1086 void RecurrenceRule::setByYearDays(
const QList<int> &byYearDays )
1091 d->mByYearDays = byYearDays;
1095 void RecurrenceRule::setByWeekNumbers(
const QList<int> &byWeekNumbers )
1100 d->mByWeekNumbers = byWeekNumbers;
1104 void RecurrenceRule::setByMonths(
const QList<int> &byMonths )
1109 d->mByMonths = byMonths;
1113 void RecurrenceRule::setBySetPos(
const QList<int> &bySetPos )
1118 d->mBySetPos = bySetPos;
1122 void RecurrenceRule::setWeekStart(
short weekStart )
1127 d->mWeekStart = weekStart;
1133 d->mDateStart = d->mDateStart.toTimeSpec( oldSpec );
1134 d->mDateStart.setTimeSpec( newSpec );
1135 if ( d->mDuration == 0 ) {
1136 d->mDateEnd = d->mDateEnd.toTimeSpec( oldSpec );
1137 d->mDateEnd.setTimeSpec( newSpec );
1199 void RecurrenceRule::Private::buildConstraints()
1201 mTimedRepetition = 0;
1202 mNoByRules = mBySetPos.isEmpty();
1203 mConstraints.clear();
1204 Constraint con( mDateStart.timeSpec() );
1205 if ( mWeekStart > 0 ) {
1206 con.setWeekstart( mWeekStart );
1208 mConstraints.append( con );
1212 Constraint::List tmp;
1214 #define intConstraint( list, setElement ) \
1215 if ( !list.isEmpty() ) { \
1216 mNoByRules = false; \
1217 iend = list.count(); \
1218 if ( iend == 1 ) { \
1219 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1220 mConstraints[c].setElement( list[0] ); \
1223 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1224 for ( i = 0; i < iend; ++i ) { \
1225 con = mConstraints[c]; \
1226 con.setElement( list[i] ); \
1227 tmp.append( con ); \
1230 mConstraints = tmp; \
1235 intConstraint( mBySeconds, setSecond );
1236 intConstraint( mByMinutes, setMinute );
1237 intConstraint( mByHours, setHour );
1238 intConstraint( mByMonthDays, setDay );
1239 intConstraint( mByMonths, setMonth );
1240 intConstraint( mByYearDays, setYearday );
1241 intConstraint( mByWeekNumbers, setWeeknumber );
1242 #undef intConstraint
1244 if ( !mByDays.isEmpty() ) {
1246 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) {
1247 for ( i = 0, iend = mByDays.count(); i < iend; ++i ) {
1248 con = mConstraints[c];
1249 con.setWeekday( mByDays[i].day() );
1250 con.setWeekdaynr( mByDays[i].pos() );
1258 #define fixConstraint( setElement, value ) \
1260 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1261 mConstraints[c].setElement( value ); \
1268 if ( mPeriod == rWeekly && mByDays.isEmpty() ) {
1269 fixConstraint( setWeekday, mDateStart.date().dayOfWeek() );
1274 switch ( mPeriod ) {
1276 if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
1277 mByYearDays.isEmpty() && mByMonths.isEmpty() ) {
1278 fixConstraint( setMonth, mDateStart.date().month() );
1281 if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
1282 mByYearDays.isEmpty() && mByMonthDays.isEmpty() ) {
1283 fixConstraint( setDay, mDateStart.date().day() );
1287 if ( mByHours.isEmpty() ) {
1288 fixConstraint( setHour, mDateStart.time().hour() );
1291 if ( mByMinutes.isEmpty() ) {
1292 fixConstraint( setMinute, mDateStart.time().minute() );
1295 if ( mBySeconds.isEmpty() ) {
1296 fixConstraint( setSecond, mDateStart.time().second() );
1302 #undef fixConstraint
1305 switch ( mPeriod ) {
1307 mTimedRepetition = mFrequency * 3600;
1310 mTimedRepetition = mFrequency * 60;
1313 mTimedRepetition = mFrequency;
1319 for ( c = 0, cend = mConstraints.count(); c < cend; ) {
1320 if ( mConstraints[c].isConsistent( mPeriod ) ) {
1323 mConstraints.removeAt( c );
1332 bool RecurrenceRule::Private::buildCache()
const
1334 Q_ASSERT( mDuration > 0 );
1337 Constraint interval( getNextValidDateInterval( mDateStart, mPeriod ) );
1340 DateTimeList dts = datesForInterval( interval, mPeriod );
1343 int i = dts.
findLT( mDateStart );
1345 dts.erase( dts.begin(), dts.begin() + i + 1 );
1350 for (
int loopnr = 0; loopnr < LOOP_LIMIT && dts.count() < mDuration; ++loopnr ) {
1351 interval.increase( mPeriod, mFrequency );
1353 dts += datesForInterval( interval, mPeriod );
1355 if ( dts.count() > mDuration ) {
1357 dts.erase( dts.begin() + mDuration, dts.end() );
1367 if (
int( dts.count() ) == mDuration ) {
1368 mCachedDateEnd = dts.last();
1372 mCachedDateEnd = KDateTime();
1373 mCachedLastDate = interval.intervalDateTime( mPeriod );
1381 KDateTime dt = kdt.toTimeSpec( d->mDateStart.timeSpec() );
1382 for (
int i = 0, iend = d->mConstraints.count(); i < iend; ++i ) {
1383 if ( d->mConstraints[i].matches( dt, recurrenceType() ) ) {
1394 if ( !qd.isValid() || !d->mDateStart.isValid() ) {
1402 if ( qd < d->mDateStart.date() ) {
1407 if ( d->mDuration >= 0 ) {
1408 endDate =
endDt().date();
1409 if ( qd > endDate ) {
1417 for ( i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i ) {
1418 match = d->mConstraints[i].matches( qd, recurrenceType() );
1424 KDateTime start( qd, QTime( 0, 0, 0 ), d->mDateStart.timeSpec() );
1425 Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
1428 if ( !interval.matches( qd, recurrenceType() ) ) {
1434 KDateTime end = start.addDays( 1 );
1436 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1437 for ( i = 0, iend = dts.count(); i < iend; ++i ) {
1438 if ( dts[i].date() >= qd ) {
1439 return dts[i].date() == qd;
1442 interval.increase( recurrenceType(),
frequency() );
1443 }
while ( interval.intervalDateTime( recurrenceType() ) < end );
1448 KDateTime start( qd, QTime( 0, 0, 0 ), timeSpec );
1449 KDateTime end = start.addDays( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1450 start = start.toTimeSpec( d->mDateStart.timeSpec() );
1451 if ( end < d->mDateStart ) {
1454 if ( start < d->mDateStart ) {
1455 start = d->mDateStart;
1459 if ( d->mDuration >= 0 ) {
1460 KDateTime endRecur =
endDt();
1461 if ( endRecur.isValid() ) {
1462 if ( start > endRecur ) {
1465 if ( end > endRecur ) {
1471 if ( d->mTimedRepetition ) {
1473 int n =
static_cast<int>( ( d->mDateStart.secsTo_long( start ) - 1 ) % d->mTimedRepetition );
1474 return start.addSecs( d->mTimedRepetition - n ) < end;
1478 QDate startDay = start.date();
1479 QDate endDay = end.addSecs( -1 ).date();
1480 int dayCount = startDay.daysTo( endDay ) + 1;
1485 for ( i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i ) {
1486 match = d->mConstraints[i].matches( startDay, recurrenceType() );
1487 for (
int day = 1; day < dayCount && !match; ++day ) {
1488 match = d->mConstraints[i].matches( startDay.addDays( day ), recurrenceType() );
1495 Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
1499 Constraint intervalm = interval;
1501 match = intervalm.matches( startDay, recurrenceType() );
1502 for (
int day = 1; day < dayCount && !match; ++day ) {
1503 match = intervalm.matches( startDay.addDays( day ), recurrenceType() );
1508 intervalm.increase( recurrenceType(),
frequency() );
1509 }
while ( intervalm.intervalDateTime( recurrenceType() ) < end );
1518 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1519 int i = dts.
findGE( start );
1521 return dts[i] <= end;
1523 interval.increase( recurrenceType(),
frequency() );
1524 }
while ( interval.intervalDateTime( recurrenceType() ) < end );
1532 KDateTime dt( kdt.toTimeSpec( d->mDateStart.timeSpec() ) );
1535 return recursOn( dt.date(), dt.timeSpec() );
1537 if ( dt < d->mDateStart ) {
1541 if ( d->mDuration >= 0 && dt >
endDt() ) {
1545 if ( d->mTimedRepetition ) {
1547 return !( d->mDateStart.secsTo_long( dt ) % d->mTimedRepetition );
1557 Constraint interval( d->getNextValidDateInterval( dt, recurrenceType() ) );
1559 if ( interval.matches( dt, recurrenceType() ) ) {
1571 KDateTime start( date, QTime( 0, 0, 0 ), timeSpec );
1572 KDateTime end = start.addDays( 1 ).addSecs( -1 );
1574 for (
int i = 0, iend = dts.count(); i < iend; ++i ) {
1575 lst += dts[i].toTimeSpec( timeSpec ).time();
1584 KDateTime toDate( dt.toTimeSpec( d->mDateStart.timeSpec() ) );
1587 if ( toDate < d->mDateStart ) {
1591 if ( d->mDuration > 0 && toDate >=
endDt() ) {
1592 return d->mDuration;
1595 if ( d->mTimedRepetition ) {
1597 return static_cast<int>( d->mDateStart.secsTo_long( toDate ) / d->mTimedRepetition );
1605 return durationTo( KDateTime( date, QTime( 23, 59, 59 ), d->mDateStart.timeSpec() ) );
1611 KDateTime toDate( afterDate.toTimeSpec( d->mDateStart.timeSpec() ) );
1614 if ( !toDate.isValid() || toDate < d->mDateStart ) {
1618 if ( d->mTimedRepetition ) {
1620 KDateTime prev = toDate;
1621 if ( d->mDuration >= 0 &&
endDt().isValid() && toDate >
endDt() ) {
1622 prev =
endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1624 int n =
static_cast<int>( ( d->mDateStart.secsTo_long( prev ) - 1 ) % d->mTimedRepetition );
1628 prev = prev.addSecs( -n - 1 );
1629 return prev >= d->mDateStart ? prev : KDateTime();
1633 if ( d->mDuration > 0 ) {
1634 if ( !d->mCached ) {
1637 int i = d->mCachedDates.findLT( toDate );
1639 return d->mCachedDates[i];
1644 KDateTime prev = toDate;
1645 if ( d->mDuration >= 0 &&
endDt().isValid() && toDate >
endDt() ) {
1646 prev =
endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1649 Constraint interval( d->getPreviousValidDateInterval( prev, recurrenceType() ) );
1650 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1651 int i = dts.
findLT( prev );
1653 return ( dts[i] >= d->mDateStart ) ? dts[i] : KDateTime();
1657 while ( interval.intervalDateTime( recurrenceType() ) > d->mDateStart ) {
1658 interval.increase( recurrenceType(), -
int(
frequency() ) );
1660 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1662 if ( !dts.isEmpty() ) {
1664 if ( prev.isValid() && prev >= d->mDateStart ) {
1677 KDateTime fromDate( preDate.toTimeSpec( d->mDateStart.timeSpec() ) );
1679 if ( d->mDuration >= 0 &&
endDt().isValid() && fromDate >=
endDt() ) {
1684 if ( fromDate < d->mDateStart ) {
1685 fromDate = d->mDateStart.addSecs( -1 );
1688 if ( d->mTimedRepetition ) {
1690 int n =
static_cast<int>( ( d->mDateStart.secsTo_long( fromDate ) + 1 ) % d->mTimedRepetition );
1691 KDateTime next = fromDate.addSecs( d->mTimedRepetition - n + 1 );
1692 return d->mDuration < 0 || !
endDt().isValid() || next <=
endDt() ? next : KDateTime();
1695 if ( d->mDuration > 0 ) {
1696 if ( !d->mCached ) {
1699 int i = d->mCachedDates.findGT( fromDate );
1701 return d->mCachedDates[i];
1705 KDateTime end =
endDt();
1706 Constraint interval( d->getNextValidDateInterval( fromDate, recurrenceType() ) );
1707 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1708 int i = dts.
findGT( fromDate );
1710 return ( d->mDuration < 0 || dts[i] <= end ) ? dts[i] : KDateTime();
1712 interval.increase( recurrenceType(),
frequency() );
1713 if ( d->mDuration >= 0 && interval.intervalDateTime( recurrenceType() ) > end ) {
1722 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1723 if ( dts.count() > 0 ) {
1724 KDateTime ret( dts[0] );
1725 if ( d->mDuration >= 0 && ret > end ) {
1731 interval.increase( recurrenceType(),
frequency() );
1732 }
while ( ++loop < LOOP_LIMIT &&
1733 ( d->mDuration < 0 || interval.intervalDateTime( recurrenceType() ) < end ) );
1738 const KDateTime &dtEnd )
const
1740 const KDateTime start = dtStart.toTimeSpec( d->mDateStart.timeSpec() );
1741 const KDateTime end = dtEnd.toTimeSpec( d->mDateStart.timeSpec() );
1743 if ( end < d->mDateStart ) {
1746 KDateTime enddt = end;
1747 if ( d->mDuration >= 0 ) {
1748 const KDateTime endRecur =
endDt();
1749 if ( endRecur.isValid() ) {
1750 if ( start > endRecur ) {
1753 if ( end >= endRecur ) {
1759 if ( d->mTimedRepetition ) {
1763 qint64 offsetFromNextOccurrence;
1764 if ( d->mDateStart < start ) {
1765 offsetFromNextOccurrence =
1766 d->mTimedRepetition - ( d->mDateStart.secsTo_long( start ) % d->mTimedRepetition );
1768 offsetFromNextOccurrence = -( d->mDateStart.secsTo_long( start ) % d->mTimedRepetition );
1770 KDateTime dt = start.addSecs( offsetFromNextOccurrence );
1771 if ( dt <= enddt ) {
1772 int numberOfOccurrencesWithinInterval =
1773 static_cast<int>( dt.secsTo_long( enddt ) / d->mTimedRepetition ) + 1;
1775 numberOfOccurrencesWithinInterval = qMin( numberOfOccurrencesWithinInterval, LOOP_LIMIT );
1777 i < numberOfOccurrencesWithinInterval;
1778 dt = dt.addSecs( d->mTimedRepetition ), ++i ) {
1785 KDateTime st = start;
1787 if ( d->mDuration > 0 ) {
1788 if ( !d->mCached ) {
1791 if ( d->mCachedDateEnd.isValid() && start > d->mCachedDateEnd ) {
1794 int i = d->mCachedDates.
findGE( start );
1796 int iend = d->mCachedDates.findGT( enddt, i );
1798 iend = d->mCachedDates.count();
1802 while ( i < iend ) {
1803 result += d->mCachedDates[i++];
1806 if ( d->mCachedDateEnd.isValid() ) {
1808 }
else if ( !result.isEmpty() ) {
1809 result += KDateTime();
1816 st = d->mCachedLastDate.addSecs( 1 );
1819 Constraint interval( d->getNextValidDateInterval( st, recurrenceType() ) );
1822 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1824 int iend = dts.count();
1831 int j = dts.
findGT( enddt, i );
1836 while ( i < iend ) {
1840 interval.increase( recurrenceType(),
frequency() );
1841 }
while ( ++loop < LOOP_LIMIT &&
1842 interval.intervalDateTime( recurrenceType() ) < end );
1851 Constraint RecurrenceRule::Private::getPreviousValidDateInterval(
const KDateTime &dt,
1852 PeriodType type )
const
1855 KDateTime start = mDateStart;
1856 KDateTime nextValid( start );
1858 KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
1871 periods =
static_cast<int>( start.secsTo_long( toDate ) / modifier );
1873 if ( mFrequency > 0 ) {
1874 periods = ( periods / mFrequency ) * mFrequency;
1876 nextValid = start.addSecs( modifier * periods );
1879 toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
1880 start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
1883 periods = start.daysTo( toDate ) / modifier;
1885 if ( mFrequency > 0 ) {
1886 periods = ( periods / mFrequency ) * mFrequency;
1888 nextValid = start.addDays( modifier * periods );
1892 periods = 12 * ( toDate.date().year() - start.date().year() ) +
1893 ( toDate.date().month() - start.date().month() );
1895 if ( mFrequency > 0 ) {
1896 periods = ( periods / mFrequency ) * mFrequency;
1900 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
1901 nextValid.setDate( start.date().addMonths( periods ) );
1904 periods = ( toDate.date().year() - start.date().year() );
1906 if ( mFrequency > 0 ) {
1907 periods = ( periods / mFrequency ) * mFrequency;
1909 nextValid.setDate( start.date().addYears( periods ) );
1915 return Constraint( nextValid, type, mWeekStart );
1922 Constraint RecurrenceRule::Private::getNextValidDateInterval(
const KDateTime &dt,
1923 PeriodType type )
const
1927 KDateTime start = mDateStart;
1928 KDateTime nextValid( start );
1930 KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
1943 periods =
static_cast<int>( start.secsTo_long( toDate ) / modifier );
1944 periods = qMax( 0L, periods );
1945 if ( periods > 0 && mFrequency > 0 ) {
1946 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1948 nextValid = start.addSecs( modifier * periods );
1952 toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
1953 start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
1956 periods = start.daysTo( toDate ) / modifier;
1957 periods = qMax( 0L, periods );
1958 if ( periods > 0 && mFrequency > 0 ) {
1959 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1961 nextValid = start.addDays( modifier * periods );
1965 periods = 12 * ( toDate.date().year() - start.date().year() ) +
1966 ( toDate.date().month() - start.date().month() );
1967 periods = qMax( 0L, periods );
1968 if ( periods > 0 && mFrequency > 0 ) {
1969 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1973 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
1974 nextValid.setDate( start.date().addMonths( periods ) );
1978 periods = ( toDate.date().year() - start.date().year() );
1979 periods = qMax( 0L, periods );
1980 if ( periods > 0 && mFrequency > 0 ) {
1981 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1983 nextValid.setDate( start.date().addYears( periods ) );
1989 return Constraint( nextValid, type, mWeekStart );
1992 DateTimeList RecurrenceRule::Private::datesForInterval(
const Constraint &interval,
1993 PeriodType type )
const
2002 for (
int i = 0, iend = mConstraints.count(); i < iend; ++i ) {
2003 Constraint merged( interval );
2004 if ( merged.merge( mConstraints[i] ) ) {
2006 if ( merged.year > 0 && merged.hour >= 0 && merged.minute >= 0 && merged.second >= 0 ) {
2009 QList<KDateTime> lstnew = merged.dateTimes( type );
2026 if ( !mBySetPos.isEmpty() ) {
2029 for (
int i = 0, iend = mBySetPos.count(); i < iend; ++i ) {
2030 int pos = mBySetPos[i];
2035 pos += tmplst.count();
2037 if ( pos >= 0 && pos < tmplst.count() ) {
2038 lst.append( tmplst[pos] );
2052 if ( !d->mRRule.isEmpty() ) {
2053 kDebug() <<
" RRULE=" << d->mRRule;
2057 kDebug() <<
" Period type:" << int( recurrenceType() ) <<
", frequency:" <<
frequency();
2058 kDebug() <<
" #occurrences:" <<
duration();
2059 kDebug() <<
" start date:" << dumpTime(
startDt() )
2060 <<
", end date:" << dumpTime(
endDt() );
2062 #define dumpByIntList(list,label) \
2063 if ( !list.isEmpty() ) {\
2065 for ( int i = 0, iend = list.count(); i < iend; ++i ) {\
2066 lst.append( QString::number( list[i] ) );\
2068 kDebug() << " " << label << lst.join( ", " );\
2070 dumpByIntList( d->mBySeconds,
"BySeconds: " );
2071 dumpByIntList( d->mByMinutes,
"ByMinutes: " );
2072 dumpByIntList( d->mByHours,
"ByHours: " );
2073 if ( !d->mByDays.isEmpty() ) {
2075 for (
int i = 0, iend = d->mByDays.count(); i < iend; ++i ) {\
2076 lst.append( ( d->mByDays[i].pos() ? QString::number( d->mByDays[i].pos() ) :
"" ) +
2077 DateHelper::dayName( d->mByDays[i].day() ) );
2079 kDebug() <<
" ByDays: " << lst.join(
", " );
2081 dumpByIntList( d->mByMonthDays,
"ByMonthDays:" );
2082 dumpByIntList( d->mByYearDays,
"ByYearDays: " );
2083 dumpByIntList( d->mByWeekNumbers,
"ByWeekNr: " );
2084 dumpByIntList( d->mByMonths,
"ByMonths: " );
2085 dumpByIntList( d->mBySetPos,
"BySetPos: " );
2086 #undef dumpByIntList
2088 kDebug() <<
" Week start:" << DateHelper::dayName( d->mWeekStart );
2090 kDebug() <<
" Constraints:";
2092 for (
int i = 0, iend = d->mConstraints.count(); i < iend; ++i ) {
2093 d->mConstraints[i].dump();
2099 void Constraint::dump()
const
2101 kDebug() <<
" ~> Y=" << year
2107 <<
", wd=" << weekday
2108 <<
",#wd=" << weekdaynr
2109 <<
", #w=" << weeknumber
2110 <<
", yd=" << yearday;
2114 QString dumpTime(
const KDateTime &dt )
2117 if ( !dt.isValid() ) {
2121 if ( dt.isDateOnly() ) {
2122 result = dt.toString(
"%a %Y-%m-%d %:Z" );
2124 result = dt.toString(
"%a %Y-%m-%d %H:%M:%S %:Z" );
2125 if ( dt.isSecondOccurrence() ) {
2126 result += QLatin1String(
" (2nd)" );
2129 if ( dt.timeSpec() == KDateTime::Spec::ClockTime() ) {
2130 result += QLatin1String(
"Clock" );
2141 return d->mDateStart;
2151 return d->mFrequency;
2156 return d->mDuration;
2159 QString RecurrenceRule::rrule()
const
2171 return d->mIsReadOnly;
2176 d->mIsReadOnly = readOnly;
2181 return d->mPeriod != rNone;
2189 const QList<int> &RecurrenceRule::bySeconds()
const
2191 return d->mBySeconds;
2194 const QList<int> &RecurrenceRule::byMinutes()
const
2196 return d->mByMinutes;
2199 const QList<int> &RecurrenceRule::byHours()
const
2204 const QList<RecurrenceRule::WDayPos> &RecurrenceRule::byDays()
const
2209 const QList<int> &RecurrenceRule::byMonthDays()
const
2211 return d->mByMonthDays;
2214 const QList<int> &RecurrenceRule::byYearDays()
const
2216 return d->mByYearDays;
2219 const QList<int> &RecurrenceRule::byWeekNumbers()
const
2221 return d->mByWeekNumbers;
2224 const QList<int> &RecurrenceRule::byMonths()
const
2226 return d->mByMonths;
2229 const QList<int> &RecurrenceRule::bySetPos()
const
2231 return d->mBySetPos;
2234 short RecurrenceRule::weekStart()
const
2236 return d->mWeekStart;
2239 RecurrenceRule::RuleObserver::~RuleObserver()
2243 RecurrenceRule::WDayPos::WDayPos(
int ps,
short dy )
2244 : mDay( dy ), mPos( ps )
2248 void RecurrenceRule::WDayPos::setDay(
short dy )
2253 short RecurrenceRule::WDayPos::day()
const
2258 void RecurrenceRule::WDayPos::setPos(
int ps )
2263 int RecurrenceRule::WDayPos::pos()
const
void setFrequency(int freq)
Sets the recurrence frequency, in terms of the recurrence time period type.
structure for describing the n-th weekday of the month/year.
void dump() const
Debug output.
bool dateMatchesRules(const KDateTime &dt) const
Returns true if the date matches the rules.
void setDuration(int duration)
Sets the total number of times the event is to occur, including both the first and last...
void sortUnique()
Sort the list.
bool isReadOnly() const
Returns true if the recurrence is read-only; false if it can be changed.
RecurrenceRule()
/************************************************************************** RecurrenceRule * ...
void setRRule(const QString &rrule)
Set the RRULE string for the rule.
void setReadOnly(bool readOnly)
Set if recurrence is read-only or can be changed.
void clear()
Turns off recurrence for the event.
void setEndDt(const KDateTime &endDateTime)
Sets the date and time of the last recurrence.
bool allDay() const
Returns whether the start date has no time associated.
bool recursAt(const KDateTime &dt) const
Returns true if the date/time specified is one at which the event will recur.
A QList which can be sorted.
void shiftTimes(const KDateTime::Spec &oldSpec, const KDateTime::Spec &newSpec)
Shift the times of the rule so that they appear at the same clock time as before but in a new time zo...
uint frequency() const
Returns the recurrence frequency, in terms of the recurrence time period type.
KDateTime endDt(bool *result=0) const
Returns the date and time of the last recurrence.
KDateTime startDt() const
Returns the recurrence start date/time.
void clear()
Removes all recurrence and exception rules and dates.
bool recursOn(const QDate &date, const KDateTime::Spec &timeSpec) const
Returns true if the date specified is one on which the event will recur.
TimeList recurTimesOn(const QDate &date, const KDateTime::Spec &timeSpec) const
Returns a list of the times on the specified date at which the recurrence will occur.
KDateTime getNextDate(const KDateTime &preDateTime) const
Returns the date and time of the next recurrence, after the specified date/time.
bool recurs() const
Returns the event's recurrence status.
void removeObserver(RuleObserver *observer)
Removes an observer that was added with addObserver.
int findGE(const T &value, int start=0) const
Search the list for the first item >= value.
PeriodType
enum for describing the frequency how an event recurs, if at all.
void addObserver(RuleObserver *observer)
Installs an observer.
int durationTo(const KDateTime &dt) const
Returns the number of recurrences up to and including the date/time specified.
int findGT(const T &value, int start=0) const
Search the list for the first item > value.
int duration() const
Returns -1 if the event recurs infinitely, 0 if the end date is set, otherwise the total number of re...
void setAllDay(bool allDay)
Sets whether the dtstart is all-day (i.e.
int findLT(const T &value, int start=0) const
Search the list for the last item < value.
void setStartDt(const KDateTime &start)
Sets the recurrence start date/time.
DateTimeList timesInInterval(const KDateTime &start, const KDateTime &end) const
Returns a list of all the times at which the recurrence will occur between two specified times...
This class represents a recurrence rule for a calendar incidence.
KDateTime getPreviousDate(const KDateTime &afterDateTime) const
Returns the date and time of the last previous recurrence, before the specified date/time.