00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include "kalarm.h"
00022
00023 #include <qbitarray.h>
00024 #include <kdebug.h>
00025
00026 #include <libkcal/icalformat.h>
00027
00028 #include "datetime.h"
00029 #include "functions.h"
00030 #include "karecurrence.h"
00031
00032 using namespace KCal;
00033
00034
00035 KARecurrence::Feb29Type KARecurrence::mDefaultFeb29 = KARecurrence::FEB29_FEB29;
00036
00037
00038
00039
00040
00041
00042
00043
00044 bool KARecurrence::set(Type recurType, int freq, int count, int f29, const DateTime& start, const QDateTime& end)
00045 {
00046 mCachedType = -1;
00047 RecurrenceRule::PeriodType rrtype;
00048 switch (recurType)
00049 {
00050 case MINUTELY: rrtype = RecurrenceRule::rMinutely; break;
00051 case DAILY: rrtype = RecurrenceRule::rDaily; break;
00052 case WEEKLY: rrtype = RecurrenceRule::rWeekly; break;
00053 case MONTHLY_DAY: rrtype = RecurrenceRule::rMonthly; break;
00054 case ANNUAL_DATE: rrtype = RecurrenceRule::rYearly; break;
00055 case NO_RECUR: rrtype = RecurrenceRule::rNone; break;
00056 default:
00057 return false;
00058 }
00059 if (!init(rrtype, freq, count, f29, start, end))
00060 return false;
00061 switch (recurType)
00062 {
00063 case WEEKLY:
00064 {
00065 QBitArray days(7);
00066 days.setBit(start.date().dayOfWeek() - 1);
00067 addWeeklyDays(days);
00068 break;
00069 }
00070 case MONTHLY_DAY:
00071 addMonthlyDate(start.date().day());
00072 break;
00073 case ANNUAL_DATE:
00074 addYearlyDate(start.date().day());
00075 addYearlyMonth(start.date().month());
00076 break;
00077 default:
00078 break;
00079 }
00080 return true;
00081 }
00082
00083
00084
00085
00086
00087 bool KARecurrence::init(RecurrenceRule::PeriodType recurType, int freq, int count, int f29, const DateTime& start,
00088 const QDateTime& end)
00089 {
00090 mCachedType = -1;
00091 Feb29Type feb29Type = (f29 == -1) ? mDefaultFeb29 : static_cast<Feb29Type>(f29);
00092 mFeb29Type = FEB29_FEB29;
00093 clear();
00094 if (count < -1)
00095 return false;
00096 bool dateOnly = start.isDateOnly();
00097 if (!count && (!dateOnly && !end.isValid()
00098 || dateOnly && !end.date().isValid()))
00099 return false;
00100 switch (recurType)
00101 {
00102 case RecurrenceRule::rMinutely:
00103 case RecurrenceRule::rDaily:
00104 case RecurrenceRule::rWeekly:
00105 case RecurrenceRule::rMonthly:
00106 case RecurrenceRule::rYearly:
00107 break;
00108 case rNone:
00109 return true;
00110 default:
00111 return false;
00112 }
00113 setNewRecurrenceType(recurType, freq);
00114 if (count)
00115 setDuration(count);
00116 else if (dateOnly)
00117 setEndDate(end.date());
00118 else
00119 setEndDateTime(end);
00120 QDateTime startdt = start.dateTime();
00121 if (recurType == RecurrenceRule::rYearly
00122 && feb29Type == FEB29_FEB28 || feb29Type == FEB29_MAR1)
00123 {
00124 int year = startdt.date().year();
00125 if (!QDate::leapYear(year)
00126 && startdt.date().dayOfYear() == (feb29Type == FEB29_MAR1 ? 60 : 59))
00127 {
00128
00129
00130
00131
00132
00133 while (!QDate::leapYear(--year)) ;
00134 startdt.setDate(QDate(year, 2, 29));
00135 }
00136 mFeb29Type = feb29Type;
00137 }
00138 if (dateOnly)
00139 setStartDate(startdt.date());
00140 else
00141 setStartDateTime(startdt);
00142 return true;
00143 }
00144
00145
00146
00147
00148 bool KARecurrence::set(const QString& icalRRULE)
00149 {
00150 static QString RRULE = QString::fromLatin1("RRULE:");
00151 mCachedType = -1;
00152 clear();
00153 if (icalRRULE.isEmpty())
00154 return true;
00155 ICalFormat format;
00156 if (!format.fromString(defaultRRule(true),
00157 (icalRRULE.startsWith(RRULE) ? icalRRULE.mid(RRULE.length()) : icalRRULE)))
00158 return false;
00159 fix();
00160 return true;
00161 }
00162
00163
00164
00165
00166
00167
00168
00169
00170
00171
00172 void KARecurrence::fix()
00173 {
00174 mCachedType = -1;
00175 mFeb29Type = FEB29_FEB29;
00176 int convert = 0;
00177 int days[2] = { 0, 0 };
00178 RecurrenceRule* rrules[2];
00179 RecurrenceRule::List rrulelist = rRules();
00180 RecurrenceRule::List::ConstIterator rr = rrulelist.begin();
00181 for (int i = 0; i < 2 && rr != rrulelist.end(); ++i, ++rr)
00182 {
00183 RecurrenceRule* rrule = *rr;
00184 rrules[i] = rrule;
00185 bool stop = true;
00186 int rtype = recurrenceType(rrule);
00187 switch (rtype)
00188 {
00189 case rHourly:
00190
00191 rrule->setRecurrenceType(RecurrenceRule::rMinutely);
00192 rrule->setFrequency(rrule->frequency() * 60);
00193
00194 case rMinutely:
00195 case rDaily:
00196 case rWeekly:
00197 case rMonthlyDay:
00198 case rMonthlyPos:
00199 case rYearlyPos:
00200 if (!convert)
00201 ++rr;
00202 break;
00203 case rOther:
00204 if (dailyType(rrule))
00205 {
00206 if (!convert)
00207 ++rr;
00208 }
00209 break;
00210 case rYearlyDay:
00211 {
00212
00213 if (convert)
00214 {
00215
00216
00217 if (days[0] != 29
00218 || rrule->frequency() != rrules[0]->frequency()
00219 || rrule->startDt() != rrules[0]->startDt())
00220 break;
00221 }
00222 QValueList<int> ds = rrule->byYearDays();
00223 if (!ds.isEmpty() && ds.first() == 60)
00224 {
00225 ++convert;
00226 days[i] = 60;
00227 stop = false;
00228 break;
00229 }
00230 break;
00231 }
00232 case rYearlyMonth:
00233 {
00234 QValueList<int> ds = rrule->byMonthDays();
00235 if (!ds.isEmpty())
00236 {
00237 int day = ds.first();
00238 if (convert)
00239 {
00240
00241
00242 if (day == days[0] || day == -1 && days[0] == 60
00243 || rrule->frequency() != rrules[0]->frequency()
00244 || rrule->startDt() != rrules[0]->startDt())
00245 break;
00246 }
00247 if (ds.count() > 1)
00248 {
00249 ds.clear();
00250 ds.append(day);
00251 rrule->setByMonthDays(ds);
00252 }
00253 if (day == -1)
00254 {
00255
00256 QValueList<int> months = rrule->byMonths();
00257 if (months.count() != 1 || months.first() != 2)
00258 day = 0;
00259 }
00260 if (day == 29 || day == -1)
00261 {
00262 ++convert;
00263 days[i] = day;
00264 stop = false;
00265 break;
00266 }
00267 }
00268 if (!convert)
00269 ++rr;
00270 break;
00271 }
00272 default:
00273 break;
00274 }
00275 if (stop)
00276 break;
00277 }
00278
00279
00280 for ( ; rr != rrulelist.end(); ++rr)
00281 removeRRule(*rr);
00282
00283 QDate end;
00284 int count;
00285 QValueList<int> months;
00286 if (convert == 2)
00287 {
00288
00289
00290
00291 if (days[0] != 29)
00292 {
00293
00294 RecurrenceRule* rr = rrules[0];
00295 rrules[0] = rrules[1];
00296 rrules[1] = rr;
00297 int d = days[0];
00298 days[0] = days[1];
00299 days[1] = d;
00300 }
00301
00302 months = rrules[0]->byMonths();
00303 if (months.remove(2))
00304 rrules[0]->setByMonths(months);
00305
00306 count = combineDurations(rrules[0], rrules[1], end);
00307 mFeb29Type = (days[1] == 60) ? FEB29_MAR1 : FEB29_FEB28;
00308 }
00309 else if (convert == 1 && days[0] == 60)
00310 {
00311
00312
00313 count = duration();
00314 if (!count)
00315 end = endDate();
00316 mFeb29Type = FEB29_MAR1;
00317 }
00318 else
00319 return;
00320
00321
00322 setNewRecurrenceType(RecurrenceRule::rYearly, frequency());
00323 RecurrenceRule* rrule = defaultRRule();
00324 months.append(2);
00325 rrule->setByMonths(months);
00326 QValueList<int> ds;
00327 ds.append(29);
00328 rrule->setByMonthDays(ds);
00329 if (count)
00330 setDuration(count);
00331 else
00332 setEndDate(end);
00333 }
00334
00335
00336
00337
00338
00339 void KARecurrence::writeRecurrence(KCal::Recurrence& recur) const
00340 {
00341 recur.clear();
00342 recur.setStartDateTime(startDateTime());
00343 recur.setExDates(exDates());
00344 recur.setExDateTimes(exDateTimes());
00345 const RecurrenceRule* rrule = defaultRRuleConst();
00346 if (!rrule)
00347 return;
00348 int freq = frequency();
00349 int count = duration();
00350 static_cast<KARecurrence*>(&recur)->setNewRecurrenceType(rrule->recurrenceType(), freq);
00351 if (count)
00352 recur.setDuration(count);
00353 else
00354 recur.setEndDateTime(endDateTime());
00355 switch (type())
00356 {
00357 case DAILY:
00358 if (rrule->byDays().isEmpty())
00359 break;
00360
00361 case WEEKLY:
00362 case MONTHLY_POS:
00363 recur.defaultRRule(true)->setByDays(rrule->byDays());
00364 break;
00365 case MONTHLY_DAY:
00366 recur.defaultRRule(true)->setByMonthDays(rrule->byMonthDays());
00367 break;
00368 case ANNUAL_POS:
00369 recur.defaultRRule(true)->setByMonths(rrule->byMonths());
00370 recur.defaultRRule()->setByDays(rrule->byDays());
00371 break;
00372 case ANNUAL_DATE:
00373 {
00374 QValueList<int> months = rrule->byMonths();
00375 QValueList<int> days = monthDays();
00376 bool special = (mFeb29Type != FEB29_FEB29 && !days.isEmpty()
00377 && days.first() == 29 && months.remove(2));
00378 RecurrenceRule* rrule1 = recur.defaultRRule();
00379 rrule1->setByMonths(months);
00380 rrule1->setByMonthDays(days);
00381 if (!special)
00382 break;
00383
00384
00385
00386 RecurrenceRule* rrule2 = new RecurrenceRule();
00387 rrule2->setRecurrenceType(RecurrenceRule::rYearly);
00388 rrule2->setFrequency(freq);
00389 rrule2->setStartDt(startDateTime());
00390 rrule2->setFloats(doesFloat());
00391 if (!count)
00392 rrule2->setEndDt(endDateTime());
00393 if (mFeb29Type == FEB29_MAR1)
00394 {
00395 QValueList<int> ds;
00396 ds.append(60);
00397 rrule2->setByYearDays(ds);
00398 }
00399 else
00400 {
00401 QValueList<int> ds;
00402 ds.append(-1);
00403 rrule2->setByMonthDays(ds);
00404 QValueList<int> ms;
00405 ms.append(2);
00406 rrule2->setByMonths(ms);
00407 }
00408
00409 if (months.isEmpty())
00410 {
00411
00412
00413 if (count)
00414 rrule2->setDuration(count);
00415 recur.unsetRecurs();
00416 }
00417 else
00418 {
00419
00420
00421 if (count)
00422 {
00423 rrule1->setDuration(-1);
00424 rrule2->setDuration(-1);
00425 if (count > 0)
00426 {
00427
00428
00429
00430
00431
00432
00433
00434
00435
00436 QDateTime end = endDateTime();
00437 kdDebug()<<"29th recurrence: count="<<count<<", end date="<<end.toString()<<endl;
00438 int count1 = rrule1->durationTo(end)
00439 - (rrule1->recursOn(startDate()) ? 0 : 1);
00440 if (count1 > 0)
00441 rrule1->setDuration(count1);
00442 else
00443 rrule1->setEndDt(startDateTime());
00444 int count2 = rrule2->durationTo(end)
00445 - (rrule2->recursOn(startDate()) ? 0 : 1);
00446 if (count2 > 0)
00447 rrule2->setDuration(count2);
00448 else
00449 rrule2->setEndDt(startDateTime());
00450 }
00451 }
00452 }
00453 recur.addRRule(rrule2);
00454 break;
00455 }
00456 default:
00457 break;
00458 }
00459 }
00460
00461
00462
00463
00464 QDateTime KARecurrence::endDateTime() const
00465 {
00466 if (mFeb29Type == FEB29_FEB29 || duration() <= 1)
00467 {
00468
00469
00470
00471
00472
00473 return Recurrence::endDateTime();
00474 }
00475
00476
00477
00478
00479
00480
00481 RecurrenceRule* rrule = new RecurrenceRule();
00482 rrule->setRecurrenceType(RecurrenceRule::rYearly);
00483 QDateTime dt = startDateTime();
00484 QDate d = dt.date();
00485 switch (d.day())
00486 {
00487 case 29:
00488
00489
00490 d.setYMD(d.year(), d.month(), 28);
00491 break;
00492 case 28:
00493 if (d.month() != 2 || mFeb29Type != FEB29_FEB28 || QDate::leapYear(d.year()))
00494 {
00495
00496 d.setYMD(d.year(), d.month(), 27);
00497 }
00498 break;
00499 case 1:
00500 if (d.month() == 3 && mFeb29Type == FEB29_MAR1 && !QDate::leapYear(d.year()))
00501 {
00502
00503
00504 d.setYMD(d.year(), 2, 28);
00505 }
00506 break;
00507 default:
00508 break;
00509 }
00510 dt.setDate(d);
00511 rrule->setStartDt(dt);
00512 rrule->setFloats(doesFloat());
00513 rrule->setFrequency(frequency());
00514 rrule->setDuration(duration());
00515 QValueList<int> ds;
00516 ds.append(28);
00517 rrule->setByMonthDays(ds);
00518 rrule->setByMonths(defaultRRuleConst()->byMonths());
00519 dt = rrule->endDt();
00520 delete rrule;
00521
00522
00523
00524 if (mFeb29Type == FEB29_FEB28 && dt.date().month() == 2 && !QDate::leapYear(dt.date().year()))
00525 return dt;
00526 return dt.addDays(1);
00527 }
00528
00529
00530
00531
00532 QDate KARecurrence::endDate() const
00533 {
00534 QDateTime end = endDateTime();
00535 return end.isValid() ? end.date() : QDate();
00536 }
00537
00538
00539
00540
00541
00542 bool KARecurrence::recursOn(const QDate& dt) const
00543 {
00544 if (!Recurrence::recursOn(dt))
00545 return false;
00546 if (dt != startDate())
00547 return true;
00548
00549
00550 if (rDates().contains(dt))
00551 return true;
00552 RecurrenceRule::List rulelist = rRules();
00553 for (RecurrenceRule::List::ConstIterator rr = rulelist.begin(); rr != rulelist.end(); ++rr)
00554 if ((*rr)->recursOn(dt))
00555 return true;
00556 DateTimeList dtlist = rDateTimes();
00557 for (DateTimeList::ConstIterator rdt = dtlist.begin(); rdt != dtlist.end(); ++rdt)
00558 if ((*rdt).date() == dt)
00559 return true;
00560 return false;
00561 }
00562
00563
00564
00565
00566
00567 int KARecurrence::combineDurations(const RecurrenceRule* rrule1, const RecurrenceRule* rrule2, QDate& end) const
00568 {
00569 int count1 = rrule1->duration();
00570 int count2 = rrule2->duration();
00571 if (count1 == -1 && count2 == -1)
00572 return -1;
00573
00574
00575
00576 if (count1 && !count2 && rrule2->endDt().date() == startDateTime().date())
00577 return count1;
00578 if (count2 && !count1 && rrule1->endDt().date() == startDateTime().date())
00579 return count2;
00580
00581
00582
00583
00584
00585 if (!count1 || !count2)
00586 count1 = count2 = 0;
00587
00588 QDateTime end1 = rrule1->endDt();
00589 QDateTime end2 = rrule2->endDt();
00590 if (end1.date() == end2.date())
00591 {
00592 end = end1.date();
00593 return count1 + count2;
00594 }
00595 const RecurrenceRule* rr1;
00596 const RecurrenceRule* rr2;
00597 if (end2.isValid()
00598 && (!end1.isValid() || end1.date() > end2.date()))
00599 {
00600
00601 rr1 = rrule2;
00602 rr2 = rrule1;
00603 QDateTime e = end1;
00604 end1 = end2;
00605 end2 = e;
00606 }
00607 else
00608 {
00609 rr1 = rrule1;
00610 rr2 = rrule2;
00611 }
00612
00613
00614 RecurrenceRule rr(*rr1);
00615 rr.setDuration(-1);
00616 QDateTime next1 = rr.getNextDate(end1).date();
00617 if (!next1.isValid())
00618 end = end1.date();
00619 else
00620 {
00621 if (end2.isValid() && next1 > end2)
00622 {
00623
00624
00625
00626 end = end2.date();
00627 return count1 + count2;
00628 }
00629 QDate prev2 = rr2->getPreviousDate(next1).date();
00630 end = (prev2 > end1.date()) ? prev2 : end1.date();
00631 }
00632 if (count2)
00633 count2 = rr2->durationTo(end);
00634 return count1 + count2;
00635 }
00636
00637
00638
00639
00640
00641 int KARecurrence::longestInterval() const
00642 {
00643 int freq = frequency();
00644 switch (type())
00645 {
00646 case MINUTELY:
00647 return freq;
00648
00649 case DAILY:
00650 {
00651 QValueList<RecurrenceRule::WDayPos> days = defaultRRuleConst()->byDays();
00652 if (days.isEmpty())
00653 return freq * 1440;
00654
00655
00656
00657 bool ds[7] = { false, false, false, false, false, false, false };
00658 for (QValueList<RecurrenceRule::WDayPos>::ConstIterator it = days.begin(); it != days.end(); ++it)
00659 if ((*it).pos() == 0)
00660 ds[(*it).day() - 1] = true;
00661 if (freq % 7)
00662 {
00663
00664
00665 int first = -1;
00666 int last = -1;
00667 int maxgap = 1;
00668 for (int i = 0; i < freq*7; i += freq)
00669 {
00670 if (ds[i % 7])
00671 {
00672 if (first < 0)
00673 first = i;
00674 else if (i - last > maxgap)
00675 maxgap = i - last;
00676 last = i;
00677 }
00678 }
00679 int wrap = freq*7 - last + first;
00680 if (wrap > maxgap)
00681 maxgap = wrap;
00682 return maxgap * 1440;
00683 }
00684 else
00685 {
00686
00687
00688 return ds[startDate().dayOfWeek() - 1] ? freq * 1440 : 0;
00689 }
00690 }
00691 case WEEKLY:
00692 {
00693
00694
00695 QBitArray ds = days();
00696 int first = -1;
00697 int last = -1;
00698 int maxgap = 1;
00699 for (int i = 0; i < 7; ++i)
00700 {
00701 if (ds.testBit(KAlarm::localeDayInWeek_to_weekDay(i) - 1))
00702 {
00703 if (first < 0)
00704 first = i;
00705 else if (i - last > maxgap)
00706 maxgap = i - last;
00707 last = i;
00708 }
00709 }
00710 if (first < 0)
00711 break;
00712 int span = last - first;
00713 if (freq > 1)
00714 return (freq*7 - span) * 1440;
00715 if (7 - span > maxgap)
00716 return (7 - span) * 1440;
00717 return maxgap * 1440;
00718 }
00719 case MONTHLY_DAY:
00720 case MONTHLY_POS:
00721 return freq * 1440 * 31;
00722
00723 case ANNUAL_DATE:
00724 case ANNUAL_POS:
00725 {
00726
00727
00728 const QValueList<int> months = yearMonths();
00729 if (months.isEmpty())
00730 break;
00731 if (months.count() > 1)
00732 {
00733 int first = -1;
00734 int last = -1;
00735 int maxgap = 0;
00736 for (QValueListConstIterator<int> it = months.begin(); it != months.end(); ++it)
00737 {
00738 if (first < 0)
00739 first = *it;
00740 else
00741 {
00742 int span = QDate(2001, last, 1).daysTo(QDate(2001, *it, 1));
00743 if (span > maxgap)
00744 maxgap = span;
00745 }
00746 last = *it;
00747 }
00748 int span = QDate(2001, first, 1).daysTo(QDate(2001, last, 1));
00749 if (freq > 1)
00750 return (freq*365 - span) * 1440;
00751 if (365 - span > maxgap)
00752 return (365 - span) * 1440;
00753 return maxgap * 1440;
00754 }
00755
00756 }
00757 default:
00758 break;
00759 }
00760 return 0;
00761 }
00762
00763
00764
00765
00766 KARecurrence::Type KARecurrence::type() const
00767 {
00768 if (mCachedType == -1)
00769 mCachedType = type(defaultRRuleConst());
00770 return static_cast<Type>(mCachedType);
00771 }
00772
00773 KARecurrence::Type KARecurrence::type(const RecurrenceRule* rrule)
00774 {
00775 switch (recurrenceType(rrule))
00776 {
00777 case rMinutely: return MINUTELY;
00778 case rDaily: return DAILY;
00779 case rWeekly: return WEEKLY;
00780 case rMonthlyDay: return MONTHLY_DAY;
00781 case rMonthlyPos: return MONTHLY_POS;
00782 case rYearlyMonth: return ANNUAL_DATE;
00783 case rYearlyPos: return ANNUAL_POS;
00784 default:
00785 if (dailyType(rrule))
00786 return DAILY;
00787 return NO_RECUR;
00788 }
00789 }
00790
00791
00792
00793
00794 bool KARecurrence::dailyType(const RecurrenceRule* rrule)
00795 {
00796 if (rrule->recurrenceType() != RecurrenceRule::rDaily
00797 || !rrule->bySeconds().isEmpty()
00798 || !rrule->byMinutes().isEmpty()
00799 || !rrule->byHours().isEmpty()
00800 || !rrule->byWeekNumbers().isEmpty()
00801 || !rrule->byMonthDays().isEmpty()
00802 || !rrule->byMonths().isEmpty()
00803 || !rrule->bySetPos().isEmpty()
00804 || !rrule->byYearDays().isEmpty())
00805 return false;
00806 QValueList<RecurrenceRule::WDayPos> days = rrule->byDays();
00807 if (days.isEmpty())
00808 return true;
00809
00810 bool found = false;
00811 for (QValueList<RecurrenceRule::WDayPos>::ConstIterator it = days.begin(); it != days.end(); ++it)
00812 {
00813 if ((*it).pos() != 0)
00814 return false;
00815 found = true;
00816 }
00817 return found;
00818
00819 }