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;
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() ) ) {
1398 if ( qd < d->mDateStart.date() ) {
1403 if ( d->mDuration >= 0 ) {
1404 endDate =
endDt().date();
1405 if ( qd > endDate ) {
1413 for ( i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i ) {
1414 match = d->mConstraints[i].matches( qd, recurrenceType() );
1420 KDateTime start( qd, QTime( 0, 0, 0 ), d->mDateStart.timeSpec() );
1421 Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
1424 if ( !interval.matches( qd, recurrenceType() ) ) {
1430 KDateTime end = start.addDays(1);
1432 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1433 for ( i = 0, iend = dts.count(); i < iend; ++i ) {
1434 if ( dts[i].date() >= qd ) {
1435 return dts[i].date() == qd;
1438 interval.increase( recurrenceType(),
frequency() );
1439 }
while ( interval.intervalDateTime( recurrenceType() ) < end );
1444 KDateTime start( qd, QTime( 0, 0, 0 ), timeSpec );
1445 KDateTime end = start.addDays( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1446 start = start.toTimeSpec( d->mDateStart.timeSpec() );
1447 if ( end < d->mDateStart ) {
1450 if ( start < d->mDateStart ) {
1451 start = d->mDateStart;
1455 if ( d->mDuration >= 0 ) {
1456 KDateTime endRecur =
endDt();
1457 if ( endRecur.isValid() ) {
1458 if ( start > endRecur ) {
1461 if ( end > endRecur ) {
1467 if ( d->mTimedRepetition ) {
1469 int n =
static_cast<int>( ( d->mDateStart.secsTo_long( start ) - 1 ) % d->mTimedRepetition );
1470 return start.addSecs( d->mTimedRepetition - n ) < end;
1474 QDate startDay = start.date();
1475 QDate endDay = end.addSecs( -1 ).date();
1476 int dayCount = startDay.daysTo( endDay ) + 1;
1481 for ( i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i ) {
1482 match = d->mConstraints[i].matches( startDay, recurrenceType() );
1483 for (
int day = 1; day < dayCount && !match; ++day ) {
1484 match = d->mConstraints[i].matches( startDay.addDays( day ), recurrenceType() );
1491 Constraint interval( d->getNextValidDateInterval( start, recurrenceType() ) );
1495 Constraint intervalm = interval;
1497 match = intervalm.matches( startDay, recurrenceType() );
1498 for (
int day = 1; day < dayCount && !match; ++day ) {
1499 match = intervalm.matches( startDay.addDays( day ), recurrenceType() );
1504 intervalm.increase( recurrenceType(),
frequency() );
1505 }
while ( intervalm.intervalDateTime( recurrenceType() ) < end );
1514 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1515 int i = dts.
findGE( start );
1517 return dts[i] <= end;
1519 interval.increase( recurrenceType(),
frequency() );
1520 }
while ( interval.intervalDateTime( recurrenceType() ) < end );
1528 KDateTime dt( kdt.toTimeSpec( d->mDateStart.timeSpec() ) );
1531 return recursOn( dt.date(), dt.timeSpec() );
1533 if ( dt < d->mDateStart ) {
1537 if ( d->mDuration >= 0 && dt >
endDt() ) {
1541 if ( d->mTimedRepetition ) {
1543 return !( d->mDateStart.secsTo_long( dt ) % d->mTimedRepetition );
1553 Constraint interval( d->getNextValidDateInterval( dt, recurrenceType() ) );
1555 if ( interval.matches( dt, recurrenceType() ) ) {
1567 KDateTime start( date, QTime( 0, 0, 0 ), timeSpec );
1568 KDateTime end = start.addDays( 1 ).addSecs( -1 );
1570 for (
int i = 0, iend = dts.count(); i < iend; ++i ) {
1571 lst += dts[i].toTimeSpec( timeSpec ).time();
1580 KDateTime toDate( dt.toTimeSpec( d->mDateStart.timeSpec() ) );
1583 if ( toDate < d->mDateStart ) {
1587 if ( d->mDuration > 0 && toDate >=
endDt() ) {
1588 return d->mDuration;
1591 if ( d->mTimedRepetition ) {
1593 return static_cast<int>( d->mDateStart.secsTo_long( toDate ) / d->mTimedRepetition );
1601 return durationTo( KDateTime( date, QTime( 23, 59, 59 ), d->mDateStart.timeSpec() ) );
1607 KDateTime toDate( afterDate.toTimeSpec( d->mDateStart.timeSpec() ) );
1610 if ( !toDate.isValid() || toDate < d->mDateStart ) {
1614 if ( d->mTimedRepetition ) {
1616 KDateTime prev = toDate;
1617 if ( d->mDuration >= 0 &&
endDt().isValid() && toDate >
endDt() ) {
1618 prev =
endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1620 int n =
static_cast<int>( ( d->mDateStart.secsTo_long( prev ) - 1 ) % d->mTimedRepetition );
1624 prev = prev.addSecs( -n - 1 );
1625 return prev >= d->mDateStart ? prev : KDateTime();
1629 if ( d->mDuration > 0 ) {
1630 if ( !d->mCached ) {
1633 int i = d->mCachedDates.findLT( toDate );
1635 return d->mCachedDates[i];
1640 KDateTime prev = toDate;
1641 if ( d->mDuration >= 0 &&
endDt().isValid() && toDate >
endDt() ) {
1642 prev =
endDt().addSecs( 1 ).toTimeSpec( d->mDateStart.timeSpec() );
1645 Constraint interval( d->getPreviousValidDateInterval( prev, recurrenceType() ) );
1646 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1647 int i = dts.
findLT( prev );
1649 return ( dts[i] >= d->mDateStart ) ? dts[i] : KDateTime();
1653 while ( interval.intervalDateTime( recurrenceType() ) > d->mDateStart ) {
1654 interval.increase( recurrenceType(), -
int(
frequency() ) );
1656 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1658 if ( !dts.isEmpty() ) {
1660 if ( prev.isValid() && prev >= d->mDateStart ) {
1673 KDateTime fromDate( preDate.toTimeSpec( d->mDateStart.timeSpec() ) );
1675 if ( d->mDuration >= 0 &&
endDt().isValid() && fromDate >=
endDt() ) {
1680 if ( fromDate < d->mDateStart ) {
1681 fromDate = d->mDateStart.addSecs( -1 );
1684 if ( d->mTimedRepetition ) {
1686 int n =
static_cast<int>( ( d->mDateStart.secsTo_long( fromDate ) + 1 ) % d->mTimedRepetition );
1687 KDateTime next = fromDate.addSecs( d->mTimedRepetition - n + 1 );
1688 return d->mDuration < 0 || !
endDt().isValid() || next <=
endDt() ? next : KDateTime();
1691 if ( d->mDuration > 0 ) {
1692 if ( !d->mCached ) {
1695 int i = d->mCachedDates.findGT( fromDate );
1697 return d->mCachedDates[i];
1701 KDateTime end =
endDt();
1702 Constraint interval( d->getNextValidDateInterval( fromDate, recurrenceType() ) );
1703 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1704 int i = dts.
findGT( fromDate );
1706 return ( d->mDuration < 0 || dts[i] <= end ) ? dts[i] : KDateTime();
1708 interval.increase( recurrenceType(),
frequency() );
1709 if ( d->mDuration >= 0 && interval.intervalDateTime( recurrenceType() ) > end ) {
1718 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1719 if ( dts.count() > 0 ) {
1720 KDateTime ret( dts[0] );
1721 if ( d->mDuration >= 0 && ret > end ) {
1727 interval.increase( recurrenceType(),
frequency() );
1728 }
while ( ++loop < LOOP_LIMIT &&
1729 ( d->mDuration < 0 || interval.intervalDateTime( recurrenceType() ) < end ) );
1734 const KDateTime &dtEnd )
const
1736 KDateTime start = dtStart.toTimeSpec( d->mDateStart.timeSpec() );
1737 KDateTime end = dtEnd.toTimeSpec( d->mDateStart.timeSpec() );
1739 if ( end < d->mDateStart ) {
1742 KDateTime enddt = end;
1743 if ( d->mDuration >= 0 ) {
1744 KDateTime endRecur =
endDt();
1745 if ( endRecur.isValid() ) {
1746 if ( start > endRecur ) {
1749 if ( end > endRecur ) {
1755 if ( d->mTimedRepetition ) {
1757 int n =
static_cast<int>( ( d->mDateStart.secsTo_long( start ) - 1 ) % d->mTimedRepetition );
1758 KDateTime dt = start.addSecs( d->mTimedRepetition - n );
1760 n =
static_cast<int>( ( dt.secsTo_long( enddt ) - 1 ) / d->mTimedRepetition ) + 1;
1762 n = qMin( n, LOOP_LIMIT );
1763 for (
int i = 0; i < n; dt = dt.addSecs( d->mTimedRepetition ), ++i ) {
1770 KDateTime st = start;
1772 if ( d->mDuration > 0 ) {
1773 if ( !d->mCached ) {
1776 if ( d->mCachedDateEnd.isValid() && start > d->mCachedDateEnd ) {
1779 int i = d->mCachedDates.
findGE( start );
1781 int iend = d->mCachedDates.findGT( enddt, i );
1783 iend = d->mCachedDates.count();
1787 while ( i < iend ) {
1788 result += d->mCachedDates[i++];
1791 if ( d->mCachedDateEnd.isValid() ) {
1793 }
else if ( !result.isEmpty() ) {
1794 result += KDateTime();
1801 st = d->mCachedLastDate.addSecs( 1 );
1804 Constraint interval( d->getNextValidDateInterval( st, recurrenceType() ) );
1807 DateTimeList dts = d->datesForInterval( interval, recurrenceType() );
1809 int iend = dts.count();
1816 int j = dts.
findGT( enddt, i );
1821 while ( i < iend ) {
1825 interval.increase( recurrenceType(),
frequency() );
1826 }
while ( ++loop < LOOP_LIMIT &&
1827 interval.intervalDateTime( recurrenceType() ) < end );
1836 Constraint RecurrenceRule::Private::getPreviousValidDateInterval(
const KDateTime &dt,
1837 PeriodType type )
const
1840 KDateTime start = mDateStart;
1841 KDateTime nextValid( start );
1843 KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
1856 periods =
static_cast<int>( start.secsTo_long( toDate ) / modifier );
1858 if ( mFrequency > 0 ) {
1859 periods = ( periods / mFrequency ) * mFrequency;
1861 nextValid = start.addSecs( modifier * periods );
1864 toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
1865 start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
1868 periods = start.daysTo( toDate ) / modifier;
1870 if ( mFrequency > 0 ) {
1871 periods = ( periods / mFrequency ) * mFrequency;
1873 nextValid = start.addDays( modifier * periods );
1877 periods = 12 * ( toDate.date().year() - start.date().year() ) +
1878 ( toDate.date().month() - start.date().month() );
1880 if ( mFrequency > 0 ) {
1881 periods = ( periods / mFrequency ) * mFrequency;
1885 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
1886 nextValid.setDate( start.date().addMonths( periods ) );
1889 periods = ( toDate.date().year() - start.date().year() );
1891 if ( mFrequency > 0 ) {
1892 periods = ( periods / mFrequency ) * mFrequency;
1894 nextValid.setDate( start.date().addYears( periods ) );
1900 return Constraint( nextValid, type, mWeekStart );
1907 Constraint RecurrenceRule::Private::getNextValidDateInterval(
const KDateTime &dt,
1908 PeriodType type )
const
1912 KDateTime start = mDateStart;
1913 KDateTime nextValid( start );
1915 KDateTime toDate( dt.toTimeSpec( start.timeSpec() ) );
1928 periods =
static_cast<int>( start.secsTo_long( toDate ) / modifier );
1929 periods = qMax( 0L, periods );
1930 if ( periods > 0 && mFrequency > 0 ) {
1931 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1933 nextValid = start.addSecs( modifier * periods );
1937 toDate = toDate.addDays( -( 7 + toDate.date().dayOfWeek() - mWeekStart ) % 7 );
1938 start = start.addDays( -( 7 + start.date().dayOfWeek() - mWeekStart ) % 7 );
1941 periods = start.daysTo( toDate ) / modifier;
1942 periods = qMax( 0L, periods );
1943 if ( periods > 0 && mFrequency > 0 ) {
1944 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1946 nextValid = start.addDays( modifier * periods );
1950 periods = 12 * ( toDate.date().year() - start.date().year() ) +
1951 ( toDate.date().month() - start.date().month() );
1952 periods = qMax( 0L, periods );
1953 if ( periods > 0 && mFrequency > 0 ) {
1954 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1958 start.setDate( QDate( start.date().year(), start.date().month(), 1 ) );
1959 nextValid.setDate( start.date().addMonths( periods ) );
1963 periods = ( toDate.date().year() - start.date().year() );
1964 periods = qMax( 0L, periods );
1965 if ( periods > 0 && mFrequency > 0 ) {
1966 periods += ( mFrequency - 1 - ( ( periods - 1 ) % mFrequency ) );
1968 nextValid.setDate( start.date().addYears( periods ) );
1974 return Constraint( nextValid, type, mWeekStart );
1977 DateTimeList RecurrenceRule::Private::datesForInterval(
const Constraint &interval,
1978 PeriodType type )
const
1987 for (
int i = 0, iend = mConstraints.count(); i < iend; ++i ) {
1988 Constraint merged( interval );
1989 if ( merged.merge( mConstraints[i] ) ) {
1991 if ( merged.year > 0 && merged.hour >= 0 && merged.minute >= 0 && merged.second >= 0 ) {
1994 QList<KDateTime> lstnew = merged.dateTimes( type );
2011 if ( !mBySetPos.isEmpty() ) {
2014 for (
int i = 0, iend = mBySetPos.count(); i < iend; ++i ) {
2015 int pos = mBySetPos[i];
2020 pos += tmplst.count();
2022 if ( pos >= 0 && pos < tmplst.count() ) {
2023 lst.append( tmplst[pos] );
2037 if ( !d->mRRule.isEmpty() ) {
2038 kDebug() <<
" RRULE=" << d->mRRule;
2042 kDebug() <<
" Period type:" << int( recurrenceType() ) <<
", frequency:" <<
frequency();
2043 kDebug() <<
" #occurrences:" <<
duration();
2044 kDebug() <<
" start date:" << dumpTime(
startDt() )
2045 <<
", end date:" << dumpTime(
endDt() );
2047 #define dumpByIntList(list,label) \
2048 if ( !list.isEmpty() ) {\
2050 for ( int i = 0, iend = list.count(); i < iend; ++i ) {\
2051 lst.append( QString::number( list[i] ) );\
2053 kDebug() << " " << label << lst.join( ", " );\
2055 dumpByIntList( d->mBySeconds,
"BySeconds: " );
2056 dumpByIntList( d->mByMinutes,
"ByMinutes: " );
2057 dumpByIntList( d->mByHours,
"ByHours: " );
2058 if ( !d->mByDays.isEmpty() ) {
2060 for (
int i = 0, iend = d->mByDays.count(); i < iend; ++i ) {\
2061 lst.append( ( d->mByDays[i].pos() ? QString::number( d->mByDays[i].pos() ) :
"" ) +
2062 DateHelper::dayName( d->mByDays[i].day() ) );
2064 kDebug() <<
" ByDays: " << lst.join(
", " );
2066 dumpByIntList( d->mByMonthDays,
"ByMonthDays:" );
2067 dumpByIntList( d->mByYearDays,
"ByYearDays: " );
2068 dumpByIntList( d->mByWeekNumbers,
"ByWeekNr: " );
2069 dumpByIntList( d->mByMonths,
"ByMonths: " );
2070 dumpByIntList( d->mBySetPos,
"BySetPos: " );
2071 #undef dumpByIntList
2073 kDebug() <<
" Week start:" << DateHelper::dayName( d->mWeekStart );
2075 kDebug() <<
" Constraints:";
2077 for (
int i = 0, iend = d->mConstraints.count(); i < iend; ++i ) {
2078 d->mConstraints[i].dump();
2084 void Constraint::dump()
const
2086 kDebug() <<
" ~> Y=" << year
2092 <<
", wd=" << weekday
2093 <<
",#wd=" << weekdaynr
2094 <<
", #w=" << weeknumber
2095 <<
", yd=" << yearday;
2099 QString dumpTime(
const KDateTime &dt )
2102 if ( !dt.isValid() ) {
2106 if ( dt.isDateOnly() ) {
2107 result = dt.toString(
"%a %Y-%m-%d %:Z" );
2109 result = dt.toString(
"%a %Y-%m-%d %H:%M:%S %:Z" );
2110 if ( dt.isSecondOccurrence() ) {
2111 result += QLatin1String(
" (2nd)" );
2114 if ( dt.timeSpec() == KDateTime::Spec::ClockTime() ) {
2115 result += QLatin1String(
"Clock" );
2126 return d->mDateStart;
2136 return d->mFrequency;
2141 return d->mDuration;
2144 QString RecurrenceRule::rrule()
const
2156 return d->mIsReadOnly;
2161 d->mIsReadOnly = readOnly;
2166 return d->mPeriod != rNone;
2174 const QList<int> &RecurrenceRule::bySeconds()
const
2176 return d->mBySeconds;
2179 const QList<int> &RecurrenceRule::byMinutes()
const
2181 return d->mByMinutes;
2184 const QList<int> &RecurrenceRule::byHours()
const
2189 const QList<RecurrenceRule::WDayPos> &RecurrenceRule::byDays()
const
2194 const QList<int> &RecurrenceRule::byMonthDays()
const
2196 return d->mByMonthDays;
2199 const QList<int> &RecurrenceRule::byYearDays()
const
2201 return d->mByYearDays;
2204 const QList<int> &RecurrenceRule::byWeekNumbers()
const
2206 return d->mByWeekNumbers;
2209 const QList<int> &RecurrenceRule::byMonths()
const
2211 return d->mByMonths;
2214 const QList<int> &RecurrenceRule::bySetPos()
const
2216 return d->mBySetPos;
2219 short RecurrenceRule::weekStart()
const
2221 return d->mWeekStart;
2224 RecurrenceRule::RuleObserver::~RuleObserver()
2228 RecurrenceRule::WDayPos::WDayPos(
int ps,
short dy )
2229 : mDay( dy ), mPos( ps )
2233 void RecurrenceRule::WDayPos::setDay(
short dy )
2238 short RecurrenceRule::WDayPos::day()
const
2243 void RecurrenceRule::WDayPos::setPos(
int ps )
2248 int RecurrenceRule::WDayPos::pos()
const