23 #include "recurrencerule.h"
28 #include <QtCore/QDateTime>
29 #include <QtCore/QList>
30 #include <QtCore/QStringList>
38 const int LOOP_LIMIT = 10000;
40 static QString dumpTime(
const KDateTime &dt );
62 static QString dayName(
short day );
64 static QDate getNthWeek(
int year,
int weeknumber,
short weekstart = 1 );
65 static int weekNumbersInYear(
int year,
short weekstart = 1 );
66 static int getWeekNumber(
const QDate &date,
short weekstart,
int *year = 0 );
67 static int getWeekNumberNeg(
const QDate &date,
short weekstart,
int *year = 0 );
70 static QDate getDate(
int year,
int month,
int day )
73 return QDate( year, month, day );
79 return QDate( year, month, 1 ).addDays( day );
87 QString DateHelper::dayName(
short day )
110 QDate DateHelper::getNthWeek(
int year,
int weeknumber,
short weekstart )
112 if ( weeknumber == 0 ) {
117 QDate dt( year, 1, 4 );
118 int adjust = -( 7 + dt.dayOfWeek() - weekstart ) % 7;
119 if ( weeknumber > 0 ) {
120 dt = dt.addDays( 7 * (weeknumber-1) + adjust );
121 }
else if ( weeknumber < 0 ) {
122 dt = dt.addYears( 1 );
123 dt = dt.addDays( 7 * weeknumber + adjust );
128 int DateHelper::getWeekNumber(
const QDate &date,
short weekstart,
int *year )
132 dt = dt.addDays( -( 7 + dt.dayOfWeek() - weekstart ) % 7 );
134 int daysto = dt.daysTo( date );
138 dt = QDate( y, 1, 4 );
139 dt = dt.addDays( -( 7 + dt.dayOfWeek() - weekstart ) % 7 );
140 daysto = dt.daysTo( date );
141 }
else if ( daysto > 355 ) {
143 QDate dtn( y+1, 1, 4 );
144 dtn = dtn.addDays( -( 7 + dtn.dayOfWeek() - weekstart ) % 7 );
145 int dayston = dtn.daysTo( date );
146 if ( dayston >= 0 ) {
155 return daysto / 7 + 1;
158 int DateHelper::weekNumbersInYear(
int year,
short weekstart )
160 QDate dt( year, 1, weekstart );
161 QDate dt1( year + 1, 1, weekstart );
162 return dt.daysTo( dt1 ) / 7;
166 int DateHelper::getWeekNumberNeg(
const QDate &date,
short weekstart,
int *year )
168 int weekpos = getWeekNumber( date, weekstart, year );
169 return weekNumbersInYear( *year, weekstart ) - weekpos - 1;
180 typedef QList<Constraint> List;
182 explicit Constraint( KDateTime::Spec,
int wkst = 1 );
185 void setYear(
int n )
190 void setMonth(
int n )
200 void setHour(
int n )
205 void setMinute(
int n )
210 void setSecond(
int n )
215 void setWeekday(
int n )
220 void setWeekdaynr(
int n )
225 void setWeeknumber(
int n )
230 void setYearday(
int n )
235 void setWeekstart(
int n )
240 void setSecondOccurrence(
int n )
242 secondOccurrence = n;
257 KDateTime::Spec timespec;
258 bool secondOccurrence;
263 bool merge(
const Constraint &interval );
264 bool isConsistent()
const;
269 void appendDateTime(
const QDate &date,
const QTime &time, QList<KDateTime> &list )
const;
273 mutable bool useCachedDt;
274 mutable KDateTime cachedDt;
277 Constraint::Constraint( KDateTime::Spec spec,
int wkst )
286 timespec( dt.timeSpec() )
289 readDateTime( dt, type );
292 void Constraint::clear()
304 secondOccurrence =
false;
313 if ( weeknumber == 0 ) {
314 if ( year > 0 && year != dt.year() ) {
319 if ( weeknumber > 0 &&
320 weeknumber != DateHelper::getWeekNumber( dt, weekstart, &y ) ) {
323 if ( weeknumber < 0 &&
324 weeknumber != DateHelper::getWeekNumberNeg( dt, weekstart, &y ) ) {
327 if ( year > 0 && year != y ) {
332 if ( month > 0 && month != dt.month() ) {
335 if ( day > 0 && day != dt.day() ) {
338 if ( day < 0 && dt.day() != ( dt.daysInMonth() + day + 1 ) ) {
342 if ( weekday != dt.dayOfWeek() ) {
345 if ( weekdaynr != 0 ) {
348 if ( ( type == RecurrenceRule::rMonthly ) ||
349 ( type == RecurrenceRule::rYearly && month > 0 ) ) {
351 if ( weekdaynr > 0 &&
352 weekdaynr != ( dt.day() - 1 ) / 7 + 1 ) {
355 if ( weekdaynr < 0 &&
356 weekdaynr != -( ( dt.daysInMonth() - dt.day() ) / 7 + 1 ) ) {
361 if ( weekdaynr > 0 &&
362 weekdaynr != ( dt.dayOfYear() - 1 ) / 7 + 1 ) {
365 if ( weekdaynr < 0 &&
366 weekdaynr != -( ( dt.daysInYear() - dt.dayOfYear() ) / 7 + 1 ) ) {
372 if ( yearday > 0 && yearday != dt.dayOfYear() ) {
375 if ( yearday < 0 && yearday != dt.daysInYear() - dt.dayOfYear() + 1 ) {
386 if ( ( hour >= 0 && ( hour != dt.time().hour() ||
387 secondOccurrence != dt.isSecondOccurrence() ) ) ||
388 ( minute >= 0 && minute != dt.time().minute() ) ||
389 ( second >= 0 && second != dt.time().second() ) ||
390 !matches( dt.date(), type ) ) {
411 bool subdaily =
true;
413 case RecurrenceRule::rSecondly:
414 t.setHMS( hour, minute, second );
416 case RecurrenceRule::rMinutely:
417 t.setHMS( hour, minute, 0 );
419 case RecurrenceRule::rHourly:
420 t.setHMS( hour, 0, 0 );
422 case RecurrenceRule::rDaily:
424 case RecurrenceRule::rWeekly:
425 d = DateHelper::getNthWeek( year, weeknumber, weekstart );
428 case RecurrenceRule::rMonthly:
429 d.setYMD( year, month, 1 );
432 case RecurrenceRule::rYearly:
433 d.setYMD( year, 1, 1 );
440 d = DateHelper::getDate( year, (month>0)?month:1, day?day:1 );
442 cachedDt = KDateTime( d, t, timespec );
443 if ( secondOccurrence ) {
444 cachedDt.setSecondOccurrence(
true );
450 bool Constraint::merge(
const Constraint &interval )
452 #define mergeConstraint( name, cmparison ) \
453 if ( interval.name cmparison ) { \
454 if ( !( name cmparison ) ) { \
455 name = interval.name; \
456 } else if ( name != interval.name ) { \
463 mergeConstraint( year, > 0 );
464 mergeConstraint( month, > 0 );
465 mergeConstraint( day, != 0 );
466 mergeConstraint( hour, >= 0 );
467 mergeConstraint( minute, >= 0 );
468 mergeConstraint( second, >= 0 );
470 mergeConstraint( weekday, != 0 );
471 mergeConstraint( weekdaynr, != 0 );
472 mergeConstraint( weeknumber, != 0 );
473 mergeConstraint( yearday, != 0 );
475 #undef mergeConstraint
498 QList<KDateTime> result;
500 if ( !isConsistent( type ) ) {
505 QTime tm( hour, minute, second );
507 if ( !done && day && month > 0 ) {
508 appendDateTime( DateHelper::getDate( year, month, day ), tm, result );
512 if ( !done && weekday == 0 && weeknumber == 0 && yearday == 0 ) {
514 uint mstart = ( month > 0 ) ? month : 1;
515 uint mend = ( month <= 0 ) ? 12 : month;
516 for ( uint m = mstart; m <= mend; ++m ) {
520 }
else if ( day < 0 ) {
521 QDate date( year, month, 1 );
522 dstart = dend = date.daysInMonth() + day + 1;
524 QDate date( year, month, 1 );
526 dend = date.daysInMonth();
529 for ( QDate dt( year, m, dstart ); ; dt = dt.addDays( 1 ) ) {
530 appendDateTime( dt, tm, result );
541 if ( !done && yearday != 0 ) {
543 QDate d( year + ( ( yearday > 0 ) ? 0 : 1 ), 1, 1 );
544 d = d.addDays( yearday - ( ( yearday > 0 ) ? 1 : 0 ) );
545 appendDateTime( d, tm, result );
550 if ( !done && weeknumber != 0 ) {
551 QDate wst( DateHelper::getNthWeek( year, weeknumber, weekstart ) );
552 if ( weekday != 0 ) {
553 wst = wst.addDays( ( 7 + weekday - weekstart ) % 7 );
554 appendDateTime( wst, tm, result );
556 for (
int i = 0; i < 7; ++i ) {
557 appendDateTime( wst, tm, result );
558 wst = wst.addDays( 1 );
565 if ( !done && weekday != 0 ) {
566 QDate dt( year, 1, 1 );
570 bool inMonth = ( type == RecurrenceRule::rMonthly ) ||
571 ( type == RecurrenceRule::rYearly && month > 0 );
572 if ( inMonth && month > 0 ) {
573 dt = QDate( year, month, 1 );
576 if ( weekdaynr < 0 ) {
579 dt = dt.addMonths( 1 );
581 dt = dt.addYears( 1 );
584 int adj = ( 7 + weekday - dt.dayOfWeek() ) % 7;
585 dt = dt.addDays( adj );
587 if ( weekdaynr > 0 ) {
588 dt = dt.addDays( ( weekdaynr - 1 ) * 7 );
589 appendDateTime( dt, tm, result );
590 }
else if ( weekdaynr < 0 ) {
591 dt = dt.addDays( weekdaynr * 7 );
592 appendDateTime( dt, tm, result );
595 for (
int i = 0; i < maxloop; ++i ) {
596 appendDateTime( dt, tm, result );
597 dt = dt.addDays( 7 );
603 QList<KDateTime> valid;
604 for (
int i = 0, iend = result.count(); i < iend; ++i ) {
605 if ( matches( result[i], type ) ) {
606 valid.append( result[i] );
614 void Constraint::appendDateTime(
const QDate &date,
const QTime &time,
615 QList<KDateTime> &list )
const
617 KDateTime dt( date, time, timespec );
618 if ( dt.isValid() ) {
619 if ( secondOccurrence ) {
620 dt.setSecondOccurrence(
true );
629 intervalDateTime( type );
633 case RecurrenceRule::rSecondly:
634 cachedDt = cachedDt.addSecs( freq );
636 case RecurrenceRule::rMinutely:
637 cachedDt = cachedDt.addSecs( 60 * freq );
639 case RecurrenceRule::rHourly:
640 cachedDt = cachedDt.addSecs( 3600 * freq );
642 case RecurrenceRule::rDaily:
643 cachedDt = cachedDt.addDays( freq );
645 case RecurrenceRule::rWeekly:
646 cachedDt = cachedDt.addDays( 7 * freq );
648 case RecurrenceRule::rMonthly:
649 cachedDt = cachedDt.addMonths( freq );
651 case RecurrenceRule::rYearly:
652 cachedDt = cachedDt.addYears( freq );
658 readDateTime( cachedDt, type );
669 case RecurrenceRule::rSecondly:
670 second = dt.time().second();
671 case RecurrenceRule::rMinutely:
672 minute = dt.time().minute();
673 case RecurrenceRule::rHourly:
674 hour = dt.time().hour();
675 secondOccurrence = dt.isSecondOccurrence();
676 case RecurrenceRule::rDaily:
677 day = dt.date().day();
678 case RecurrenceRule::rMonthly:
679 month = dt.date().month();
680 case RecurrenceRule::rYearly:
681 year = dt.date().year();
683 case RecurrenceRule::rWeekly:
685 weeknumber = DateHelper::getWeekNumber( dt.date(), weekstart, &year );
700 class KCal::RecurrenceRule::Private
708 mIsReadOnly( false ),
714 Private &operator=(
const Private &other );
715 bool operator==(
const Private &other )
const;
718 void buildConstraints();
719 bool buildCache()
const;
720 Constraint getNextValidDateInterval(
const KDateTime &preDate,
PeriodType type )
const;
721 Constraint getPreviousValidDateInterval(
const KDateTime &afterDate,
PeriodType type )
const;
727 KDateTime mDateStart;
737 QList<int> mBySeconds;
738 QList<int> mByMinutes;
741 QList<WDayPos> mByDays;
742 QList<int> mByMonthDays;
743 QList<int> mByYearDays;
744 QList<int> mByWeekNumbers;
745 QList<int> mByMonths;
746 QList<int> mBySetPos;
749 Constraint::List mConstraints;
750 QList<RuleObserver*> mObservers;
754 mutable KDateTime mCachedDateEnd;
755 mutable KDateTime mCachedLastDate;
756 mutable bool mCached;
761 uint mTimedRepetition;
764 RecurrenceRule::Private::Private(
RecurrenceRule *parent,
const Private &p )
767 mPeriod( p.mPeriod ),
768 mDateStart( p.mDateStart ),
769 mFrequency( p.mFrequency ),
770 mDuration( p.mDuration ),
771 mDateEnd( p.mDateEnd ),
773 mBySeconds( p.mBySeconds ),
774 mByMinutes( p.mByMinutes ),
775 mByHours( p.mByHours ),
776 mByDays( p.mByDays ),
777 mByMonthDays( p.mByMonthDays ),
778 mByYearDays( p.mByYearDays ),
779 mByWeekNumbers( p.mByWeekNumbers ),
780 mByMonths( p.mByMonths ),
781 mBySetPos( p.mBySetPos ),
782 mWeekStart( p.mWeekStart ),
784 mIsReadOnly( p.mIsReadOnly ),
790 RecurrenceRule::Private &RecurrenceRule::Private::operator=(
const Private &p )
799 mDateStart = p.mDateStart;
800 mFrequency = p.mFrequency;
801 mDuration = p.mDuration;
802 mDateEnd = p.mDateEnd;
804 mBySeconds = p.mBySeconds;
805 mByMinutes = p.mByMinutes;
806 mByHours = p.mByHours;
808 mByMonthDays = p.mByMonthDays;
809 mByYearDays = p.mByYearDays;
810 mByWeekNumbers = p.mByWeekNumbers;
811 mByMonths = p.mByMonths;
812 mBySetPos = p.mBySetPos;
813 mWeekStart = p.mWeekStart;
815 mIsReadOnly = p.mIsReadOnly;
823 bool RecurrenceRule::Private::operator==(
const Private &r )
const
826 mPeriod == r.mPeriod &&
827 mDateStart == r.mDateStart &&
828 mDuration == r.mDuration &&
829 mDateEnd == r.mDateEnd &&
830 mFrequency == r.mFrequency &&
831 mIsReadOnly == r.mIsReadOnly &&
832 mAllDay == r.mAllDay &&
833 mBySeconds == r.mBySeconds &&
834 mByMinutes == r.mByMinutes &&
835 mByHours == r.mByHours &&
836 mByDays == r.mByDays &&
837 mByMonthDays == r.mByMonthDays &&
838 mByYearDays == r.mByYearDays &&
839 mByWeekNumbers == r.mByWeekNumbers &&
840 mByMonths == r.mByMonths &&
841 mBySetPos == r.mBySetPos &&
842 mWeekStart == r.mWeekStart;
845 void RecurrenceRule::Private::clear()
855 mByMonthDays.clear();
857 mByWeekNumbers.clear();
865 void RecurrenceRule::Private::setDirty()
869 mCachedDates.clear();
870 for (
int i = 0, iend = mObservers.count(); i < iend; ++i ) {
871 if ( mObservers[i] ) {
872 mObservers[i]->recurrenceChanged( mParent );
883 : d( new Private( this ) )
888 : d( new Private( this, *r.d ) )
892 RecurrenceRule::~RecurrenceRule()
916 if ( !d->mObservers.contains( observer ) ) {
917 d->mObservers.append( observer );
923 if ( d->mObservers.contains( observer ) ) {
924 d->mObservers.removeAll( observer );
928 void RecurrenceRule::setRecurrenceType( PeriodType period )
942 if ( d->mPeriod == rNone ) {
945 if ( d->mDuration < 0 ) {
948 if ( d->mDuration == 0 ) {
958 if ( !d->buildCache() ) {
965 return d->mCachedDateEnd;
973 d->mDateEnd = dateTime;
1001 void RecurrenceRule::setDirty()
1011 d->mDateStart = start;
1020 d->mFrequency = freq;
1024 void RecurrenceRule::setBySeconds(
const QList<int> bySeconds )
1029 d->mBySeconds = bySeconds;
1033 void RecurrenceRule::setByMinutes(
const QList<int> byMinutes )
1038 d->mByMinutes = byMinutes;
1042 void RecurrenceRule::setByHours(
const QList<int> byHours )
1047 d->mByHours = byHours;
1051 void RecurrenceRule::setByDays(
const QList<WDayPos> byDays )
1056 d->mByDays = byDays;
1060 void RecurrenceRule::setByMonthDays(
const QList<int> byMonthDays )
1065 d->mByMonthDays = byMonthDays;
1069 void RecurrenceRule::setByYearDays(
const QList<int> byYearDays )
1074 d->mByYearDays = byYearDays;
1078 void RecurrenceRule::setByWeekNumbers(
const QList<int> byWeekNumbers )
1083 d->mByWeekNumbers = byWeekNumbers;
1087 void RecurrenceRule::setByMonths(
const QList<int> byMonths )
1092 d->mByMonths = byMonths;
1096 void RecurrenceRule::setBySetPos(
const QList<int> bySetPos )
1101 d->mBySetPos = bySetPos;
1105 void RecurrenceRule::setWeekStart(
short weekStart )
1110 d->mWeekStart = weekStart;
1116 d->mDateStart = d->mDateStart.toTimeSpec( oldSpec );
1117 d->mDateStart.setTimeSpec( newSpec );
1118 if ( d->mDuration == 0 ) {
1119 d->mDateEnd = d->mDateEnd.toTimeSpec( oldSpec );
1120 d->mDateEnd.setTimeSpec( newSpec );
1182 void RecurrenceRule::Private::buildConstraints()
1184 mTimedRepetition = 0;
1185 mNoByRules = mBySetPos.isEmpty();
1186 mConstraints.clear();
1187 Constraint con( mDateStart.timeSpec() );
1188 if ( mWeekStart > 0 ) {
1189 con.setWeekstart( mWeekStart );
1191 mConstraints.append( con );
1195 Constraint::List tmp;
1197 #define intConstraint( list, setElement ) \
1198 if ( !list.isEmpty() ) { \
1199 mNoByRules = false; \
1200 iend = list.count(); \
1201 if ( iend == 1 ) { \
1202 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1203 mConstraints[c].setElement( list[0] ); \
1206 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1207 for ( i = 0; i < iend; ++i ) { \
1208 con = mConstraints[c]; \
1209 con.setElement( list[i] ); \
1210 tmp.append( con ); \
1213 mConstraints = tmp; \
1218 intConstraint( mBySeconds, setSecond );
1219 intConstraint( mByMinutes, setMinute );
1220 intConstraint( mByHours, setHour );
1221 intConstraint( mByMonthDays, setDay );
1222 intConstraint( mByMonths, setMonth );
1223 intConstraint( mByYearDays, setYearday );
1224 intConstraint( mByWeekNumbers, setWeeknumber );
1225 #undef intConstraint
1227 if ( !mByDays.isEmpty() ) {
1229 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) {
1230 for ( i = 0, iend = mByDays.count(); i < iend; ++i ) {
1231 con = mConstraints[c];
1232 con.setWeekday( mByDays[i].day() );
1233 con.setWeekdaynr( mByDays[i].pos() );
1241 #define fixConstraint( setElement, value ) \
1243 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1244 mConstraints[c].setElement( value ); \
1251 if ( mPeriod == rWeekly && mByDays.isEmpty() ) {
1252 fixConstraint( setWeekday, mDateStart.date().dayOfWeek() );
1257 switch ( mPeriod ) {
1259 if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
1260 mByYearDays.isEmpty() && mByMonths.isEmpty() ) {
1261 fixConstraint( setMonth, mDateStart.date().month() );
1264 if ( mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
1265 mByYearDays.isEmpty() && mByMonthDays.isEmpty() ) {
1266 fixConstraint( setDay, mDateStart.date().day() );
1270 if ( mByHours.isEmpty() ) {
1271 fixConstraint( setHour, mDateStart.time().hour() );
1274 if ( mByMinutes.isEmpty() ) {
1275 fixConstraint( setMinute, mDateStart.time().minute() );
1278 if ( mBySeconds.isEmpty() ) {
1279 fixConstraint( setSecond, mDateStart.time().second() );
1285 #undef fixConstraint
1288 switch ( mPeriod ) {
1290 mTimedRepetition = mFrequency * 3600;
1293 mTimedRepetition = mFrequency * 60;
1296 mTimedRepetition = mFrequency;
1302 for ( c = 0, cend = mConstraints.count(); c < cend; ) {
1303 if ( mConstraints[c].isConsistent( mPeriod ) ) {
1306 mConstraints.removeAt( c );
1315 bool RecurrenceRule::Private::buildCache()
const
1319 Constraint interval( getNextValidDateInterval( mDateStart, mPeriod ) );
1322 DateTimeList dts = datesForInterval( interval, mPeriod );
1325 int i = dts.
findLT( mDateStart );
1327 dts.erase( dts.begin(), dts.begin() + i + 1 );
1331 int dtnr = dts.count();
1334 while ( loopnr < LOOP_LIMIT && dtnr < mDuration ) {
1335 interval.increase( mPeriod, mFrequency );
1337 dts += datesForInterval( interval, mPeriod );
1341 if ( dts.count() > mDuration ) {
1343 dts.erase( dts.begin() + mDuration, dts.end() );
1353 if (
int( dts.count() ) == mDuration ) {
1354 mCachedDateEnd = dts.last();
1358 mCachedDateEnd = KDateTime();
1359 mCachedLastDate = interval.intervalDateTime( mPeriod );
1367 KDateTime dt = kdt.toTimeSpec( d->mDateStart.timeSpec() );
1368 for (
int i = 0, iend = d->mConstraints.count(); i < iend; ++i ) {
1369 if ( d->mConstraints[i].matches( dt, recurrenceType() ) ) {
1382 if ( qd < d->mDateStart.date() ) {
1387 if ( d->mDuration >= 0 ) {
1388 endDate =
endDt().date();
1389 if ( qd > endDate ) {
1397 for ( i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i ) {
1398 match = d->mConstraints[i].matches( qd, recurrenceType() );
1404 KDateTime start( qd, QTime( 0, 0, 0 ), d->mDateStart.timeSpec() );
1405 Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
1408 if ( !interval.matches( qd, recurrenceType() ) ) {
1414 KDateTime end = start.addDays(1);
1416 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1417 for ( i = 0, iend = dts.count(); i < iend; ++i ) {
1418 if ( dts[i].date() >= qd ) {
1419 return dts[i].date() == qd;
1422 interval.increase( recurrenceType(),
frequency() );
1423 }
while ( interval.intervalDateTime( recurrenceType() ) < end );
1428 KDateTime start( qd, QTime( 0, 0, 0 ), timeSpec );
1429 KDateTime end = start.addDays( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1430 start = start.toTimeSpec( d->mDateStart.timeSpec() );
1431 if ( end < d->mDateStart ) {
1434 if ( start < d->mDateStart ) {
1435 start = d->mDateStart;
1439 if ( d->mDuration >= 0 ) {
1440 KDateTime endRecur =
endDt();
1441 if ( endRecur.isValid() ) {
1442 if ( start > endRecur ) {
1445 if ( end > endRecur ) {
1451 if ( d->mTimedRepetition ) {
1453 int n =
static_cast<int>( ( d->mDateStart.secsTo_long( start ) - 1 ) % d->mTimedRepetition );
1454 return start.addSecs( d->mTimedRepetition - n ) < end;
1458 QDate startDay = start.date();
1459 QDate endDay = end.addSecs( -1 ).date();
1460 int dayCount = startDay.daysTo( endDay ) + 1;
1465 for ( i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i ) {
1466 match = d->mConstraints[i].matches( startDay, recurrenceType() );
1467 for (
int day = 1; day < dayCount && !match; ++day ) {
1468 match = d->mConstraints[i].matches( startDay.addDays( day ), recurrenceType() );
1475 Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
1479 Constraint intervalm = interval;
1481 match = intervalm.matches( startDay, recurrenceType() );
1482 for (
int day = 1; day < dayCount && !match; ++day ) {
1483 match = intervalm.matches( startDay.addDays( day ), recurrenceType() );
1488 intervalm.increase( recurrenceType(),
frequency() );
1489 }
while ( intervalm.intervalDateTime( recurrenceType() ) < end );
1498 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1499 int i = dts.
findGE( start );
1501 return dts[i] <= end;
1503 interval.increase( recurrenceType(),
frequency() );
1504 }
while ( interval.intervalDateTime( recurrenceType() ) < end );
1512 KDateTime dt( kdt.toTimeSpec( d->mDateStart.timeSpec() ) );
1515 return recursOn( dt.date(), dt.timeSpec() );
1517 if ( dt < d->mDateStart ) {
1521 if ( d->mDuration >= 0 && dt >
endDt() ) {
1525 if ( d->mTimedRepetition ) {
1527 return !( d->mDateStart.secsTo_long( dt ) % d->mTimedRepetition );
1537 Constraint interval( d->getNextValidDateInterval( dt, recurrenceType() ) );
1539 if ( interval.matches( dt, recurrenceType() ) ) {
1551 KDateTime start( date, QTime( 0, 0, 0 ), timeSpec );
1552 KDateTime end = start.addDays( 1 ).addSecs( -1 );
1554 for (
int i = 0, iend = dts.count(); i < iend; ++i ) {
1555 lst += dts[i].toTimeSpec( timeSpec ).time();
1564 KDateTime toDate( dt.toTimeSpec( d->mDateStart.timeSpec() ) );
1567 if ( toDate < d->mDateStart ) {
1571 if ( d->mDuration > 0 && toDate >=
endDt() ) {
1572 return d->mDuration;
1575 if ( d->mTimedRepetition ) {
1577 return static_cast<int>( d->mDateStart.secsTo_long( toDate ) / d->mTimedRepetition );
1585 return durationTo( KDateTime( date, QTime( 23, 59, 59 ), d->mDateStart.timeSpec() ) );
1591 KDateTime toDate( afterDate.toTimeSpec( d->mDateStart.timeSpec() ) );
1594 if ( !toDate.isValid() || toDate < d->mDateStart ) {
1598 if ( d->mTimedRepetition ) {
1600 KDateTime prev = toDate;
1601 if ( d->mDuration >= 0 &&
endDt().isValid() && toDate >
endDt() ) {
1602 prev =
endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1604 int n =
static_cast<int>( ( d->mDateStart.secsTo_long( prev ) - 1 ) % d->mTimedRepetition );
1608 prev = prev.addSecs( -n - 1 );
1609 return prev >= d->mDateStart ? prev : KDateTime();
1613 if ( d->mDuration > 0 ) {
1614 if ( !d->mCached ) {
1617 int i = d->mCachedDates.findLT( toDate );
1619 return d->mCachedDates[i];
1624 KDateTime prev = toDate;
1625 if ( d->mDuration >= 0 &&
endDt().isValid() && toDate >
endDt() ) {
1626 prev =
endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1629 Constraint interval( d->getPreviousValidDateInterval( prev, recurrenceType() ) );
1630 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1631 int i = dts.
findLT( prev );
1633 return ( dts[i] >= d->mDateStart ) ? dts[i] : KDateTime();
1637 while ( interval.intervalDateTime( recurrenceType() ) > d->mDateStart ) {
1638 interval.increase( recurrenceType(), -
int(
frequency() ) );
1640 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1642 if ( !dts.isEmpty() ) {
1644 if ( prev.isValid() && prev >= d->mDateStart ) {
1657 KDateTime fromDate( preDate.toTimeSpec( d->mDateStart.timeSpec() ) );
1659 if ( d->mDuration >= 0 &&
endDt().isValid() && fromDate >=
endDt() ) {
1664 if ( fromDate < d->mDateStart ) {
1665 fromDate = d->mDateStart.addSecs( -1 );
1668 if ( d->mTimedRepetition ) {
1670 int n =
static_cast<int>( ( d->mDateStart.secsTo_long( fromDate ) + 1 ) % d->mTimedRepetition );
1671 KDateTime next = fromDate.addSecs( d->mTimedRepetition - n + 1 );
1672 return d->mDuration < 0 || !
endDt().isValid() || next <=
endDt() ? next : KDateTime();
1675 if ( d->mDuration > 0 ) {
1676 if ( !d->mCached ) {
1679 int i = d->mCachedDates.findGT( fromDate );
1681 return d->mCachedDates[i];
1685 KDateTime end =
endDt();
1686 Constraint interval( d->getNextValidDateInterval( fromDate, recurrenceType() ) );
1687 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1688 int i = dts.
findGT( fromDate );
1690 return ( d->mDuration < 0 || dts[i] <= end ) ? dts[i] : KDateTime();
1692 interval.increase( recurrenceType(),
frequency() );
1693 if ( d->mDuration >= 0 && interval.intervalDateTime( recurrenceType() ) > end ) {
1702 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1703 if ( dts.count() > 0 ) {
1704 KDateTime ret( dts[0] );
1705 if ( d->mDuration >= 0 && ret > end ) {
1711 interval.increase( recurrenceType(),
frequency() );
1712 }
while ( ++loop < LOOP_LIMIT &&
1713 ( d->mDuration < 0 || interval.intervalDateTime( recurrenceType() ) < end ) );
1718 const KDateTime &dtEnd )
const
1720 KDateTime start = dtStart.toTimeSpec( d->mDateStart.timeSpec() );
1721 KDateTime end = dtEnd.toTimeSpec( d->mDateStart.timeSpec() );
1723 if ( end < d->mDateStart ) {
1726 KDateTime enddt = end;
1727 if ( d->mDuration >= 0 ) {
1728 KDateTime endRecur =
endDt();
1729 if ( endRecur.isValid() ) {
1730 if ( start > endRecur ) {
1733 if ( end > endRecur ) {
1739 if ( d->mTimedRepetition ) {
1741 int n =
static_cast<int>( ( d->mDateStart.secsTo_long( start ) - 1 ) % d->mTimedRepetition );
1742 KDateTime dt = start.addSecs( d->mTimedRepetition - n );
1744 n =
static_cast<int>( ( dt.secsTo_long( enddt ) - 1 ) / d->mTimedRepetition ) + 1;
1746 n = qMin( n, LOOP_LIMIT );
1747 for (
int i = 0; i < n; dt = dt.addSecs( d->mTimedRepetition ), ++i ) {
1754 KDateTime st = start;
1756 if ( d->mDuration > 0 ) {
1757 if ( !d->mCached ) {
1760 if ( d->mCachedDateEnd.isValid() && start > d->mCachedDateEnd ) {
1763 int i = d->mCachedDates.
findGE( start );
1765 int iend = d->mCachedDates.findGT( enddt, i );
1767 iend = d->mCachedDates.count();
1771 while ( i < iend ) {
1772 result += d->mCachedDates[i++];
1775 if ( d->mCachedDateEnd.isValid() ) {
1777 }
else if ( !result.isEmpty() ) {
1778 result += KDateTime();
1785 st = d->mCachedLastDate.addSecs( 1 );
1788 Constraint interval( d->getNextValidDateInterval( st, recurrenceType() ) );
1791 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1793 int iend = dts.count();
1800 int j = dts.
findGT( enddt, i );
1805 while ( i < iend ) {
1809 interval.increase( recurrenceType(),
frequency() );
1810 }
while ( ++loop < LOOP_LIMIT &&
1811 interval.intervalDateTime( recurrenceType() ) < end );
1820 Constraint RecurrenceRule::Private::getPreviousValidDateInterval(
const KDateTime &dt,
1821 PeriodType type )
const
1824 KDateTime start = mDateStart;
1825 KDateTime nextValid( start );
1827 KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
1840 periods =
static_cast<int>( start.secsTo_long( toDate ) / modifier );
1842 if ( mFrequency > 0 ) {
1843 periods = ( periods / mFrequency ) * mFrequency;
1845 nextValid = start.addSecs( modifier * periods );
1848 toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
1849 start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
1852 periods = start.daysTo( toDate ) / modifier;
1854 if ( mFrequency > 0 ) {
1855 periods = ( periods / mFrequency ) * mFrequency;
1857 nextValid = start.addDays( modifier * periods );
1861 periods = 12 * ( toDate.date().year() - start.date().year() ) +
1862 ( toDate.date().month() - start.date().month() );
1864 if ( mFrequency > 0 ) {
1865 periods = ( periods / mFrequency ) * mFrequency;
1869 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
1870 nextValid.setDate( start.date().addMonths( periods ) );
1873 periods = ( toDate.date().year() - start.date().year() );
1875 if ( mFrequency > 0 ) {
1876 periods = ( periods / mFrequency ) * mFrequency;
1878 nextValid.setDate( start.date().addYears( periods ) );
1884 return Constraint( nextValid, type, mWeekStart );
1891 Constraint RecurrenceRule::Private::getNextValidDateInterval(
const KDateTime &dt,
1892 PeriodType type )
const
1896 KDateTime start = mDateStart;
1897 KDateTime nextValid( start );
1899 KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
1912 periods =
static_cast<int>( start.secsTo_long( toDate ) / modifier );
1913 periods = qMax( 0L, periods );
1914 if ( periods > 0 && mFrequency > 0 ) {
1915 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1917 nextValid = start.addSecs( modifier * periods );
1921 toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
1922 start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
1925 periods = start.daysTo( toDate ) / modifier;
1926 periods = qMax( 0L, periods );
1927 if ( periods > 0 && mFrequency > 0 ) {
1928 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1930 nextValid = start.addDays( modifier * periods );
1934 periods = 12 * ( toDate.date().year() - start.date().year() ) +
1935 ( toDate.date().month() - start.date().month() );
1936 periods = qMax( 0L, periods );
1937 if ( periods > 0 && mFrequency > 0 ) {
1938 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1942 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
1943 nextValid.setDate( start.date().addMonths( periods ) );
1947 periods = ( toDate.date().year() - start.date().year() );
1948 periods = qMax( 0L, periods );
1949 if ( periods > 0 && mFrequency > 0 ) {
1950 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1952 nextValid.setDate( start.date().addYears( periods ) );
1958 return Constraint( nextValid, type, mWeekStart );
1961 DateTimeList RecurrenceRule::Private::datesForInterval(
const Constraint &interval,
1962 PeriodType type )
const
1971 for (
int i = 0, iend = mConstraints.count(); i < iend; ++i ) {
1972 Constraint merged( interval );
1973 if ( merged.merge( mConstraints[i] ) ) {
1975 if ( merged.year > 0 && merged.hour >= 0 && merged.minute >= 0 && merged.second >= 0 ) {
1978 QList<KDateTime> lstnew = merged.dateTimes( type );
1995 if ( !mBySetPos.isEmpty() ) {
1998 for (
int i = 0, iend = mBySetPos.count(); i < iend; ++i ) {
1999 int pos = mBySetPos[i];
2004 pos += tmplst.count();
2006 if ( pos >= 0 && pos < tmplst.count() ) {
2007 lst.append( tmplst[pos] );
2021 if ( !d->mRRule.isEmpty() ) {
2022 kDebug() <<
" RRULE=" << d->mRRule;
2026 kDebug() <<
" Period type:" << recurrenceType()
2028 kDebug() <<
" #occurrences:" <<
duration();
2029 kDebug() <<
" start date:" << dumpTime(
startDt() )
2030 <<
", end date:" << dumpTime(
endDt() );
2032 #define dumpByIntList(list,label) \
2033 if ( !list.isEmpty() ) {\
2035 for ( int i = 0, iend = list.count(); i < iend; ++i ) {\
2036 lst.append( QString::number( list[i] ) );\
2038 kDebug() << " " << label << lst.join( ", " );\
2040 dumpByIntList( d->mBySeconds,
"BySeconds: " );
2041 dumpByIntList( d->mByMinutes,
"ByMinutes: " );
2042 dumpByIntList( d->mByHours,
"ByHours: " );
2043 if ( !d->mByDays.isEmpty() ) {
2045 for (
int i = 0, iend = d->mByDays.count(); i < iend; ++i ) {\
2046 lst.append( ( d->mByDays[i].pos() ? QString::number( d->mByDays[i].pos() ) :
"" ) +
2047 DateHelper::dayName( d->mByDays[i].day() ) );
2049 kDebug() <<
" ByDays: " << lst.join(
", " );
2051 dumpByIntList( d->mByMonthDays,
"ByMonthDays:" );
2052 dumpByIntList( d->mByYearDays,
"ByYearDays: " );
2053 dumpByIntList( d->mByWeekNumbers,
"ByWeekNr: " );
2054 dumpByIntList( d->mByMonths,
"ByMonths: " );
2055 dumpByIntList( d->mBySetPos,
"BySetPos: " );
2056 #undef dumpByIntList
2058 kDebug() <<
" Week start:" << DateHelper::dayName( d->mWeekStart );
2060 kDebug() <<
" Constraints:";
2062 for (
int i = 0, iend = d->mConstraints.count(); i < iend; ++i ) {
2063 d->mConstraints[i].dump();
2069 void Constraint::dump()
const
2071 kDebug() <<
" ~> Y=" << year
2077 <<
", wd=" << weekday
2078 <<
",#wd=" << weekdaynr
2079 <<
", #w=" << weeknumber
2080 <<
", yd=" << yearday;
2084 QString dumpTime(
const KDateTime &dt )
2087 if ( !dt.isValid() ) {
2091 if ( dt.isDateOnly() ) {
2092 result = dt.toString(
"%a %Y-%m-%d %:Z" );
2094 result = dt.toString(
"%a %Y-%m-%d %H:%M:%S %:Z" );
2095 if ( dt.isSecondOccurrence() ) {
2096 result += QLatin1String(
" (2nd)" );
2099 if ( dt.timeSpec() == KDateTime::Spec::ClockTime() ) {
2100 result += QLatin1String(
"Clock" );
2111 return d->mDateStart;
2121 return d->mFrequency;
2126 return d->mDuration;
2129 QString RecurrenceRule::rrule()
const
2141 return d->mIsReadOnly;
2146 d->mIsReadOnly = readOnly;
2151 return d->mPeriod != rNone;
2159 const QList<int> &RecurrenceRule::bySeconds()
const
2161 return d->mBySeconds;
2164 const QList<int> &RecurrenceRule::byMinutes()
const
2166 return d->mByMinutes;
2169 const QList<int> &RecurrenceRule::byHours()
const
2174 const QList<RecurrenceRule::WDayPos> &RecurrenceRule::byDays()
const
2179 const QList<int> &RecurrenceRule::byMonthDays()
const
2181 return d->mByMonthDays;
2184 const QList<int> &RecurrenceRule::byYearDays()
const
2186 return d->mByYearDays;
2189 const QList<int> &RecurrenceRule::byWeekNumbers()
const
2191 return d->mByWeekNumbers;
2194 const QList<int> &RecurrenceRule::byMonths()
const
2196 return d->mByMonths;
2199 const QList<int> &RecurrenceRule::bySetPos()
const
2201 return d->mBySetPos;
2204 short RecurrenceRule::weekStart()
const
2206 return d->mWeekStart;
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...
void setAllDay(bool allDay)
Sets whether the dtstart is all-day (i.e.
void setDuration(int duration)
Sets the total number of times the event is to occur, including both the first and last...
bool recursAt(const KDateTime &dt) const
Returns true if the date/time specified is one at which the event will recur.
void removeObserver(RuleObserver *observer)
Removes an observer that was added with addObserver.
void setFrequency(int freq)
Sets the recurrence frequency, in terms of the recurrence time period type.
int findLT(const T &value, int start=0) const
Search the list for the last item < value.
bool allDay() const
Returns whether the start date has no time associated.
void addObserver(RuleObserver *observer)
Installs an observer.
bool isReadOnly() const
Returns true if the recurrence is read-only; false if it can be changed.
void clear()
Removes all recurrence and exception rules and dates.
int durationTo(const KDateTime &dt) const
Returns the number of recurrences up to and including the date/time specified.
bool dateMatchesRules(const KDateTime &dt) const
Returns true if the date matches the rules.
int findGE(const T &value, int start=0) const
Search the list for the first item >= value.
KDateTime getNextDate(const KDateTime &preDateTime) const
Returns the date and time of the next recurrence, after the specified date/time.
void setReadOnly(bool readOnly)
Set if recurrence is read-only or can be changed.
int findGT(const T &value, int start=0) const
Search the list for the first item > value.
uint frequency() const
Returns the recurrence frequency, in terms of the recurrence time period type.
void sortUnique()
Sort the list.
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...
bool recurs() const
Returns the event's recurrence status.
A QList which can be sorted.
KDateTime startDt() const
Returns the recurrence start date/time.
bool recursOn(const QDate &date, const KDateTime::Spec &timeSpec) const
Returns true if the date specified is one on which the event will recur.
KDateTime getPreviousDate(const KDateTime &afterDateTime) const
Returns the date and time of the last previous recurrence, before the specified date/time.
void clear()
Turns off recurrence for the event.
RecurrenceRule()
/************************************************************************** RecurrenceRule * ...
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 endDt(bool *result=0) const
Returns the date and time of the last recurrence.
void setEndDt(const KDateTime &endDateTime)
Sets the date and time of the last recurrence.
void setRRule(const QString &rrule)
Set the RRULE string for the rule.
PeriodType
enum for describing the frequency how an event recurs, if at all.
int duration() const
Returns -1 if the event recurs infinitely, 0 if the end date is set, otherwise the total number of re...
void setStartDt(const KDateTime &start)
Sets the recurrence start date/time.
This class represents a recurrence rule for a calendar incidence.
void dump() const
Debug output.