kalarm

karecurrence.cpp
1/*
2 * karecurrence.cpp - recurrence with special yearly February 29th handling
3 * Program: kalarm
4 * Copyright © 2005,2006,2008 by David Jarvie <djarvie@kde.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 */
20
21#include "kalarm.h"
22
23#include <tqbitarray.h>
24#include <kdebug.h>
25
26#include <libkcal/icalformat.h>
27
28#include "datetime.h"
29#include "functions.h"
30#include "karecurrence.h"
31
32using namespace KCal;
33
34/*=============================================================================
35= Class KARecurrence
36= The purpose of this class is to represent the restricted range of recurrence
37= types which are handled by KAlarm, and to translate between these and the
38= libkcal Recurrence class. In particular, it handles yearly recurrences on
39= 29th February specially:
40=
41= KARecurrence allows annual 29th February recurrences to fall on 28th
42= February or 1st March, or not at all, in non-leap years. It allows such
43= 29th February recurrences to be combined with the 29th of other months in
44= a simple way, represented simply as the 29th of multiple months including
45= February. For storage in the libkcal calendar, the 29th day of the month
46= recurrence for other months is combined with a last-day-of-February or a
47= 60th-day-of-the-year recurrence rule, thereby conforming to RFC2445.
48=============================================================================*/
49
50
51KARecurrence::Feb29Type KARecurrence::mDefaultFeb29 = KARecurrence::FEB29_FEB29;
52
53
54/******************************************************************************
55* Set up a KARecurrence from recurrence parameters, using the start date to
56* determine the recurrence day/month as appropriate.
57* Only a restricted subset of recurrence types is allowed.
58* Reply = true if successful.
59*/
60bool KARecurrence::set(Type recurType, int freq, int count, int f29, const DateTime& start, const TQDateTime& end)
61{
62 mCachedType = -1;
64 switch (recurType)
65 {
66 case MINUTELY: rrtype = RecurrenceRule::rMinutely; break;
67 case DAILY: rrtype = RecurrenceRule::rDaily; break;
68 case WEEKLY: rrtype = RecurrenceRule::rWeekly; break;
69 case MONTHLY_DAY: rrtype = RecurrenceRule::rMonthly; break;
70 case ANNUAL_DATE: rrtype = RecurrenceRule::rYearly; break;
71 case NO_RECUR: rrtype = RecurrenceRule::rNone; break;
72 default:
73 return false;
74 }
75 if (!init(rrtype, freq, count, f29, start, end))
76 return false;
77 switch (recurType)
78 {
79 case WEEKLY:
80 {
81 TQBitArray days(7);
82 days.setBit(start.date().dayOfWeek() - 1);
83 addWeeklyDays(days);
84 break;
85 }
86 case MONTHLY_DAY:
87 addMonthlyDate(start.date().day());
88 break;
89 case ANNUAL_DATE:
90 addYearlyDate(start.date().day());
91 addYearlyMonth(start.date().month());
92 break;
93 default:
94 break;
95 }
96 return true;
97}
98
99/******************************************************************************
100* Initialise a KARecurrence from recurrence parameters.
101* Reply = true if successful.
102*/
103bool KARecurrence::init(RecurrenceRule::PeriodType recurType, int freq, int count, int f29, const DateTime& start,
104 const TQDateTime& end)
105{
106 mCachedType = -1;
107 Feb29Type feb29Type = (f29 == -1) ? mDefaultFeb29 : static_cast<Feb29Type>(f29);
108 mFeb29Type = FEB29_FEB29;
109 clear();
110 if (count < -1)
111 return false;
112 bool dateOnly = start.isDateOnly();
113 if (!count && ((!dateOnly && !end.isValid())
114 || (dateOnly && !end.date().isValid())))
115 return false;
116 switch (recurType)
117 {
118 case RecurrenceRule::rMinutely:
119 case RecurrenceRule::rDaily:
120 case RecurrenceRule::rWeekly:
121 case RecurrenceRule::rMonthly:
122 case RecurrenceRule::rYearly:
123 break;
124 case rNone:
125 return true;
126 default:
127 return false;
128 }
129 setNewRecurrenceType(recurType, freq);
130 if (count)
131 setDuration(count);
132 else if (dateOnly)
133 setEndDate(end.date());
134 else
135 setEndDateTime(end);
136 TQDateTime startdt = start.dateTime();
137 if ((recurType == RecurrenceRule::rYearly
138 && feb29Type == FEB29_FEB28) || feb29Type == FEB29_MAR1)
139 {
140 int year = startdt.date().year();
141 if (!TQDate::leapYear(year)
142 && startdt.date().dayOfYear() == (feb29Type == FEB29_MAR1 ? 60 : 59))
143 {
144 /* The event start date is February 28th or March 1st, but it
145 * is a recurrence on February 29th (recurring on February 28th
146 * or March 1st in non-leap years). Adjust the start date to
147 * be on February 29th in the last previous leap year.
148 * This is necessary because KARecurrence represents all types
149 * of 29th February recurrences by a simple 29th February.
150 */
151 while (!TQDate::leapYear(--year)) ;
152 startdt.setDate(TQDate(year, 2, 29));
153 }
154 mFeb29Type = feb29Type;
155 }
156 if (dateOnly)
157 setStartDate(startdt.date());
158 else
159 setStartDateTime(startdt);
160 return true;
161}
162
163/******************************************************************************
164 * Initialise the recurrence from an iCalendar RRULE string.
165 */
166bool KARecurrence::set(const TQString& icalRRULE)
167{
168 static TQString RRULE = TQString::fromLatin1("RRULE:");
169 mCachedType = -1;
170 clear();
171 if (icalRRULE.isEmpty())
172 return true;
173 ICalFormat format;
174 if (!format.fromString(defaultRRule(true),
175 (icalRRULE.startsWith(RRULE) ? icalRRULE.mid(RRULE.length()) : icalRRULE)))
176 return false;
177 fix();
178 return true;
179}
180
181/******************************************************************************
182* Must be called after presetting with a KCal::Recurrence, to convert the
183* recurrence to KARecurrence types:
184* - Convert hourly recurrences to minutely.
185* - Remove all but the first day in yearly date recurrences.
186* - Check for yearly recurrences falling on February 29th and adjust them as
187* necessary. A 29th of the month rule can be combined with either a 60th day
188* of the year rule or a last day of February rule.
189*/
190void KARecurrence::fix()
191{
192 mCachedType = -1;
193 mFeb29Type = FEB29_FEB29;
194 int convert = 0;
195 int days[2] = { 0, 0 };
196 RecurrenceRule* rrules[2];
197 RecurrenceRule::List rrulelist = rRules();
198 RecurrenceRule::List::ConstIterator rr = rrulelist.begin();
199 for (int i = 0; i < 2 && rr != rrulelist.end(); ++i, ++rr)
200 {
201 RecurrenceRule* rrule = *rr;
202 rrules[i] = rrule;
203 bool stop = true;
204 int rtype = recurrenceType(rrule);
205 switch (rtype)
206 {
207 case rHourly:
208 // Convert an hourly recurrence to a minutely one
209 rrule->setRecurrenceType(RecurrenceRule::rMinutely);
210 rrule->setFrequency(rrule->frequency() * 60);
211 // fall through to rMinutely
212 case rMinutely:
213 case rDaily:
214 case rWeekly:
215 case rMonthlyDay:
216 case rMonthlyPos:
217 case rYearlyPos:
218 if (!convert)
219 ++rr; // remove all rules except the first
220 break;
221 case rOther:
222 if (dailyType(rrule))
223 { // it's a daily rule with BYDAYS
224 if (!convert)
225 ++rr; // remove all rules except the first
226 }
227 break;
228 case rYearlyDay:
229 {
230 // Ensure that the yearly day number is 60 (i.e. Feb 29th/Mar 1st)
231 if (convert)
232 {
233 // This is the second rule.
234 // Ensure that it can be combined with the first one.
235 if (days[0] != 29
236 || rrule->frequency() != rrules[0]->frequency()
237 || rrule->startDt() != rrules[0]->startDt())
238 break;
239 }
240 TQValueList<int> ds = rrule->byYearDays();
241 if (!ds.isEmpty() && ds.first() == 60)
242 {
243 ++convert; // this rule needs to be converted
244 days[i] = 60;
245 stop = false;
246 break;
247 }
248 break; // not day 60, so remove this rule
249 }
250 case rYearlyMonth:
251 {
252 TQValueList<int> ds = rrule->byMonthDays();
253 if (!ds.isEmpty())
254 {
255 int day = ds.first();
256 if (convert)
257 {
258 // This is the second rule.
259 // Ensure that it can be combined with the first one.
260 if (day == days[0] || (day == -1 && days[0] == 60)
261 || rrule->frequency() != rrules[0]->frequency()
262 || rrule->startDt() != rrules[0]->startDt())
263 break;
264 }
265 if (ds.count() > 1)
266 {
267 ds.clear(); // remove all but the first day
268 ds.append(day);
269 rrule->setByMonthDays(ds);
270 }
271 if (day == -1)
272 {
273 // Last day of the month - only combine if it's February
274 TQValueList<int> months = rrule->byMonths();
275 if (months.count() != 1 || months.first() != 2)
276 day = 0;
277 }
278 if (day == 29 || day == -1)
279 {
280 ++convert; // this rule may need to be converted
281 days[i] = day;
282 stop = false;
283 break;
284 }
285 }
286 if (!convert)
287 ++rr;
288 break;
289 }
290 default:
291 break;
292 }
293 if (stop)
294 break;
295 }
296
297 // Remove surplus rules
298 for ( ; rr != rrulelist.end(); ++rr)
299 {
300 removeRRule(*rr);
301 delete *rr;
302 }
303
304 TQDate end;
305 int count;
306 TQValueList<int> months;
307 if (convert == 2)
308 {
309 // There are two yearly recurrence rules to combine into a February 29th recurrence.
310 // Combine the two recurrence rules into a single rYearlyMonth rule falling on Feb 29th.
311 // Find the duration of the two RRULEs combined, using the shorter of the two if they differ.
312 if (days[0] != 29)
313 {
314 // Swap the two rules so that the 29th rule is the first
315 RecurrenceRule* rr = rrules[0];
316 rrules[0] = rrules[1]; // the 29th rule
317 rrules[1] = rr;
318 int d = days[0];
319 days[0] = days[1];
320 days[1] = d; // the non-29th day
321 }
322 // If February is included in the 29th rule, remove it to avoid duplication
323 months = rrules[0]->byMonths();
324 if (months.remove(2))
325 rrules[0]->setByMonths(months);
326
327 count = combineDurations(rrules[0], rrules[1], end);
328 mFeb29Type = (days[1] == 60) ? FEB29_MAR1 : FEB29_FEB28;
329 }
330 else if (convert == 1 && days[0] == 60)
331 {
332 // There is a single 60th day of the year rule.
333 // Convert it to a February 29th recurrence.
334 count = duration();
335 if (!count)
336 end = endDate();
337 mFeb29Type = FEB29_MAR1;
338 }
339 else
340 return;
341
342 // Create the new February 29th recurrence
343 setNewRecurrenceType(RecurrenceRule::rYearly, frequency());
344 RecurrenceRule* rrule = defaultRRule();
345 months.append(2);
346 rrule->setByMonths(months);
347 TQValueList<int> ds;
348 ds.append(29);
349 rrule->setByMonthDays(ds);
350 if (count)
351 setDuration(count);
352 else
353 setEndDate(end);
354}
355
356/******************************************************************************
357* Get the next time the recurrence occurs, strictly after a specified time.
358*/
359TQDateTime KARecurrence::getNextDateTime(const TQDateTime& preDateTime) const
360{
361 switch (type())
362 {
363 case ANNUAL_DATE:
364 case ANNUAL_POS:
365 {
366 Recurrence recur;
367 writeRecurrence(recur);
368 return recur.getNextDateTime(preDateTime);
369 }
370 default:
371 return Recurrence::getNextDateTime(preDateTime);
372 }
373}
374
375/******************************************************************************
376* Get the previous time the recurrence occurred, strictly before a specified time.
377*/
378TQDateTime KARecurrence::getPreviousDateTime(const TQDateTime& afterDateTime) const
379{
380 switch (type())
381 {
382 case ANNUAL_DATE:
383 case ANNUAL_POS:
384 {
385 Recurrence recur;
386 writeRecurrence(recur);
387 return recur.getPreviousDateTime(afterDateTime);
388 }
389 default:
390 return Recurrence::getPreviousDateTime(afterDateTime);
391 }
392}
393
394/******************************************************************************
395* Initialise a KCal::Recurrence to be the same as this instance.
396* Additional recurrence rules are created as necessary if it recurs on Feb 29th.
397*/
398void KARecurrence::writeRecurrence(KCal::Recurrence& recur) const
399{
400 recur.clear();
401 recur.setStartDateTime(startDateTime());
402 recur.setExDates(exDates());
403 recur.setExDateTimes(exDateTimes());
404 const RecurrenceRule* rrule = defaultRRuleConst();
405 if (!rrule)
406 return;
407 int freq = frequency();
408 int count = duration();
409 static_cast<KARecurrence*>(&recur)->setNewRecurrenceType(rrule->recurrenceType(), freq);
410 if (count)
411 recur.setDuration(count);
412 else
413 recur.setEndDateTime(endDateTime());
414 switch (type())
415 {
416 case DAILY:
417 if (rrule->byDays().isEmpty())
418 break;
419 // fall through to rWeekly
420 case WEEKLY:
421 case MONTHLY_POS:
422 recur.defaultRRule(true)->setByDays(rrule->byDays());
423 break;
424 case MONTHLY_DAY:
425 recur.defaultRRule(true)->setByMonthDays(rrule->byMonthDays());
426 break;
427 case ANNUAL_POS:
428 recur.defaultRRule(true)->setByMonths(rrule->byMonths());
429 recur.defaultRRule()->setByDays(rrule->byDays());
430 break;
431 case ANNUAL_DATE:
432 {
433 TQValueList<int> months = rrule->byMonths();
434 TQValueList<int> days = monthDays();
435 bool special = (mFeb29Type != FEB29_FEB29 && !days.isEmpty()
436 && days.first() == 29 && months.remove(2));
437 RecurrenceRule* rrule1 = recur.defaultRRule();
438 rrule1->setByMonths(months);
439 rrule1->setByMonthDays(days);
440 if (!special)
441 break;
442
443 // It recurs on the 29th February.
444 // Create an additional 60th day of the year, or last day of February, rule.
445 RecurrenceRule* rrule2 = new RecurrenceRule();
446 rrule2->setRecurrenceType(RecurrenceRule::rYearly);
447 rrule2->setFrequency(freq);
448 rrule2->setStartDt(startDateTime());
449 rrule2->setFloats(doesFloat());
450 if (!count)
451 rrule2->setEndDt(endDateTime());
452 if (mFeb29Type == FEB29_MAR1)
453 {
454 TQValueList<int> ds;
455 ds.append(60);
456 rrule2->setByYearDays(ds);
457 }
458 else
459 {
460 TQValueList<int> ds;
461 ds.append(-1);
462 rrule2->setByMonthDays(ds);
463 TQValueList<int> ms;
464 ms.append(2);
465 rrule2->setByMonths(ms);
466 }
467
468 if (months.isEmpty())
469 {
470 // Only February recurs.
471 // Replace the RRULE and keep the recurrence count the same.
472 if (count)
473 rrule2->setDuration(count);
474 recur.unsetRecurs();
475 }
476 else
477 {
478 // Months other than February also recur on the 29th.
479 // Remove February from the list and add a separate RRULE for February.
480 if (count)
481 {
482 rrule1->setDuration(-1);
483 rrule2->setDuration(-1);
484 if (count > 0)
485 {
486 /* Adjust counts in the two rules to keep the correct occurrence total.
487 * Note that durationTo() always includes the start date. Since for an
488 * individual RRULE the start date may not actually be included, we need
489 * to decrement the count if the start date doesn't actually recur in
490 * this RRULE.
491 * Note that if the count is small, one of the rules may not recur at
492 * all. In that case, retain it so that the February 29th characteristic
493 * is not lost should the user later change the recurrence count.
494 */
495 TQDateTime end = endDateTime();
496kdDebug()<<"29th recurrence: count="<<count<<", end date="<<end.toString()<<endl;
497 int count1 = rrule1->durationTo(end)
498 - (rrule1->recursOn(startDate()) ? 0 : 1);
499 if (count1 > 0)
500 rrule1->setDuration(count1);
501 else
502 rrule1->setEndDt(startDateTime());
503 int count2 = rrule2->durationTo(end)
504 - (rrule2->recursOn(startDate()) ? 0 : 1);
505 if (count2 > 0)
506 rrule2->setDuration(count2);
507 else
508 rrule2->setEndDt(startDateTime());
509 }
510 }
511 }
512 recur.addRRule(rrule2);
513 break;
514 }
515 default:
516 break;
517 }
518}
519
520/******************************************************************************
521* Return the date/time of the last recurrence.
522*/
523TQDateTime KARecurrence::endDateTime() const
524{
525 if (mFeb29Type == FEB29_FEB29 || duration() <= 1)
526 {
527 /* Either it doesn't have any special February 29th treatment,
528 * it's infinite (count = -1), the end date is specified
529 * (count = 0), or it ends on the start date (count = 1).
530 * So just use the normal KCal end date calculation.
531 */
532 return Recurrence::endDateTime();
533 }
534
535 /* Create a temporary recurrence rule to find the end date.
536 * In a standard KCal recurrence, the 29th February only occurs once every
537 * 4 years. So shift the temporary recurrence date to the 28th to ensure
538 * that it occurs every year, thus giving the correct occurrence count.
539 */
540 RecurrenceRule* rrule = new RecurrenceRule();
541 rrule->setRecurrenceType(RecurrenceRule::rYearly);
542 TQDateTime dt = startDateTime();
543 TQDate d = dt.date();
544 switch (d.day())
545 {
546 case 29:
547 // The start date is definitely a recurrence date, so shift
548 // start date to the temporary recurrence date of the 28th
549 d.setYMD(d.year(), d.month(), 28);
550 break;
551 case 28:
552 if (d.month() != 2 || mFeb29Type != FEB29_FEB28 || TQDate::leapYear(d.year()))
553 {
554 // Start date is not a recurrence date, so shift it to 27th
555 d.setYMD(d.year(), d.month(), 27);
556 }
557 break;
558 case 1:
559 if (d.month() == 3 && mFeb29Type == FEB29_MAR1 && !TQDate::leapYear(d.year()))
560 {
561 // Start date is a March 1st recurrence date, so shift
562 // start date to the temporary recurrence date of the 28th
563 d.setYMD(d.year(), 2, 28);
564 }
565 break;
566 default:
567 break;
568 }
569 dt.setDate(d);
570 rrule->setStartDt(dt);
571 rrule->setFloats(doesFloat());
572 rrule->setFrequency(frequency());
573 rrule->setDuration(duration());
574 TQValueList<int> ds;
575 ds.append(28);
576 rrule->setByMonthDays(ds);
577 rrule->setByMonths(defaultRRuleConst()->byMonths());
578 dt = rrule->endDt();
579 delete rrule;
580
581 // We've found the end date for a recurrence on the 28th. Unless that date
582 // is a real February 28th recurrence, adjust to the actual recurrence date.
583 if (mFeb29Type == FEB29_FEB28 && dt.date().month() == 2 && !TQDate::leapYear(dt.date().year()))
584 return dt;
585 return dt.addDays(1);
586}
587
588/******************************************************************************
589* Return the date/time of the last recurrence.
590*/
591TQDate KARecurrence::endDate() const
592{
593 TQDateTime end = endDateTime();
594 return end.isValid() ? end.date() : TQDate();
595}
596
597/******************************************************************************
598* Return whether the event will recur on the specified date.
599* The start date only returns true if it matches the recurrence rules.
600*/
601bool KARecurrence::recursOn(const TQDate& dt) const
602{
603 if (!Recurrence::recursOn(dt))
604 return false;
605 if (dt != startDate())
606 return true;
607 // We know now that it isn't in EXDATES or EXRULES,
608 // so we just need to check if it's in RDATES or RRULES
609 if (rDates().contains(dt))
610 return true;
611 RecurrenceRule::List rulelist = rRules();
612 for (RecurrenceRule::List::ConstIterator rr = rulelist.begin(); rr != rulelist.end(); ++rr)
613 if ((*rr)->recursOn(dt))
614 return true;
615 DateTimeList dtlist = rDateTimes();
616 for (DateTimeList::ConstIterator rdt = dtlist.begin(); rdt != dtlist.end(); ++rdt)
617 if ((*rdt).date() == dt)
618 return true;
619 return false;
620}
621
622/******************************************************************************
623* Find the duration of two RRULEs combined.
624* Use the shorter of the two if they differ.
625*/
626int KARecurrence::combineDurations(const RecurrenceRule* rrule1, const RecurrenceRule* rrule2, TQDate& end) const
627{
628 int count1 = rrule1->duration();
629 int count2 = rrule2->duration();
630 if (count1 == -1 && count2 == -1)
631 return -1;
632
633 // One of the RRULEs may not recur at all if the recurrence count is small.
634 // In this case, its end date will have been set to the start date.
635 if (count1 && !count2 && rrule2->endDt().date() == startDateTime().date())
636 return count1;
637 if (count2 && !count1 && rrule1->endDt().date() == startDateTime().date())
638 return count2;
639
640 /* The duration counts will be different even for RRULEs of the same length,
641 * because the first RRULE only actually occurs every 4 years. So we need to
642 * compare the end dates.
643 */
644 if (!count1 || !count2)
645 count1 = count2 = 0;
646 // Get the two rules sorted by end date.
647 TQDateTime end1 = rrule1->endDt();
648 TQDateTime end2 = rrule2->endDt();
649 if (end1.date() == end2.date())
650 {
651 end = end1.date();
652 return count1 + count2;
653 }
654 const RecurrenceRule* rr1; // earlier end date
655 const RecurrenceRule* rr2; // later end date
656 if (end2.isValid()
657 && (!end1.isValid() || end1.date() > end2.date()))
658 {
659 // Swap the two rules to make rr1 have the earlier end date
660 rr1 = rrule2;
661 rr2 = rrule1;
662 TQDateTime e = end1;
663 end1 = end2;
664 end2 = e;
665 }
666 else
667 {
668 rr1 = rrule1;
669 rr2 = rrule2;
670 }
671
672 // Get the date of the next occurrence after the end of the earlier ending rule
673 RecurrenceRule rr(*rr1);
674 rr.setDuration(-1);
675 TQDateTime next1(rr.getNextDate(end1).date());
676 if (!next1.isValid())
677 end = end1.date();
678 else
679 {
680 if (end2.isValid() && next1 > end2)
681 {
682 // The next occurrence after the end of the earlier ending rule
683 // is later than the end of the later ending rule. So simply use
684 // the end date of the later rule.
685 end = end2.date();
686 return count1 + count2;
687 }
688 TQDate prev2 = rr2->getPreviousDate(next1).date();
689 end = (prev2 > end1.date()) ? prev2 : end1.date();
690 }
691 if (count2)
692 count2 = rr2->durationTo(end);
693 return count1 + count2;
694}
695
696/******************************************************************************
697 * Return the longest interval (in minutes) between recurrences.
698 * Reply = 0 if it never recurs.
699 */
700int KARecurrence::longestInterval() const
701{
702 int freq = frequency();
703 switch (type())
704 {
705 case MINUTELY:
706 return freq;
707
708 case DAILY:
709 {
710 TQValueList<RecurrenceRule::WDayPos> days = defaultRRuleConst()->byDays();
711 if (days.isEmpty())
712 return freq * 1440;
713
714 // It recurs only on certain days of the week, so the maximum interval
715 // may be greater than the frequency.
716 bool ds[7] = { false, false, false, false, false, false, false };
717 for (TQValueList<RecurrenceRule::WDayPos>::ConstIterator it = days.begin(); it != days.end(); ++it)
718 if ((*it).pos() == 0)
719 ds[(*it).day() - 1] = true;
720 if (freq % 7)
721 {
722 // It will recur on every day of the week in some week or other
723 // (except for those days which are excluded).
724 int first = -1;
725 int last = -1;
726 int maxgap = 1;
727 for (int i = 0; i < freq*7; i += freq)
728 {
729 if (ds[i % 7])
730 {
731 if (first < 0)
732 first = i;
733 else if (i - last > maxgap)
734 maxgap = i - last;
735 last = i;
736 }
737 }
738 int wrap = freq*7 - last + first;
739 if (wrap > maxgap)
740 maxgap = wrap;
741 return maxgap * 1440;
742 }
743 else
744 {
745 // It will recur on the same day of the week every time.
746 // Ensure that the day is a day which is not excluded.
747 return ds[startDate().dayOfWeek() - 1] ? freq * 1440 : 0;
748 }
749 }
750 case WEEKLY:
751 {
752 // Find which days of the week it recurs on, and if on more than
753 // one, reduce the maximum interval accordingly.
754 TQBitArray ds = days();
755 int first = -1;
756 int last = -1;
757 int maxgap = 1;
758 for (int i = 0; i < 7; ++i)
759 {
760 if (ds.testBit(KAlarm::localeDayInWeek_to_weekDay(i) - 1))
761 {
762 if (first < 0)
763 first = i;
764 else if (i - last > maxgap)
765 maxgap = i - last;
766 last = i;
767 }
768 }
769 if (first < 0)
770 break; // no days recur
771 int span = last - first;
772 if (freq > 1)
773 return (freq*7 - span) * 1440;
774 if (7 - span > maxgap)
775 return (7 - span) * 1440;
776 return maxgap * 1440;
777 }
778 case MONTHLY_DAY:
779 case MONTHLY_POS:
780 return freq * 1440 * 31;
781
782 case ANNUAL_DATE:
783 case ANNUAL_POS:
784 {
785 // Find which months of the year it recurs on, and if on more than
786 // one, reduce the maximum interval accordingly.
787 const TQValueList<int> months = yearMonths(); // month list is sorted
788 if (months.isEmpty())
789 break; // no months recur
790 if (months.count() == 1)
791 return freq * 1440 * 365;
792 int first = -1;
793 int last = -1;
794 int maxgap = 0;
795 for (TQValueListConstIterator<int> it = months.begin(); it != months.end(); ++it)
796 {
797 if (first < 0)
798 first = *it;
799 else
800 {
801 int span = TQDate(2001, last, 1).daysTo(TQDate(2001, *it, 1));
802 if (span > maxgap)
803 maxgap = span;
804 }
805 last = *it;
806 }
807 int span = TQDate(2001, first, 1).daysTo(TQDate(2001, last, 1));
808 if (freq > 1)
809 return (freq*365 - span) * 1440;
810 if (365 - span > maxgap)
811 return (365 - span) * 1440;
812 return maxgap * 1440;
813 }
814 default:
815 break;
816 }
817 return 0;
818}
819
820/******************************************************************************
821 * Return the recurrence's period type.
822 */
823KARecurrence::Type KARecurrence::type() const
824{
825 if (mCachedType == -1)
826 mCachedType = type(defaultRRuleConst());
827 return static_cast<Type>(mCachedType);
828}
829
830KARecurrence::Type KARecurrence::type(const RecurrenceRule* rrule)
831{
832 switch (recurrenceType(rrule))
833 {
834 case rMinutely: return MINUTELY;
835 case rDaily: return DAILY;
836 case rWeekly: return WEEKLY;
837 case rMonthlyDay: return MONTHLY_DAY;
838 case rMonthlyPos: return MONTHLY_POS;
839 case rYearlyMonth: return ANNUAL_DATE;
840 case rYearlyPos: return ANNUAL_POS;
841 default:
842 if (dailyType(rrule))
843 return DAILY;
844 return NO_RECUR;
845 }
846}
847
848/******************************************************************************
849 * Check if the rule is a daily rule with or without BYDAYS specified.
850 */
851bool KARecurrence::dailyType(const RecurrenceRule* rrule)
852{
853 if (rrule->recurrenceType() != RecurrenceRule::rDaily
854 || !rrule->bySeconds().isEmpty()
855 || !rrule->byMinutes().isEmpty()
856 || !rrule->byHours().isEmpty()
857 || !rrule->byWeekNumbers().isEmpty()
858 || !rrule->byMonthDays().isEmpty()
859 || !rrule->byMonths().isEmpty()
860 || !rrule->bySetPos().isEmpty()
861 || !rrule->byYearDays().isEmpty())
862 return false;
863 TQValueList<RecurrenceRule::WDayPos> days = rrule->byDays();
864 if (days.isEmpty())
865 return true;
866 // Check that all the positions are zero (i.e. every time)
867 bool found = false;
868 for (TQValueList<RecurrenceRule::WDayPos>::ConstIterator it = days.begin(); it != days.end(); ++it)
869 {
870 if ((*it).pos() != 0)
871 return false;
872 found = true;
873 }
874 return found;
875
876}
bool fromString(Calendar *calendar, const TQString &)
TQDateTime getPreviousDate(const TQDateTime &afterDateTime) const
TQDateTime endDt(bool *result=0) const
void setDuration(int duration)
int durationTo(const TQDateTime &) const
TQDateTime startDt() const
bool recursOn(const TQDate &qd) const
void setFrequency(int freq)
void setFloats(bool floats)
void setEndDt(const TQDateTime &endDateTime)
uint frequency() const
int duration() const
void setStartDt(const TQDateTime &start)
void setEndDateTime(const TQDateTime &endDateTime)
void setStartDateTime(const TQDateTime &start)
TQDateTime getPreviousDateTime(const TQDateTime &afterDateTime) const
void setDuration(int duration)
TQDateTime getNextDateTime(const TQDateTime &preDateTime) const
miscellaneous functions