11#include "recurrencerule.h"
12#include "kcalendarcore_debug.h"
13#include "recurrencehelper_p.h"
24const 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);
75QString 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(
"??");
98QDate 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);
116int 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;
146int 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;
154int 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;
275Constraint::Constraint(
const QTimeZone &timeZone,
int wkst)
284 , timeZone(dt.timeZone())
287 readDateTime(dt, type);
290void 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);
434bool 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;
753RecurrenceRule::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)
782RecurrenceRule::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;
816bool 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;
825void RecurrenceRule::Private::clear()
835 mByMonthDays.clear();
837 mByWeekNumbers.clear();
846void 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))
873RecurrenceRule::~RecurrenceRule()
897 if (!d->mObservers.contains(observer)) {
898 d->mObservers.append(observer);
904 d->mObservers.removeAll(observer);
907void 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()) {
982void RecurrenceRule::setDirty()
992 d->mDateStart =
start;
1001 d->mFrequency = freq;
1005void RecurrenceRule::setBySeconds(
const QList<int> &bySeconds)
1010 d->mBySeconds = bySeconds;
1014void RecurrenceRule::setByMinutes(
const QList<int> &byMinutes)
1019 d->mByMinutes = byMinutes;
1023void RecurrenceRule::setByHours(
const QList<int> &byHours)
1028 d->mByHours = byHours;
1037 d->mByDays = byDays;
1041void RecurrenceRule::setByMonthDays(
const QList<int> &byMonthDays)
1046 d->mByMonthDays = byMonthDays;
1050void RecurrenceRule::setByYearDays(
const QList<int> &byYearDays)
1055 d->mByYearDays = byYearDays;
1059void RecurrenceRule::setByWeekNumbers(
const QList<int> &byWeekNumbers)
1064 d->mByWeekNumbers = byWeekNumbers;
1068void RecurrenceRule::setByMonths(
const QList<int> &byMonths)
1073 d->mByMonths = byMonths;
1077void RecurrenceRule::setBySetPos(
const QList<int> &bySetPos)
1082 d->mBySetPos = bySetPos;
1086void 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);
1163void 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);
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());
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);
1307bool 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) {
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);
1808Constraint 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);
1880Constraint 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);
1950QList<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: "));
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();
2054void 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;
2101QString RecurrenceRule::rrule()
const
2113 return d->mIsReadOnly;
2118 d->mIsReadOnly = readOnly;
2123 return d->mPeriod != rNone;
2131const QList<int> &RecurrenceRule::bySeconds()
const
2133 return d->mBySeconds;
2136const QList<int> &RecurrenceRule::byMinutes()
const
2138 return d->mByMinutes;
2141const QList<int> &RecurrenceRule::byHours()
const
2151const QList<int> &RecurrenceRule::byMonthDays()
const
2153 return d->mByMonthDays;
2156const QList<int> &RecurrenceRule::byYearDays()
const
2158 return d->mByYearDays;
2161const QList<int> &RecurrenceRule::byWeekNumbers()
const
2163 return d->mByWeekNumbers;
2166const QList<int> &RecurrenceRule::byMonths()
const
2168 return d->mByMonths;
2171const QList<int> &RecurrenceRule::bySetPos()
const
2173 return d->mBySetPos;
2176short RecurrenceRule::weekStart()
const
2178 return d->mWeekStart;
2181RecurrenceRule::RuleObserver::~RuleObserver()
2185RecurrenceRule::WDayPos::WDayPos(
int ps,
short dy)
2191void RecurrenceRule::WDayPos::setDay(
short dy)
2196short RecurrenceRule::WDayPos::day()
const
2201void RecurrenceRule::WDayPos::setPos(
int ps)
2206int 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;
structure for describing the n-th weekday of the month/year.
This class represents a recurrence rule for a calendar incidence.
void setAllDay(bool allDay)
Sets whether the dtstart is all-day (i.e.
void setReadOnly(bool readOnly)
Set if recurrence is read-only or can be changed.
QDateTime startDt() const
Returns the recurrence start date/time.
QDateTime endDt(bool *result=nullptr) const
Returns the date and time of the last recurrence.
QDateTime getNextDate(const QDateTime &preDateTime) const
Returns the date and time of the next recurrence, after the specified date/time.
void clear()
Turns off recurrence for the event.
void setDuration(int duration)
Sets the total number of times the event is to occur, including both the first and last.
bool allDay() const
Returns whether the start date has no time associated.
void dump() const
Debug output.
bool recurs() const
Returns the event's recurrence status.
void setFrequency(int freq)
Sets the recurrence frequency, in terms of the recurrence time period type.
void setEndDt(const QDateTime &endDateTime)
Sets the date and time of the last recurrence.
QDateTime getPreviousDate(const QDateTime &afterDateTime) const
Returns the date and time of the last previous recurrence, before the specified date/time.
RecurrenceRule()
/************************************************************************** RecurrenceRule *
uint frequency() const
Returns the recurrence frequency, in terms of the recurrence time period type.
int duration() const
Returns -1 if the event recurs infinitely, 0 if the end date is set, otherwise the total number of re...
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.
PeriodType
enum for describing the frequency how an event recurs, if at all.
bool recursOn(const QDate &date, const QTimeZone &timeZone) const
Returns true if the date specified is one on which the event will recur.
bool dateMatchesRules(const QDateTime &dt) const
Returns true if the date matches the rules.
void setRRule(const QString &rrule)
Set the RRULE string for the rule.
void setStartDt(const QDateTime &start)
Sets the recurrence start date/time.
int durationTo(const QDateTime &dt) const
Returns the number of recurrences up to and including the date/time specified.
bool recursAt(const QDateTime &dt) const
Returns true if the date/time specified is one at which the event will recur.
bool isReadOnly() const
Returns true if the recurrence is read-only; false if it can be changed.
void addObserver(RuleObserver *observer)
Installs an observer.
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.
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...
friend KCALENDARCORE_EXPORT QDataStream & operator<<(QDataStream &out, const KCalendarCore::RecurrenceRule *)
RecurrenceRule serializer and deserializer.
void removeObserver(RuleObserver *observer)
Removes an observer that was added with addObserver.
Q_SCRIPTABLE Q_NOREPLY void start()
This file is part of the API for handling calendar data and defines the IncidenceBase class.
Type type(const QSqlDatabase &db)
Namespace for all KCalendarCore types.
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
Alarm deserializer.
KCALENDARCORE_EXPORT QDataStream & operator<<(QDataStream &out, const KCalendarCore::Alarm::Ptr &)
Alarm serializer.
KCALENDARCORE_EXPORT bool identical(const QDateTime &dt1, const QDateTime &dt2)
Compare two QDateTimes for extended equality.
QStringView merge(QStringView lhs, QStringView rhs)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
bool operator==(const StyleDelim &l, const StyleDelim &r)
QDate addDays(qint64 ndays) const const
int dayOfWeek() const const
int dayOfYear() const const
int daysInMonth() const const
int daysInYear() const const
qint64 daysTo(QDate d) const const
bool isValid(int year, int month, int day)
bool setDate(int year, int month, int day)
QDateTime addDays(qint64 ndays) const const
QDateTime addMonths(int nmonths) const const
QDateTime addSecs(qint64 s) const const
QDateTime addYears(int nyears) const const
bool isValid() const const
qint64 secsTo(const QDateTime &other) const const
QTimeZone timeZone() const const
QString toString(QStringView format, QCalendar cal) const const
QDateTime toTimeZone(const QTimeZone &timeZone) const const
void append(QList< T > &&value)
qsizetype count() const const
bool isEmpty() const const
QString number(double n, char format, int precision)
QString join(QChar separator) const const