11 #include "recurrencerule.h"
12 #include "kcalendarcore_debug.h"
13 #include "recurrencehelper_p.h"
16 #include <QDataStream>
17 #include <QStringList>
24 const int LOOP_LIMIT = 10000;
50 static QString dayName(
short day);
52 static QDate getNthWeek(
int year,
int weeknumber,
short weekstart = 1);
53 static int weekNumbersInYear(
int year,
short weekstart = 1);
54 static int getWeekNumber(
const QDate &date,
short weekstart,
int *year =
nullptr);
55 static int getWeekNumberNeg(
const QDate &date,
short weekstart,
int *year =
nullptr);
58 static QDate getDate(
int year,
int month,
int day)
61 return QDate(year, month, day);
75 QString DateHelper::dayName(
short day)
79 return QStringLiteral(
"MO");
81 return QStringLiteral(
"TU");
83 return QStringLiteral(
"WE");
85 return QStringLiteral(
"TH");
87 return QStringLiteral(
"FR");
89 return QStringLiteral(
"SA");
91 return QStringLiteral(
"SU");
93 return QStringLiteral(
"??");
98 QDate DateHelper::getNthWeek(
int year,
int weeknumber,
short weekstart)
100 if (weeknumber == 0) {
105 QDate dt(year, 1, 4);
106 int adjust = -(7 + dt.dayOfWeek() - weekstart) % 7;
107 if (weeknumber > 0) {
108 dt = dt.addDays(7 * (weeknumber - 1) + adjust);
109 }
else if (weeknumber < 0) {
111 dt = dt.addDays(7 * weeknumber + adjust);
116 int DateHelper::getWeekNumber(
const QDate &date,
short weekstart,
int *year)
120 dt = dt.addDays(-(7 + dt.dayOfWeek() - weekstart) % 7);
122 qint64 daysto = dt.daysTo(date);
127 dt = dt.addDays(-(7 + dt.dayOfWeek() - weekstart) % 7);
128 daysto = dt.daysTo(date);
129 }
else if (daysto > 355) {
131 QDate dtn(y + 1, 1, 4);
132 dtn = dtn.addDays(-(7 + dtn.dayOfWeek() - weekstart) % 7);
133 qint64 dayston = dtn.daysTo(date);
143 return daysto / 7 + 1;
146 int DateHelper::weekNumbersInYear(
int year,
short weekstart)
148 QDate dt(year, 1, weekstart);
149 QDate dt1(year + 1, 1, weekstart);
150 return dt.daysTo(dt1) / 7;
154 int DateHelper::getWeekNumberNeg(
const QDate &date,
short weekstart,
int *year)
156 int weekpos = getWeekNumber(date, weekstart, year);
157 return weekNumbersInYear(*year, weekstart) - weekpos - 1;
167 return mDay == pos2.mDay && mPos == pos2.mPos;
172 return !operator==(pos2);
187 explicit Constraint(
const QTimeZone &,
int wkst = 1);
210 void setMinute(
int n)
215 void setSecond(
int n)
220 void setWeekday(
int n)
225 void setWeekdaynr(
int n)
230 void setWeeknumber(
int n)
235 void setYearday(
int n)
240 void setWeekstart(
int n)
262 bool merge(
const Constraint &interval);
271 mutable bool useCachedDt;
275 Constraint::Constraint(
const QTimeZone &timeZone,
int wkst)
284 , timeZone(dt.timeZone())
287 readDateTime(dt, type);
290 void Constraint::clear()
310 if (weeknumber == 0) {
311 if (year > 0 && year != dt.
year()) {
316 if (weeknumber > 0 && weeknumber != DateHelper::getWeekNumber(dt, weekstart, &y)) {
319 if (weeknumber < 0 && weeknumber != DateHelper::getWeekNumberNeg(dt, weekstart, &y)) {
322 if (year > 0 && year != y) {
327 if (month > 0 && month != dt.
month()) {
330 if (day > 0 && day != dt.
day()) {
340 if (weekdaynr != 0) {
343 if ((type == RecurrenceRule::rMonthly) || (type == RecurrenceRule::rYearly && month > 0)) {
345 if (weekdaynr > 0 && weekdaynr != (dt.
day() - 1) / 7 + 1) {
348 if (weekdaynr < 0 && weekdaynr != -((dt.
daysInMonth() - dt.
day()) / 7 + 1)) {
353 if (weekdaynr > 0 && weekdaynr != (dt.
dayOfYear() - 1) / 7 + 1) {
362 if (yearday > 0 && yearday != dt.
dayOfYear()) {
376 if ((hour >= 0 && hour != dt.
time().
hour()) || (minute >= 0 && minute != dt.
time().
minute()) || (second >= 0 && second != dt.
time().
second())
377 || !matches(dt.
date(), type)) {
398 bool subdaily =
true;
400 case RecurrenceRule::rSecondly:
401 t.setHMS(hour, minute, second);
403 case RecurrenceRule::rMinutely:
404 t.setHMS(hour, minute, 0);
406 case RecurrenceRule::rHourly:
407 t.setHMS(hour, 0, 0);
409 case RecurrenceRule::rDaily:
411 case RecurrenceRule::rWeekly:
412 d = DateHelper::getNthWeek(year, weeknumber, weekstart);
415 case RecurrenceRule::rMonthly:
419 case RecurrenceRule::rYearly:
427 d = DateHelper::getDate(year, (month > 0) ? month : 1, day ? day : 1);
434 bool Constraint::merge(
const Constraint &interval)
437 #define mergeConstraint( name, cmparison ) \
438 if ( interval.name cmparison ) { \
439 if ( !( name cmparison ) ) { \
440 name = interval.name; \
441 } else if ( name != interval.name ) { \
449 mergeConstraint(year, > 0);
450 mergeConstraint(month, > 0);
451 mergeConstraint(day, != 0);
452 mergeConstraint(hour, >= 0);
453 mergeConstraint(minute, >= 0);
454 mergeConstraint(second, >= 0);
456 mergeConstraint(weekday, != 0);
457 mergeConstraint(weekdaynr, != 0);
458 mergeConstraint(weeknumber, != 0);
459 mergeConstraint(yearday, != 0);
461 #undef mergeConstraint
485 if (!isConsistent(type)) {
490 QTime tm(hour, minute, second);
493 if (day && month > 0) {
494 appendDateTime(DateHelper::getDate(year, month, day), tm, result);
498 if (!done && weekday == 0 && weeknumber == 0 && yearday == 0) {
500 uint mstart = (month > 0) ? month : 1;
501 uint mend = (month <= 0) ? 12 : month;
502 for (uint m = mstart; m <= mend; ++m) {
507 }
else if (day < 0) {
508 QDate date(year, month, 1);
511 QDate date(year, month, 1);
517 appendDateTime(dt, tm, result);
528 if (!done && yearday != 0) {
530 QDate d(year + ((yearday > 0) ? 0 : 1), 1, 1);
531 d = d.addDays(yearday - ((yearday > 0) ? 1 : 0));
532 appendDateTime(d, tm, result);
537 if (!done && weeknumber != 0) {
538 QDate wst(DateHelper::getNthWeek(year, weeknumber, weekstart));
540 wst = wst.addDays((7 + weekday - weekstart) % 7);
541 appendDateTime(wst, tm, result);
543 for (
int i = 0; i < 7; ++i) {
544 appendDateTime(wst, tm, result);
545 wst = wst.addDays(1);
552 if (!done && weekday != 0) {
553 QDate dt(year, 1, 1);
557 bool inMonth = (
type == RecurrenceRule::rMonthly) || (type == RecurrenceRule::rYearly && month > 0);
558 if (inMonth && month > 0) {
559 dt =
QDate(year, month, 1);
570 int adj = (7 + weekday - dt.dayOfWeek()) % 7;
574 dt = dt.
addDays((weekdaynr - 1) * 7);
575 appendDateTime(dt, tm, result);
576 }
else if (weekdaynr < 0) {
577 dt = dt.
addDays(weekdaynr * 7);
578 appendDateTime(dt, tm, result);
581 for (
int i = 0; i < maxloop; ++i) {
582 appendDateTime(dt, tm, result);
590 for (
int i = 0, iend = result.
count(); i < iend; ++i) {
591 if (matches(result[i], type)) {
611 intervalDateTime(type);
615 case RecurrenceRule::rSecondly:
616 cachedDt = cachedDt.addSecs(freq);
618 case RecurrenceRule::rMinutely:
619 cachedDt = cachedDt.addSecs(60 * freq);
621 case RecurrenceRule::rHourly:
622 cachedDt = cachedDt.addSecs(3600 * freq);
624 case RecurrenceRule::rDaily:
625 cachedDt = cachedDt.addDays(freq);
627 case RecurrenceRule::rWeekly:
628 cachedDt = cachedDt.addDays(7 * freq);
630 case RecurrenceRule::rMonthly:
631 cachedDt = cachedDt.addMonths(freq);
633 case RecurrenceRule::rYearly:
634 cachedDt = cachedDt.addYears(freq);
640 readDateTime(cachedDt, type);
651 case RecurrenceRule::rSecondly:
654 case RecurrenceRule::rMinutely:
657 case RecurrenceRule::rHourly:
660 case RecurrenceRule::rDaily:
663 case RecurrenceRule::rMonthly:
666 case RecurrenceRule::rYearly:
669 case RecurrenceRule::rWeekly:
671 weeknumber = DateHelper::getWeekNumber(dt.
date(), weekstart, &year);
703 Private &operator=(
const Private &other);
707 void buildConstraints();
708 bool buildCache()
const;
709 Constraint getNextValidDateInterval(
const QDateTime &preDate, PeriodType type)
const;
710 Constraint getPreviousValidDateInterval(
const QDateTime &afterDate, PeriodType type)
const;
711 QList<QDateTime> datesForInterval(
const Constraint &interval, PeriodType type)
const;
738 Constraint::List mConstraints;
745 mutable bool mCached;
750 uint mTimedRepetition;
753 RecurrenceRule::Private::Private(
RecurrenceRule *parent,
const Private &p)
757 , mDateStart(p.mDateStart)
758 , mFrequency(p.mFrequency)
759 , mDuration(p.mDuration)
760 , mDateEnd(p.mDateEnd)
763 mBySeconds(p.mBySeconds)
764 , mByMinutes(p.mByMinutes)
765 , mByHours(p.mByHours)
767 , mByMonthDays(p.mByMonthDays)
768 , mByYearDays(p.mByYearDays)
769 , mByWeekNumbers(p.mByWeekNumbers)
770 , mByMonths(p.mByMonths)
771 , mBySetPos(p.mBySetPos)
772 , mWeekStart(p.mWeekStart)
775 mIsReadOnly(p.mIsReadOnly)
777 , mNoByRules(p.mNoByRules)
782 RecurrenceRule::Private &RecurrenceRule::Private::operator=(
const Private &p)
791 mDateStart = p.mDateStart;
792 mFrequency = p.mFrequency;
793 mDuration = p.mDuration;
794 mDateEnd = p.mDateEnd;
796 mBySeconds = p.mBySeconds;
797 mByMinutes = p.mByMinutes;
798 mByHours = p.mByHours;
800 mByMonthDays = p.mByMonthDays;
801 mByYearDays = p.mByYearDays;
802 mByWeekNumbers = p.mByWeekNumbers;
803 mByMonths = p.mByMonths;
804 mBySetPos = p.mBySetPos;
805 mWeekStart = p.mWeekStart;
807 mIsReadOnly = p.mIsReadOnly;
809 mNoByRules = p.mNoByRules;
816 bool RecurrenceRule::Private::operator==(
const Private &r)
const
818 return mPeriod == r.mPeriod &&
identical(mDateStart, r.mDateStart) && mDuration == r.mDuration
819 &&
identical(mDateEnd, r.mDateEnd) && mFrequency == r.mFrequency && mIsReadOnly == r.mIsReadOnly
820 && mAllDay == r.mAllDay && mBySeconds == r.mBySeconds && mByMinutes == r.mByMinutes && mByHours == r.mByHours && mByDays == r.mByDays
821 && mByMonthDays == r.mByMonthDays && mByYearDays == r.mByYearDays && mByWeekNumbers == r.mByWeekNumbers && mByMonths == r.mByMonths
822 && mBySetPos == r.mBySetPos && mWeekStart == r.mWeekStart && mNoByRules == r.mNoByRules;
825 void RecurrenceRule::Private::clear()
835 mByMonthDays.clear();
837 mByWeekNumbers.clear();
846 void RecurrenceRule::Private::setDirty()
850 mCachedDates.clear();
851 for (
int i = 0, iend = mObservers.count(); i < iend; ++i) {
853 mObservers[i]->recurrenceChanged(mParent);
864 : d(new Private(this))
869 : d(new Private(this, *r.d))
873 RecurrenceRule::~RecurrenceRule()
897 if (!d->mObservers.contains(observer)) {
898 d->mObservers.append(observer);
904 d->mObservers.removeAll(observer);
907 void RecurrenceRule::setRecurrenceType(PeriodType period)
921 if (d->mPeriod == rNone) {
924 if (d->mDuration < 0) {
927 if (d->mDuration == 0) {
937 if (!d->buildCache()) {
944 return d->mCachedDateEnd;
952 d->mDateEnd = dateTime;
953 if (d->mDateEnd.isValid()) {
982 void RecurrenceRule::setDirty()
992 d->mDateStart =
start;
1001 d->mFrequency = freq;
1005 void RecurrenceRule::setBySeconds(
const QList<int> &bySeconds)
1010 d->mBySeconds = bySeconds;
1014 void RecurrenceRule::setByMinutes(
const QList<int> &byMinutes)
1019 d->mByMinutes = byMinutes;
1023 void RecurrenceRule::setByHours(
const QList<int> &byHours)
1028 d->mByHours = byHours;
1037 d->mByDays = byDays;
1041 void RecurrenceRule::setByMonthDays(
const QList<int> &byMonthDays)
1046 d->mByMonthDays = byMonthDays;
1050 void RecurrenceRule::setByYearDays(
const QList<int> &byYearDays)
1055 d->mByYearDays = byYearDays;
1059 void RecurrenceRule::setByWeekNumbers(
const QList<int> &byWeekNumbers)
1064 d->mByWeekNumbers = byWeekNumbers;
1068 void RecurrenceRule::setByMonths(
const QList<int> &byMonths)
1073 d->mByMonths = byMonths;
1077 void RecurrenceRule::setBySetPos(
const QList<int> &bySetPos)
1082 d->mBySetPos = bySetPos;
1086 void RecurrenceRule::setWeekStart(
short weekStart)
1091 d->mWeekStart = weekStart;
1097 d->mDateStart = d->mDateStart.toTimeZone(oldTz);
1098 d->mDateStart.setTimeZone(newTz);
1099 if (d->mDuration == 0) {
1100 d->mDateEnd = d->mDateEnd.toTimeZone(oldTz);
1101 d->mDateEnd.setTimeZone(newTz);
1163 void RecurrenceRule::Private::buildConstraints()
1165 mTimedRepetition = 0;
1166 mNoByRules = mBySetPos.isEmpty();
1167 mConstraints.clear();
1168 Constraint con(mDateStart.timeZone());
1169 if (mWeekStart > 0) {
1170 con.setWeekstart(mWeekStart);
1172 mConstraints.append(con);
1178 Constraint::List tmp;
1181 #define intConstraint( list, setElement ) \
1182 if ( !list.isEmpty() ) { \
1183 mNoByRules = false; \
1184 iend = list.count(); \
1185 if ( iend == 1 ) { \
1186 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1187 mConstraints[c].setElement( list[0] ); \
1190 tmp.reserve(mConstraints.count() * iend); \
1191 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1192 for ( i = 0; i < iend; ++i ) { \
1193 con = mConstraints[c]; \
1194 con.setElement( list[i] ); \
1195 tmp.append( con ); \
1198 mConstraints = tmp; \
1204 intConstraint(mBySeconds, setSecond);
1205 intConstraint(mByMinutes, setMinute);
1206 intConstraint(mByHours, setHour);
1207 intConstraint(mByMonthDays, setDay);
1208 intConstraint(mByMonths, setMonth);
1209 intConstraint(mByYearDays, setYearday);
1210 intConstraint(mByWeekNumbers, setWeeknumber);
1211 #undef intConstraint
1213 if (!mByDays.isEmpty()) {
1215 tmp.reserve(mConstraints.count() * mByDays.count());
1216 for (c = 0, cend = mConstraints.count(); c < cend; ++c) {
1217 for (i = 0, iend = mByDays.count(); i < iend; ++i) {
1218 con = mConstraints[c];
1219 con.setWeekday(mByDays[i].day());
1220 con.setWeekdaynr(mByDays[i].pos());
1229 #define fixConstraint( setElement, value ) \
1231 for ( c = 0, cend = mConstraints.count(); c < cend; ++c ) { \
1232 mConstraints[c].setElement( value ); \
1240 if (mPeriod == rWeekly && mByDays.isEmpty()) {
1241 fixConstraint(setWeekday, mDateStart.date().dayOfWeek());
1248 if (mByDays.isEmpty() && mByWeekNumbers.isEmpty() && mByYearDays.isEmpty() && mByMonths.isEmpty()) {
1249 fixConstraint(setMonth, mDateStart.date().month());
1253 if (mByDays.isEmpty() && mByWeekNumbers.isEmpty() && mByYearDays.isEmpty() && mByMonthDays.isEmpty()) {
1254 fixConstraint(setDay, mDateStart.date().day());
1259 if (mByHours.isEmpty()) {
1260 fixConstraint(setHour, mDateStart.time().hour());
1264 if (mByMinutes.isEmpty()) {
1265 fixConstraint(setMinute, mDateStart.time().minute());
1269 if (mBySeconds.isEmpty()) {
1270 fixConstraint(setSecond, mDateStart.time().second());
1277 #undef fixConstraint
1282 mTimedRepetition = mFrequency * 3600;
1285 mTimedRepetition = mFrequency * 60;
1288 mTimedRepetition = mFrequency;
1294 for (c = 0, cend = mConstraints.count(); c < cend;) {
1295 if (mConstraints[c].isConsistent(mPeriod)) {
1298 mConstraints.removeAt(c);
1307 bool RecurrenceRule::Private::buildCache()
const
1309 Q_ASSERT(mDuration > 0);
1312 Constraint interval(getNextValidDateInterval(mDateStart, mPeriod));
1314 auto dts = datesForInterval(interval, mPeriod);
1317 const auto it = strictLowerBound(dts.begin(), dts.end(), mDateStart);
1318 if (it != dts.end()) {
1319 dts.erase(dts.begin(), it + 1);
1324 for (
int loopnr = 0; loopnr < LOOP_LIMIT && dts.count() < mDuration; ++loopnr) {
1325 interval.increase(mPeriod, mFrequency);
1327 dts += datesForInterval(interval, mPeriod);
1329 if (dts.count() > mDuration) {
1331 dts.erase(dts.begin() + mDuration, dts.end());
1341 if (
int(dts.count()) == mDuration) {
1342 mCachedDateEnd = dts.last();
1347 mCachedLastDate = interval.intervalDateTime(mPeriod);
1356 for (
int i = 0, iend = d->mConstraints.count(); i < iend; ++i) {
1357 if (d->mConstraints[i].matches(dt, recurrenceType())) {
1369 if (!qd.
isValid() || !d->mDateStart.isValid()) {
1377 if (qd < d->mDateStart.date()) {
1382 if (d->mDuration >= 0) {
1392 for (i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i) {
1393 match = d->mConstraints[i].matches(qd, recurrenceType());
1400 Constraint interval(d->getNextValidDateInterval(
start, recurrenceType()));
1403 if (!interval.matches(qd, recurrenceType())) {
1411 auto dts = d->datesForInterval(interval, recurrenceType());
1412 for (i = 0, iend = dts.count(); i < iend; ++i) {
1413 if (dts[i].date() >= qd) {
1414 return dts[i].date() == qd;
1417 interval.increase(recurrenceType(),
frequency());
1418 }
while (interval.intervalDateTime(recurrenceType()) < end);
1424 QDateTime end =
start.addDays(1).toTimeZone(d->mDateStart.timeZone());
1425 start =
start.toTimeZone(d->mDateStart.timeZone());
1426 if (end < d->mDateStart) {
1429 if (start < d->mDateStart) {
1430 start = d->mDateStart;
1434 if (d->mDuration >= 0) {
1437 if (
start > endRecur) {
1440 if (end > endRecur) {
1446 if (d->mTimedRepetition) {
1448 int n =
static_cast<int>((d->mDateStart.secsTo(
start) - 1) % d->mTimedRepetition);
1449 return start.addSecs(d->mTimedRepetition - n) < end;
1454 QDate endDay = end.addSecs(-1).date();
1455 int dayCount = startDay.
daysTo(endDay) + 1;
1460 for (i = 0, iend = d->mConstraints.count(); i < iend && !match; ++i) {
1461 match = d->mConstraints[i].matches(startDay, recurrenceType());
1462 for (
int day = 1; day < dayCount && !match; ++day) {
1463 match = d->mConstraints[i].matches(startDay.
addDays(day), recurrenceType());
1470 Constraint interval(d->getNextValidDateInterval(
start, recurrenceType()));
1473 Constraint intervalm = interval;
1475 match = intervalm.matches(startDay, recurrenceType());
1476 for (
int day = 1; day < dayCount && !match; ++day) {
1477 match = intervalm.matches(startDay.
addDays(day), recurrenceType());
1482 intervalm.increase(recurrenceType(),
frequency());
1483 }
while (intervalm.intervalDateTime(recurrenceType()).isValid() && intervalm.intervalDateTime(recurrenceType()) < end);
1492 auto dts = d->datesForInterval(interval, recurrenceType());
1493 const auto it = std::lower_bound(dts.constBegin(), dts.constEnd(),
start);
1494 if (it != dts.constEnd()) {
1497 interval.increase(recurrenceType(),
frequency());
1498 }
while (interval.intervalDateTime(recurrenceType()).isValid() && interval.intervalDateTime(recurrenceType()) < end);
1511 if (dt < d->mDateStart) {
1515 if (d->mDuration >= 0 && dt >
endDt()) {
1519 if (d->mTimedRepetition) {
1521 return !(d->mDateStart.secsTo(dt) % d->mTimedRepetition);
1531 Constraint interval(d->getNextValidDateInterval(dt, recurrenceType()));
1533 if (interval.matches(dt, recurrenceType())) {
1548 for (
int i = 0, iend = dts.count(); i < iend; ++i) {
1549 lst += dts[i].toTimeZone(timeZone).time();
1561 if (toDate < d->mDateStart) {
1565 if (d->mDuration > 0 && toDate >=
endDt()) {
1566 return d->mDuration;
1569 if (d->mTimedRepetition) {
1571 return static_cast<int>(d->mDateStart.secsTo(toDate) / d->mTimedRepetition);
1588 if (!toDate.
isValid() || toDate < d->mDateStart) {
1592 if (d->mTimedRepetition) {
1595 if (d->mDuration >= 0 &&
endDt().isValid() && toDate >
endDt()) {
1598 int n =
static_cast<int>((d->mDateStart.secsTo(prev) - 1) % d->mTimedRepetition);
1603 return prev >= d->mDateStart ? prev :
QDateTime();
1607 if (d->mDuration > 0) {
1611 const auto it = strictLowerBound(d->mCachedDates.constBegin(), d->mCachedDates.constEnd(), toDate);
1612 if (it != d->mCachedDates.constEnd()) {
1619 if (d->mDuration >= 0 &&
endDt().isValid() && toDate >
endDt()) {
1623 Constraint interval(d->getPreviousValidDateInterval(prev, recurrenceType()));
1624 const auto dts = d->datesForInterval(interval, recurrenceType());
1625 const auto it = strictLowerBound(dts.begin(), dts.end(), prev);
1626 if (it != dts.end()) {
1627 return ((*it) >= d->mDateStart) ? (*it) :
QDateTime();
1631 while (interval.intervalDateTime(recurrenceType()) > d->mDateStart) {
1632 interval.increase(recurrenceType(), -
int(
frequency()));
1634 auto dts = d->datesForInterval(interval, recurrenceType());
1636 if (!dts.isEmpty()) {
1638 if (prev.
isValid() && prev >= d->mDateStart) {
1653 if (d->mDuration >= 0 &&
endDt().isValid() && fromDate >=
endDt()) {
1658 if (fromDate < d->mDateStart) {
1659 fromDate = d->mDateStart.
addSecs(-1);
1662 if (d->mTimedRepetition) {
1664 int n =
static_cast<int>((d->mDateStart.secsTo(fromDate) + 1) % d->mTimedRepetition);
1669 if (d->mDuration > 0) {
1673 const auto it = std::upper_bound(d->mCachedDates.constBegin(), d->mCachedDates.constEnd(), fromDate);
1674 if (it != d->mCachedDates.constEnd()) {
1680 Constraint interval(d->getNextValidDateInterval(fromDate, recurrenceType()));
1681 const auto dts = d->datesForInterval(interval, recurrenceType());
1682 const auto it = std::upper_bound(dts.begin(), dts.end(), fromDate);
1683 if (it != dts.end()) {
1684 return (d->mDuration < 0 || *it <= end) ? *it :
QDateTime();
1686 interval.increase(recurrenceType(),
frequency());
1687 if (d->mDuration >= 0 && interval.intervalDateTime(recurrenceType()) > end) {
1696 auto dts = d->datesForInterval(interval, recurrenceType());
1697 if (!dts.isEmpty()) {
1699 if (d->mDuration >= 0 && ret > end) {
1705 interval.increase(recurrenceType(),
frequency());
1706 }
while (++loop < LOOP_LIMIT && (d->mDuration < 0 || interval.intervalDateTime(recurrenceType()) < end));
1715 if (end < d->mDateStart) {
1719 if (d->mDuration >= 0) {
1722 if (
start > endRecur) {
1725 if (end >= endRecur) {
1731 if (d->mTimedRepetition) {
1735 qint64 offsetFromNextOccurrence;
1736 if (d->mDateStart <
start) {
1737 offsetFromNextOccurrence = d->mTimedRepetition - (d->mDateStart.secsTo(
start) % d->mTimedRepetition);
1739 offsetFromNextOccurrence = -(d->mDateStart.secsTo(
start) % d->mTimedRepetition);
1743 int numberOfOccurrencesWithinInterval =
static_cast<int>(dt.
secsTo(enddt) / d->mTimedRepetition) + 1;
1745 numberOfOccurrencesWithinInterval = qMin(numberOfOccurrencesWithinInterval, LOOP_LIMIT);
1746 for (
int i = 0; i < numberOfOccurrencesWithinInterval; dt = dt.
addSecs(d->mTimedRepetition), ++i) {
1755 if (d->mDuration > 0) {
1759 if (d->mCachedDateEnd.isValid() &&
start > d->mCachedDateEnd) {
1762 const auto it = std::lower_bound(d->mCachedDates.constBegin(), d->mCachedDates.constEnd(),
start);
1763 if (it != d->mCachedDates.constEnd()) {
1764 const auto itEnd = std::upper_bound(it, d->mCachedDates.constEnd(), enddt);
1765 if (itEnd != d->mCachedDates.constEnd()) {
1768 std::copy(it, itEnd, std::back_inserter(result));
1770 if (d->mCachedDateEnd.isValid()) {
1772 }
else if (!result.
isEmpty()) {
1780 st = d->mCachedLastDate.
addSecs(1);
1783 Constraint interval(d->getNextValidDateInterval(st, recurrenceType()));
1786 auto dts = d->datesForInterval(interval, recurrenceType());
1787 auto it = dts.begin();
1788 auto itEnd = dts.end();
1790 it = std::lower_bound(dts.begin(), dts.end(), st);
1792 itEnd = std::upper_bound(it, dts.end(), enddt);
1793 if (itEnd != dts.end()) {
1796 std::copy(it, itEnd, std::back_inserter(result));
1798 interval.increase(recurrenceType(),
frequency());
1799 }
while (++loop < LOOP_LIMIT && interval.intervalDateTime(recurrenceType()) < end);
1808 Constraint RecurrenceRule::Private::getPreviousValidDateInterval(
const QDateTime &dt, PeriodType type)
const
1829 periods =
static_cast<int>(
start.secsTo(toDate) / modifier);
1831 if (mFrequency > 0) {
1832 periods = (periods / mFrequency) * mFrequency;
1834 nextValid =
start.addSecs(modifier * periods);
1837 toDate = toDate.addDays(-(7 + toDate.date().dayOfWeek() - mWeekStart) % 7);
1838 start =
start.addDays(-(7 +
start.date().dayOfWeek() - mWeekStart) % 7);
1842 periods =
start.daysTo(toDate) / modifier;
1844 if (mFrequency > 0) {
1845 periods = (periods / mFrequency) * mFrequency;
1847 nextValid =
start.addDays(modifier * periods);
1850 periods = 12 * (toDate.date().year() -
start.date().year()) + (toDate.date().month() -
start.date().month());
1852 if (mFrequency > 0) {
1853 periods = (periods / mFrequency) * mFrequency;
1858 nextValid.setDate(
start.date().addMonths(periods));
1862 periods = (toDate.date().year() -
start.date().year());
1864 if (mFrequency > 0) {
1865 periods = (periods / mFrequency) * mFrequency;
1867 nextValid.setDate(
start.date().addYears(periods));
1873 return Constraint(nextValid, type, mWeekStart);
1880 Constraint RecurrenceRule::Private::getNextValidDateInterval(
const QDateTime &dt, PeriodType type)
const
1902 periods =
static_cast<int>(
start.secsTo(toDate) / modifier);
1903 periods = qMax(0L, periods);
1904 if (periods > 0 && mFrequency > 0) {
1905 periods += (mFrequency - 1 - ((periods - 1) % mFrequency));
1907 nextValid =
start.addSecs(modifier * periods);
1911 toDate = toDate.addDays(-(7 + toDate.date().dayOfWeek() - mWeekStart) % 7);
1912 start =
start.addDays(-(7 +
start.date().dayOfWeek() - mWeekStart) % 7);
1916 periods =
start.daysTo(toDate) / modifier;
1917 periods = qMax(0L, periods);
1918 if (periods > 0 && mFrequency > 0) {
1919 periods += (mFrequency - 1 - ((periods - 1) % mFrequency));
1921 nextValid =
start.addDays(modifier * periods);
1924 periods = 12 * (toDate.date().year() -
start.date().year()) + (toDate.date().month() -
start.date().month());
1925 periods = qMax(0L, periods);
1926 if (periods > 0 && mFrequency > 0) {
1927 periods += (mFrequency - 1 - ((periods - 1) % mFrequency));
1932 nextValid.setDate(
start.date().addMonths(periods));
1936 periods = (toDate.date().year() -
start.date().year());
1937 periods = qMax(0L, periods);
1938 if (periods > 0 && mFrequency > 0) {
1939 periods += (mFrequency - 1 - ((periods - 1) % mFrequency));
1941 nextValid.setDate(
start.date().addYears(periods));
1947 return Constraint(nextValid, type, mWeekStart);
1950 QList<QDateTime> RecurrenceRule::Private::datesForInterval(
const Constraint &interval, PeriodType type)
const
1959 for (
int i = 0, iend = mConstraints.count(); i < iend; ++i) {
1960 Constraint merged(interval);
1961 if (merged.merge(mConstraints[i])) {
1963 if (merged.year > 0 && merged.hour >= 0 && merged.minute >= 0 && merged.second >= 0) {
1972 sortAndRemoveDuplicates(lst);
1983 if (!mBySetPos.isEmpty()) {
1986 for (
int i = 0, iend = mBySetPos.count(); i < iend; ++i) {
1987 int pos = mBySetPos[i];
1992 pos += tmplst.count();
1994 if (pos >= 0 && pos < tmplst.count()) {
1998 sortAndRemoveDuplicates(lst);
2008 if (!d->mRRule.isEmpty()) {
2009 qCDebug(KCALCORE_LOG) <<
" RRULE=" << d->mRRule;
2011 qCDebug(KCALCORE_LOG) <<
" Read-Only:" <<
isReadOnly();
2013 qCDebug(KCALCORE_LOG) <<
" Period type:" << int(recurrenceType()) <<
", frequency:" <<
frequency();
2014 qCDebug(KCALCORE_LOG) <<
" #occurrences:" <<
duration();
2015 qCDebug(KCALCORE_LOG) <<
" start date:" << dumpTime(
startDt(),
allDay()) <<
", end date:" << dumpTime(
endDt(),
allDay());
2017 #define dumpByIntList(list,label) \
2018 if ( !list.isEmpty() ) {\
2020 for ( int i = 0, iend = list.count(); i < iend; ++i ) {\
2021 lst.append( QString::number( list[i] ) );\
2023 qCDebug(KCALCORE_LOG) << " " << label << lst.join(QLatin1String(", ") );\
2026 dumpByIntList(d->mBySeconds, QStringLiteral(
"BySeconds: "));
2027 dumpByIntList(d->mByMinutes, QStringLiteral(
"ByMinutes: "));
2028 dumpByIntList(d->mByHours, QStringLiteral(
"ByHours: "));
2029 if (!d->mByDays.isEmpty()) {
2031 for (
int i = 0, iend = d->mByDays.count(); i < iend; ++i) {
2036 dumpByIntList(d->mByMonthDays, QStringLiteral(
"ByMonthDays:"));
2037 dumpByIntList(d->mByYearDays, QStringLiteral(
"ByYearDays: "));
2038 dumpByIntList(d->mByWeekNumbers, QStringLiteral(
"ByWeekNr: "));
2039 dumpByIntList(d->mByMonths, QStringLiteral(
"ByMonths: "));
2040 dumpByIntList(d->mBySetPos, QStringLiteral(
"BySetPos: "));
2041 #undef dumpByIntList
2043 qCDebug(KCALCORE_LOG) <<
" Week start:" << DateHelper::dayName(d->mWeekStart);
2045 qCDebug(KCALCORE_LOG) <<
" Constraints:";
2047 for (
int i = 0, iend = d->mConstraints.count(); i < iend; ++i) {
2048 d->mConstraints[i].dump();
2054 void Constraint::dump()
const
2056 qCDebug(KCALCORE_LOG) <<
" ~> Y=" << year <<
", M=" << month <<
", D=" << day <<
", H=" << hour <<
", m=" << minute <<
", S=" << second
2057 <<
", wd=" << weekday <<
",#wd=" << weekdaynr <<
", #w=" << weeknumber <<
", yd=" << yearday;
2069 result = dt.
toString(QStringLiteral(
"ddd yyyy-MM-dd t"));
2071 result = dt.
toString(QStringLiteral(
"ddd yyyy-MM-dd hh:mm:ss t"));
2083 return d->mDateStart;
2093 return d->mFrequency;
2098 return d->mDuration;
2101 QString RecurrenceRule::rrule()
const
2113 return d->mIsReadOnly;
2118 d->mIsReadOnly = readOnly;
2123 return d->mPeriod != rNone;
2131 const QList<int> &RecurrenceRule::bySeconds()
const
2133 return d->mBySeconds;
2136 const QList<int> &RecurrenceRule::byMinutes()
const
2138 return d->mByMinutes;
2141 const QList<int> &RecurrenceRule::byHours()
const
2151 const QList<int> &RecurrenceRule::byMonthDays()
const
2153 return d->mByMonthDays;
2156 const QList<int> &RecurrenceRule::byYearDays()
const
2158 return d->mByYearDays;
2161 const QList<int> &RecurrenceRule::byWeekNumbers()
const
2163 return d->mByWeekNumbers;
2166 const QList<int> &RecurrenceRule::byMonths()
const
2168 return d->mByMonths;
2171 const QList<int> &RecurrenceRule::bySetPos()
const
2173 return d->mBySetPos;
2176 short RecurrenceRule::weekStart()
const
2178 return d->mWeekStart;
2181 RecurrenceRule::RuleObserver::~RuleObserver()
2185 RecurrenceRule::WDayPos::WDayPos(
int ps,
short dy)
2191 void RecurrenceRule::WDayPos::setDay(
short dy)
2196 short RecurrenceRule::WDayPos::day()
const
2201 void RecurrenceRule::WDayPos::setPos(
int ps)
2206 int RecurrenceRule::WDayPos::pos()
const
2213 out << c.year << c.month << c.day << c.hour << c.minute << c.second << c.weekday << c.weekdaynr << c.weeknumber << c.yearday << c.weekstart;
2214 serializeQTimeZoneAsSpec(out, c.timeZone);
2222 bool secondOccurrence;
2223 in >> c.year >> c.month >> c.day >> c.hour >> c.minute >> c.second >> c.weekday >> c.weekdaynr >> c.weeknumber >> c.yearday >> c.weekstart;
2224 deserializeSpecAsQTimeZone(in, c.timeZone);
2225 in >> secondOccurrence;
2231 out << w.mDay << w.mPos;
2237 in >> w.mDay >> w.mPos;
2247 RecurrenceRule::Private *d = r->d;
2248 out << d->mRRule <<
static_cast<quint32
>(d->mPeriod);
2249 serializeQDateTimeAsKDateTime(out, d->mDateStart);
2250 out << d->mFrequency << d->mDuration;
2251 serializeQDateTimeAsKDateTime(out, d->mDateEnd);
2252 out << d->mBySeconds << d->mByMinutes << d->mByHours << d->mByDays << d->mByMonthDays << d->mByYearDays << d->mByWeekNumbers << d->mByMonths << d->mBySetPos
2253 << d->mWeekStart << d->mConstraints << d->mAllDay << d->mNoByRules << d->mTimedRepetition << d->mIsReadOnly;
2264 RecurrenceRule::Private *d = r->d;
2266 in >> d->mRRule >> period;
2267 deserializeKDateTimeAsQDateTime(in, d->mDateStart);
2268 in >> d->mFrequency >> d->mDuration;
2269 deserializeKDateTimeAsQDateTime(in, d->mDateEnd);
2270 in >> d->mBySeconds >> d->mByMinutes >> d->mByHours >> d->mByDays >> d->mByMonthDays >> d->mByYearDays >> d->mByWeekNumbers >> d->mByMonths >> d->mBySetPos
2271 >> d->mWeekStart >> d->mConstraints >> d->mAllDay >> d->mNoByRules >> d->mTimedRepetition >> d->mIsReadOnly;