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 
32 using 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 
51 KARecurrence::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 */
60 bool 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 */
103 bool 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  */
166 bool 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 */
190 void 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 */
359 TQDateTime 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 */
378 TQDateTime 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 */
398 void 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();
496 kdDebug()<<"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 */
523 TQDateTime 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 */
591 TQDate 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 */
601 bool 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 */
626 int 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  */
700 int 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  */
823 KARecurrence::Type KARecurrence::type() const
824 {
825  if (mCachedType == -1)
826  mCachedType = type(defaultRRuleConst());
827  return static_cast<Type>(mCachedType);
828 }
829 
830 KARecurrence::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  */
851 bool 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)
TQDateTime endDateTime() const
void setEndDateTime(const TQDateTime &endDateTime)
void setStartDateTime(const TQDateTime &start)
TQDateTime getPreviousDateTime(const TQDateTime &afterDateTime) const
bool recursOn(const TQDate &qd) const
void setDuration(int duration)
TQDateTime getNextDateTime(const TQDateTime &preDateTime) const
miscellaneous functions