9 #include "recurrencerule.h" 11 #include "kcalendarcore_debug.h" 12 #include "recurrencehelper_p.h" 14 #include <QDataStream> 15 #include <QStringList> 23 const int LOOP_LIMIT = 10000;
49 static QString dayName(
short day);
51 static QDate getNthWeek(
int year,
int weeknumber,
short weekstart = 1);
52 static int weekNumbersInYear(
int year,
short weekstart = 1);
53 static int getWeekNumber(
const QDate &date,
short weekstart,
int *year =
nullptr);
54 static int getWeekNumberNeg(
const QDate &date,
short weekstart,
int *year =
nullptr);
57 static QDate getDate(
int year,
int month,
int day)
60 return QDate(year, month, day);
74 QString DateHelper::dayName(
short day)
78 return QStringLiteral(
"MO");
80 return QStringLiteral(
"TU");
82 return QStringLiteral(
"WE");
84 return QStringLiteral(
"TH");
86 return QStringLiteral(
"FR");
88 return QStringLiteral(
"SA");
90 return QStringLiteral(
"SU");
92 return QStringLiteral(
"??");
97 QDate DateHelper::getNthWeek(
int year,
int weeknumber,
short weekstart)
99 if (weeknumber == 0) {
104 QDate dt(year, 1, 4);
105 int adjust = -(7 + dt.dayOfWeek() - weekstart) % 7;
106 if (weeknumber > 0) {
107 dt = dt.addDays(7 * (weeknumber - 1) + adjust);
108 }
else if (weeknumber < 0) {
110 dt = dt.addDays(7 * weeknumber + adjust);
115 int DateHelper::getWeekNumber(
const QDate &date,
short weekstart,
int *year)
119 dt = dt.addDays(-(7 + dt.dayOfWeek() - weekstart) % 7);
121 qint64 daysto = dt.daysTo(date);
126 dt = dt.addDays(-(7 + dt.dayOfWeek() - weekstart) % 7);
127 daysto = dt.daysTo(date);
128 }
else if (daysto > 355) {
130 QDate dtn(y + 1, 1, 4);
131 dtn = dtn.addDays(-(7 + dtn.dayOfWeek() - weekstart) % 7);
132 qint64 dayston = dtn.daysTo(date);
142 return daysto / 7 + 1;
145 int DateHelper::weekNumbersInYear(
int year,
short weekstart)
147 QDate dt(year, 1, weekstart);
148 QDate dt1(year + 1, 1, weekstart);
149 return dt.daysTo(dt1) / 7;
153 int DateHelper::getWeekNumberNeg(
const QDate &date,
short weekstart,
int *year)
155 int weekpos = getWeekNumber(date, weekstart, year);
156 return weekNumbersInYear(*year, weekstart) - weekpos - 1;
166 return mDay == pos2.mDay && mPos == pos2.mPos;
184 explicit Constraint(
const QTimeZone &,
int wkst = 1);
207 void setMinute(
int n)
212 void setSecond(
int n)
217 void setWeekday(
int n)
222 void setWeekdaynr(
int n)
227 void setWeeknumber(
int n)
232 void setYearday(
int n)
237 void setWeekstart(
int n)
259 bool merge(
const Constraint &interval);
268 mutable bool useCachedDt;
272 Constraint::Constraint(
const QTimeZone &timeZone,
int wkst)
281 timeZone(dt.timeZone())
284 readDateTime(dt, type);
287 void Constraint::clear()
307 if (weeknumber == 0) {
308 if (year > 0 && year != dt.
year()) {
313 if (weeknumber > 0 &&
314 weeknumber != DateHelper::getWeekNumber(dt, weekstart, &y)) {
317 if (weeknumber < 0 &&
318 weeknumber != DateHelper::getWeekNumberNeg(dt, weekstart, &y)) {
321 if (year > 0 && year != y) {
326 if (month > 0 && month != dt.
month()) {
329 if (day > 0 && day != dt.
day()) {
339 if (weekdaynr != 0) {
342 if ((type == RecurrenceRule::rMonthly) ||
343 (type == RecurrenceRule::rYearly && month > 0)) {
346 weekdaynr != (dt.
day() - 1) / 7 + 1) {
356 weekdaynr != (dt.
dayOfYear() - 1) / 7 + 1) {
366 if (yearday > 0 && yearday != dt.
dayOfYear()) {
380 if ((hour >= 0 && hour != dt.
time().
hour()) ||
381 (minute >= 0 && minute != dt.
time().
minute()) ||
382 (second >= 0 && second != dt.
time().
second()) ||
383 !matches(dt.
date(), type)) {
404 bool subdaily =
true;
406 case RecurrenceRule::rSecondly:
407 t.setHMS(hour, minute, second);
409 case RecurrenceRule::rMinutely:
410 t.setHMS(hour, minute, 0);
412 case RecurrenceRule::rHourly:
413 t.setHMS(hour, 0, 0);
415 case RecurrenceRule::rDaily:
417 case RecurrenceRule::rWeekly:
418 d = DateHelper::getNthWeek(year, weeknumber, weekstart);
421 case RecurrenceRule::rMonthly:
425 case RecurrenceRule::rYearly:
433 d = DateHelper::getDate(year, (month > 0) ? month : 1, day ? day : 1);
440 bool Constraint::merge(
const Constraint &interval)
442 #define mergeConstraint( name, cmparison ) \ 443 if ( interval.name cmparison ) { \ 444 if ( !( name cmparison ) ) { \ 445 name = interval.name; \ 446 } else if ( name != interval.name ) { \ 453 mergeConstraint(year, > 0);
454 mergeConstraint(month, > 0);
455 mergeConstraint(day, != 0);
456 mergeConstraint(hour, >= 0);
457 mergeConstraint(minute, >= 0);
458 mergeConstraint(second, >= 0);
460 mergeConstraint(weekday, != 0);
461 mergeConstraint(weekdaynr, != 0);
462 mergeConstraint(weeknumber, != 0);
463 mergeConstraint(yearday, != 0);
465 #undef mergeConstraint 489 if (!isConsistent(type)) {
494 QTime tm(hour, minute, second);
497 if (day && month > 0) {
498 appendDateTime(DateHelper::getDate(year, month, day), tm, result);
502 if (!done && weekday == 0 && weeknumber == 0 && yearday == 0) {
504 uint mstart = (month > 0) ? month : 1;
505 uint mend = (month <= 0) ? 12 : month;
506 for (uint m = mstart; m <= mend; ++m) {
510 }
else if (day < 0) {
511 QDate date(year, month, 1);
514 QDate date(year, month, 1);
520 appendDateTime(dt, tm, result);
531 if (!done && yearday != 0) {
533 QDate d(year + ((yearday > 0) ? 0 : 1), 1, 1);
534 d = d.addDays(yearday - ((yearday > 0) ? 1 : 0));
535 appendDateTime(d, tm, result);
540 if (!done && weeknumber != 0) {
541 QDate wst(DateHelper::getNthWeek(year, weeknumber, weekstart));
543 wst = wst.addDays((7 + weekday - weekstart) % 7);
544 appendDateTime(wst, tm, result);
546 for (
int i = 0; i < 7; ++i) {
547 appendDateTime(wst, tm, result);
548 wst = wst.addDays(1);
555 if (!done && weekday != 0) {
556 QDate dt(year, 1, 1);
560 bool inMonth = (type == RecurrenceRule::rMonthly) ||
561 (type == RecurrenceRule::rYearly && month > 0);
562 if (inMonth && month > 0) {
563 dt =
QDate(year, month, 1);
574 int adj = (7 + weekday - dt.dayOfWeek()) % 7;
578 dt = dt.
addDays((weekdaynr - 1) * 7);
579 appendDateTime(dt, tm, result);
580 }
else if (weekdaynr < 0) {
581 dt = dt.
addDays(weekdaynr * 7);
582 appendDateTime(dt, tm, result);
585 for (
int i = 0; i < maxloop; ++i) {
586 appendDateTime(dt, tm, result);
594 for (
int i = 0, iend = result.
count(); i < iend; ++i) {
595 if (matches(result[i], type)) {
604 void Constraint::appendDateTime(
const QDate &date,
const QTime &time,
616 intervalDateTime(type);
620 case RecurrenceRule::rSecondly:
621 cachedDt = cachedDt.addSecs(freq);
623 case RecurrenceRule::rMinutely:
624 cachedDt = cachedDt.addSecs(60 * freq);
626 case RecurrenceRule::rHourly:
627 cachedDt = cachedDt.addSecs(3600 * freq);
629 case RecurrenceRule::rDaily:
630 cachedDt = cachedDt.addDays(freq);
632 case RecurrenceRule::rWeekly:
633 cachedDt = cachedDt.addDays(7 * freq);
635 case RecurrenceRule::rMonthly:
636 cachedDt = cachedDt.addMonths(freq);
638 case RecurrenceRule::rYearly:
639 cachedDt = cachedDt.addYears(freq);
645 readDateTime(cachedDt, type);
656 case RecurrenceRule::rSecondly:
659 case RecurrenceRule::rMinutely:
662 case RecurrenceRule::rHourly:
665 case RecurrenceRule::rDaily:
668 case RecurrenceRule::rMonthly:
671 case RecurrenceRule::rYearly:
674 case RecurrenceRule::rWeekly:
676 weeknumber = DateHelper::getWeekNumber(dt.
date(), weekstart, &year);
708 Private &
operator=(
const Private &other);
712 void buildConstraints();
713 bool buildCache()
const;
714 Constraint getNextValidDateInterval(
const QDateTime &preDate, PeriodType type)
const;
715 Constraint getPreviousValidDateInterval(
const QDateTime &afterDate, PeriodType type)
const;
716 QList<QDateTime> datesForInterval(
const Constraint &interval, PeriodType type)
const;
743 Constraint::List mConstraints;
750 mutable bool mCached;
755 uint mTimedRepetition;
758 RecurrenceRule::Private::Private(
RecurrenceRule *parent,
const Private &p)
762 mDateStart(p.mDateStart),
763 mFrequency(p.mFrequency),
764 mDuration(p.mDuration),
765 mDateEnd(p.mDateEnd),
767 mBySeconds(p.mBySeconds),
768 mByMinutes(p.mByMinutes),
769 mByHours(p.mByHours),
771 mByMonthDays(p.mByMonthDays),
772 mByYearDays(p.mByYearDays),
773 mByWeekNumbers(p.mByWeekNumbers),
774 mByMonths(p.mByMonths),
775 mBySetPos(p.mBySetPos),
776 mWeekStart(p.mWeekStart),
778 mIsReadOnly(p.mIsReadOnly),
780 mNoByRules(p.mNoByRules)
785 RecurrenceRule::Private &RecurrenceRule::Private::operator=(
const Private &p)
794 mDateStart = p.mDateStart;
795 mFrequency = p.mFrequency;
796 mDuration = p.mDuration;
797 mDateEnd = p.mDateEnd;
799 mBySeconds = p.mBySeconds;
800 mByMinutes = p.mByMinutes;
801 mByHours = p.mByHours;
803 mByMonthDays = p.mByMonthDays;
804 mByYearDays = p.mByYearDays;
805 mByWeekNumbers = p.mByWeekNumbers;
806 mByMonths = p.mByMonths;
807 mBySetPos = p.mBySetPos;
808 mWeekStart = p.mWeekStart;
810 mIsReadOnly = p.mIsReadOnly;
812 mNoByRules = p.mNoByRules;
819 bool RecurrenceRule::Private::operator==(
const Private &r)
const 822 mPeriod == r.mPeriod &&
823 ((mDateStart == r.mDateStart) ||
824 (!mDateStart.isValid() && !r.mDateStart.isValid())) &&
825 mDuration == r.mDuration &&
826 ((mDateEnd == r.mDateEnd) ||
827 (!mDateEnd.isValid() && !r.mDateEnd.isValid())) &&
828 mFrequency == r.mFrequency &&
829 mIsReadOnly == r.mIsReadOnly &&
830 mAllDay == r.mAllDay &&
831 mBySeconds == r.mBySeconds &&
832 mByMinutes == r.mByMinutes &&
833 mByHours == r.mByHours &&
834 mByDays == r.mByDays &&
835 mByMonthDays == r.mByMonthDays &&
836 mByYearDays == r.mByYearDays &&
837 mByWeekNumbers == r.mByWeekNumbers &&
838 mByMonths == r.mByMonths &&
839 mBySetPos == r.mBySetPos &&
840 mWeekStart == r.mWeekStart &&
841 mNoByRules == r.mNoByRules;
844 void RecurrenceRule::Private::clear()
854 mByMonthDays.clear();
856 mByWeekNumbers.clear();
865 void RecurrenceRule::Private::setDirty()
869 mCachedDates.clear();
870 for (
int i = 0, iend = mObservers.count(); i < iend; ++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 d->mObservers.removeAll(observer);
926 void RecurrenceRule::setRecurrenceType(
PeriodType period)
940 if (d->mPeriod == rNone) {
943 if (d->mDuration < 0) {
946 if (d->mDuration == 0) {
956 if (!d->buildCache()) {
963 return d->mCachedDateEnd;
971 d->mDateEnd = dateTime;
972 if (d->mDateEnd.isValid()) {
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;
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.toTimeZone(oldTz);
1117 d->mDateStart.setTimeZone(newTz);
1118 if (d->mDuration == 0) {
1119 d->mDateEnd = d->mDateEnd.toTimeZone(oldTz);
1120 d->mDateEnd.setTimeZone(newTz);
1182 void RecurrenceRule::Private::buildConstraints()
1184 mTimedRepetition = 0;
1185 mNoByRules = mBySetPos.isEmpty();
1186 mConstraints.clear();
1187 Constraint con(mDateStart.timeZone());
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 tmp.reserve(mConstraints.count() * iend); \ 1207 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \ 1208 for ( i = 0; i < iend; ++i ) { \ 1209 con = mConstraints[c]; \ 1210 con.setElement( list[i] ); \ 1211 tmp.append( con ); \ 1214 mConstraints = tmp; \ 1219 intConstraint(mBySeconds, setSecond);
1220 intConstraint(mByMinutes, setMinute);
1221 intConstraint(mByHours, setHour);
1222 intConstraint(mByMonthDays, setDay);
1223 intConstraint(mByMonths, setMonth);
1224 intConstraint(mByYearDays, setYearday);
1225 intConstraint(mByWeekNumbers, setWeeknumber);
1226 #undef intConstraint 1228 if (!mByDays.isEmpty()) {
1230 tmp.reserve(mConstraints.count() * mByDays.count());
1231 for (c = 0, cend = mConstraints.count(); c < cend; ++c) {
1232 for (i = 0, iend = mByDays.count(); i < iend; ++i) {
1233 con = mConstraints[c];
1234 con.setWeekday(mByDays[i].day());
1235 con.setWeekdaynr(mByDays[i].pos());
1243 #define fixConstraint( setElement, value ) \ 1245 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \ 1246 mConstraints[c].setElement( value ); \ 1253 if (mPeriod == rWeekly && mByDays.isEmpty()) {
1254 fixConstraint(setWeekday, mDateStart.date().dayOfWeek());
1261 if (mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
1262 mByYearDays.isEmpty() && mByMonths.isEmpty()) {
1263 fixConstraint(setMonth, mDateStart.date().month());
1267 if (mByDays.isEmpty() && mByWeekNumbers.isEmpty() &&
1268 mByYearDays.isEmpty() && mByMonthDays.isEmpty()) {
1269 fixConstraint(setDay, mDateStart.date().day());
1274 if (mByHours.isEmpty()) {
1275 fixConstraint(setHour, mDateStart.time().hour());
1279 if (mByMinutes.isEmpty()) {
1280 fixConstraint(setMinute, mDateStart.time().minute());
1284 if (mBySeconds.isEmpty()) {
1285 fixConstraint(setSecond, mDateStart.time().second());
1292 #undef fixConstraint 1297 mTimedRepetition = mFrequency * 3600;
1300 mTimedRepetition = mFrequency * 60;
1303 mTimedRepetition = mFrequency;
1309 for (c = 0, cend = mConstraints.count(); c < cend;) {
1310 if (mConstraints[c].isConsistent(mPeriod)) {
1313 mConstraints.removeAt(c);
1322 bool RecurrenceRule::Private::buildCache()
const 1324 Q_ASSERT(mDuration > 0);
1327 Constraint interval(getNextValidDateInterval(mDateStart, mPeriod));
1329 auto dts = datesForInterval(interval, mPeriod);
1332 const auto it = strictLowerBound(dts.begin(), dts.end(), mDateStart);
1333 if (it != dts.end()) {
1334 dts.erase(dts.begin(), it + 1);
1339 for (
int loopnr = 0; loopnr < LOOP_LIMIT && dts.count() < mDuration; ++loopnr) {
1340 interval.increase(mPeriod, mFrequency);
1342 dts += datesForInterval(interval, mPeriod);
1344 if (dts.count() > mDuration) {
1346 dts.erase(dts.begin() + mDuration, dts.end());
1356 if (
int(dts.count()) == mDuration) {
1357 mCachedDateEnd = dts.last();
1362 mCachedLastDate = interval.intervalDateTime(mPeriod);
1371 for (
int i = 0, iend = d->mConstraints.count(); i < iend; ++i) {
1372 if (d->mConstraints[i].matches(dt, recurrenceType())) {
1383 if (!qd.
isValid() || !d->mDateStart.isValid()) {
1391 if (qd < d->mDateStart.date()) {
1396 if (d->mDuration >= 0) {
1406 for (i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i) {
1407 match = d->mConstraints[i].matches(qd, recurrenceType());
1414 Constraint interval(d->getNextValidDateInterval(start, recurrenceType()));
1417 if (!interval.matches(qd, recurrenceType())) {
1425 auto dts = d->datesForInterval(interval, recurrenceType());
1426 for (i = 0, iend = dts.count(); i < iend; ++i) {
1427 if (dts[i].date() >= qd) {
1428 return dts[i].
date() == qd;
1431 interval.increase(recurrenceType(),
frequency());
1432 }
while (interval.intervalDateTime(recurrenceType()) < end);
1439 start = start.
toTimeZone(d->mDateStart.timeZone());
1440 if (end < d->mDateStart) {
1443 if (start < d->mDateStart) {
1444 start = d->mDateStart;
1448 if (d->mDuration >= 0) {
1451 if (start > endRecur) {
1454 if (end > endRecur) {
1460 if (d->mTimedRepetition) {
1462 int n =
static_cast<int>((d->mDateStart.secsTo(start) - 1) % d->mTimedRepetition);
1463 return start.
addSecs(d->mTimedRepetition - n) < end;
1469 int dayCount = startDay.
daysTo(endDay) + 1;
1474 for (i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i) {
1475 match = d->mConstraints[i].matches(startDay, recurrenceType());
1476 for (
int day = 1; day < dayCount && !match; ++day) {
1477 match = d->mConstraints[i].matches(startDay.
addDays(day), recurrenceType());
1484 Constraint interval(d->getNextValidDateInterval(start, recurrenceType()));
1487 Constraint intervalm = interval;
1489 match = intervalm.matches(startDay, recurrenceType());
1490 for (
int day = 1; day < dayCount && !match; ++day) {
1491 match = intervalm.matches(startDay.
addDays(day), recurrenceType());
1496 intervalm.increase(recurrenceType(),
frequency());
1497 }
while (intervalm.intervalDateTime(recurrenceType()).isValid() &&
1498 intervalm.intervalDateTime(recurrenceType()) < end);
1507 auto dts = d->datesForInterval(interval, recurrenceType());
1508 const auto it = std::lower_bound(dts.constBegin(), dts.constEnd(), start);
1509 if (it != dts.constEnd()) {
1512 interval.increase(recurrenceType(),
frequency());
1513 }
while (interval.intervalDateTime(recurrenceType()).isValid() &&
1514 interval.intervalDateTime(recurrenceType()) < end);
1527 if (dt < d->mDateStart) {
1531 if (d->mDuration >= 0 && dt >
endDt()) {
1535 if (d->mTimedRepetition) {
1537 return !(d->mDateStart.secsTo(dt) % d->mTimedRepetition);
1547 Constraint interval(d->getNextValidDateInterval(dt, recurrenceType()));
1549 if (interval.matches(dt, recurrenceType())) {
1564 for (
int i = 0, iend = dts.count(); i < iend; ++i) {
1565 lst += dts[i].toTimeZone(timeZone).time();
1577 if (toDate < d->mDateStart) {
1581 if (d->mDuration > 0 && toDate >=
endDt()) {
1582 return d->mDuration;
1585 if (d->mTimedRepetition) {
1587 return static_cast<int>(d->mDateStart.secsTo(toDate) / d->mTimedRepetition);
1604 if (!toDate.
isValid() || toDate < d->mDateStart) {
1608 if (d->mTimedRepetition) {
1611 if (d->mDuration >= 0 &&
endDt().isValid() && toDate >
endDt()) {
1614 int n =
static_cast<int>((d->mDateStart.secsTo(prev) - 1) % d->mTimedRepetition);
1619 return prev >= d->mDateStart ? prev :
QDateTime();
1623 if (d->mDuration > 0) {
1627 const auto it = strictLowerBound(d->mCachedDates.constBegin(), d->mCachedDates.constEnd(), toDate);
1628 if (it != d->mCachedDates.constEnd()) {
1635 if (d->mDuration >= 0 &&
endDt().isValid() && toDate >
endDt()) {
1639 Constraint interval(d->getPreviousValidDateInterval(prev, recurrenceType()));
1640 const auto dts = d->datesForInterval(interval, recurrenceType());
1641 const auto it = strictLowerBound(dts.begin(), dts.end(), prev);
1642 if (it != dts.end()) {
1643 return ((*it) >= d->mDateStart) ? (*it) :
QDateTime();
1647 while (interval.intervalDateTime(recurrenceType()) > d->mDateStart) {
1648 interval.increase(recurrenceType(), -
int(
frequency()));
1650 auto dts = d->datesForInterval(interval, recurrenceType());
1652 if (!dts.isEmpty()) {
1654 if (prev.
isValid() && prev >= d->mDateStart) {
1669 if (d->mDuration >= 0 &&
endDt().isValid() && fromDate >=
endDt()) {
1674 if (fromDate < d->mDateStart) {
1675 fromDate = d->mDateStart.
addSecs(-1);
1678 if (d->mTimedRepetition) {
1680 int n =
static_cast<int>((d->mDateStart.secsTo(fromDate) + 1) % d->mTimedRepetition);
1685 if (d->mDuration > 0) {
1689 const auto it = std::upper_bound(d->mCachedDates.constBegin(), d->mCachedDates.constEnd(), fromDate);
1690 if (it != d->mCachedDates.constEnd()) {
1696 Constraint interval(d->getNextValidDateInterval(fromDate, recurrenceType()));
1697 const auto dts = d->datesForInterval(interval, recurrenceType());
1698 const auto it = std::upper_bound(dts.begin(), dts.end(), fromDate);
1699 if (it != dts.end()) {
1700 return (d->mDuration < 0 || *it <= end) ? *it :
QDateTime();
1702 interval.increase(recurrenceType(),
frequency());
1703 if (d->mDuration >= 0 && interval.intervalDateTime(recurrenceType()) > end) {
1712 auto dts = d->datesForInterval(interval, recurrenceType());
1713 if (!dts.isEmpty()) {
1715 if (d->mDuration >= 0 && ret > end) {
1721 interval.increase(recurrenceType(),
frequency());
1722 }
while (++loop < LOOP_LIMIT &&
1723 (d->mDuration < 0 || interval.intervalDateTime(recurrenceType()) < end));
1733 if (end < d->mDateStart) {
1737 if (d->mDuration >= 0) {
1740 if (start > endRecur) {
1743 if (end >= endRecur) {
1749 if (d->mTimedRepetition) {
1753 qint64 offsetFromNextOccurrence;
1754 if (d->mDateStart < start) {
1755 offsetFromNextOccurrence =
1756 d->mTimedRepetition - (d->mDateStart.secsTo(start) % d->mTimedRepetition);
1758 offsetFromNextOccurrence = -(d->mDateStart.secsTo(start) % d->mTimedRepetition);
1762 int numberOfOccurrencesWithinInterval =
1763 static_cast<int>(dt.
secsTo(enddt) / d->mTimedRepetition) + 1;
1765 numberOfOccurrencesWithinInterval = qMin(numberOfOccurrencesWithinInterval, LOOP_LIMIT);
1767 i < numberOfOccurrencesWithinInterval;
1768 dt = dt.
addSecs(d->mTimedRepetition), ++i) {
1775 QDateTime st = start < d->mDateStart ? d->mDateStart : start;
1777 if (d->mDuration > 0) {
1781 if (d->mCachedDateEnd.isValid() && start > d->mCachedDateEnd) {
1784 const auto it = std::lower_bound(d->mCachedDates.constBegin(), d->mCachedDates.constEnd(), start);
1785 if (it != d->mCachedDates.constEnd()) {
1786 const auto itEnd = std::upper_bound(it, d->mCachedDates.constEnd(), enddt);
1787 if (itEnd != d->mCachedDates.constEnd()) {
1790 std::copy(it, itEnd, std::back_inserter(result));
1792 if (d->mCachedDateEnd.isValid()) {
1794 }
else if (!result.
isEmpty()) {
1802 st = d->mCachedLastDate.
addSecs(1);
1805 Constraint interval(d->getNextValidDateInterval(st, recurrenceType()));
1808 auto dts = d->datesForInterval(interval, recurrenceType());
1809 auto it = dts.begin();
1810 auto itEnd = dts.end();
1812 it = std::lower_bound(dts.begin(), dts.end(), st);
1814 itEnd = std::upper_bound(it, dts.end(), enddt);
1815 if (itEnd != dts.end()) {
1818 std::copy(it, itEnd, std::back_inserter(result));
1820 interval.increase(recurrenceType(),
frequency());
1821 }
while (++loop < LOOP_LIMIT &&
1822 interval.intervalDateTime(recurrenceType()) < end);
1831 Constraint RecurrenceRule::Private::getPreviousValidDateInterval(
const QDateTime &dt,
1853 periods =
static_cast<int>(start.
secsTo(toDate) / modifier);
1855 if (mFrequency > 0) {
1856 periods = (periods / mFrequency) * mFrequency;
1858 nextValid = start.
addSecs(modifier * periods);
1866 periods = start.
daysTo(toDate) / modifier;
1868 if (mFrequency > 0) {
1869 periods = (periods / mFrequency) * mFrequency;
1871 nextValid = start.
addDays(modifier * periods);
1877 if (mFrequency > 0) {
1878 periods = (periods / mFrequency) * mFrequency;
1889 if (mFrequency > 0) {
1890 periods = (periods / mFrequency) * mFrequency;
1898 return Constraint(nextValid, type, mWeekStart);
1905 Constraint RecurrenceRule::Private::getNextValidDateInterval(
const QDateTime &dt,
1928 periods =
static_cast<int>(start.
secsTo(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);
1942 periods = start.
daysTo(toDate) / modifier;
1943 periods = qMax(0L, periods);
1944 if (periods > 0 && mFrequency > 0) {
1945 periods += (mFrequency - 1 - ((periods - 1) % mFrequency));
1947 nextValid = start.
addDays(modifier * periods);
1952 periods = qMax(0L, periods);
1953 if (periods > 0 && mFrequency > 0) {
1954 periods += (mFrequency - 1 - ((periods - 1) % mFrequency));
1964 periods = qMax(0L, periods);
1965 if (periods > 0 && mFrequency > 0) {
1966 periods += (mFrequency - 1 - ((periods - 1) % mFrequency));
1974 return Constraint(nextValid, type, mWeekStart);
1977 QList<QDateTime> RecurrenceRule::Private::datesForInterval(
const Constraint &interval,
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) {
2000 sortAndRemoveDuplicates(lst);
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()) {
2026 sortAndRemoveDuplicates(lst);
2036 if (!d->mRRule.isEmpty()) {
2037 qCDebug(KCALCORE_LOG) <<
" RRULE=" << d->mRRule;
2039 qCDebug(KCALCORE_LOG) <<
" Read-Only:" <<
isReadOnly();
2041 qCDebug(KCALCORE_LOG) <<
" Period type:" << int(recurrenceType()) <<
", frequency:" <<
frequency();
2042 qCDebug(KCALCORE_LOG) <<
" #occurrences:" <<
duration();
2043 qCDebug(KCALCORE_LOG) <<
" start date:" << dumpTime(
startDt(),
allDay())
2046 #define dumpByIntList(list,label) \ 2047 if ( !list.isEmpty() ) {\ 2049 for ( int i = 0, iend = list.count(); i < iend; ++i ) {\ 2050 lst.append( QString::number( list[i] ) );\ 2052 qCDebug(KCALCORE_LOG) << " " << label << lst.join(QLatin1String(", ") );\ 2054 dumpByIntList(d->mBySeconds, QStringLiteral(
"BySeconds: "));
2055 dumpByIntList(d->mByMinutes, QStringLiteral(
"ByMinutes: "));
2056 dumpByIntList(d->mByHours, QStringLiteral(
"ByHours: "));
2057 if (!d->mByDays.isEmpty()) {
2059 for (
int i = 0, iend = d->mByDays.count(); i < iend; ++i) {
2061 DateHelper::dayName(d->mByDays[i].day()));
2065 dumpByIntList(d->mByMonthDays, QStringLiteral(
"ByMonthDays:"));
2066 dumpByIntList(d->mByYearDays, QStringLiteral(
"ByYearDays: "));
2067 dumpByIntList(d->mByWeekNumbers, QStringLiteral(
"ByWeekNr: "));
2068 dumpByIntList(d->mByMonths, QStringLiteral(
"ByMonths: "));
2069 dumpByIntList(d->mBySetPos, QStringLiteral(
"BySetPos: "));
2070 #undef dumpByIntList 2072 qCDebug(KCALCORE_LOG) <<
" Week start:" << DateHelper::dayName(d->mWeekStart);
2074 qCDebug(KCALCORE_LOG) <<
" Constraints:";
2076 for (
int i = 0, iend = d->mConstraints.count(); i < iend; ++i) {
2077 d->mConstraints[i].dump();
2083 void Constraint::dump()
const 2085 qCDebug(KCALCORE_LOG) <<
" ~> Y=" << year
2091 <<
", wd=" << weekday
2092 <<
",#wd=" << weekdaynr
2093 <<
", #w=" << weeknumber
2094 <<
", yd=" << yearday;
2106 result = dt.
toString(QStringLiteral(
"ddd yyyy-MM-dd t"));
2108 result = dt.
toString(QStringLiteral(
"ddd yyyy-MM-dd hh:mm:ss t"));
2120 return d->mDateStart;
2130 return d->mFrequency;
2135 return d->mDuration;
2138 QString RecurrenceRule::rrule()
const 2150 return d->mIsReadOnly;
2155 d->mIsReadOnly = readOnly;
2160 return d->mPeriod != rNone;
2168 const QList<int> &RecurrenceRule::bySeconds()
const 2170 return d->mBySeconds;
2173 const QList<int> &RecurrenceRule::byMinutes()
const 2175 return d->mByMinutes;
2178 const QList<int> &RecurrenceRule::byHours()
const 2188 const QList<int> &RecurrenceRule::byMonthDays()
const 2190 return d->mByMonthDays;
2193 const QList<int> &RecurrenceRule::byYearDays()
const 2195 return d->mByYearDays;
2198 const QList<int> &RecurrenceRule::byWeekNumbers()
const 2200 return d->mByWeekNumbers;
2203 const QList<int> &RecurrenceRule::byMonths()
const 2205 return d->mByMonths;
2208 const QList<int> &RecurrenceRule::bySetPos()
const 2210 return d->mBySetPos;
2213 short RecurrenceRule::weekStart()
const 2215 return d->mWeekStart;
2218 RecurrenceRule::RuleObserver::~RuleObserver()
2222 RecurrenceRule::WDayPos::WDayPos(
int ps,
short dy)
2223 : mDay(dy), mPos(ps)
2227 void RecurrenceRule::WDayPos::setDay(
short dy)
2232 short RecurrenceRule::WDayPos::day()
const 2237 void RecurrenceRule::WDayPos::setPos(
int ps)
2242 int RecurrenceRule::WDayPos::pos()
const 2249 out << c.year << c.month << c.day << c.hour << c.minute << c.second
2250 << c.weekday << c.weekdaynr << c.weeknumber << c.yearday << c.weekstart;
2251 serializeQTimeZoneAsSpec(out, c.timeZone);
2259 bool secondOccurrence;
2260 in >> c.year >> c.month >> c.day >> c.hour >> c.minute >> c.second
2261 >> c.weekday >> c.weekdaynr >> c.weeknumber >> c.yearday >> c.weekstart;
2262 deserializeSpecAsQTimeZone(in, c.timeZone);
2263 in >> secondOccurrence;
2269 out << w.mDay << w.mPos;
2275 in >> w.mDay >> w.mPos;
2285 RecurrenceRule::Private *d = r->d;
2286 out << d->mRRule <<
static_cast<quint32
>(d->mPeriod);
2287 serializeQDateTimeAsKDateTime(out, d->mDateStart);
2288 out << d->mFrequency << d->mDuration;
2289 serializeQDateTimeAsKDateTime(out, d->mDateEnd);
2290 out << d->mBySeconds << d->mByMinutes << d->mByHours << d->mByDays << d->mByMonthDays
2291 << d->mByYearDays << d->mByWeekNumbers << d->mByMonths << d->mBySetPos
2292 << d->mWeekStart << d->mConstraints << d->mAllDay << d->mNoByRules << d->mTimedRepetition
2304 RecurrenceRule::Private *d = r->d;
2306 in >> d->mRRule >> period;
2307 deserializeKDateTimeAsQDateTime(in, d->mDateStart);
2308 in >> d->mFrequency >> d->mDuration;
2309 deserializeKDateTimeAsQDateTime(in, d->mDateEnd);
2310 in >> d->mBySeconds >> d->mByMinutes >> d->mByHours >> d->mByDays >> d->mByMonthDays
2311 >> d->mByYearDays >> d->mByWeekNumbers >> d->mByMonths >> d->mBySetPos
2312 >> d->mWeekStart >> d->mConstraints >> d->mAllDay >> d->mNoByRules >> d->mTimedRepetition
qint64 daysTo(const QDate &d) const const
KCALENDARCORE_EXPORT QDataStream & operator<<(QDataStream &out, const KCalendarCore::Alarm::Ptr &)
Alarm serializer.
QString toString(Qt::DateFormat format) const const
QDateTime addMonths(int nmonths) const const
QDate addMonths(int nmonths) const const
bool dateMatchesRules(const QDateTime &dt) const
Returns true if the date matches the rules.
bool isReadOnly() const
Returns true if the recurrence is read-only; false if it can be changed.
RecurrenceRule()
/************************************************************************** RecurrenceRule * ...
QDateTime getPreviousDate(const QDateTime &afterDateTime) const
Returns the date and time of the last previous recurrence, before the specified date/time.
QDateTime addYears(int nyears) const const
bool recurs() const
Returns the event's recurrence status.
void setDuration(int duration)
Sets the total number of times the event is to occur, including both the first and last...
QDateTime startDt() const
Returns the recurrence start date/time.
void setStartDt(const QDateTime &start)
Sets the recurrence start date/time.
qint64 daysTo(const QDateTime &other) const const
void addObserver(RuleObserver *observer)
Installs an observer.
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
Alarm deserializer.
int daysInYear() const const
QDateTime endDt(bool *result=nullptr) const
Returns the date and time of the last recurrence.
void removeObserver(RuleObserver *observer)
Removes an observer that was added with addObserver.
QString join(const QString &separator) const const
void clear()
Turns off recurrence for the event.
uint frequency() const
Returns the recurrence frequency, in terms of the recurrence time period type.
QDateTime toTimeZone(const QTimeZone &timeZone) const const
bool operator==(const Qt3DRender::QGraphicsApiFilter &reference, const Qt3DRender::QGraphicsApiFilter &sample)
structure for describing the n-th weekday of the month/year.
bool recursAt(const QDateTime &dt) const
Returns true if the date/time specified is one at which the event will recur.
bool recursOn(const QDate &date, const QTimeZone &timeZone) const
Returns true if the date specified is one on which the event will recur.
void setFrequency(int freq)
Sets the recurrence frequency, in terms of the recurrence time period type.
TimeList recurTimesOn(const QDate &date, const QTimeZone &timeZone) const
Returns a list of the times on the specified date at which the recurrence will occur.
int dayOfWeek() const const
QString number(int n, int base)
int count(const T &value) const const
void setAllDay(bool allDay)
Sets whether the dtstart is all-day (i.e.
void append(const T &value)
void clear()
Removes all recurrence and exception rules and dates.
Recurrence & operator=(const Recurrence &r)
Assignment operator.
bool isEmpty() const const
void shiftTimes(const QTimeZone &oldTz, const QTimeZone &newTz)
Shift the times of the rule so that they appear at the same clock time as before but in a new time zo...
PeriodType
enum for describing the frequency how an event recurs, if at all.
bool isValid() const const
int daysInMonth() const const
void setDate(const QDate &date)
friend KCALENDARCORE_EXPORT QDataStream & operator<<(QDataStream &out, const KCalendarCore::RecurrenceRule *)
RecurrenceRule serializer and deserializer.
QDate addYears(int nyears) const const
bool operator==(const Recurrence &r) const
Comparison operator for equality.
void setReadOnly(bool readOnly)
Set if recurrence is read-only or can be changed.
bool isValid() const const
bool setDate(int year, int month, int day)
qint64 secsTo(const QDateTime &other) const const
void setRRule(const QString &rrule)
Set the RRULE string for the rule.
int durationTo(const QDateTime &dt) const
Returns the number of recurrences up to and including the date/time specified.
int dayOfYear() const const
void setEndDt(const QDateTime &endDateTime)
Sets the date and time of the last recurrence.
int duration() const
Returns -1 if the event recurs infinitely, 0 if the end date is set, otherwise the total number of re...
QDate addDays(qint64 ndays) const const
QDateTime getNextDate(const QDateTime &preDateTime) const
Returns the date and time of the next recurrence, after the specified date/time.
QList< QDateTime > timesInInterval(const QDateTime &start, const QDateTime &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.
QDateTime addSecs(qint64 s) const const
void dump() const
Debug output.
QDateTime addDays(qint64 ndays) const const
QVariant merge(const QVariant &lhs, const QVariant &rhs)
bool allDay() const
Returns whether the start date has no time associated.
Namespace for all KCalendarCore types.
QTimeZone timeZone() const const