~ubuntu-branches/ubuntu/oneiric/kdepim/oneiric-updates

« back to all changes in this revision

Viewing changes to kalarm/cal/karecurrence.cpp

  • Committer: Package Import Robot
  • Author(s): Philip Muškovac
  • Date: 2011-06-28 19:33:24 UTC
  • mfrom: (0.2.13) (0.1.13 sid)
  • Revision ID: package-import@ubuntu.com-20110628193324-8yvjs8sdv9rdoo6c
Tags: 4:4.7.0-0ubuntu1
* New upstream release
  - update install files
  - add missing kdepim-doc package to control file
  - Fix Vcs lines
  - kontact breaks/replaces korganizer << 4:4.6.80
  - tighten the dependency of kdepim-dev on libkdepim4 to fix lintian error

Show diffs side-by-side

added added

removed removed

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