Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / calendar / libecal / e-cal-recur.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Evolution calendar recurrence rule functions
4  *
5  * Copyright (C) 2000 Ximian, Inc.
6  *
7  * Author: Damon Chaplin <damon@ximian.com>
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of version 2 of the GNU Lesser General Public
11  * License as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <stdlib.h>
28 #include <string.h>
29 #include <glib.h>
30 #include <glib/gi18n-lib.h>
31 #include "e-cal-recur.h"
32 #include "e-cal-time-util.h"
33
34
35 /*
36  * Introduction to The Recurrence Generation Functions:
37  *
38  * Note: This is pretty complicated. See the iCalendar spec (RFC 2445) for
39  *       the specification of the recurrence rules and lots of examples
40  *       (sections 4.3.10 & 4.8.5). We also want to support the older
41  *       vCalendar spec, though this should be easy since it is basically a
42  *       subset of iCalendar.
43  *
44  * o An iCalendar event can have any number of recurrence rules specifying
45  *   occurrences of the event, as well as dates & times of specific
46  *   occurrences. It can also have any number of recurrence rules and
47  *   specific dates & times specifying exceptions to the occurrences.
48  *   So we first merge all the occurrences generated, eliminating any
49  *   duplicates, then we generate all the exceptions and remove these to
50  *   form the final set of occurrences.
51  *
52  * o There are 7 frequencies of occurrences: YEARLY, MONTHLY, WEEKLY, DAILY,
53  *   HOURLY, MINUTELY & SECONDLY. The 'interval' property specifies the
54  *   multiples of the frequency which we step by. We generate a 'set' of
55  *   occurrences for each period defined by the frequency & interval.
56  *   So for a YEARLY frequency with an interval of 3, we generate a set of
57  *   occurrences for every 3rd year. We use complete years here -  any
58  *   generated occurrences that occur before the event's start (or after its
59  *   end) are just discarded.
60  *
61  * o There are 8 frequency modifiers: BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY,
62  *   BYDAY, BYHOUR, BYMINUTE & BYSECOND. These can either add extra occurrences
63  *   or filter out occurrences. For example 'FREQ=YEARLY;BYMONTH=1,2' produces
64  *   2 occurrences for each year rather than the default 1. And
65  *   'FREQ=DAILY;BYMONTH=1' filters out all occurrences except those in Jan.
66  *   If the modifier works on periods which are less than the recurrence
67  *   frequency, then extra occurrences are added, otherwise occurrences are
68  *   filtered. So we have 2 functions for each modifier - one to expand events
69  *   and the other to filter. We use a table of functions for each frequency
70  *   which points to the appropriate function to use for each modifier.
71  *
72  * o Any number of frequency modifiers can be used in a recurrence rule.
73  *   (Though the iCalendar spec says that BYWEEKNO can only be used in a YEARLY
74  *   rule, and some modifiers aren't appropriate for some frequencies - e.g.
75  *   BYMONTHDAY is not really useful in a WEEKLY frequency, and BYYEARDAY is
76  *   not useful in a MONTHLY or WEEKLY frequency).
77  *   The frequency modifiers are applied in the order given above. The first 5
78  *   modifier rules (BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY & BYDAY) all
79  *   produce the days on which the occurrences take place, and so we have to
80  *   compute some of these in parallel rather than sequentially, or we may end
81  *   up with too many days.
82  *
83  * o Note that some expansion functions may produce days which are invalid,
84  *   e.g. 31st September, 30th Feb. These invalid days are removed before the
85  *   BYHOUR, BYMINUTE & BYSECOND modifier functions are applied.
86  *
87  * o After the set of occurrences for the frequency interval are generated,
88  *   the BYSETPOS property is used to select which of the occurrences are
89  *   finally output. If BYSETPOS is not specified then all the occurrences are
90  *   output.
91  *
92  *
93  * FIXME: I think there are a few errors in this code:
94  *
95  *  1) I'm not sure it should be generating events in parallel like it says
96  *     above. That needs to be checked.
97  *
98  *  2) I didn't think about timezone changes when implementing this. I just
99  *     assumed all the occurrences of the event would be in local time.
100  *     But when clocks go back or forwards due to daylight-saving time, some
101  *     special handling may be needed, especially for the shorter frequencies.
102  *     e.g. for a MINUTELY frequency it should probably iterate over all the
103  *     minutes before and after clocks go back (i.e. some may be the same local
104  *     time but have different UTC offsets). For longer frequencies, if an
105  *     occurrence lands on the overlapping or non-existant time when clocks
106  *     go back/forward, then it may need to choose which of the times to use
107  *     or move the time forward or something. I'm not sure this is clear in the
108  *     spec.
109  */
110
111 /* This is the maximum year we will go up to (inclusive). Since we use time_t
112    values we can't go past 2037 anyway, and some of our VTIMEZONEs may stop
113    at 2037 as well. */
114 #define MAX_YEAR        2037
115
116 /* Define this for some debugging output. */
117 #if 0
118 #define CAL_OBJ_DEBUG   1
119 #endif
120
121 /* We will use icalrecurrencetype instead of this eventually. */
122 typedef struct {
123         icalrecurrencetype_frequency freq;
124
125         int            interval;
126
127         /* Specifies the end of the recurrence, inclusive. No occurrences are
128            generated after this date. If it is 0, the event recurs forever. */
129         time_t         enddate;
130
131         /* WKST property - the week start day: 0 = Monday to 6 = Sunday. */
132         gint           week_start_day;
133
134
135         /* NOTE: I've used GList's here, but it doesn't matter if we use
136            other data structures like arrays. The code should be easy to
137            change. So long as it is easy to see if the modifier is set. */
138
139         /* For BYMONTH modifier. A list of GINT_TO_POINTERs, 0-11. */
140         GList         *bymonth;
141
142         /* For BYWEEKNO modifier. A list of GINT_TO_POINTERs, [+-]1-53. */
143         GList         *byweekno;
144
145         /* For BYYEARDAY modifier. A list of GINT_TO_POINTERs, [+-]1-366. */
146         GList         *byyearday;
147
148         /* For BYMONTHDAY modifier. A list of GINT_TO_POINTERs, [+-]1-31. */
149         GList         *bymonthday;
150
151         /* For BYDAY modifier. A list of GINT_TO_POINTERs, in pairs.
152            The first of each pair is the weekday, 0 = Monday to 6 = Sunday.
153            The second of each pair is the week number [+-]0-53. */
154         GList         *byday;
155
156         /* For BYHOUR modifier. A list of GINT_TO_POINTERs, 0-23. */
157         GList         *byhour;
158
159         /* For BYMINUTE modifier. A list of GINT_TO_POINTERs, 0-59. */
160         GList         *byminute;
161
162         /* For BYSECOND modifier. A list of GINT_TO_POINTERs, 0-60. */
163         GList         *bysecond;
164
165         /* For BYSETPOS modifier. A list of GINT_TO_POINTERs, +ve or -ve. */
166         GList         *bysetpos;
167 } ECalRecurrence;
168
169 /* This is what we use to pass to all the filter functions. */
170 typedef struct _RecurData RecurData;
171 struct _RecurData {
172         ECalRecurrence *recur;
173
174         /* This is used for the WEEKLY frequency. It is the offset from the
175            week_start_day. */
176         gint weekday_offset;
177
178         /* This is used for fast lookup in BYMONTH filtering. */
179         guint8 months[12];
180
181         /* This is used for fast lookup in BYYEARDAY filtering. */
182         guint8 yeardays[367], neg_yeardays[367]; /* Days are 1 - 366. */
183
184         /* This is used for fast lookup in BYMONTHDAY filtering. */
185         guint8 monthdays[32], neg_monthdays[32]; /* Days are 1 to 31. */
186
187         /* This is used for fast lookup in BYDAY filtering. */
188         guint8 weekdays[7];
189
190         /* This is used for fast lookup in BYHOUR filtering. */
191         guint8 hours[24];
192
193         /* This is used for fast lookup in BYMINUTE filtering. */
194         guint8 minutes[60];
195
196         /* This is used for fast lookup in BYSECOND filtering. */
197         guint8 seconds[62];
198 };
199
200 /* This is what we use to represent a date & time. */
201 typedef struct _CalObjTime CalObjTime;
202 struct _CalObjTime {
203         guint16 year;
204         guint8 month;           /* 0 - 11 */
205         guint8 day;             /* 1 - 31 */
206         guint8 hour;            /* 0 - 23 */
207         guint8 minute;          /* 0 - 59 */
208         guint8 second;          /* 0 - 59 (maybe up to 61 for leap seconds) */
209         guint8 flags;           /* The meaning of this depends on where the
210                                    CalObjTime is used. In most cases this is
211                                    set to TRUE to indicate that this is an
212                                    RDATE with an end or a duration set.
213                                    In the exceptions code, this is set to TRUE
214                                    to indicate that this is an EXDATE with a
215                                    DATE value. */
216 };
217
218 /* This is what we use to represent specific recurrence dates.
219    Note that we assume it starts with a CalObjTime when sorting. */
220 typedef struct _CalObjRecurrenceDate CalObjRecurrenceDate;
221 struct _CalObjRecurrenceDate {
222         CalObjTime start;
223         ECalComponentPeriod *period;
224 };
225
226 /* The paramter we use to store the enddate in RRULE and EXRULE properties. */
227 #define EVOLUTION_END_DATE_PARAMETER    "X-EVOLUTION-ENDDATE"
228
229 typedef gboolean (*CalObjFindStartFn) (CalObjTime *event_start,
230                                        CalObjTime *event_end,
231                                        RecurData  *recur_data,
232                                        CalObjTime *interval_start,
233                                        CalObjTime *interval_end,
234                                        CalObjTime *cotime);
235 typedef gboolean (*CalObjFindNextFn)  (CalObjTime *cotime,
236                                        CalObjTime *event_end,
237                                        RecurData  *recur_data,
238                                        CalObjTime *interval_end);
239 typedef GArray*  (*CalObjFilterFn)    (RecurData  *recur_data,
240                                        GArray     *occs);
241
242 typedef struct _ECalRecurVTable ECalRecurVTable;
243 struct _ECalRecurVTable {
244         CalObjFindStartFn find_start_position;
245         CalObjFindNextFn find_next_position;
246
247         CalObjFilterFn bymonth_filter;
248         CalObjFilterFn byweekno_filter;
249         CalObjFilterFn byyearday_filter;
250         CalObjFilterFn bymonthday_filter;
251         CalObjFilterFn byday_filter;
252         CalObjFilterFn byhour_filter;
253         CalObjFilterFn byminute_filter;
254         CalObjFilterFn bysecond_filter;
255 };
256
257
258 /* This is used to specify which parts of the CalObjTime to compare in
259    cal_obj_time_compare(). */
260 typedef enum {
261         CALOBJ_YEAR,
262         CALOBJ_MONTH,
263         CALOBJ_DAY,
264         CALOBJ_HOUR,
265         CALOBJ_MINUTE,
266         CALOBJ_SECOND
267 } CalObjTimeComparison;
268
269 static void e_cal_recur_generate_instances_of_rule (ECalComponent       *comp,
270                                                   icalproperty  *prop,
271                                                   time_t         start,
272                                                   time_t         end,
273                                                   ECalRecurInstanceFn cb,
274                                                   gpointer       cb_data,
275                                                   ECalRecurResolveTimezoneFn  tz_cb,
276                                                   gpointer       tz_cb_data,
277                                                   icaltimezone  *default_timezone);
278
279 static ECalRecurrence * e_cal_recur_from_icalproperty (icalproperty *prop,
280                                                     gboolean exception,
281                                                     icaltimezone *zone,
282                                                     gboolean convert_end_date);
283 static gint e_cal_recur_ical_weekday_to_weekday (enum icalrecurrencetype_weekday day);
284 static void     e_cal_recur_free                        (ECalRecurrence *r);
285
286
287 static gboolean cal_object_get_rdate_end        (CalObjTime     *occ,
288                                                  GArray         *rdate_periods);
289 static void     cal_object_compute_duration     (CalObjTime     *start,
290                                                  CalObjTime     *end,
291                                                  gint           *days,
292                                                  gint           *seconds);
293
294 static gboolean generate_instances_for_chunk    (ECalComponent          *comp,
295                                                  time_t                  comp_dtstart,
296                                                  icaltimezone           *zone,
297                                                  GSList                 *rrules,
298                                                  GSList                 *rdates,
299                                                  GSList                 *exrules,
300                                                  GSList                 *exdates,
301                                                  gboolean                single_rule,
302                                                  CalObjTime             *event_start,
303                                                  time_t                  interval_start,
304                                                  CalObjTime             *chunk_start,
305                                                  CalObjTime             *chunk_end,
306                                                  gint                    duration_days,
307                                                  gint                    duration_seconds,
308                                                  gboolean                convert_end_date,
309                                                  ECalRecurInstanceFn     cb,
310                                                  gpointer                cb_data);
311
312 static GArray* cal_obj_expand_recurrence        (CalObjTime       *event_start,
313                                                  icaltimezone     *zone,
314                                                  ECalRecurrence   *recur,
315                                                  CalObjTime       *interval_start,
316                                                  CalObjTime       *interval_end,
317                                                  gboolean         *finished);
318
319 static GArray*  cal_obj_generate_set_yearly     (RecurData      *recur_data,
320                                                  ECalRecurVTable *vtable,
321                                                  CalObjTime     *occ);
322 static GArray*  cal_obj_generate_set_monthly    (RecurData      *recur_data,
323                                                  ECalRecurVTable *vtable,
324                                                  CalObjTime     *occ);
325 static GArray*  cal_obj_generate_set_default    (RecurData      *recur_data,
326                                                  ECalRecurVTable *vtable,
327                                                  CalObjTime     *occ);
328
329
330 static ECalRecurVTable* cal_obj_get_vtable      (icalrecurrencetype_frequency recur_type);
331 static void     cal_obj_initialize_recur_data   (RecurData      *recur_data,
332                                                  ECalRecurrence *recur,
333                                                  CalObjTime     *event_start);
334 static void     cal_obj_sort_occurrences        (GArray         *occs);
335 static gint     cal_obj_time_compare_func       (const void     *arg1,
336                                                  const void     *arg2);
337 static void     cal_obj_remove_duplicates_and_invalid_dates (GArray     *occs);
338 static void     cal_obj_remove_exceptions       (GArray         *occs,
339                                                  GArray         *ex_occs);
340 static GArray*  cal_obj_bysetpos_filter         (ECalRecurrence *recur,
341                                                  GArray         *occs);
342
343
344 static gboolean cal_obj_yearly_find_start_position (CalObjTime *event_start,
345                                                     CalObjTime *event_end,
346                                                     RecurData  *recur_data,
347                                                     CalObjTime *interval_start,
348                                                     CalObjTime *interval_end,
349                                                     CalObjTime *cotime);
350 static gboolean cal_obj_yearly_find_next_position  (CalObjTime *cotime,
351                                                     CalObjTime *event_end,
352                                                     RecurData  *recur_data,
353                                                     CalObjTime *interval_end);
354
355 static gboolean cal_obj_monthly_find_start_position (CalObjTime *event_start,
356                                                      CalObjTime *event_end,
357                                                      RecurData  *recur_data,
358                                                      CalObjTime *interval_start,
359                                                      CalObjTime *interval_end,
360                                                      CalObjTime *cotime);
361 static gboolean cal_obj_monthly_find_next_position  (CalObjTime *cotime,
362                                                      CalObjTime *event_end,
363                                                      RecurData  *recur_data,
364                                                      CalObjTime *interval_end);
365
366 static gboolean cal_obj_weekly_find_start_position (CalObjTime *event_start,
367                                                     CalObjTime *event_end,
368                                                     RecurData  *recur_data,
369                                                     CalObjTime *interval_start,
370                                                     CalObjTime *interval_end,
371                                                     CalObjTime *cotime);
372 static gboolean cal_obj_weekly_find_next_position  (CalObjTime *cotime,
373                                                     CalObjTime *event_end,
374                                                     RecurData  *recur_data,
375                                                     CalObjTime *interval_end);
376
377 static gboolean cal_obj_daily_find_start_position  (CalObjTime *event_start,
378                                                     CalObjTime *event_end,
379                                                     RecurData  *recur_data,
380                                                     CalObjTime *interval_start,
381                                                     CalObjTime *interval_end,
382                                                     CalObjTime *cotime);
383 static gboolean cal_obj_daily_find_next_position   (CalObjTime *cotime,
384                                                     CalObjTime *event_end,
385                                                     RecurData  *recur_data,
386                                                     CalObjTime *interval_end);
387
388 static gboolean cal_obj_hourly_find_start_position (CalObjTime *event_start,
389                                                     CalObjTime *event_end,
390                                                     RecurData  *recur_data,
391                                                     CalObjTime *interval_start,
392                                                     CalObjTime *interval_end,
393                                                     CalObjTime *cotime);
394 static gboolean cal_obj_hourly_find_next_position  (CalObjTime *cotime,
395                                                     CalObjTime *event_end,
396                                                     RecurData  *recur_data,
397                                                     CalObjTime *interval_end);
398
399 static gboolean cal_obj_minutely_find_start_position (CalObjTime *event_start,
400                                                       CalObjTime *event_end,
401                                                       RecurData  *recur_data,
402                                                       CalObjTime *interval_start,
403                                                       CalObjTime *interval_end,
404                                                       CalObjTime *cotime);
405 static gboolean cal_obj_minutely_find_next_position  (CalObjTime *cotime,
406                                                       CalObjTime *event_end,
407                                                       RecurData  *recur_data,
408                                                       CalObjTime *interval_end);
409
410 static gboolean cal_obj_secondly_find_start_position (CalObjTime *event_start,
411                                                       CalObjTime *event_end,
412                                                       RecurData  *recur_data,
413                                                       CalObjTime *interval_start,
414                                                       CalObjTime *interval_end,
415                                                       CalObjTime *cotime);
416 static gboolean cal_obj_secondly_find_next_position  (CalObjTime *cotime,
417                                                       CalObjTime *event_end,
418                                                       RecurData  *recur_data,
419                                                       CalObjTime *interval_end);
420
421 static GArray* cal_obj_bymonth_expand           (RecurData  *recur_data,
422                                                  GArray     *occs);
423 static GArray* cal_obj_bymonth_filter           (RecurData  *recur_data,
424                                                  GArray     *occs);
425 static GArray* cal_obj_byweekno_expand          (RecurData  *recur_data,
426                                                  GArray     *occs);
427 #if 0
428 /* This isn't used at present. */
429 static GArray* cal_obj_byweekno_filter          (RecurData  *recur_data,
430                                                  GArray     *occs);
431 #endif
432 static GArray* cal_obj_byyearday_expand         (RecurData  *recur_data,
433                                                  GArray     *occs);
434 static GArray* cal_obj_byyearday_filter         (RecurData  *recur_data,
435                                                  GArray     *occs);
436 static GArray* cal_obj_bymonthday_expand        (RecurData  *recur_data,
437                                                  GArray     *occs);
438 static GArray* cal_obj_bymonthday_filter        (RecurData  *recur_data,
439                                                  GArray     *occs);
440 static GArray* cal_obj_byday_expand_yearly      (RecurData  *recur_data,
441                                                  GArray     *occs);
442 static GArray* cal_obj_byday_expand_monthly     (RecurData  *recur_data,
443                                                  GArray     *occs);
444 static GArray* cal_obj_byday_expand_weekly      (RecurData  *recur_data,
445                                                  GArray     *occs);
446 static GArray* cal_obj_byday_filter             (RecurData  *recur_data,
447                                                  GArray     *occs);
448 static GArray* cal_obj_byhour_expand            (RecurData  *recur_data,
449                                                  GArray     *occs);
450 static GArray* cal_obj_byhour_filter            (RecurData  *recur_data,
451                                                  GArray     *occs);
452 static GArray* cal_obj_byminute_expand          (RecurData  *recur_data,
453                                                  GArray     *occs);
454 static GArray* cal_obj_byminute_filter          (RecurData  *recur_data,
455                                                  GArray     *occs);
456 static GArray* cal_obj_bysecond_expand          (RecurData  *recur_data,
457                                                  GArray     *occs);
458 static GArray* cal_obj_bysecond_filter          (RecurData  *recur_data,
459                                                  GArray     *occs);
460
461 static void cal_obj_time_add_months             (CalObjTime *cotime,
462                                                  gint        months);
463 static void cal_obj_time_add_days               (CalObjTime *cotime,
464                                                  gint        days);
465 static void cal_obj_time_add_hours              (CalObjTime *cotime,
466                                                  gint        hours);
467 static void cal_obj_time_add_minutes            (CalObjTime *cotime,
468                                                  gint        minutes);
469 static void cal_obj_time_add_seconds            (CalObjTime *cotime,
470                                                  gint        seconds);
471 static gint cal_obj_time_compare                (CalObjTime *cotime1,
472                                                  CalObjTime *cotime2,
473                                                  CalObjTimeComparison type);
474 static gint cal_obj_time_weekday                (CalObjTime *cotime);
475 static gint cal_obj_time_weekday_offset         (CalObjTime *cotime,
476                                                  ECalRecurrence *recur);
477 static gint cal_obj_time_day_of_year            (CalObjTime *cotime);
478 static void cal_obj_time_find_first_week        (CalObjTime *cotime,
479                                                  RecurData  *recur_data);
480 static void cal_object_time_from_time           (CalObjTime *cotime,
481                                                  time_t      t,
482                                                  icaltimezone *zone);
483 static gint cal_obj_date_only_compare_func      (const void *arg1,
484                                                  const void *arg2);
485
486
487
488 static gboolean e_cal_recur_ensure_end_dates    (ECalComponent  *comp,
489                                                  gboolean        refresh,
490                                                  ECalRecurResolveTimezoneFn tz_cb,
491                                                  gpointer        tz_cb_data);
492 static gboolean e_cal_recur_ensure_rule_end_date        (ECalComponent  *comp,
493                                                  icalproperty   *prop,
494                                                  gboolean        exception,
495                                                  gboolean        refresh,
496                                                  ECalRecurResolveTimezoneFn tz_cb,
497                                                  gpointer        tz_cb_data);
498 static gboolean e_cal_recur_ensure_rule_end_date_cb     (ECalComponent  *comp,
499                                                          time_t          instance_start,
500                                                          time_t          instance_end,
501                                                          gpointer        data);
502 static time_t e_cal_recur_get_rule_end_date     (icalproperty   *prop,
503                                                  icaltimezone   *default_timezone);
504 static void e_cal_recur_set_rule_end_date               (icalproperty   *prop,
505                                                  time_t          end_date);
506
507
508 #ifdef CAL_OBJ_DEBUG
509 static char* cal_obj_time_to_string             (CalObjTime     *cotime);
510 #endif
511
512
513 static ECalRecurVTable cal_obj_yearly_vtable = {
514         cal_obj_yearly_find_start_position,
515         cal_obj_yearly_find_next_position,
516
517         cal_obj_bymonth_expand,
518         cal_obj_byweekno_expand,
519         cal_obj_byyearday_expand,
520         cal_obj_bymonthday_expand,
521         cal_obj_byday_expand_yearly,
522         cal_obj_byhour_expand,
523         cal_obj_byminute_expand,
524         cal_obj_bysecond_expand
525 };
526
527 static ECalRecurVTable cal_obj_monthly_vtable = {
528         cal_obj_monthly_find_start_position,
529         cal_obj_monthly_find_next_position,
530
531         cal_obj_bymonth_filter,
532         NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
533         NULL, /* BYYEARDAY is not useful in a MONTHLY frequency. */
534         cal_obj_bymonthday_expand,
535         cal_obj_byday_expand_monthly,
536         cal_obj_byhour_expand,
537         cal_obj_byminute_expand,
538         cal_obj_bysecond_expand
539 };
540
541 static ECalRecurVTable cal_obj_weekly_vtable = {
542         cal_obj_weekly_find_start_position,
543         cal_obj_weekly_find_next_position,
544
545         cal_obj_bymonth_filter,
546         NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
547         NULL, /* BYYEARDAY is not useful in a WEEKLY frequency. */
548         NULL, /* BYMONTHDAY is not useful in a WEEKLY frequency. */
549         cal_obj_byday_expand_weekly,
550         cal_obj_byhour_expand,
551         cal_obj_byminute_expand,
552         cal_obj_bysecond_expand
553 };
554
555 static ECalRecurVTable cal_obj_daily_vtable = {
556         cal_obj_daily_find_start_position,
557         cal_obj_daily_find_next_position,
558
559         cal_obj_bymonth_filter,
560         NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
561         cal_obj_byyearday_filter,
562         cal_obj_bymonthday_filter,
563         cal_obj_byday_filter,
564         cal_obj_byhour_expand,
565         cal_obj_byminute_expand,
566         cal_obj_bysecond_expand
567 };
568
569 static ECalRecurVTable cal_obj_hourly_vtable = {
570         cal_obj_hourly_find_start_position,
571         cal_obj_hourly_find_next_position,
572
573         cal_obj_bymonth_filter,
574         NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
575         cal_obj_byyearday_filter,
576         cal_obj_bymonthday_filter,
577         cal_obj_byday_filter,
578         cal_obj_byhour_filter,
579         cal_obj_byminute_expand,
580         cal_obj_bysecond_expand
581 };
582
583 static ECalRecurVTable cal_obj_minutely_vtable = {
584         cal_obj_minutely_find_start_position,
585         cal_obj_minutely_find_next_position,
586
587         cal_obj_bymonth_filter,
588         NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
589         cal_obj_byyearday_filter,
590         cal_obj_bymonthday_filter,
591         cal_obj_byday_filter,
592         cal_obj_byhour_filter,
593         cal_obj_byminute_filter,
594         cal_obj_bysecond_expand
595 };
596
597 static ECalRecurVTable cal_obj_secondly_vtable = {
598         cal_obj_secondly_find_start_position,
599         cal_obj_secondly_find_next_position,
600
601         cal_obj_bymonth_filter,
602         NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
603         cal_obj_byyearday_filter,
604         cal_obj_bymonthday_filter,
605         cal_obj_byday_filter,
606         cal_obj_byhour_filter,
607         cal_obj_byminute_filter,
608         cal_obj_bysecond_filter
609 };
610
611 /**
612  * e_cal_recur_generate_instances:
613  * @comp: A calendar component object.
614  * @start: Range start time.
615  * @end: Range end time.
616  * @cb: Callback function.
617  * @cb_data: Closure data for the callback function.
618  * @tz_cb: Callback for retrieving timezones.
619  * @tz_cb_data: Closure data for the timezone callback.
620  * @default_timezone: Default timezone to use when a timezone cannot be
621  * found.
622  *
623  * Calls the given callback function for each occurrence of the event that
624  * intersects the range between the given @start and @end times (the end time is
625  * not included). Note that the occurrences may start before the given start
626  * time.
627  *
628  * If the callback routine returns FALSE the occurrence generation stops.
629  *
630  * Both start and end can be -1, in which case we start at the events first
631  * instance and continue until it ends, or forever if it has no enddate.
632  *
633  * The tz_cb is used to resolve references to timezones. It is passed a TZID
634  * and should return the icaltimezone* corresponding to that TZID. We need to
635  * do this as we access timezones in different ways on the client & server.
636  *
637  * The default_timezone argument is used for DTSTART or DTEND properties that
638  * are DATE values or do not have a TZID (i.e. floating times).
639  */
640 void
641 e_cal_recur_generate_instances (ECalComponent           *comp,
642                               time_t                     start,
643                               time_t                     end,
644                               ECalRecurInstanceFn        cb,
645                               gpointer                   cb_data,
646                               ECalRecurResolveTimezoneFn  tz_cb,
647                               gpointer                   tz_cb_data,
648                               icaltimezone              *default_timezone)
649 {
650 #if 0
651         g_print ("In e_cal_recur_generate_instances comp: %p\n", comp);
652         g_print ("  start: %li - %s", start, ctime (&start));
653         g_print ("  end  : %li - %s", end, ctime (&end));
654 #endif
655         e_cal_recur_generate_instances_of_rule (comp, NULL, start, end,
656                                                 cb, cb_data, tz_cb, tz_cb_data,
657                                                 default_timezone);
658 }
659
660
661 /*
662  * Calls the given callback function for each occurrence of the given
663  * recurrence rule between the given start and end times. If the rule is NULL
664  * it uses all the rules from the component.
665  *
666  * If the callback routine returns FALSE the occurrence generation stops.
667  *
668  * The use of the specific rule is for determining the end of a rule when
669  * COUNT is set. The callback will count instances and store the enddate
670  * when COUNT is reached.
671  *
672  * Both start and end can be -1, in which case we start at the events first
673  * instance and continue until it ends, or forever if it has no enddate.
674  */
675 static void
676 e_cal_recur_generate_instances_of_rule (ECalComponent    *comp,
677                                         icalproperty     *prop,
678                                         time_t            start,
679                                         time_t            end,
680                                         ECalRecurInstanceFn  cb,
681                                         gpointer            cb_data,
682                                         ECalRecurResolveTimezoneFn  tz_cb,
683                                         gpointer                    tz_cb_data,
684                                         icaltimezone     *default_timezone)
685 {
686         ECalComponentDateTime dtstart, dtend;
687         time_t dtstart_time, dtend_time;
688         GSList *rrules = NULL, *rdates = NULL, elem;
689         GSList *exrules = NULL, *exdates = NULL;
690         CalObjTime interval_start, interval_end, event_start, event_end;
691         CalObjTime chunk_start, chunk_end;
692         gint days, seconds, year;
693         gboolean single_rule, convert_end_date = FALSE;
694         icaltimezone *start_zone = NULL, *end_zone = NULL;
695
696         g_return_if_fail (comp != NULL);
697         g_return_if_fail (cb != NULL);
698         g_return_if_fail (tz_cb != NULL);
699         g_return_if_fail (start >= -1);
700         g_return_if_fail (end >= -1);
701
702         /* Get dtstart, dtend, recurrences, and exceptions. Note that
703            cal_component_get_dtend() will convert a DURATION property to a
704            DTEND so we don't need to worry about that. */
705
706         e_cal_component_get_dtstart (comp, &dtstart);
707         e_cal_component_get_dtend (comp, &dtend);
708
709         if (!dtstart.value) {
710                 g_message ("e_cal_recur_generate_instances_of_rule(): bogus "
711                            "component, does not have DTSTART.  Skipping...");
712                 goto out;
713         }
714
715         /* For DATE-TIME values with a TZID, we use the supplied callback to
716            resolve the TZID. For DATE values and DATE-TIME values without a
717            TZID (i.e. floating times) we use the default timezone. */
718         if (dtstart.tzid && !dtstart.value->is_date) {
719                 start_zone = (*tz_cb) (dtstart.tzid, tz_cb_data);
720                 if (!start_zone)
721                         start_zone = default_timezone;
722         } else {
723                 start_zone = default_timezone;
724
725                 /* Flag that we need to convert the saved ENDDATE property
726                    to the default timezone. */
727                 convert_end_date = TRUE;
728         }
729
730         dtstart_time = icaltime_as_timet_with_zone (*dtstart.value,
731                                                     start_zone);
732         if (start == -1)
733                 start = dtstart_time;
734
735         if (dtend.value) {
736                 /* If both DTSTART and DTEND are DATE values, and they are the
737                    same day, we add 1 day to DTEND. This means that most
738                    events created with the old Evolution behavior will still
739                    work OK. I'm not sure what Outlook does in this case. */
740                 if (dtstart.value->is_date && dtend.value->is_date) {
741                         if (icaltime_compare_date_only (*dtstart.value,
742                                                         *dtend.value) == 0) {
743                                 icaltime_adjust (dtend.value, 1, 0, 0, 0);
744                         }
745                 }
746         } else {
747                 /* If there is no DTEND, then if DTSTART is a DATE-TIME value
748                    we use the same time (so we have a single point in time).
749                    If DTSTART is a DATE value we add 1 day. */
750                 dtend.value = g_new (struct icaltimetype, 1);
751                 *dtend.value = *dtstart.value;
752
753                 if (dtstart.value->is_date) {
754                         icaltime_adjust (dtend.value, 1, 0, 0, 0);
755                 }
756         }
757
758         if (dtend.tzid && !dtend.value->is_date) {
759                 end_zone = (*tz_cb) (dtend.tzid, tz_cb_data);
760                 if (!end_zone)
761                         end_zone = default_timezone;
762         } else {
763                 end_zone = default_timezone;
764         }
765
766         /* We don't do this any more, since Outlook assumes that the DTEND
767            date is not included. */
768 #if 0
769         /* If DTEND is a DATE value, we add 1 day to it so that it includes
770            the entire day. */
771         if (dtend.value->is_date) {
772                 dtend.value->hour = 0;
773                 dtend.value->minute = 0;
774                 dtend.value->second = 0;
775                 icaltime_adjust (dtend.value, 1, 0, 0, 0);
776         }
777 #endif
778         dtend_time = icaltime_as_timet_with_zone (*dtend.value, end_zone);
779
780         /* If there is no recurrence, just call the callback if the event
781            intersects the given interval. */
782         if (!(e_cal_component_has_recurrences (comp)
783               || e_cal_component_has_exceptions (comp))) {
784                 if (e_cal_component_get_vtype (comp) == E_CAL_COMPONENT_JOURNAL) {
785                         icaltimetype start_t = icaltime_from_timet_with_zone (start, FALSE, default_timezone);
786                         icaltimetype end_t = icaltime_from_timet_with_zone (end, FALSE, default_timezone);
787                 
788                         if ((icaltime_compare_date_only (*dtstart.value, start_t) >= 0) && ((icaltime_compare_date_only (*dtstart.value, end_t) < 0)))
789                                 (* cb) (comp, dtstart_time, dtend_time, cb_data);
790                 } else if  ((end == -1 || dtstart_time < end) && dtend_time > start) {
791                         (* cb) (comp, dtstart_time, dtend_time, cb_data);
792                 }
793
794                 goto out;
795         }
796
797         /* If a specific recurrence rule is being used, set up a simple list,
798            else get the recurrence rules from the component. */
799         if (prop) {
800                 single_rule = TRUE;
801
802                 elem.data = prop;
803                 elem.next = NULL;
804                 rrules = &elem;
805         } else if (e_cal_component_is_instance (comp)) {
806                 single_rule = FALSE;
807         } else {
808                 single_rule = FALSE;
809
810                 /* Make sure all the enddates for the rules are set. */
811                 e_cal_recur_ensure_end_dates (comp, FALSE, tz_cb, tz_cb_data);
812
813                 e_cal_component_get_rrule_property_list (comp, &rrules);
814                 e_cal_component_get_rdate_list (comp, &rdates);
815                 e_cal_component_get_exrule_property_list (comp, &exrules);
816                 e_cal_component_get_exdate_list (comp, &exdates);
817         }
818
819         /* Convert the interval start & end to CalObjTime. Note that if end
820            is -1 interval_end won't be set, so don't use it!
821            Also note that we use end - 1 since we want the interval to be
822            inclusive as it makes the code simpler. We do all calculation
823            in the timezone of the DTSTART. */
824         cal_object_time_from_time (&interval_start, start, start_zone);
825         if (end != -1)
826                 cal_object_time_from_time (&interval_end, end - 1, start_zone);
827
828         cal_object_time_from_time (&event_start, dtstart_time, start_zone);
829         cal_object_time_from_time (&event_end, dtend_time, start_zone);
830         
831         /* Calculate the duration of the event, which we use for all
832            occurrences. We can't just subtract start from end since that may
833            be affected by daylight-saving time. So we want a value of days
834            + seconds. */
835         cal_object_compute_duration (&event_start, &event_end,
836                                      &days, &seconds);
837
838         /* Take off the duration from interval_start, so we get occurrences
839            that start just before the start time but overlap it. But only do
840            that if the interval is after the event's start time. */
841         if (start > dtstart_time) {
842                 cal_obj_time_add_days (&interval_start, -days);
843                 cal_obj_time_add_seconds (&interval_start, -seconds);
844         }
845
846         /* Expand the recurrence for each year between start & end, or until
847            the callback returns 0 if end is 0. We do a year at a time to
848            give the callback function a chance to break out of the loop, and
849            so we don't get into problems with infinite recurrences. Since we
850            have to work on complete sets of occurrences, if there is a yearly
851            frequency it wouldn't make sense to break it into smaller chunks,
852            since we would then be calculating the same sets several times.
853            Though this does mean that we sometimes do a lot more work than
854            is necessary, e.g. if COUNT is set to something quite low. */
855         for (year = interval_start.year;
856              (end == -1 || year <= interval_end.year) && year <= MAX_YEAR;
857              year++) {
858                 chunk_start = interval_start;
859                 chunk_start.year = year;
860                 if (end != -1)
861                         chunk_end = interval_end;
862                 chunk_end.year = year;
863
864                 if (year != interval_start.year) {
865                         chunk_start.month  = 0;
866                         chunk_start.day    = 1;
867                         chunk_start.hour   = 0;
868                         chunk_start.minute = 0;
869                         chunk_start.second = 0;
870                 }
871                 if (end == -1 || year != interval_end.year) {
872                         chunk_end.month  = 11;
873                         chunk_end.day    = 31;
874                         chunk_end.hour   = 23;
875                         chunk_end.minute = 59;
876                         chunk_end.second = 61;
877                         chunk_end.flags  = FALSE;
878                 }
879
880                 if (!generate_instances_for_chunk (comp, dtstart_time,
881                                                    start_zone,
882                                                    rrules, rdates,
883                                                    exrules, exdates,
884                                                    single_rule,
885                                                    &event_start,
886                                                    start,
887                                                    &chunk_start, &chunk_end,
888                                                    days, seconds,
889                                                    convert_end_date,
890                                                    cb, cb_data))
891                         break;
892         }
893
894         if (!prop) {
895                 e_cal_component_free_period_list (rdates);
896                 e_cal_component_free_exdate_list (exdates);
897         }
898
899  out:
900         e_cal_component_free_datetime (&dtstart);
901         e_cal_component_free_datetime (&dtend);
902 }
903
904 /* Builds a list of GINT_TO_POINTER() elements out of a short array from a
905  * struct icalrecurrencetype.
906  */
907 static GList *
908 array_to_list (short *array, int max_elements)
909 {
910         GList *l;
911         int i;
912
913         l = NULL;
914
915         for (i = 0; i < max_elements && array[i] != ICAL_RECURRENCE_ARRAY_MAX; i++)
916                 l = g_list_prepend (l, GINT_TO_POINTER ((int) (array[i])));
917         return g_list_reverse (l);
918 }
919
920 /**
921  * e_cal_recur_from_icalproperty:
922  * @prop: An RRULE or EXRULE #icalproperty.
923  * @exception: TRUE if this is an EXRULE rather than an RRULE.
924  * @zone: The DTSTART timezone, used for converting the UNTIL property if it
925  * is given as a DATE value.
926  * @convert_end_date: TRUE if the saved end date needs to be converted to the
927  * given @zone timezone. This is needed if the DTSTART is a DATE or floating
928  * time.
929  * 
930  * Converts an #icalproperty to a #ECalRecurrence.  This should be
931  * freed using the e_cal_recur_free() function.
932  * 
933  * Return value: #ECalRecurrence structure.
934  **/
935 static ECalRecurrence *
936 e_cal_recur_from_icalproperty (icalproperty *prop, gboolean exception,
937                              icaltimezone *zone, gboolean convert_end_date)
938 {
939         struct icalrecurrencetype ir;
940         ECalRecurrence *r;
941         gint max_elements, i;
942         GList *elem;
943
944         g_return_val_if_fail (prop != NULL, NULL);
945
946         r = g_new (ECalRecurrence, 1);
947
948         if (exception)
949                 ir = icalproperty_get_exrule (prop);
950         else
951                 ir = icalproperty_get_rrule (prop);
952
953         r->freq = ir.freq;
954         r->interval = ir.interval;
955
956         if (ir.count != 0) {
957                 /* If COUNT is set, we use the pre-calculated enddate.
958                    Note that this can be 0 if the RULE doesn't actually
959                    generate COUNT instances. */
960                 r->enddate = e_cal_recur_get_rule_end_date (prop, convert_end_date ? zone : NULL);
961         } else {
962                 if (icaltime_is_null_time (ir.until)) {
963                         /* If neither COUNT or UNTIL is set, the event
964                            recurs forever. */
965                         r->enddate = 0;
966                 } else if (ir.until.is_date) {
967                         /* If UNTIL is a DATE, we stop at the end of
968                            the day, in local time (with the DTSTART timezone).
969                            Note that UNTIL is inclusive so we stop before
970                            midnight. */
971                         ir.until.hour = 23;
972                         ir.until.minute = 59;
973                         ir.until.second = 59;
974                         ir.until.is_date = FALSE;
975
976                         r->enddate = icaltime_as_timet_with_zone (ir.until,
977                                                                   zone);
978 #if 0
979                         g_print ("  until: %li - %s", r->enddate, ctime (&r->enddate));
980 #endif
981
982                 } else {
983                         /* If UNTIL is a DATE-TIME, it must be in UTC. */
984                         icaltimezone *utc_zone;
985                         utc_zone = icaltimezone_get_utc_timezone ();
986                         r->enddate = icaltime_as_timet_with_zone (ir.until,
987                                                                   utc_zone);
988                 }
989         }
990
991         r->week_start_day = e_cal_recur_ical_weekday_to_weekday (ir.week_start);
992
993         r->bymonth = array_to_list (ir.by_month,
994                                     sizeof (ir.by_month) / sizeof (ir.by_month[0]));
995         for (elem = r->bymonth; elem; elem = elem->next) {
996                 /* We need to convert from 1-12 to 0-11, i.e. subtract 1. */
997                 int month = GPOINTER_TO_INT (elem->data) - 1;
998                 elem->data = GINT_TO_POINTER (month);
999         }
1000
1001         r->byweekno = array_to_list (ir.by_week_no,
1002                                      sizeof (ir.by_week_no) / sizeof (ir.by_week_no[0]));
1003
1004         r->byyearday = array_to_list (ir.by_year_day,
1005                                       sizeof (ir.by_year_day) / sizeof (ir.by_year_day[0]));
1006
1007         r->bymonthday = array_to_list (ir.by_month_day,
1008                                        sizeof (ir.by_month_day) / sizeof (ir.by_month_day[0]));
1009
1010         /* FIXME: libical only supports 8 values, out of possible 107 * 7. */
1011         r->byday = NULL;
1012         max_elements = sizeof (ir.by_day) / sizeof (ir.by_day[0]);
1013         for (i = 0; i < max_elements && ir.by_day[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) {
1014                 enum icalrecurrencetype_weekday day;
1015                 gint weeknum, weekday;
1016
1017                 day = icalrecurrencetype_day_day_of_week (ir.by_day[i]);
1018                 weeknum = icalrecurrencetype_day_position (ir.by_day[i]);
1019
1020                 weekday = e_cal_recur_ical_weekday_to_weekday (day);
1021
1022                 r->byday = g_list_prepend (r->byday,
1023                                            GINT_TO_POINTER (weeknum));
1024                 r->byday = g_list_prepend (r->byday,
1025                                            GINT_TO_POINTER (weekday));
1026         }
1027
1028         r->byhour = array_to_list (ir.by_hour,
1029                                    sizeof (ir.by_hour) / sizeof (ir.by_hour[0]));
1030
1031         r->byminute = array_to_list (ir.by_minute,
1032                                      sizeof (ir.by_minute) / sizeof (ir.by_minute[0]));
1033
1034         r->bysecond = array_to_list (ir.by_second,
1035                                      sizeof (ir.by_second) / sizeof (ir.by_second[0]));
1036
1037         r->bysetpos = array_to_list (ir.by_set_pos,
1038                                      sizeof (ir.by_set_pos) / sizeof (ir.by_set_pos[0]));
1039
1040         return r;
1041 }
1042
1043
1044 static gint
1045 e_cal_recur_ical_weekday_to_weekday     (enum icalrecurrencetype_weekday day)
1046 {
1047         gint weekday;
1048
1049         switch (day) {
1050         case ICAL_NO_WEEKDAY:           /* Monday is the default in RFC2445. */
1051         case ICAL_MONDAY_WEEKDAY:
1052                 weekday = 0;
1053                 break;
1054         case ICAL_TUESDAY_WEEKDAY:
1055                 weekday = 1;
1056                 break;
1057         case ICAL_WEDNESDAY_WEEKDAY:
1058                 weekday = 2;
1059                 break;
1060         case ICAL_THURSDAY_WEEKDAY:
1061                 weekday = 3;
1062                 break;
1063         case ICAL_FRIDAY_WEEKDAY:
1064                 weekday = 4;
1065                 break;
1066         case ICAL_SATURDAY_WEEKDAY:
1067                 weekday = 5;
1068                 break;
1069         case ICAL_SUNDAY_WEEKDAY:
1070                 weekday = 6;
1071                 break;
1072         default:
1073                 g_warning ("e_cal_recur_ical_weekday_to_weekday(): Unknown week day %d",
1074                            day);
1075                 weekday = 0;
1076         }
1077
1078         return weekday;
1079 }
1080
1081
1082 /**
1083  * e_cal_recur_free:
1084  * @r: A #ECalRecurrence structure.
1085  * 
1086  * Frees a #ECalRecurrence structure.
1087  **/
1088 static void
1089 e_cal_recur_free (ECalRecurrence *r)
1090 {
1091         g_return_if_fail (r != NULL);
1092
1093         g_list_free (r->bymonth);
1094         g_list_free (r->byweekno);
1095         g_list_free (r->byyearday);
1096         g_list_free (r->bymonthday);
1097         g_list_free (r->byday);
1098         g_list_free (r->byhour);
1099         g_list_free (r->byminute);
1100         g_list_free (r->bysecond);
1101         g_list_free (r->bysetpos);
1102
1103         g_free (r);
1104 }
1105
1106 /* Generates one year's worth of recurrence instances.  Returns TRUE if all the
1107  * callback invocations returned TRUE, or FALSE when any one of them returns
1108  * FALSE, i.e. meaning that the instance generation should be stopped.
1109  *
1110  * This should only output instances whose start time is between chunk_start
1111  * and chunk_end (inclusive), or we may generate duplicates when we do the next
1112  * chunk. (This applies mainly to weekly recurrences, since weeks can span 2
1113  * years.)
1114  *
1115  * It should also only output instances that are on or after the event's
1116  * DTSTART property and that intersect the required interval, between
1117  * interval_start and interval_end.
1118  */
1119 static gboolean
1120 generate_instances_for_chunk (ECalComponent     *comp,
1121                               time_t             comp_dtstart,
1122                               icaltimezone      *zone,
1123                               GSList            *rrules,
1124                               GSList            *rdates,
1125                               GSList            *exrules,
1126                               GSList            *exdates,
1127                               gboolean           single_rule,
1128                               CalObjTime        *event_start,
1129                               time_t             interval_start,
1130                               CalObjTime        *chunk_start,
1131                               CalObjTime        *chunk_end,
1132                               gint               duration_days,
1133                               gint               duration_seconds,
1134                               gboolean           convert_end_date,
1135                               ECalRecurInstanceFn cb,
1136                               gpointer           cb_data)
1137 {
1138         GArray *occs, *ex_occs, *tmp_occs, *rdate_periods;
1139         CalObjTime cotime, *occ;
1140         GSList *elem;
1141         gint i;
1142         time_t start_time, end_time;
1143         struct icaltimetype start_tt, end_tt;
1144         gboolean cb_status = TRUE, rule_finished, finished = TRUE;
1145
1146 #if 0
1147         g_print ("In generate_instances_for_chunk rrules: %p\n"
1148                  "  %i/%i/%i %02i:%02i:%02i - %i/%i/%i %02i:%02i:%02i\n",
1149                  rrules,
1150                  chunk_start->day, chunk_start->month + 1,
1151                  chunk_start->year, chunk_start->hour,
1152                  chunk_start->minute, chunk_start->second,
1153                  chunk_end->day, chunk_end->month + 1,
1154                  chunk_end->year, chunk_end->hour,
1155                  chunk_end->minute, chunk_end->second);
1156 #endif
1157
1158         occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1159         ex_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1160         rdate_periods = g_array_new (FALSE, FALSE,
1161                                      sizeof (CalObjRecurrenceDate));
1162
1163         /* The original DTSTART property is included in the occurrence set,
1164            but not if we are just generating occurrences for a single rule. */
1165         if (!single_rule) {
1166                 /* We add it if it is in this chunk. If it is after this chunk
1167                    we set finished to FALSE, since we know we aren't finished
1168                    yet. */
1169                 if (cal_obj_time_compare_func (event_start, chunk_end) >= 0)
1170                         finished = FALSE;
1171                 else if (cal_obj_time_compare_func (event_start, chunk_start) >= 0)
1172                         g_array_append_vals (occs, event_start, 1);
1173         }
1174         
1175         /* Expand each of the recurrence rules. */
1176         for (elem = rrules; elem; elem = elem->next) {
1177                 icalproperty *prop;
1178                 ECalRecurrence *r;
1179
1180                 prop = elem->data;
1181                 r = e_cal_recur_from_icalproperty (prop, FALSE, zone,
1182                                                  convert_end_date);
1183
1184                 tmp_occs = cal_obj_expand_recurrence (event_start, zone, r,
1185                                                       chunk_start,
1186                                                       chunk_end,
1187                                                       &rule_finished);
1188                 e_cal_recur_free (r);
1189
1190                 /* If any of the rules return FALSE for finished, we know we
1191                    have to carry on so we set finished to FALSE. */
1192                 if (!rule_finished)
1193                         finished = FALSE;
1194
1195                 g_array_append_vals (occs, tmp_occs->data, tmp_occs->len);
1196                 g_array_free (tmp_occs, TRUE);
1197         }
1198
1199         /* Add on specific RDATE occurrence dates. If they have an end time
1200            or duration set, flag them as RDATEs, and store a pointer to the
1201            period in the rdate_periods array. Otherwise we can just treat them
1202            as normal occurrences. */
1203         for (elem = rdates; elem; elem = elem->next) {
1204                 ECalComponentPeriod *p;
1205                 CalObjRecurrenceDate rdate;
1206
1207                 p = elem->data;
1208
1209                 /* FIXME: We currently assume RDATEs are in the same timezone
1210                    as DTSTART. We should get the RDATE timezone and convert
1211                    to the DTSTART timezone first. */
1212                 cotime.year     = p->start.year;
1213                 cotime.month    = p->start.month - 1;
1214                 cotime.day      = p->start.day;
1215                 cotime.hour     = p->start.hour;
1216                 cotime.minute   = p->start.minute;
1217                 cotime.second   = p->start.second;
1218                 cotime.flags    = FALSE;
1219
1220                 /* If the rdate is after the current chunk we set finished
1221                    to FALSE, and we skip it. */
1222                 if (cal_obj_time_compare_func (&cotime, chunk_end) >= 0) {
1223                         finished = FALSE;
1224                         continue;
1225                 }
1226
1227                 /* Check if the end date or duration is set. If it is we need
1228                    to store it so we can get it later. (libical seems to set
1229                    second to -1 to denote an unset time. See icalvalue.c)
1230                    FIXME. */
1231                 if (p->type != E_CAL_COMPONENT_PERIOD_DATETIME
1232                     || p->u.end.second != -1) {
1233                         cotime.flags = TRUE;
1234
1235                         rdate.start = cotime;
1236                         rdate.period = p;
1237                         g_array_append_val (rdate_periods, rdate);
1238                 }
1239
1240                 g_array_append_val (occs, cotime);
1241         }
1242
1243         /* Expand each of the exception rules. */
1244         for (elem = exrules; elem; elem = elem->next) {
1245                 icalproperty *prop;
1246                 ECalRecurrence *r;
1247
1248                 prop = elem->data;
1249                 r = e_cal_recur_from_icalproperty (prop, FALSE, zone,
1250                                                  convert_end_date);
1251
1252                 tmp_occs = cal_obj_expand_recurrence (event_start, zone, r,
1253                                                       chunk_start,
1254                                                       chunk_end,
1255                                                       &rule_finished);
1256                 e_cal_recur_free (r);
1257
1258                 g_array_append_vals (ex_occs, tmp_occs->data, tmp_occs->len);
1259                 g_array_free (tmp_occs, TRUE);
1260         }
1261
1262         /* Add on specific exception dates. */
1263         for (elem = exdates; elem; elem = elem->next) {
1264                 ECalComponentDateTime *cdt;
1265
1266                 cdt = elem->data;
1267
1268                 /* FIXME: We currently assume EXDATEs are in the same timezone
1269                    as DTSTART. We should get the EXDATE timezone and convert
1270                    to the DTSTART timezone first. */
1271                 cotime.year     = cdt->value->year;
1272                 cotime.month    = cdt->value->month - 1;
1273                 cotime.day      = cdt->value->day;
1274
1275                 /* If the EXDATE has a DATE value, set the time to the start
1276                    of the day and set flags to TRUE so we know to skip all
1277                    occurrences on that date. */
1278                 if (cdt->value->is_date) {
1279                         cotime.hour     = 0;
1280                         cotime.minute   = 0;
1281                         cotime.second   = 0;
1282                         cotime.flags    = TRUE;
1283                 } else {
1284                         cotime.hour     = cdt->value->hour;
1285                         cotime.minute   = cdt->value->minute;
1286                         cotime.second   = cdt->value->second;
1287                         cotime.flags    = FALSE;
1288                 }
1289
1290                 g_array_append_val (ex_occs, cotime);
1291         }
1292
1293
1294         /* Sort all the arrays. */
1295         cal_obj_sort_occurrences (occs);
1296         cal_obj_sort_occurrences (ex_occs);
1297
1298         qsort (rdate_periods->data, rdate_periods->len,
1299                sizeof (CalObjRecurrenceDate), cal_obj_time_compare_func);
1300
1301         /* Create the final array, by removing the exceptions from the
1302            occurrences, and removing any duplicates. */
1303         cal_obj_remove_exceptions (occs, ex_occs);
1304
1305         /* Call the callback for each occurrence. If it returns 0 we break
1306            out of the loop. */
1307         for (i = 0; i < occs->len; i++) {
1308                 /* Convert each CalObjTime into a start & end time_t, and
1309                    check it is within the bounds of the event & interval. */
1310                 occ = &g_array_index (occs, CalObjTime, i);
1311 #if 0
1312                 g_print ("Checking occurrence: %s\n",
1313                          cal_obj_time_to_string (occ));
1314 #endif
1315                 start_tt = icaltime_null_time ();
1316                 start_tt.year   = occ->year;
1317                 start_tt.month  = occ->month + 1;
1318                 start_tt.day    = occ->day;
1319                 start_tt.hour   = occ->hour;
1320                 start_tt.minute = occ->minute;
1321                 start_tt.second = occ->second;
1322                 start_time = icaltime_as_timet_with_zone (start_tt, zone);
1323
1324                 if (start_time == -1) {
1325                         g_warning ("time_t out of range");
1326                         finished = TRUE;
1327                         break;
1328                 }
1329
1330                 /* Check to ensure that the start time is at or after the
1331                    event's DTSTART time, and that it is inside the chunk that
1332                    we are currently working on. (Note that the chunk_end time
1333                    is never after the interval end time, so this also tests
1334                    that we don't go past the end of the required interval). */
1335                 if (start_time < comp_dtstart
1336                     || cal_obj_time_compare_func (occ, chunk_start) < 0
1337                     || cal_obj_time_compare_func (occ, chunk_end) > 0) {
1338 #if 0
1339                         g_print ("  start time invalid\n");
1340 #endif
1341                         continue;
1342                 }
1343
1344                 if (occ->flags) {
1345                         /* If it is an RDATE, we see if the end date or
1346                            duration was set. If not, we use the same duration
1347                            as the original occurrence. */
1348                         if (!cal_object_get_rdate_end (occ, rdate_periods)) {
1349                                 cal_obj_time_add_days (occ, duration_days);
1350                                 cal_obj_time_add_seconds (occ,
1351                                                           duration_seconds);
1352                         }
1353                 } else {
1354                         cal_obj_time_add_days (occ, duration_days);
1355                         cal_obj_time_add_seconds (occ, duration_seconds);
1356                 }
1357
1358                 end_tt = icaltime_null_time ();
1359                 end_tt.year   = occ->year;
1360                 end_tt.month  = occ->month + 1;
1361                 end_tt.day    = occ->day;
1362                 end_tt.hour   = occ->hour;
1363                 end_tt.minute = occ->minute;
1364                 end_tt.second = occ->second;
1365                 end_time = icaltime_as_timet_with_zone (end_tt, zone);
1366
1367                 if (end_time == -1) {
1368                         g_warning ("time_t out of range");
1369                         finished = TRUE;
1370                         break;
1371                 }
1372
1373                 /* Check that the end time is after the interval start, so we
1374                    know that it intersects the required interval. */
1375                 if (end_time <= interval_start) {
1376 #if 0
1377                         g_print ("  end time invalid\n");
1378 #endif
1379                         continue;
1380                 }
1381
1382                 cb_status = (*cb) (comp, start_time, end_time, cb_data);
1383                 if (!cb_status)
1384                         break;
1385         }
1386
1387         g_array_free (occs, TRUE);
1388         g_array_free (ex_occs, TRUE);
1389         g_array_free (rdate_periods, TRUE);
1390
1391         /* We return TRUE (i.e. carry on) only if the callback has always
1392            returned TRUE and we know that we have more occurrences to generate
1393            (i.e. finished is FALSE). */
1394         return cb_status && !finished;
1395 }
1396
1397
1398 /* This looks up the occurrence time in the sorted rdate_periods array, and
1399    tries to compute the end time of the occurrence. If no end time or duration
1400    is set it returns FALSE and the default duration will be used. */
1401 static gboolean
1402 cal_object_get_rdate_end        (CalObjTime     *occ,
1403                                  GArray         *rdate_periods)
1404 {
1405         CalObjRecurrenceDate *rdate = NULL;
1406         ECalComponentPeriod *p;
1407         gint lower, upper, middle, cmp = 0;
1408
1409         lower = 0;
1410         upper = rdate_periods->len;
1411
1412         while (lower < upper) {
1413                 middle = (lower + upper) >> 1;
1414           
1415                 rdate = &g_array_index (rdate_periods, CalObjRecurrenceDate,
1416                                         middle);
1417
1418                 cmp = cal_obj_time_compare_func (occ, &rdate->start);
1419           
1420                 if (cmp == 0)
1421                         break;
1422                 else if (cmp < 0)
1423                         upper = middle;
1424                 else
1425                         lower = middle + 1;
1426         }
1427
1428         /* This should never happen. */
1429         if (cmp == 0) {
1430                 g_warning ("Recurrence date not found");
1431                 return FALSE;
1432         }
1433
1434         p = rdate->period;
1435         if (p->type == E_CAL_COMPONENT_PERIOD_DATETIME) {
1436                 /* FIXME: We currently assume RDATEs are in the same timezone
1437                    as DTSTART. We should get the RDATE timezone and convert
1438                    to the DTSTART timezone first. */
1439                 occ->year     = p->u.end.year;
1440                 occ->month    = p->u.end.month - 1;
1441                 occ->day      = p->u.end.day;
1442                 occ->hour     = p->u.end.hour;
1443                 occ->minute   = p->u.end.minute;
1444                 occ->second   = p->u.end.second;
1445                 occ->flags    = FALSE;
1446         } else {
1447                 cal_obj_time_add_days (occ, p->u.duration.weeks * 7
1448                                        + p->u.duration.days);
1449                 cal_obj_time_add_hours (occ, p->u.duration.hours);
1450                 cal_obj_time_add_minutes (occ, p->u.duration.minutes);
1451                 cal_obj_time_add_seconds (occ, p->u.duration.seconds);
1452         }
1453
1454         return TRUE;
1455 }
1456
1457
1458 static void
1459 cal_object_compute_duration (CalObjTime *start,
1460                              CalObjTime *end,
1461                              gint       *days,
1462                              gint       *seconds)
1463 {
1464         GDate start_date, end_date;
1465         gint start_seconds, end_seconds;
1466
1467         g_date_clear (&start_date, 1);
1468         g_date_clear (&end_date, 1);
1469         g_date_set_dmy (&start_date, start->day, start->month + 1,
1470                         start->year);
1471         g_date_set_dmy (&end_date, end->day, end->month + 1,
1472                         end->year);
1473
1474         *days = g_date_get_julian (&end_date) - g_date_get_julian (&start_date);
1475         start_seconds = start->hour * 3600 + start->minute * 60
1476                 + start->second;
1477         end_seconds = end->hour * 3600 + end->minute * 60 + end->second;
1478
1479         *seconds = end_seconds - start_seconds;
1480         if (*seconds < 0) {
1481                 *days = *days - 1;
1482                 *seconds += 24 * 60 * 60;
1483         }
1484 }
1485
1486
1487 /* Returns an unsorted GArray of CalObjTime's resulting from expanding the
1488    given recurrence rule within the given interval. Note that it doesn't
1489    clip the generated occurrences to the interval, i.e. if the interval
1490    starts part way through the year this function still returns all the
1491    occurrences for the year. Clipping is done later.
1492    The finished flag is set to FALSE if there are more occurrences to generate
1493    after the given interval.*/
1494 static GArray*
1495 cal_obj_expand_recurrence               (CalObjTime       *event_start,
1496                                          icaltimezone     *zone,
1497                                          ECalRecurrence   *recur,
1498                                          CalObjTime       *interval_start,
1499                                          CalObjTime       *interval_end,
1500                                          gboolean         *finished)
1501 {
1502         ECalRecurVTable *vtable;
1503         CalObjTime *event_end = NULL, event_end_cotime;
1504         RecurData recur_data;
1505         CalObjTime occ, *cotime;
1506         GArray *all_occs, *occs;
1507         gint len;
1508
1509         /* This is the resulting array of CalObjTime elements. */
1510         all_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1511
1512         *finished = TRUE;
1513
1514         vtable = cal_obj_get_vtable (recur->freq);
1515         if (!vtable)
1516                 return all_occs;
1517
1518         /* Calculate some useful data such as some fast lookup tables. */
1519         cal_obj_initialize_recur_data (&recur_data, recur, event_start);
1520
1521         /* Compute the event_end, if the recur's enddate is set. */
1522         if (recur->enddate > 0) {
1523                 cal_object_time_from_time (&event_end_cotime,
1524                                            recur->enddate, zone);
1525                 event_end = &event_end_cotime;
1526
1527                 /* If the enddate is before the requested interval return. */
1528                 if (cal_obj_time_compare_func (event_end, interval_start) < 0)
1529                         return all_occs;
1530         }
1531
1532         /* Set finished to FALSE if we know there will be more occurrences to
1533            do after this interval. */
1534         if (!interval_end || !event_end
1535             || cal_obj_time_compare_func (event_end, interval_end) > 0)
1536                 *finished = FALSE;
1537
1538         /* Get the first period based on the frequency and the interval that
1539            intersects the interval between start and end. */
1540         if ((*vtable->find_start_position) (event_start, event_end,
1541                                             &recur_data,
1542                                             interval_start, interval_end,
1543                                             &occ))
1544                 return all_occs;
1545
1546         /* Loop until the event ends or we go past the end of the required
1547            interval. */
1548         for (;;) {
1549                 /* Generate the set of occurrences for this period. */
1550                 switch (recur->freq) {
1551                 case ICAL_YEARLY_RECURRENCE:
1552                         occs = cal_obj_generate_set_yearly (&recur_data,
1553                                                             vtable, &occ);
1554                         break;
1555                 case ICAL_MONTHLY_RECURRENCE:
1556                         occs = cal_obj_generate_set_monthly (&recur_data,
1557                                                              vtable, &occ);
1558                         break;
1559                 default:
1560                         occs = cal_obj_generate_set_default (&recur_data,
1561                                                              vtable, &occ);
1562                         break;
1563                 }
1564
1565                 /* Sort the occurrences and remove duplicates. */
1566                 cal_obj_sort_occurrences (occs);
1567                 cal_obj_remove_duplicates_and_invalid_dates (occs);
1568
1569                 /* Apply the BYSETPOS property. */
1570                 occs = cal_obj_bysetpos_filter (recur, occs);
1571
1572                 /* Remove any occs after event_end. */
1573                 len = occs->len - 1;
1574                 if (event_end) {
1575                         while (len >= 0) {
1576                                 cotime = &g_array_index (occs, CalObjTime,
1577                                                          len);
1578                                 if (cal_obj_time_compare_func (cotime,
1579                                                                event_end) <= 0)
1580                                         break;
1581                                 len--;
1582                         }
1583                 }
1584
1585                 /* Add the occurrences onto the main array. */
1586                 if (len >= 0)
1587                         g_array_append_vals (all_occs, occs->data, len + 1);
1588
1589                 g_array_free (occs, TRUE);
1590
1591                 /* Skip to the next period, or exit the loop if finished. */
1592                 if ((*vtable->find_next_position) (&occ, event_end,
1593                                                    &recur_data, interval_end))
1594                         break;
1595         }
1596
1597         return all_occs;
1598 }
1599
1600
1601 static GArray*
1602 cal_obj_generate_set_yearly     (RecurData *recur_data,
1603                                  ECalRecurVTable *vtable,
1604                                  CalObjTime *occ)
1605 {
1606         ECalRecurrence *recur = recur_data->recur;
1607         GArray *occs_arrays[4], *occs, *occs2;
1608         gint num_occs_arrays = 0, i;
1609
1610         /* This is a bit complicated, since the iCalendar spec says that
1611            several BYxxx modifiers can be used simultaneously. So we have to
1612            be quite careful when determining the days of the occurrences.
1613            The BYHOUR, BYMINUTE & BYSECOND modifiers are no problem at all.
1614
1615            The modifiers we have to worry about are: BYMONTH, BYWEEKNO,
1616            BYYEARDAY, BYMONTHDAY & BYDAY. We can't do these sequentially
1617            since each filter will mess up the results of the previous one.
1618            But they aren't all completely independant, e.g. BYMONTHDAY and
1619            BYDAY are related to BYMONTH, and BYDAY is related to BYWEEKNO.
1620
1621            BYDAY & BYMONTHDAY can also be applied independently, which makes
1622            it worse. So we assume that if BYMONTH or BYWEEKNO is used, then
1623            the BYDAY modifier applies to those, else it is applied
1624            independantly.
1625
1626            We expand the occurrences in parallel into the occs_arrays[] array,
1627            and then merge them all into one GArray before expanding BYHOUR,
1628            BYMINUTE & BYSECOND. */
1629
1630         if (recur->bymonth) {
1631                 occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1632                 g_array_append_vals (occs, occ, 1);
1633
1634                 occs = (*vtable->bymonth_filter) (recur_data, occs);
1635
1636                 /* If BYMONTHDAY & BYDAY are both set we need to expand them
1637                    in parallel and add the results. */
1638                 if (recur->bymonthday && recur->byday) {
1639                         /* Copy the occs array. */
1640                         occs2 = g_array_new (FALSE, FALSE,
1641                                              sizeof (CalObjTime));
1642                         g_array_append_vals (occs2, occs->data, occs->len);
1643
1644                         occs = (*vtable->bymonthday_filter) (recur_data, occs);
1645                         /* Note that we explicitly call the monthly version
1646                            of the BYDAY expansion filter. */
1647                         occs2 = cal_obj_byday_expand_monthly (recur_data,
1648                                                               occs2);
1649
1650                         /* Add the 2 resulting arrays together. */
1651                         g_array_append_vals (occs, occs2->data, occs2->len);
1652                         g_array_free (occs2, TRUE);
1653                 } else {
1654                         occs = (*vtable->bymonthday_filter) (recur_data, occs);
1655                         /* Note that we explicitly call the monthly version
1656                            of the BYDAY expansion filter. */
1657                         occs = cal_obj_byday_expand_monthly (recur_data, occs);
1658                 }
1659
1660                 occs_arrays[num_occs_arrays++] = occs;
1661         }
1662
1663         if (recur->byweekno) {
1664                 occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1665                 g_array_append_vals (occs, occ, 1);
1666
1667                 occs = (*vtable->byweekno_filter) (recur_data, occs);
1668                 /* Note that we explicitly call the weekly version of the
1669                    BYDAY expansion filter. */
1670                 occs = cal_obj_byday_expand_weekly (recur_data, occs);
1671
1672                 occs_arrays[num_occs_arrays++] = occs;
1673         }
1674
1675         if (recur->byyearday) {
1676                 occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1677                 g_array_append_vals (occs, occ, 1);
1678
1679                 occs = (*vtable->byyearday_filter) (recur_data, occs);
1680
1681                 occs_arrays[num_occs_arrays++] = occs;
1682         }
1683
1684         /* If BYMONTHDAY is set, and BYMONTH is not set, we need to
1685            expand BYMONTHDAY independantly. */
1686         if (recur->bymonthday && !recur->bymonth) {
1687                 occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1688                 g_array_append_vals (occs, occ, 1);
1689
1690                 occs = (*vtable->bymonthday_filter) (recur_data, occs);
1691
1692                 occs_arrays[num_occs_arrays++] = occs;
1693         }
1694
1695         /* If BYDAY is set, and BYMONTH and BYWEEKNO are not set, we need to
1696            expand BYDAY independantly. */
1697         if (recur->byday && !recur->bymonth && !recur->byweekno) {
1698                 occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1699                 g_array_append_vals (occs, occ, 1);
1700
1701                 occs = (*vtable->byday_filter) (recur_data, occs);
1702
1703                 occs_arrays[num_occs_arrays++] = occs;
1704         }
1705
1706         /* Add all the arrays together. If no filters were used we just
1707            create an array with one element. */
1708         if (num_occs_arrays > 0) {
1709                 occs = occs_arrays[0];
1710                 for (i = 1; i < num_occs_arrays; i++) {
1711                         occs2 = occs_arrays[i];
1712                         g_array_append_vals (occs, occs2->data, occs2->len);
1713                         g_array_free (occs2, TRUE);
1714                 }
1715         } else {
1716                 occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1717                 g_array_append_vals (occs, occ, 1);
1718         }
1719
1720         /* Now expand BYHOUR, BYMINUTE & BYSECOND. */
1721         occs = (*vtable->byhour_filter) (recur_data, occs);
1722         occs = (*vtable->byminute_filter) (recur_data, occs);
1723         occs = (*vtable->bysecond_filter) (recur_data, occs);
1724
1725         return occs;
1726 }
1727
1728
1729 static GArray*
1730 cal_obj_generate_set_monthly    (RecurData *recur_data,
1731                                  ECalRecurVTable *vtable,
1732                                  CalObjTime *occ)
1733 {
1734         GArray *occs, *occs2;
1735
1736         /* We start with just the one time in each set. */
1737         occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1738         g_array_append_vals (occs, occ, 1);
1739
1740         occs = (*vtable->bymonth_filter) (recur_data, occs);
1741
1742         /* We need to combine the output of BYMONTHDAY & BYDAY, by doing them
1743            in parallel rather than sequentially. If we did them sequentially
1744            then we would lose the occurrences generated by BYMONTHDAY, and
1745            instead have repetitions of the occurrences from BYDAY. */
1746         if (recur_data->recur->bymonthday && recur_data->recur->byday) {
1747                 occs2 = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1748                 g_array_append_vals (occs2, occs->data, occs->len);
1749
1750                 occs = (*vtable->bymonthday_filter) (recur_data, occs);
1751                 occs2 = (*vtable->byday_filter) (recur_data, occs2);
1752
1753                 g_array_append_vals (occs, occs2->data, occs2->len);
1754                 g_array_free (occs2, TRUE);
1755         } else {
1756                 occs = (*vtable->bymonthday_filter) (recur_data, occs);
1757                 occs = (*vtable->byday_filter) (recur_data, occs);
1758         }
1759
1760         occs = (*vtable->byhour_filter) (recur_data, occs);
1761         occs = (*vtable->byminute_filter) (recur_data, occs);
1762         occs = (*vtable->bysecond_filter) (recur_data, occs);
1763
1764         return occs;
1765 }
1766
1767
1768 static GArray*
1769 cal_obj_generate_set_default    (RecurData *recur_data,
1770                                  ECalRecurVTable *vtable,
1771                                  CalObjTime *occ)
1772 {
1773         GArray *occs;
1774
1775 #if 0
1776         g_print ("Generating set for %i/%i/%i %02i:%02i:%02i\n",
1777                  occ->day, occ->month + 1, occ->year, occ->hour, occ->minute,
1778                  occ->second);
1779 #endif
1780
1781         /* We start with just the one time in the set. */
1782         occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1783         g_array_append_vals (occs, occ, 1);
1784
1785         occs = (*vtable->bymonth_filter) (recur_data, occs);
1786         if (vtable->byweekno_filter)
1787                 occs = (*vtable->byweekno_filter) (recur_data, occs);
1788         if (vtable->byyearday_filter)
1789                 occs = (*vtable->byyearday_filter) (recur_data, occs);
1790         if (vtable->bymonthday_filter)
1791                 occs = (*vtable->bymonthday_filter) (recur_data, occs);
1792         occs = (*vtable->byday_filter) (recur_data, occs);
1793
1794         occs = (*vtable->byhour_filter) (recur_data, occs);
1795         occs = (*vtable->byminute_filter) (recur_data, occs);
1796         occs = (*vtable->bysecond_filter) (recur_data, occs);
1797
1798         return occs;
1799 }
1800
1801
1802
1803 /* Returns the function table corresponding to the recurrence frequency. */
1804 static ECalRecurVTable* cal_obj_get_vtable (icalrecurrencetype_frequency recur_type)
1805 {
1806         ECalRecurVTable* vtable;
1807
1808         switch (recur_type) {
1809         case ICAL_YEARLY_RECURRENCE:
1810                 vtable = &cal_obj_yearly_vtable;
1811                 break;
1812         case ICAL_MONTHLY_RECURRENCE:
1813                 vtable = &cal_obj_monthly_vtable;
1814                 break;
1815         case ICAL_WEEKLY_RECURRENCE:
1816                 vtable = &cal_obj_weekly_vtable;
1817                 break;
1818         case ICAL_DAILY_RECURRENCE:
1819                 vtable = &cal_obj_daily_vtable;
1820                 break;
1821         case ICAL_HOURLY_RECURRENCE:
1822                 vtable = &cal_obj_hourly_vtable;
1823                 break;
1824         case ICAL_MINUTELY_RECURRENCE:
1825                 vtable = &cal_obj_minutely_vtable;
1826                 break;
1827         case ICAL_SECONDLY_RECURRENCE:
1828                 vtable = &cal_obj_secondly_vtable;
1829                 break;
1830         default:
1831                 g_warning ("Unknown recurrence frequency");
1832                 vtable = NULL;
1833         }
1834
1835         return vtable;
1836 }
1837
1838
1839 /* This creates a number of fast lookup tables used when filtering with the
1840    modifier properties BYMONTH, BYYEARDAY etc. */
1841 static void
1842 cal_obj_initialize_recur_data (RecurData  *recur_data,
1843                                ECalRecurrence *recur,
1844                                CalObjTime *event_start)
1845 {
1846         GList *elem;
1847         gint month, yearday, monthday, weekday, week_num, hour, minute, second;
1848
1849         /* Clear the entire RecurData. */
1850         memset (recur_data, 0, sizeof (RecurData));
1851
1852         recur_data->recur = recur;
1853
1854         /* Set the weekday, used for the WEEKLY frequency and the BYWEEKNO
1855            modifier. */
1856         recur_data->weekday_offset = cal_obj_time_weekday_offset (event_start,
1857                                                                   recur);
1858
1859         /* Create an array of months from bymonths for fast lookup. */
1860         elem = recur->bymonth;
1861         while (elem) {
1862                 month = GPOINTER_TO_INT (elem->data);
1863                 recur_data->months[month] = 1;
1864                 elem = elem->next;
1865         }
1866
1867         /* Create an array of yeardays from byyearday for fast lookup.
1868            We create a second array to handle the negative values. The first
1869            element there corresponds to the last day of the year. */
1870         elem = recur->byyearday;
1871         while (elem) {
1872                 yearday = GPOINTER_TO_INT (elem->data);
1873                 if (yearday >= 0)
1874                         recur_data->yeardays[yearday] = 1;
1875                 else
1876                         recur_data->neg_yeardays[-yearday] = 1;
1877                 elem = elem->next;
1878         }
1879
1880         /* Create an array of monthdays from bymonthday for fast lookup.
1881            We create a second array to handle the negative values. The first
1882            element there corresponds to the last day of the month. */
1883         elem = recur->bymonthday;
1884         while (elem) {
1885                 monthday = GPOINTER_TO_INT (elem->data);
1886                 if (monthday >= 0)
1887                         recur_data->monthdays[monthday] = 1;
1888                 else
1889                         recur_data->neg_monthdays[-monthday] = 1;
1890                 elem = elem->next;
1891         }
1892
1893         /* Create an array of weekdays from byday for fast lookup. */
1894         elem = recur->byday;
1895         while (elem) {
1896                 weekday = GPOINTER_TO_INT (elem->data);
1897                 elem = elem->next;
1898                 /* The week number is not used when filtering. */
1899                 week_num = GPOINTER_TO_INT (elem->data);
1900                 elem = elem->next;
1901
1902                 recur_data->weekdays[weekday] = 1;
1903         }
1904
1905         /* Create an array of hours from byhour for fast lookup. */
1906         elem = recur->byhour;
1907         while (elem) {
1908                 hour = GPOINTER_TO_INT (elem->data);
1909                 recur_data->hours[hour] = 1;
1910                 elem = elem->next;
1911         }
1912
1913         /* Create an array of minutes from byminutes for fast lookup. */
1914         elem = recur->byminute;
1915         while (elem) {
1916                 minute = GPOINTER_TO_INT (elem->data);
1917                 recur_data->minutes[minute] = 1;
1918                 elem = elem->next;
1919         }
1920
1921         /* Create an array of seconds from byseconds for fast lookup. */
1922         elem = recur->bysecond;
1923         while (elem) {
1924                 second = GPOINTER_TO_INT (elem->data);
1925                 recur_data->seconds[second] = 1;
1926                 elem = elem->next;
1927         }
1928 }
1929
1930
1931 static void
1932 cal_obj_sort_occurrences (GArray *occs)
1933 {
1934         qsort (occs->data, occs->len, sizeof (CalObjTime),
1935                cal_obj_time_compare_func);
1936 }
1937
1938
1939 static void
1940 cal_obj_remove_duplicates_and_invalid_dates (GArray *occs)
1941 {
1942         static const int days_in_month[12] = {
1943                 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
1944         };
1945
1946         CalObjTime *occ, *prev_occ = NULL;
1947         gint len, i, j = 0, year, month, days;
1948         gboolean keep_occ;
1949
1950         len = occs->len;
1951         for (i = 0; i < len; i++) {
1952                 occ = &g_array_index (occs, CalObjTime, i);
1953                 keep_occ = TRUE;
1954
1955                 if (prev_occ && cal_obj_time_compare_func (occ,
1956                                                            prev_occ) == 0)
1957                         keep_occ = FALSE;
1958
1959                 year = occ->year;
1960                 month = occ->month;
1961                 days = days_in_month[occ->month];
1962                 /* If it is february and a leap year, add a day. */
1963                 if (month == 1 && (year % 4 == 0
1964                                    && (year % 100 != 0
1965                                        || year % 400 == 0)))
1966                         days++;
1967
1968                 if (occ->day > days) {
1969                         /* move occurrence to the last day of the month */
1970                         occ->day = days;
1971                 }
1972
1973                 if (keep_occ) {
1974                         if (i != j)
1975                                 g_array_index (occs, CalObjTime, j)
1976                                         = g_array_index (occs, CalObjTime, i);
1977                         j++;
1978                 }
1979
1980                 prev_occ = occ;
1981         }
1982
1983         g_array_set_size (occs, j);
1984 }
1985
1986
1987 /* Removes the exceptions from the ex_occs array from the occurrences in the
1988    occs array, and removes any duplicates. Both arrays are sorted. */
1989 static void
1990 cal_obj_remove_exceptions (GArray *occs,
1991                            GArray *ex_occs)
1992 {
1993         CalObjTime *occ, *prev_occ = NULL, *ex_occ = NULL, *last_occ_kept;
1994         gint i, j = 0, cmp, ex_index, occs_len, ex_occs_len;
1995         gboolean keep_occ, current_time_is_exception = FALSE;
1996
1997         if (occs->len == 0)
1998                 return;
1999
2000         ex_index = 0;
2001         occs_len = occs->len;
2002         ex_occs_len = ex_occs->len;
2003
2004         if (ex_occs_len > 0)
2005                 ex_occ = &g_array_index (ex_occs, CalObjTime, ex_index);
2006
2007         for (i = 0; i < occs_len; i++) {
2008                 occ = &g_array_index (occs, CalObjTime, i);
2009                 keep_occ = TRUE;
2010
2011                 /* If the occurrence is a duplicate of the previous one, skip
2012                    it. */
2013                 if (prev_occ
2014                     && cal_obj_time_compare_func (occ, prev_occ) == 0) {
2015                         keep_occ = FALSE;
2016
2017                         /* If this occurrence is an RDATE with an end or
2018                            duration set, and the previous occurrence in the
2019                            array was kept, set the RDATE flag of the last one,
2020                            so we still use the end date or duration. */
2021                         if (occ->flags && !current_time_is_exception) {
2022                                 last_occ_kept = &g_array_index (occs,
2023                                                                 CalObjTime,
2024                                                                 j - 1);
2025                                 last_occ_kept->flags = TRUE;
2026                         }
2027                 } else {
2028                         /* We've found a new occurrence time. Reset the flag
2029                            to indicate that it hasn't been found in the
2030                            exceptions array (yet). */
2031                         current_time_is_exception = FALSE;
2032
2033                         if (ex_occ) {
2034                                 /* Step through the exceptions until we come
2035                                    to one that matches or follows this
2036                                    occurrence. */
2037                                 while (ex_occ) {
2038                                         /* If the exception is an EXDATE with
2039                                            a DATE value, we only have to
2040                                            compare the date. */
2041                                         if (ex_occ->flags)
2042                                                 cmp = cal_obj_date_only_compare_func (ex_occ, occ);
2043                                         else
2044                                                 cmp = cal_obj_time_compare_func (ex_occ, occ);
2045
2046                                         if (cmp > 0)
2047                                                 break;
2048
2049                                         /* Move to the next exception, or set
2050                                            ex_occ to NULL when we reach the
2051                                            end of array. */
2052                                         ex_index++;
2053                                         if (ex_index < ex_occs_len)
2054                                                 ex_occ = &g_array_index (ex_occs, CalObjTime, ex_index);
2055                                         else
2056                                                 ex_occ = NULL;
2057
2058                                         /* If the exception did match this
2059                                            occurrence we remove it, and set the
2060                                            flag to indicate that the current
2061                                            time is an exception. */
2062                                         if (cmp == 0) {
2063                                                 current_time_is_exception = TRUE;
2064                                                 keep_occ = FALSE;
2065                                                 break;
2066                                         }
2067                                 }
2068                         }
2069                 }
2070
2071                 if (keep_occ) {
2072                         /* We are keeping this occurrence, so we move it to
2073                            the next free space, unless its position hasn't
2074                            changed (i.e. all previous occurrences were also
2075                            kept). */
2076                         if (i != j)
2077                                 g_array_index (occs, CalObjTime, j)
2078                                         = g_array_index (occs, CalObjTime, i);
2079                         j++;
2080                 }
2081
2082                 prev_occ = occ;
2083         }
2084
2085         g_array_set_size (occs, j);
2086 }
2087
2088
2089
2090 static GArray*
2091 cal_obj_bysetpos_filter (ECalRecurrence *recur,
2092                          GArray     *occs)
2093 {
2094         GArray *new_occs;
2095         CalObjTime *occ;
2096         GList *elem;
2097         gint len, pos;
2098
2099         /* If BYSETPOS has not been specified, or the array is empty, just
2100            return the array. */
2101         elem = recur->bysetpos;
2102         if (!elem || occs->len == 0)
2103                 return occs;
2104
2105         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2106
2107         /* Iterate over the indices given in bysetpos, adding the corresponding
2108            element from occs to new_occs. */
2109         len = occs->len;
2110         while (elem) {
2111                 pos = GPOINTER_TO_INT (elem->data);
2112
2113                 /* Negative values count back from the end of the array. */
2114                 if (pos < 0)
2115                         pos += len;
2116                 /* Positive values need to be decremented since the array is
2117                    0-based. */
2118                 else
2119                         pos--;
2120
2121                 if (pos >= 0 && pos < len) {
2122                         occ = &g_array_index (occs, CalObjTime, pos);
2123                         g_array_append_vals (new_occs, occ, 1);
2124                 }
2125                 elem = elem->next;
2126         }
2127
2128         g_array_free (occs, TRUE);
2129
2130         return new_occs;
2131 }
2132
2133
2134
2135
2136 /* Finds the first year from the event_start, counting in multiples of the
2137    recurrence interval, that intersects the given interval. It returns TRUE
2138    if there is no intersection. */
2139 static gboolean
2140 cal_obj_yearly_find_start_position (CalObjTime *event_start,
2141                                     CalObjTime *event_end,
2142                                     RecurData  *recur_data,
2143                                     CalObjTime *interval_start,
2144                                     CalObjTime *interval_end,
2145                                     CalObjTime *cotime)
2146 {
2147         *cotime = *event_start;
2148
2149         /* Move on to the next interval, if the event starts before the
2150            given interval. */
2151         if (cotime->year < interval_start->year) {
2152                 gint years = interval_start->year - cotime->year
2153                         + recur_data->recur->interval - 1;
2154                 years -= years % recur_data->recur->interval;
2155                 /* NOTE: The day may now be invalid, e.g. 29th Feb. */
2156                 cotime->year += years;
2157         }
2158
2159         if ((event_end && cotime->year > event_end->year)
2160             || (interval_end && cotime->year > interval_end->year))
2161                 return TRUE;
2162
2163         return FALSE;
2164 }
2165
2166
2167 static gboolean
2168 cal_obj_yearly_find_next_position (CalObjTime *cotime,
2169                                    CalObjTime *event_end,
2170                                    RecurData  *recur_data,
2171                                    CalObjTime *interval_end)
2172 {
2173         /* NOTE: The day may now be invalid, e.g. 29th Feb. */
2174         cotime->year += recur_data->recur->interval;
2175
2176         if ((event_end && cotime->year > event_end->year)
2177             || (interval_end && cotime->year > interval_end->year))
2178                 return TRUE;
2179
2180         return FALSE;
2181 }
2182
2183
2184
2185 static gboolean
2186 cal_obj_monthly_find_start_position (CalObjTime *event_start,
2187                                      CalObjTime *event_end,
2188                                      RecurData  *recur_data,
2189                                      CalObjTime *interval_start,
2190                                      CalObjTime *interval_end,
2191                                      CalObjTime *cotime)
2192 {
2193         *cotime = *event_start;
2194
2195         /* Move on to the next interval, if the event starts before the
2196            given interval. */
2197         if (cal_obj_time_compare (cotime, interval_start, CALOBJ_MONTH) < 0) {
2198                 gint months = (interval_start->year - cotime->year) * 12
2199                         + interval_start->month - cotime->month
2200                         + recur_data->recur->interval - 1;
2201                 months -= months % recur_data->recur->interval;
2202                 /* NOTE: The day may now be invalid, e.g. 31st Sep. */
2203                 cal_obj_time_add_months (cotime, months);
2204         }
2205
2206         if (event_end && cal_obj_time_compare (cotime, event_end,
2207                                                CALOBJ_MONTH) > 0)
2208                 return TRUE;
2209         if (interval_end && cal_obj_time_compare (cotime, interval_end,
2210                                                   CALOBJ_MONTH) > 0)
2211                 return TRUE;
2212
2213         return FALSE;
2214 }
2215
2216
2217 static gboolean
2218 cal_obj_monthly_find_next_position (CalObjTime *cotime,
2219                                     CalObjTime *event_end,
2220                                     RecurData  *recur_data,
2221                                     CalObjTime *interval_end)
2222 {
2223         /* NOTE: The day may now be invalid, e.g. 31st Sep. */
2224         cal_obj_time_add_months (cotime, recur_data->recur->interval);
2225
2226         if (event_end && cal_obj_time_compare (cotime, event_end,
2227                                                CALOBJ_MONTH) > 0)
2228                 return TRUE;
2229         if (interval_end && cal_obj_time_compare (cotime, interval_end,
2230                                                   CALOBJ_MONTH) > 0)
2231                 return TRUE;
2232
2233         return FALSE;
2234 }
2235
2236
2237
2238 static gboolean
2239 cal_obj_weekly_find_start_position (CalObjTime *event_start,
2240                                     CalObjTime *event_end,
2241                                     RecurData  *recur_data,
2242                                     CalObjTime *interval_start,
2243                                     CalObjTime *interval_end,
2244                                     CalObjTime *cotime)
2245 {
2246         GDate event_start_date, interval_start_date;
2247         guint32 event_start_julian, interval_start_julian;
2248         gint interval_start_weekday_offset;
2249         CalObjTime week_start;
2250
2251         if (event_end && cal_obj_time_compare (event_end, interval_start,
2252                                                CALOBJ_DAY) < 0)
2253                 return TRUE;
2254         if (interval_end && cal_obj_time_compare (event_start, interval_end,
2255                                                   CALOBJ_DAY) > 0)
2256                 return TRUE;
2257
2258         *cotime = *event_start;
2259
2260         /* Convert the event start and interval start to GDates, so we can
2261            easily find the number of days between them. */
2262         g_date_clear (&event_start_date, 1);
2263         g_date_set_dmy (&event_start_date, event_start->day,
2264                         event_start->month + 1, event_start->year);
2265         g_date_clear (&interval_start_date, 1);
2266         g_date_set_dmy (&interval_start_date, interval_start->day,
2267                         interval_start->month + 1, interval_start->year);
2268
2269         /* Calculate the start of the weeks corresponding to the event start
2270            and interval start. */
2271         event_start_julian = g_date_get_julian (&event_start_date);
2272         event_start_julian -= recur_data->weekday_offset;
2273
2274         interval_start_julian = g_date_get_julian (&interval_start_date);
2275         interval_start_weekday_offset = cal_obj_time_weekday_offset (interval_start, recur_data->recur);
2276         interval_start_julian -= interval_start_weekday_offset;
2277
2278         /* We want to find the first full week using the recurrence interval
2279            that intersects the given interval dates. */
2280         if (event_start_julian < interval_start_julian) {
2281                 gint weeks = (interval_start_julian - event_start_julian) / 7;
2282                 weeks += recur_data->recur->interval - 1;
2283                 weeks -= weeks % recur_data->recur->interval;
2284                 cal_obj_time_add_days (cotime, weeks * 7);
2285         }
2286
2287         week_start = *cotime;
2288         cal_obj_time_add_days (&week_start, -recur_data->weekday_offset);
2289
2290         if (event_end && cal_obj_time_compare (&week_start, event_end,
2291                                                CALOBJ_DAY) > 0)
2292                 return TRUE;
2293         if (interval_end && cal_obj_time_compare (&week_start, interval_end,
2294                                                   CALOBJ_DAY) > 0)
2295                 return TRUE;
2296
2297         return FALSE;
2298 }
2299
2300
2301 static gboolean
2302 cal_obj_weekly_find_next_position (CalObjTime *cotime,
2303                                    CalObjTime *event_end,
2304                                    RecurData  *recur_data,
2305                                    CalObjTime *interval_end)
2306 {
2307         CalObjTime week_start;
2308
2309         cal_obj_time_add_days (cotime, recur_data->recur->interval * 7);
2310
2311         /* Return TRUE if the start of this week is after the event finishes
2312            or is after the end of the required interval. */
2313         week_start = *cotime;
2314         cal_obj_time_add_days (&week_start, -recur_data->weekday_offset);
2315
2316 #ifdef CAL_OBJ_DEBUG
2317         g_print ("Next  day: %s\n", cal_obj_time_to_string (cotime));
2318         g_print ("Week Start: %s\n", cal_obj_time_to_string (&week_start));
2319 #endif
2320
2321         if (event_end && cal_obj_time_compare (&week_start, event_end,
2322                                                CALOBJ_DAY) > 0)
2323                 return TRUE;
2324         if (interval_end && cal_obj_time_compare (&week_start, interval_end,
2325                                                   CALOBJ_DAY) > 0) {
2326 #ifdef CAL_OBJ_DEBUG
2327                 g_print ("Interval end reached: %s\n",
2328                          cal_obj_time_to_string (interval_end));
2329 #endif
2330                 return TRUE;
2331         }
2332
2333         return FALSE;
2334 }
2335
2336
2337 static gboolean
2338 cal_obj_daily_find_start_position  (CalObjTime *event_start,
2339                                     CalObjTime *event_end,
2340                                     RecurData  *recur_data,
2341                                     CalObjTime *interval_start,
2342                                     CalObjTime *interval_end,
2343                                     CalObjTime *cotime)
2344 {
2345         GDate event_start_date, interval_start_date;
2346         guint32 event_start_julian, interval_start_julian, days;
2347
2348         if (interval_end && cal_obj_time_compare (event_start, interval_end,
2349                                                   CALOBJ_DAY) > 0)
2350                 return TRUE;
2351         if (event_end && cal_obj_time_compare (event_end, interval_start,
2352                                                CALOBJ_DAY) < 0)
2353                 return TRUE;
2354
2355         *cotime = *event_start;
2356
2357         /* Convert the event start and interval start to GDates, so we can
2358            easily find the number of days between them. */
2359         g_date_clear (&event_start_date, 1);
2360         g_date_set_dmy (&event_start_date, event_start->day,
2361                         event_start->month + 1, event_start->year);
2362         g_date_clear (&interval_start_date, 1);
2363         g_date_set_dmy (&interval_start_date, interval_start->day,
2364                         interval_start->month + 1, interval_start->year);
2365
2366         event_start_julian = g_date_get_julian (&event_start_date);
2367         interval_start_julian = g_date_get_julian (&interval_start_date);
2368
2369         if (event_start_julian < interval_start_julian) {
2370                 days = interval_start_julian - event_start_julian
2371                         + recur_data->recur->interval - 1;
2372                 days -= days % recur_data->recur->interval;
2373                 cal_obj_time_add_days (cotime, days);
2374         }
2375
2376         if (event_end && cal_obj_time_compare (cotime, event_end,
2377                                                CALOBJ_DAY) > 0)
2378                 return TRUE;
2379         if (interval_end && cal_obj_time_compare (cotime, interval_end,
2380                                                   CALOBJ_DAY) > 0)
2381                 return TRUE;
2382
2383         return FALSE;
2384 }
2385
2386
2387 static gboolean
2388 cal_obj_daily_find_next_position  (CalObjTime *cotime,
2389                                    CalObjTime *event_end,
2390                                    RecurData  *recur_data,
2391                                    CalObjTime *interval_end)
2392 {
2393         cal_obj_time_add_days (cotime, recur_data->recur->interval);
2394
2395         if (event_end && cal_obj_time_compare (cotime, event_end,
2396                                                CALOBJ_DAY) > 0)
2397                 return TRUE;
2398         if (interval_end && cal_obj_time_compare (cotime, interval_end,
2399                                                   CALOBJ_DAY) > 0)
2400                 return TRUE;
2401
2402         return FALSE;
2403 }
2404
2405
2406 static gboolean
2407 cal_obj_hourly_find_start_position (CalObjTime *event_start,
2408                                     CalObjTime *event_end,
2409                                     RecurData  *recur_data,
2410                                     CalObjTime *interval_start,
2411                                     CalObjTime *interval_end,
2412                                     CalObjTime *cotime)
2413 {
2414         GDate event_start_date, interval_start_date;
2415         guint32 event_start_julian, interval_start_julian, hours;
2416
2417         if (interval_end && cal_obj_time_compare (event_start, interval_end,
2418                                                   CALOBJ_HOUR) > 0)
2419                 return TRUE;
2420         if (event_end && cal_obj_time_compare (event_end, interval_start,
2421                                                CALOBJ_HOUR) < 0)
2422                 return TRUE;
2423
2424         *cotime = *event_start;
2425
2426         if (cal_obj_time_compare (event_start, interval_start,
2427                                   CALOBJ_HOUR) < 0) {
2428                 /* Convert the event start and interval start to GDates, so we
2429                    can easily find the number of days between them. */
2430                 g_date_clear (&event_start_date, 1);
2431                 g_date_set_dmy (&event_start_date, event_start->day,
2432                                 event_start->month + 1, event_start->year);
2433                 g_date_clear (&interval_start_date, 1);
2434                 g_date_set_dmy (&interval_start_date, interval_start->day,
2435                                 interval_start->month + 1,
2436                                 interval_start->year);
2437
2438                 event_start_julian = g_date_get_julian (&event_start_date);
2439                 interval_start_julian = g_date_get_julian (&interval_start_date);
2440
2441                 hours = (interval_start_julian - event_start_julian) * 24;
2442                 hours += interval_start->hour - event_start->hour;
2443                 hours += recur_data->recur->interval - 1;
2444                 hours -= hours % recur_data->recur->interval;
2445                 cal_obj_time_add_hours (cotime, hours);
2446         }
2447
2448         if (event_end && cal_obj_time_compare (cotime, event_end,
2449                                                CALOBJ_HOUR) > 0)
2450                 return TRUE;
2451         if (interval_end && cal_obj_time_compare (cotime, interval_end,
2452                                                   CALOBJ_HOUR) > 0)
2453                 return TRUE;
2454
2455         return FALSE;
2456 }
2457
2458
2459 static gboolean
2460 cal_obj_hourly_find_next_position (CalObjTime *cotime,
2461                                    CalObjTime *event_end,
2462                                    RecurData  *recur_data,
2463                                    CalObjTime *interval_end)
2464 {
2465         cal_obj_time_add_hours (cotime, recur_data->recur->interval);
2466
2467         if (event_end && cal_obj_time_compare (cotime, event_end,
2468                                                CALOBJ_HOUR) > 0)
2469                 return TRUE;
2470         if (interval_end && cal_obj_time_compare (cotime, interval_end,
2471                                                   CALOBJ_HOUR) > 0)
2472                 return TRUE;
2473
2474         return FALSE;
2475 }
2476
2477
2478 static gboolean
2479 cal_obj_minutely_find_start_position (CalObjTime *event_start,
2480                                       CalObjTime *event_end,
2481                                       RecurData  *recur_data,
2482                                       CalObjTime *interval_start,
2483                                       CalObjTime *interval_end,
2484                                       CalObjTime *cotime)
2485 {
2486         GDate event_start_date, interval_start_date;
2487         guint32 event_start_julian, interval_start_julian, minutes;
2488
2489         if (interval_end && cal_obj_time_compare (event_start, interval_end,
2490                                                   CALOBJ_MINUTE) > 0)
2491                 return TRUE;
2492         if (event_end && cal_obj_time_compare (event_end, interval_start,
2493                                                CALOBJ_MINUTE) < 0)
2494                 return TRUE;
2495
2496         *cotime = *event_start;
2497
2498         if (cal_obj_time_compare (event_start, interval_start,
2499                                   CALOBJ_MINUTE) < 0) {
2500                 /* Convert the event start and interval start to GDates, so we
2501                    can easily find the number of days between them. */
2502                 g_date_clear (&event_start_date, 1);
2503                 g_date_set_dmy (&event_start_date, event_start->day,
2504                                 event_start->month + 1, event_start->year);
2505                 g_date_clear (&interval_start_date, 1);
2506                 g_date_set_dmy (&interval_start_date, interval_start->day,
2507                                 interval_start->month + 1,
2508                                 interval_start->year);
2509
2510                 event_start_julian = g_date_get_julian (&event_start_date);
2511                 interval_start_julian = g_date_get_julian (&interval_start_date);
2512
2513                 minutes = (interval_start_julian - event_start_julian)
2514                         * 24 * 60;
2515                 minutes += (interval_start->hour - event_start->hour) * 24;
2516                 minutes += interval_start->minute - event_start->minute;
2517                 minutes += recur_data->recur->interval - 1;
2518                 minutes -= minutes % recur_data->recur->interval;
2519                 cal_obj_time_add_minutes (cotime, minutes);
2520         }
2521
2522         if (event_end && cal_obj_time_compare (cotime, event_end,
2523                                                CALOBJ_MINUTE) > 0)
2524                 return TRUE;
2525         if (interval_end && cal_obj_time_compare (cotime, interval_end,
2526                                                   CALOBJ_MINUTE) > 0)
2527                 return TRUE;
2528
2529         return FALSE;
2530 }
2531
2532
2533 static gboolean
2534 cal_obj_minutely_find_next_position (CalObjTime *cotime,
2535                                      CalObjTime *event_end,
2536                                      RecurData  *recur_data,
2537                                      CalObjTime *interval_end)
2538 {
2539         cal_obj_time_add_minutes (cotime, recur_data->recur->interval);
2540
2541         if (event_end && cal_obj_time_compare (cotime, event_end,
2542                                                CALOBJ_MINUTE) > 0)
2543                 return TRUE;
2544         if (interval_end && cal_obj_time_compare (cotime, interval_end,
2545                                                   CALOBJ_MINUTE) > 0)
2546                 return TRUE;
2547
2548         return FALSE;
2549 }
2550
2551
2552 static gboolean
2553 cal_obj_secondly_find_start_position (CalObjTime *event_start,
2554                                       CalObjTime *event_end,
2555                                       RecurData  *recur_data,
2556                                       CalObjTime *interval_start,
2557                                       CalObjTime *interval_end,
2558                                       CalObjTime *cotime)
2559 {
2560         GDate event_start_date, interval_start_date;
2561         guint32 event_start_julian, interval_start_julian, seconds;
2562
2563         if (interval_end && cal_obj_time_compare (event_start, interval_end,
2564                                                   CALOBJ_SECOND) > 0)
2565                 return TRUE;
2566         if (event_end && cal_obj_time_compare (event_end, interval_start,
2567                                                CALOBJ_SECOND) < 0)
2568                 return TRUE;
2569
2570         *cotime = *event_start;
2571
2572         if (cal_obj_time_compare (event_start, interval_start,
2573                                   CALOBJ_SECOND) < 0) {
2574                 /* Convert the event start and interval start to GDates, so we
2575                    can easily find the number of days between them. */
2576                 g_date_clear (&event_start_date, 1);
2577                 g_date_set_dmy (&event_start_date, event_start->day,
2578                                 event_start->month + 1, event_start->year);
2579                 g_date_clear (&interval_start_date, 1);
2580                 g_date_set_dmy (&interval_start_date, interval_start->day,
2581                                 interval_start->month + 1,
2582                                 interval_start->year);
2583
2584                 event_start_julian = g_date_get_julian (&event_start_date);
2585                 interval_start_julian = g_date_get_julian (&interval_start_date);
2586
2587                 seconds = (interval_start_julian - event_start_julian)
2588                         * 24 * 60 * 60;
2589                 seconds += (interval_start->hour - event_start->hour)
2590                         * 24 * 60;
2591                 seconds += (interval_start->minute - event_start->minute) * 60;
2592                 seconds += interval_start->second - event_start->second;
2593                 seconds += recur_data->recur->interval - 1;
2594                 seconds -= seconds % recur_data->recur->interval;
2595                 cal_obj_time_add_seconds (cotime, seconds);
2596         }
2597
2598         if (event_end && cal_obj_time_compare (cotime, event_end,
2599                                                CALOBJ_SECOND) >= 0)
2600                 return TRUE;
2601         if (interval_end && cal_obj_time_compare (cotime, interval_end,
2602                                                   CALOBJ_SECOND) >= 0)
2603                 return TRUE;
2604
2605         return FALSE;
2606 }
2607
2608
2609 static gboolean
2610 cal_obj_secondly_find_next_position (CalObjTime *cotime,
2611                                      CalObjTime *event_end,
2612                                      RecurData  *recur_data,
2613                                      CalObjTime *interval_end)
2614 {
2615         cal_obj_time_add_seconds (cotime, recur_data->recur->interval);
2616
2617         if (event_end && cal_obj_time_compare (cotime, event_end,
2618                                                CALOBJ_SECOND) >= 0)
2619                 return TRUE;
2620         if (interval_end && cal_obj_time_compare (cotime, interval_end,
2621                                                   CALOBJ_SECOND) >= 0)
2622                 return TRUE;
2623
2624         return FALSE;
2625 }
2626
2627
2628
2629
2630
2631 /* If the BYMONTH rule is specified it expands each occurrence in occs, by
2632    using each of the months in the bymonth list. */
2633 static GArray*
2634 cal_obj_bymonth_expand          (RecurData  *recur_data,
2635                                  GArray     *occs)
2636 {
2637         GArray *new_occs;
2638         CalObjTime *occ;
2639         GList *elem;
2640         gint len, i;
2641
2642         /* If BYMONTH has not been specified, or the array is empty, just
2643            return the array. */
2644         if (!recur_data->recur->bymonth || occs->len == 0)
2645                 return occs;
2646
2647         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2648
2649         len = occs->len;
2650         for (i = 0; i < len; i++) {
2651                 occ = &g_array_index (occs, CalObjTime, i);
2652
2653                 elem = recur_data->recur->bymonth;
2654                 while (elem) {
2655                         /* NOTE: The day may now be invalid, e.g. 31st Feb. */
2656                         occ->month = GPOINTER_TO_INT (elem->data);
2657                         g_array_append_vals (new_occs, occ, 1);
2658                         elem = elem->next;
2659                 }
2660         }
2661
2662         g_array_free (occs, TRUE);
2663
2664         return new_occs;
2665 }
2666
2667
2668 /* If the BYMONTH rule is specified it filters out all occurrences in occs
2669    which do not match one of the months in the bymonth list. */
2670 static GArray*
2671 cal_obj_bymonth_filter          (RecurData  *recur_data,
2672                                  GArray     *occs)
2673 {
2674         GArray *new_occs;
2675         CalObjTime *occ;
2676         gint len, i;
2677
2678         /* If BYMONTH has not been specified, or the array is empty, just
2679            return the array. */
2680         if (!recur_data->recur->bymonth || occs->len == 0)
2681                 return occs;
2682
2683         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2684
2685         len = occs->len;
2686         for (i = 0; i < len; i++) {
2687                 occ = &g_array_index (occs, CalObjTime, i);
2688                 if (recur_data->months[occ->month])
2689                         g_array_append_vals (new_occs, occ, 1);
2690         }
2691
2692         g_array_free (occs, TRUE);
2693
2694         return new_occs;
2695 }
2696
2697
2698
2699 static GArray*
2700 cal_obj_byweekno_expand         (RecurData  *recur_data,
2701                                  GArray     *occs)
2702 {
2703         GArray *new_occs;
2704         CalObjTime *occ, year_start_cotime, year_end_cotime, cotime;
2705         GList *elem;
2706         gint len, i, weekno;
2707
2708         /* If BYWEEKNO has not been specified, or the array is empty, just
2709            return the array. */
2710         if (!recur_data->recur->byweekno || occs->len == 0)
2711                 return occs;
2712
2713         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2714
2715         len = occs->len;
2716         for (i = 0; i < len; i++) {
2717                 occ = &g_array_index (occs, CalObjTime, i);
2718
2719                 /* Find the day that would correspond to week 1 (note that
2720                    week 1 is the first week starting from the specified week
2721                    start day that has 4 days in the new year). */
2722                 year_start_cotime = *occ;
2723                 cal_obj_time_find_first_week (&year_start_cotime,
2724                                               recur_data);
2725
2726                 /* Find the day that would correspond to week 1 of the next
2727                    year, which we use for -ve week numbers. */
2728                 year_end_cotime = *occ;
2729                 year_end_cotime.year++;
2730                 cal_obj_time_find_first_week (&year_end_cotime,
2731                                               recur_data);
2732
2733                 /* Now iterate over the week numbers in byweekno, generating a
2734                    new occurrence for each one. */
2735                 elem = recur_data->recur->byweekno;
2736                 while (elem) {
2737                         weekno = GPOINTER_TO_INT (elem->data);
2738                         if (weekno > 0) {
2739                                 cotime = year_start_cotime;
2740                                 cal_obj_time_add_days (&cotime,
2741                                                        (weekno - 1) * 7);
2742                         } else {
2743                                 cotime = year_end_cotime;
2744                                 cal_obj_time_add_days (&cotime, weekno * 7);
2745                         }
2746
2747                         /* Skip occurrences if they fall outside the year. */
2748                         if (cotime.year == occ->year)
2749                                 g_array_append_val (new_occs, cotime);
2750                         elem = elem->next;
2751                 }
2752         }
2753
2754         g_array_free (occs, TRUE);
2755
2756         return new_occs;
2757 }
2758
2759
2760 #if 0
2761 /* This isn't used at present. */
2762 static GArray*
2763 cal_obj_byweekno_filter         (RecurData  *recur_data,
2764                                  GArray     *occs)
2765 {
2766
2767         return occs;
2768 }
2769 #endif
2770
2771
2772 static GArray*
2773 cal_obj_byyearday_expand        (RecurData  *recur_data,
2774                                  GArray     *occs)
2775 {
2776         GArray *new_occs;
2777         CalObjTime *occ, year_start_cotime, year_end_cotime, cotime;
2778         GList *elem;
2779         gint len, i, dayno;
2780
2781         /* If BYYEARDAY has not been specified, or the array is empty, just
2782            return the array. */
2783         if (!recur_data->recur->byyearday || occs->len == 0)
2784                 return occs;
2785
2786         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2787
2788         len = occs->len;
2789         for (i = 0; i < len; i++) {
2790                 occ = &g_array_index (occs, CalObjTime, i);
2791
2792                 /* Find the day that would correspond to day 1. */
2793                 year_start_cotime = *occ;
2794                 year_start_cotime.month = 0;
2795                 year_start_cotime.day = 1;
2796
2797                 /* Find the day that would correspond to day 1 of the next
2798                    year, which we use for -ve day numbers. */
2799                 year_end_cotime = *occ;
2800                 year_end_cotime.year++;
2801                 year_end_cotime.month = 0;
2802                 year_end_cotime.day = 1;
2803
2804                 /* Now iterate over the day numbers in byyearday, generating a
2805                    new occurrence for each one. */
2806                 elem = recur_data->recur->byyearday;
2807                 while (elem) {
2808                         dayno = GPOINTER_TO_INT (elem->data);
2809                         if (dayno > 0) {
2810                                 cotime = year_start_cotime;
2811                                 cal_obj_time_add_days (&cotime, dayno - 1);
2812                         } else {
2813                                 cotime = year_end_cotime;
2814                                 cal_obj_time_add_days (&cotime, dayno);
2815                         }
2816
2817                         /* Skip occurrences if they fall outside the year. */
2818                         if (cotime.year == occ->year)
2819                                 g_array_append_val (new_occs, cotime);
2820                         elem = elem->next;
2821                 }
2822         }
2823
2824         g_array_free (occs, TRUE);
2825
2826         return new_occs;
2827 }
2828
2829
2830 /* Note: occs must not contain invalid dates, e.g. 31st September. */
2831 static GArray*
2832 cal_obj_byyearday_filter        (RecurData  *recur_data,
2833                                  GArray     *occs)
2834 {
2835         GArray *new_occs;
2836         CalObjTime *occ;
2837         gint yearday, len, i, days_in_year;
2838
2839         /* If BYYEARDAY has not been specified, or the array is empty, just
2840            return the array. */
2841         if (!recur_data->recur->byyearday || occs->len == 0)
2842                 return occs;
2843
2844         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2845
2846         len = occs->len;
2847         for (i = 0; i < len; i++) {
2848                 occ = &g_array_index (occs, CalObjTime, i);
2849                 yearday = cal_obj_time_day_of_year (occ);
2850                 if (recur_data->yeardays[yearday]) {
2851                         g_array_append_vals (new_occs, occ, 1);
2852                 } else {
2853                         days_in_year = g_date_is_leap_year (occ->year)
2854                                 ? 366 : 365;
2855                         if (recur_data->neg_yeardays[days_in_year + 1
2856                                                     - yearday])
2857                                 g_array_append_vals (new_occs, occ, 1);
2858                 }
2859         }
2860
2861         g_array_free (occs, TRUE);
2862
2863         return new_occs;
2864 }
2865
2866
2867
2868 static GArray*
2869 cal_obj_bymonthday_expand       (RecurData  *recur_data,
2870                                  GArray     *occs)
2871 {
2872         GArray *new_occs;
2873         CalObjTime *occ, month_start_cotime, month_end_cotime, cotime;
2874         GList *elem;
2875         gint len, i, dayno;
2876
2877         /* If BYMONTHDAY has not been specified, or the array is empty, just
2878            return the array. */
2879         if (!recur_data->recur->bymonthday || occs->len == 0)
2880                 return occs;
2881
2882         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2883
2884         len = occs->len;
2885         for (i = 0; i < len; i++) {
2886                 occ = &g_array_index (occs, CalObjTime, i);
2887
2888                 /* Find the day that would correspond to day 1. */
2889                 month_start_cotime = *occ;
2890                 month_start_cotime.day = 1;
2891
2892                 /* Find the day that would correspond to day 1 of the next
2893                    month, which we use for -ve day numbers. */
2894                 month_end_cotime = *occ;
2895                 month_end_cotime.month++;
2896                 month_end_cotime.day = 1;
2897
2898                 /* Now iterate over the day numbers in bymonthday, generating a
2899                    new occurrence for each one. */
2900                 elem = recur_data->recur->bymonthday;
2901                 while (elem) {
2902                         dayno = GPOINTER_TO_INT (elem->data);
2903                         if (dayno > 0) {
2904                                 cotime = month_start_cotime;
2905                                 cal_obj_time_add_days (&cotime, dayno - 1);
2906                         } else {
2907                                 cotime = month_end_cotime;
2908                                 cal_obj_time_add_days (&cotime, dayno);
2909                         }
2910                         if (cotime.month == occ->month) {
2911                                 g_array_append_val (new_occs, cotime);
2912                         } else {
2913                                 /* set to last day in month */
2914                                 cotime.month = occ->month;
2915                                 cotime.day = time_days_in_month (occ->year, occ->month);
2916                                 g_array_append_val (new_occs, cotime);
2917                         }
2918                                 
2919                         elem = elem->next;
2920                 }
2921         }
2922
2923         g_array_free (occs, TRUE);
2924
2925         return new_occs;
2926 }
2927
2928
2929 static GArray*
2930 cal_obj_bymonthday_filter       (RecurData  *recur_data,
2931                                  GArray     *occs)
2932 {
2933         GArray *new_occs;
2934         CalObjTime *occ;
2935         gint len, i, days_in_month;
2936
2937         /* If BYMONTHDAY has not been specified, or the array is empty, just
2938            return the array. */
2939         if (!recur_data->recur->bymonthday || occs->len == 0)
2940                 return occs;
2941
2942         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2943
2944         len = occs->len;
2945         for (i = 0; i < len; i++) {
2946                 occ = &g_array_index (occs, CalObjTime, i);
2947                 if (recur_data->monthdays[occ->day]) {
2948                         g_array_append_vals (new_occs, occ, 1);
2949                 } else {
2950                         days_in_month = time_days_in_month (occ->year,
2951                                                             occ->month);
2952                         if (recur_data->neg_monthdays[days_in_month + 1
2953                                                      - occ->day])
2954                                 g_array_append_vals (new_occs, occ, 1);
2955                 }
2956         }
2957
2958         g_array_free (occs, TRUE);
2959
2960         return new_occs;
2961 }
2962
2963
2964
2965 static GArray*
2966 cal_obj_byday_expand_yearly     (RecurData  *recur_data,
2967                                  GArray     *occs)
2968 {
2969         GArray *new_occs;
2970         CalObjTime *occ;
2971         GList *elem;
2972         gint len, i, weekday, week_num;
2973         gint first_weekday, last_weekday, offset;
2974         guint16 year;
2975
2976         /* If BYDAY has not been specified, or the array is empty, just
2977            return the array. */
2978         if (!recur_data->recur->byday || occs->len == 0)
2979                 return occs;
2980
2981         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2982
2983         len = occs->len;
2984         for (i = 0; i < len; i++) {
2985                 occ = &g_array_index (occs, CalObjTime, i);
2986
2987                 elem = recur_data->recur->byday;
2988                 while (elem) {
2989                         weekday = GPOINTER_TO_INT (elem->data);
2990                         elem = elem->next;
2991                         week_num = GPOINTER_TO_INT (elem->data);
2992                         elem = elem->next;
2993
2994                         year = occ->year;
2995                         if (week_num == 0) {
2996                                 /* Expand to every Mon/Tue/etc. in the year. */
2997                                 occ->month = 0;
2998                                 occ->day = 1;
2999                                 first_weekday = cal_obj_time_weekday (occ);
3000                                 offset = (weekday + 7 - first_weekday) % 7;
3001                                 cal_obj_time_add_days (occ, offset);
3002
3003                                 while (occ->year == year) {
3004                                         g_array_append_vals (new_occs, occ, 1);
3005                                         cal_obj_time_add_days (occ, 7);
3006                                 }
3007
3008                         } else if (week_num > 0) {
3009                                 /* Add the nth Mon/Tue/etc. in the year. */
3010                                 occ->month = 0;
3011                                 occ->day = 1;
3012                                 first_weekday = cal_obj_time_weekday (occ);
3013                                 offset = (weekday + 7 - first_weekday) % 7;
3014                                 offset += (week_num - 1) * 7;
3015                                 cal_obj_time_add_days (occ, offset);
3016                                 if (occ->year == year)
3017                                         g_array_append_vals (new_occs, occ, 1);
3018
3019                         } else {
3020                                 /* Add the -nth Mon/Tue/etc. in the year. */
3021                                 occ->month = 11;
3022                                 occ->day = 31;
3023                                 last_weekday = cal_obj_time_weekday (occ);
3024                                 offset = (last_weekday + 7 - weekday) % 7;
3025                                 offset += (week_num - 1) * 7;
3026                                 cal_obj_time_add_days (occ, -offset);
3027                                 if (occ->year == year)
3028                                         g_array_append_vals (new_occs, occ, 1);
3029                         }
3030
3031                         /* Reset the year, as we may have gone past the end. */
3032                         occ->year = year;
3033                 }
3034         }
3035
3036         g_array_free (occs, TRUE);
3037
3038         return new_occs;
3039 }
3040
3041
3042 static GArray*
3043 cal_obj_byday_expand_monthly    (RecurData  *recur_data,
3044                                  GArray     *occs)
3045 {
3046         GArray *new_occs;
3047         CalObjTime *occ;
3048         GList *elem;
3049         gint len, i, weekday, week_num;
3050         gint first_weekday, last_weekday, offset;
3051         guint16 year;
3052         guint8 month;
3053
3054         /* If BYDAY has not been specified, or the array is empty, just
3055            return the array. */
3056         if (!recur_data->recur->byday || occs->len == 0)
3057                 return occs;
3058
3059         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3060
3061         len = occs->len;
3062         for (i = 0; i < len; i++) {
3063                 occ = &g_array_index (occs, CalObjTime, i);
3064
3065                 elem = recur_data->recur->byday;
3066                 while (elem) {
3067                         weekday = GPOINTER_TO_INT (elem->data);
3068                         elem = elem->next;
3069                         week_num = GPOINTER_TO_INT (elem->data);
3070                         elem = elem->next;
3071
3072                         year = occ->year;
3073                         month = occ->month;
3074                         if (week_num == 0) {
3075                                 /* Expand to every Mon/Tue/etc. in the month.*/
3076                                 occ->day = 1;
3077                                 first_weekday = cal_obj_time_weekday (occ);
3078                                 offset = (weekday + 7 - first_weekday) % 7;
3079                                 cal_obj_time_add_days (occ, offset);
3080
3081                                 while (occ->year == year
3082                                        && occ->month == month) {
3083                                         g_array_append_vals (new_occs, occ, 1);
3084                                         cal_obj_time_add_days (occ, 7);
3085                                 }
3086
3087                         } else if (week_num > 0) {
3088                                 /* Add the nth Mon/Tue/etc. in the month. */
3089                                 occ->day = 1;
3090                                 first_weekday = cal_obj_time_weekday (occ);
3091                                 offset = (weekday + 7 - first_weekday) % 7;
3092                                 offset += (week_num - 1) * 7;
3093                                 cal_obj_time_add_days (occ, offset);
3094                                 if (occ->year == year && occ->month == month)
3095                                         g_array_append_vals (new_occs, occ, 1);
3096
3097                         } else {
3098                                 /* Add the -nth Mon/Tue/etc. in the month. */
3099                                 occ->day = time_days_in_month (occ->year,
3100                                                                occ->month);
3101                                 last_weekday = cal_obj_time_weekday (occ);
3102
3103                                 /* This calculates the number of days to step
3104                                    backwards from the last day of the month
3105                                    to the weekday we want. */
3106                                 offset = (last_weekday + 7 - weekday) % 7;
3107
3108                                 /* This adds on the weeks. */
3109                                 offset += (-week_num - 1) * 7;
3110
3111                                 cal_obj_time_add_days (occ, -offset);
3112                                 if (occ->year == year && occ->month == month)
3113                                         g_array_append_vals (new_occs, occ, 1);
3114                         }
3115
3116                         /* Reset the year & month, as we may have gone past
3117                            the end. */
3118                         occ->year = year;
3119                         occ->month = month;
3120                 }
3121         }
3122
3123         g_array_free (occs, TRUE);
3124
3125         return new_occs;
3126 }
3127
3128
3129 /* Note: occs must not contain invalid dates, e.g. 31st September. */
3130 static GArray*
3131 cal_obj_byday_expand_weekly     (RecurData  *recur_data,
3132                                  GArray     *occs)
3133 {
3134         GArray *new_occs;
3135         CalObjTime *occ;
3136         GList *elem;
3137         gint len, i, weekday, week_num;
3138         gint weekday_offset, new_weekday_offset;
3139
3140         /* If BYDAY has not been specified, or the array is empty, just
3141            return the array. */
3142         if (!recur_data->recur->byday || occs->len == 0)
3143                 return occs;
3144
3145         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3146
3147         len = occs->len;
3148         for (i = 0; i < len; i++) {
3149                 occ = &g_array_index (occs, CalObjTime, i);
3150
3151                 elem = recur_data->recur->byday;
3152                 while (elem) {
3153                         weekday = GPOINTER_TO_INT (elem->data);
3154                         elem = elem->next;
3155
3156                         /* FIXME: Currently we just ignore this, but maybe we
3157                            should skip all elements where week_num != 0.
3158                            The spec isn't clear about this. */
3159                         week_num = GPOINTER_TO_INT (elem->data);
3160                         elem = elem->next;
3161
3162                         weekday_offset = cal_obj_time_weekday_offset (occ, recur_data->recur);
3163                         new_weekday_offset = (weekday + 7 - recur_data->recur->week_start_day) % 7;
3164                         cal_obj_time_add_days (occ, new_weekday_offset - weekday_offset);
3165                         g_array_append_vals (new_occs, occ, 1);
3166                 }
3167         }
3168
3169         g_array_free (occs, TRUE);
3170
3171         return new_occs;
3172 }
3173
3174
3175 /* Note: occs must not contain invalid dates, e.g. 31st September. */
3176 static GArray*
3177 cal_obj_byday_filter            (RecurData  *recur_data,
3178                                  GArray     *occs)
3179 {
3180         GArray *new_occs;
3181         CalObjTime *occ;
3182         gint len, i, weekday;
3183
3184         /* If BYDAY has not been specified, or the array is empty, just
3185            return the array. */
3186         if (!recur_data->recur->byday || occs->len == 0)
3187                 return occs;
3188
3189         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3190
3191         len = occs->len;
3192         for (i = 0; i < len; i++) {
3193                 occ = &g_array_index (occs, CalObjTime, i);
3194                 weekday = cal_obj_time_weekday (occ);
3195
3196                 /* See if the weekday on its own is set. */
3197                 if (recur_data->weekdays[weekday])
3198                         g_array_append_vals (new_occs, occ, 1);
3199         }
3200
3201         g_array_free (occs, TRUE);
3202
3203         return new_occs;
3204 }
3205
3206
3207
3208 /* If the BYHOUR rule is specified it expands each occurrence in occs, by
3209    using each of the hours in the byhour list. */
3210 static GArray*
3211 cal_obj_byhour_expand           (RecurData  *recur_data,
3212                                  GArray     *occs)
3213 {
3214         GArray *new_occs;
3215         CalObjTime *occ;
3216         GList *elem;
3217         gint len, i;
3218
3219         /* If BYHOUR has not been specified, or the array is empty, just
3220            return the array. */
3221         if (!recur_data->recur->byhour || occs->len == 0)
3222                 return occs;
3223
3224         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3225
3226         len = occs->len;
3227         for (i = 0; i < len; i++) {
3228                 occ = &g_array_index (occs, CalObjTime, i);
3229
3230                 elem = recur_data->recur->byhour;
3231                 while (elem) {
3232                         occ->hour = GPOINTER_TO_INT (elem->data);
3233                         g_array_append_vals (new_occs, occ, 1);
3234                         elem = elem->next;
3235                 }
3236         }
3237
3238         g_array_free (occs, TRUE);
3239
3240         return new_occs;
3241 }
3242
3243
3244 /* If the BYHOUR rule is specified it filters out all occurrences in occs
3245    which do not match one of the hours in the byhour list. */
3246 static GArray*
3247 cal_obj_byhour_filter           (RecurData  *recur_data,
3248                                  GArray     *occs)
3249 {
3250         GArray *new_occs;
3251         CalObjTime *occ;
3252         gint len, i;
3253
3254         /* If BYHOUR has not been specified, or the array is empty, just
3255            return the array. */
3256         if (!recur_data->recur->byhour || occs->len == 0)
3257                 return occs;
3258
3259         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3260
3261         len = occs->len;
3262         for (i = 0; i < len; i++) {
3263                 occ = &g_array_index (occs, CalObjTime, i);
3264                 if (recur_data->hours[occ->hour])
3265                         g_array_append_vals (new_occs, occ, 1);
3266         }
3267
3268         g_array_free (occs, TRUE);
3269
3270         return new_occs;
3271 }
3272
3273
3274
3275 /* If the BYMINUTE rule is specified it expands each occurrence in occs, by
3276    using each of the minutes in the byminute list. */
3277 static GArray*
3278 cal_obj_byminute_expand         (RecurData  *recur_data,
3279                                  GArray     *occs)
3280 {
3281         GArray *new_occs;
3282         CalObjTime *occ;
3283         GList *elem;
3284         gint len, i;
3285
3286         /* If BYMINUTE has not been specified, or the array is empty, just
3287            return the array. */
3288         if (!recur_data->recur->byminute || occs->len == 0)
3289                 return occs;
3290
3291         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3292
3293         len = occs->len;
3294         for (i = 0; i < len; i++) {
3295                 occ = &g_array_index (occs, CalObjTime, i);
3296
3297                 elem = recur_data->recur->byminute;
3298                 while (elem) {
3299                         occ->minute = GPOINTER_TO_INT (elem->data);
3300                         g_array_append_vals (new_occs, occ, 1);
3301                         elem = elem->next;
3302                 }
3303         }
3304
3305         g_array_free (occs, TRUE);
3306
3307         return new_occs;
3308 }
3309
3310
3311 /* If the BYMINUTE rule is specified it filters out all occurrences in occs
3312    which do not match one of the minutes in the byminute list. */
3313 static GArray*
3314 cal_obj_byminute_filter         (RecurData  *recur_data,
3315                                  GArray     *occs)
3316 {
3317         GArray *new_occs;
3318         CalObjTime *occ;
3319         gint len, i;
3320
3321         /* If BYMINUTE has not been specified, or the array is empty, just
3322            return the array. */
3323         if (!recur_data->recur->byminute || occs->len == 0)
3324                 return occs;
3325
3326         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3327
3328         len = occs->len;
3329         for (i = 0; i < len; i++) {
3330                 occ = &g_array_index (occs, CalObjTime, i);
3331                 if (recur_data->minutes[occ->minute])
3332                         g_array_append_vals (new_occs, occ, 1);
3333         }
3334
3335         g_array_free (occs, TRUE);
3336
3337         return new_occs;
3338 }
3339
3340
3341
3342 /* If the BYSECOND rule is specified it expands each occurrence in occs, by
3343    using each of the seconds in the bysecond list. */
3344 static GArray*
3345 cal_obj_bysecond_expand         (RecurData  *recur_data,
3346                                  GArray     *occs)
3347 {
3348         GArray *new_occs;
3349         CalObjTime *occ;
3350         GList *elem;
3351         gint len, i;
3352
3353         /* If BYSECOND has not been specified, or the array is empty, just
3354            return the array. */
3355         if (!recur_data->recur->bysecond || occs->len == 0)
3356                 return occs;
3357
3358         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3359
3360         len = occs->len;
3361         for (i = 0; i < len; i++) {
3362                 occ = &g_array_index (occs, CalObjTime, i);
3363
3364                 elem = recur_data->recur->bysecond;
3365                 while (elem) {
3366                         occ->second = GPOINTER_TO_INT (elem->data);
3367                         g_array_append_vals (new_occs, occ, 1);
3368                         elem = elem->next;
3369                 }
3370         }
3371
3372         g_array_free (occs, TRUE);
3373
3374         return new_occs;
3375 }
3376
3377
3378 /* If the BYSECOND rule is specified it filters out all occurrences in occs
3379    which do not match one of the seconds in the bysecond list. */
3380 static GArray*
3381 cal_obj_bysecond_filter         (RecurData  *recur_data,
3382                                  GArray     *occs)
3383 {
3384         GArray *new_occs;
3385         CalObjTime *occ;
3386         gint len, i;
3387
3388         /* If BYSECOND has not been specified, or the array is empty, just
3389            return the array. */
3390         if (!recur_data->recur->bysecond || occs->len == 0)
3391                 return occs;
3392
3393         new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3394
3395         len = occs->len;
3396         for (i = 0; i < len; i++) {
3397                 occ = &g_array_index (occs, CalObjTime, i);
3398                 if (recur_data->seconds[occ->second])
3399                         g_array_append_vals (new_occs, occ, 1);
3400         }
3401
3402         g_array_free (occs, TRUE);
3403
3404         return new_occs;
3405 }
3406
3407
3408
3409
3410
3411 /* Adds a positive or negative number of months to the given CalObjTime,
3412    updating the year appropriately so we end up with a valid month.
3413    Note that the day may be invalid, e.g. 30th Feb. */
3414 static void
3415 cal_obj_time_add_months         (CalObjTime *cotime,
3416                                  gint        months)
3417 {
3418         guint month, years;
3419
3420         /* We use a guint to avoid overflow on the guint8. */
3421         month = cotime->month + months;
3422         cotime->month = month % 12;
3423         if (month > 0) {
3424                 cotime->year += month / 12;
3425         } else {
3426                 years = month / 12;
3427                 if (cotime->month != 0) {
3428                         cotime->month += 12;
3429                         years -= 1;
3430                 }
3431                 cotime->year += years;
3432         }
3433 }
3434
3435
3436 /* Adds a positive or negative number of days to the given CalObjTime,
3437    updating the month and year appropriately so we end up with a valid day. */
3438 static void
3439 cal_obj_time_add_days           (CalObjTime *cotime,
3440                                  gint        days)
3441 {
3442         gint day, days_in_month;
3443
3444         /* We use a guint to avoid overflow on the guint8. */
3445         day = cotime->day;
3446         day += days;
3447
3448         if (days >= 0) {
3449                 for (;;) {
3450                         days_in_month = time_days_in_month (cotime->year,
3451                                                             cotime->month);
3452                         if (day <= days_in_month)
3453                                 break;
3454
3455                         cotime->month++;
3456                         if (cotime->month >= 12) {
3457                                 cotime->year++;
3458                                 cotime->month = 0;
3459                         }
3460
3461                         day -= days_in_month;
3462                 }
3463
3464                 cotime->day = (guint8) day;
3465         } else {
3466                 while (day <= 0) {
3467                         if (cotime->month == 0) {
3468                                 cotime->year--;
3469                                 cotime->month = 11;
3470                         } else {
3471                                 cotime->month--;
3472                         }
3473
3474                         days_in_month = time_days_in_month (cotime->year,
3475                                                             cotime->month);
3476                         day += days_in_month;
3477                 }
3478
3479                 cotime->day = (guint8) day;
3480         }
3481 }
3482
3483
3484 /* Adds a positive or negative number of hours to the given CalObjTime,
3485    updating the day, month & year appropriately so we end up with a valid
3486    time. */
3487 static void
3488 cal_obj_time_add_hours          (CalObjTime *cotime,
3489                                  gint        hours)
3490 {
3491         gint hour, days;
3492
3493         /* We use a gint to avoid overflow on the guint8. */
3494         hour = cotime->hour + hours;
3495         cotime->hour = hour % 24;
3496         if (hour >= 0) {
3497                 if (hour >= 24)
3498                         cal_obj_time_add_days (cotime, hour / 24);
3499         } else {
3500                 days = hour / 24;
3501                 if (cotime->hour != 0) {
3502                         cotime->hour += 24;
3503                         days -= 1;
3504                 }
3505                 cal_obj_time_add_days (cotime, days);
3506         }
3507 }
3508
3509
3510 /* Adds a positive or negative number of minutes to the given CalObjTime,
3511    updating the rest of the CalObjTime appropriately. */
3512 static void
3513 cal_obj_time_add_minutes        (CalObjTime *cotime,
3514                                  gint        minutes)
3515 {
3516         gint minute, hours;
3517
3518         /* We use a gint to avoid overflow on the guint8. */
3519         minute = cotime->minute + minutes;
3520         cotime->minute = minute % 60;
3521         if (minute >= 0) {
3522                 if (minute >= 60)
3523                         cal_obj_time_add_hours (cotime, minute / 60);
3524         } else {
3525                 hours = minute / 60;
3526                 if (cotime->minute != 0) {
3527                         cotime->minute += 60;
3528                         hours -= 1;
3529                 }
3530                 cal_obj_time_add_hours (cotime, hours);
3531         }
3532 }
3533
3534
3535 /* Adds a positive or negative number of seconds to the given CalObjTime,
3536    updating the rest of the CalObjTime appropriately. */
3537 static void
3538 cal_obj_time_add_seconds        (CalObjTime *cotime,
3539                                  gint        seconds)
3540 {
3541         gint second, minutes;
3542
3543         /* We use a gint to avoid overflow on the guint8. */
3544         second = cotime->second + seconds;
3545         cotime->second = second % 60;
3546         if (second >= 0) {
3547                 if (second >= 60)
3548                         cal_obj_time_add_minutes (cotime, second / 60);
3549         } else {
3550                 minutes = second / 60;
3551                 if (cotime->second != 0) {
3552                         cotime->second += 60;
3553                         minutes -= 1;
3554                 }
3555                 cal_obj_time_add_minutes (cotime, minutes);
3556         }
3557 }
3558
3559
3560 /* Compares 2 CalObjTimes. Returns -1 if the cotime1 is before cotime2, 0 if
3561    they are the same, or 1 if cotime1 is after cotime2. The comparison type
3562    specifies which parts of the times we are interested in, e.g. if CALOBJ_DAY
3563    is used we only want to know if the days are different. */
3564 static gint
3565 cal_obj_time_compare            (CalObjTime *cotime1,
3566                                  CalObjTime *cotime2,
3567                                  CalObjTimeComparison type)
3568 {
3569         if (cotime1->year < cotime2->year)
3570                 return -1;
3571         if (cotime1->year > cotime2->year)
3572                 return 1;
3573
3574         if (type == CALOBJ_YEAR)
3575                 return 0;
3576
3577         if (cotime1->month < cotime2->month)
3578                 return -1;
3579         if (cotime1->month > cotime2->month)
3580                 return 1;
3581
3582         if (type == CALOBJ_MONTH)
3583                 return 0;
3584
3585         if (cotime1->day < cotime2->day)
3586                 return -1;
3587         if (cotime1->day > cotime2->day)
3588                 return 1;
3589
3590         if (type == CALOBJ_DAY)
3591                 return 0;
3592
3593         if (cotime1->hour < cotime2->hour)
3594                 return -1;
3595         if (cotime1->hour > cotime2->hour)
3596                 return 1;
3597
3598         if (type == CALOBJ_HOUR)
3599                 return 0;
3600
3601         if (cotime1->minute < cotime2->minute)
3602                 return -1;
3603         if (cotime1->minute > cotime2->minute)
3604                 return 1;
3605
3606         if (type == CALOBJ_MINUTE)
3607                 return 0;
3608
3609         if (cotime1->second < cotime2->second)
3610                 return -1;
3611         if (cotime1->second > cotime2->second)
3612                 return 1;
3613
3614         return 0;
3615 }
3616
3617
3618 /* This is the same as the above function, but without the comparison type.
3619    It is used for qsort(). */
3620 static gint
3621 cal_obj_time_compare_func (const void *arg1,
3622                            const void *arg2)
3623 {
3624         CalObjTime *cotime1, *cotime2;
3625         gint retval;
3626
3627         cotime1 = (CalObjTime*) arg1;
3628         cotime2 = (CalObjTime*) arg2;
3629
3630         if (cotime1->year < cotime2->year)
3631                 retval = -1;
3632         else if (cotime1->year > cotime2->year)
3633                 retval = 1;
3634
3635         else if (cotime1->month < cotime2->month)
3636                 retval = -1;
3637         else if (cotime1->month > cotime2->month)
3638                 retval = 1;
3639
3640         else if (cotime1->day < cotime2->day)
3641                 retval = -1;
3642         else if (cotime1->day > cotime2->day)
3643                 retval = 1;
3644
3645         else if (cotime1->hour < cotime2->hour)
3646                 retval = -1;
3647         else if (cotime1->hour > cotime2->hour)
3648                 retval = 1;
3649
3650         else if (cotime1->minute < cotime2->minute)
3651                 retval = -1;
3652         else if (cotime1->minute > cotime2->minute)
3653                 retval = 1;
3654
3655         else if (cotime1->second < cotime2->second)
3656                 retval = -1;
3657         else if (cotime1->second > cotime2->second)
3658                 retval = 1;
3659
3660         else
3661                 retval = 0;
3662
3663 #if 0
3664         g_print ("%s - ", cal_obj_time_to_string (cotime1));
3665         g_print ("%s : %i\n", cal_obj_time_to_string (cotime2), retval);
3666 #endif
3667
3668         return retval;
3669 }
3670
3671
3672 static gint
3673 cal_obj_date_only_compare_func (const void *arg1,
3674                                 const void *arg2)
3675 {
3676         CalObjTime *cotime1, *cotime2;
3677
3678         cotime1 = (CalObjTime*) arg1;
3679         cotime2 = (CalObjTime*) arg2;
3680
3681         if (cotime1->year < cotime2->year)
3682                 return -1;
3683         if (cotime1->year > cotime2->year)
3684                 return 1;
3685
3686         if (cotime1->month < cotime2->month)
3687                 return -1;
3688         if (cotime1->month > cotime2->month)
3689                 return 1;
3690
3691         if (cotime1->day < cotime2->day)
3692                 return -1;
3693         if (cotime1->day > cotime2->day)
3694                 return 1;
3695
3696         return 0;
3697 }
3698
3699
3700 /* Returns the weekday of the given CalObjTime, from 0 (Mon) - 6 (Sun). */
3701 static gint
3702 cal_obj_time_weekday            (CalObjTime *cotime)
3703 {
3704         GDate date;
3705         gint weekday;
3706
3707         g_date_clear (&date, 1);
3708         g_date_set_dmy (&date, cotime->day, cotime->month + 1, cotime->year);
3709
3710         /* This results in a value of 0 (Monday) - 6 (Sunday). */
3711         weekday = g_date_get_weekday (&date) - 1;
3712
3713         return weekday;
3714 }
3715
3716
3717 /* Returns the weekday of the given CalObjTime, from 0 - 6. The week start
3718    day is Monday by default, but can be set in the recurrence rule. */
3719 static gint
3720 cal_obj_time_weekday_offset     (CalObjTime *cotime,
3721                                  ECalRecurrence *recur)
3722 {
3723         GDate date;
3724         gint weekday, offset;
3725
3726         g_date_clear (&date, 1);
3727         g_date_set_dmy (&date, cotime->day, cotime->month + 1, cotime->year);
3728
3729         /* This results in a value of 0 (Monday) - 6 (Sunday). */
3730         weekday = g_date_get_weekday (&date) - 1;
3731
3732         /* This calculates the offset of our day from the start of the week.
3733            We just add on a week (to avoid any possible negative values) and
3734            then subtract the specified week start day, then convert it into a
3735            value from 0-6. */
3736         offset = (weekday + 7 - recur->week_start_day) % 7;
3737
3738         return offset;
3739 }
3740
3741
3742 /* Returns the day of the year of the given CalObjTime, from 1 - 366. */
3743 static gint
3744 cal_obj_time_day_of_year                (CalObjTime *cotime)
3745 {
3746         GDate date;
3747
3748         g_date_clear (&date, 1);
3749         g_date_set_dmy (&date, cotime->day, cotime->month + 1, cotime->year);
3750
3751         return g_date_get_day_of_year (&date);
3752 }
3753
3754
3755 /* Finds the first week in the given CalObjTime's year, using the same weekday
3756    as the event start day (i.e. from the RecurData).
3757    The first week of the year is the first week starting from the specified
3758    week start day that has 4 days in the new year. It may be in the previous
3759    year. */
3760 static void
3761 cal_obj_time_find_first_week    (CalObjTime *cotime,
3762                                  RecurData  *recur_data)
3763 {
3764         GDate date;
3765         gint weekday, week_start_day, first_full_week_start_offset, offset;
3766
3767         /* Find out the weekday of the 1st of the year, 0 (Mon) - 6 (Sun). */
3768         g_date_clear (&date, 1);
3769         g_date_set_dmy (&date, 1, 1, cotime->year);
3770         weekday = g_date_get_weekday (&date) - 1;
3771
3772         /* Calculate the first day of the year that starts a new week, i.e. the
3773            first week_start_day after weekday, using 0 = 1st Jan.
3774            e.g. if the 1st Jan is a Tuesday (1) and week_start_day is a
3775            Monday (0), the result will be (0 + 7 - 1) % 7 = 6 (7th Jan). */
3776         week_start_day = recur_data->recur->week_start_day;
3777         first_full_week_start_offset = (week_start_day + 7 - weekday) % 7;
3778
3779         /* Now see if we have to move backwards 1 week, i.e. if the week
3780            starts on or after Jan 5th (since the previous week has 4 days in
3781            this year and so will be the first week of the year). */
3782         if (first_full_week_start_offset >= 4)
3783                 first_full_week_start_offset -= 7;
3784
3785         /* Now add the days to get to the event's weekday. */
3786         offset = first_full_week_start_offset + recur_data->weekday_offset;
3787
3788         /* Now move the cotime to the appropriate day. */
3789         cotime->month = 0;
3790         cotime->day = 1;
3791         cal_obj_time_add_days (cotime, offset);
3792 }
3793
3794
3795 static void
3796 cal_object_time_from_time       (CalObjTime     *cotime,
3797                                  time_t          t,
3798                                  icaltimezone   *zone)
3799 {
3800         struct icaltimetype tt;
3801
3802         if (zone)
3803                 tt = icaltime_from_timet_with_zone (t, FALSE, zone);
3804         else
3805                 tt = icaltime_from_timet (t, FALSE);
3806
3807         cotime->year     = tt.year;
3808         cotime->month    = tt.month - 1;
3809         cotime->day      = tt.day;
3810         cotime->hour     = tt.hour;
3811         cotime->minute   = tt.minute;
3812         cotime->second   = tt.second;
3813         cotime->flags    = FALSE;
3814 }
3815
3816
3817 /* Debugging function to convert a CalObjTime to a string. It uses a static
3818    buffer so beware. */
3819 #ifdef CAL_OBJ_DEBUG
3820 static char*
3821 cal_obj_time_to_string          (CalObjTime     *cotime)
3822 {
3823         static char buffer[20];
3824         char *weekdays[] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
3825                              "   " };
3826         gint weekday;
3827
3828         weekday = cal_obj_time_weekday (cotime);
3829
3830         sprintf (buffer, "%s %02i/%02i/%04i %02i:%02i:%02i",
3831                  weekdays[weekday],
3832                  cotime->day, cotime->month + 1, cotime->year,
3833                  cotime->hour, cotime->minute, cotime->second);
3834         return buffer;
3835 }
3836 #endif
3837
3838
3839 /* This recalculates the end dates for recurrence & exception rules which use
3840    the COUNT property. If refresh is TRUE it will recalculate all enddates
3841    for rules which use COUNT. If refresh is FALSE, it will only calculate
3842    the enddate if it hasn't already been set. It returns TRUE if the component
3843    was changed, i.e. if the component should be saved at some point.
3844    We store the enddate in the "X-EVOLUTION-ENDDATE" parameter of the RRULE
3845    or EXRULE. */
3846 static gboolean
3847 e_cal_recur_ensure_end_dates (ECalComponent     *comp,
3848                             gboolean             refresh,
3849                             ECalRecurResolveTimezoneFn  tz_cb,
3850                             gpointer             tz_cb_data)
3851 {
3852         GSList *rrules, *exrules, *elem;
3853         gboolean changed = FALSE;
3854
3855         /* Do the RRULEs. */
3856         e_cal_component_get_rrule_property_list (comp, &rrules);
3857         for (elem = rrules; elem; elem = elem->next) {
3858                 changed |= e_cal_recur_ensure_rule_end_date (comp, elem->data,
3859                                                            FALSE, refresh,
3860                                                            tz_cb, tz_cb_data);
3861         }
3862
3863         /* Do the EXRULEs. */
3864         e_cal_component_get_exrule_property_list (comp, &exrules);
3865         for (elem = exrules; elem; elem = elem->next) {
3866                 changed |= e_cal_recur_ensure_rule_end_date (comp, elem->data,
3867                                                            TRUE, refresh,
3868                                                            tz_cb, tz_cb_data);
3869         }
3870
3871         return changed;
3872 }
3873
3874
3875 typedef struct _ECalRecurEnsureEndDateData ECalRecurEnsureEndDateData;
3876 struct _ECalRecurEnsureEndDateData {
3877         gint count;
3878         gint instances;
3879         time_t end_date;
3880 };
3881
3882
3883 static gboolean
3884 e_cal_recur_ensure_rule_end_date (ECalComponent                 *comp,
3885                                 icalproperty                    *prop,
3886                                 gboolean                         exception,
3887                                 gboolean                         refresh,
3888                                 ECalRecurResolveTimezoneFn       tz_cb,
3889                                 gpointer                         tz_cb_data)
3890 {
3891         struct icalrecurrencetype rule;
3892         ECalRecurEnsureEndDateData cb_data;
3893
3894         if (exception)
3895                 rule = icalproperty_get_exrule (prop);
3896         else
3897                 rule = icalproperty_get_rrule (prop);
3898
3899         /* If the rule doesn't use COUNT just return. */
3900         if (rule.count == 0)
3901                 return FALSE;
3902
3903         /* If refresh is FALSE, we check if the enddate is already set, and
3904            if it is we just return. */
3905         if (!refresh) {
3906                 if (e_cal_recur_get_rule_end_date (prop, NULL) != -1)
3907                         return FALSE;
3908         }
3909
3910         /* Calculate the end date. Note that we initialize end_date to 0, so
3911            if the RULE doesn't generate COUNT instances we save a time_t of 0.
3912            Also note that we use the UTC timezone as the default timezone.
3913            In get_end_date() if the DTSTART is a DATE or floating time, we will
3914            convert the ENDDATE to the current timezone. */
3915         cb_data.count = rule.count;
3916         cb_data.instances = 0;
3917         cb_data.end_date = 0;
3918         e_cal_recur_generate_instances_of_rule (comp, prop, -1, -1,
3919                                               e_cal_recur_ensure_rule_end_date_cb,
3920                                               &cb_data, tz_cb, tz_cb_data,
3921                                               icaltimezone_get_utc_timezone ());
3922
3923         /* Store the end date in the "X-EVOLUTION-ENDDATE" parameter of the
3924            rule. */
3925         e_cal_recur_set_rule_end_date (prop, cb_data.end_date);
3926                 
3927         return TRUE;
3928 }
3929
3930
3931 static gboolean
3932 e_cal_recur_ensure_rule_end_date_cb     (ECalComponent  *comp,
3933                                          time_t          instance_start,
3934                                          time_t          instance_end,
3935                                          gpointer        data)
3936 {
3937         ECalRecurEnsureEndDateData *cb_data;
3938
3939         cb_data = (ECalRecurEnsureEndDateData*) data;
3940
3941         cb_data->instances++;
3942
3943         if (cb_data->instances == cb_data->count) {
3944                 cb_data->end_date = instance_start;
3945                 return FALSE;
3946         }
3947
3948         return TRUE;
3949 }
3950
3951
3952 /* If default_timezone is set, the saved ENDDATE parameter is assumed to be
3953    in that timezone. This is used when the DTSTART is a DATE or floating
3954    value, since the RRULE end date will change depending on the timezone that
3955    it is evaluated in. */
3956 static time_t
3957 e_cal_recur_get_rule_end_date   (icalproperty   *prop,
3958                                  icaltimezone   *default_timezone)
3959 {
3960         icalparameter *param;
3961         const char *xname, *xvalue;
3962         icalvalue *value;
3963         struct icaltimetype icaltime;
3964         icaltimezone *zone;
3965
3966         param = icalproperty_get_first_parameter (prop, ICAL_X_PARAMETER);
3967         while (param) {
3968                 xname = icalparameter_get_xname (param);
3969                 if (xname && !strcmp (xname, EVOLUTION_END_DATE_PARAMETER)) {
3970                         xvalue = icalparameter_get_x (param);
3971                         value = icalvalue_new_from_string (ICAL_DATETIME_VALUE,
3972                                                            xvalue);
3973                         if (value) {
3974                                 icaltime = icalvalue_get_datetime (value);
3975                                 icalvalue_free (value);
3976
3977                                 zone = default_timezone ? default_timezone : 
3978                                         icaltimezone_get_utc_timezone ();
3979                                 return icaltime_as_timet_with_zone (icaltime,
3980                                                                     zone);
3981                         }
3982                 }
3983
3984                 param = icalproperty_get_next_parameter (prop,
3985                                                          ICAL_X_PARAMETER);
3986         }
3987
3988         return -1;
3989 }
3990
3991
3992 static void
3993 e_cal_recur_set_rule_end_date   (icalproperty   *prop,
3994                                  time_t          end_date)
3995 {
3996         icalparameter *param;
3997         icalvalue *value;
3998         icaltimezone *utc_zone;
3999         struct icaltimetype icaltime;
4000         const char *end_date_string, *xname;
4001
4002         /* We save the value as a UTC DATE-TIME. */
4003         utc_zone = icaltimezone_get_utc_timezone ();
4004         icaltime = icaltime_from_timet_with_zone (end_date, FALSE, utc_zone);
4005         value = icalvalue_new_datetime (icaltime);
4006         end_date_string = icalvalue_as_ical_string (value);
4007         icalvalue_free (value);
4008
4009         /* If we already have an X-EVOLUTION-ENDDATE parameter, set the value
4010            to the new date-time. */
4011         param = icalproperty_get_first_parameter (prop, ICAL_X_PARAMETER);
4012         while (param) {
4013                 xname = icalparameter_get_xname (param);
4014                 if (xname && !strcmp (xname, EVOLUTION_END_DATE_PARAMETER)) {
4015                         icalparameter_set_x (param, end_date_string);
4016                         return;
4017                 }
4018                 param = icalproperty_get_next_parameter (prop, ICAL_X_PARAMETER);
4019         }
4020
4021         /* Create a new X-EVOLUTION-ENDDATE and add it to the property. */
4022         param = icalparameter_new_x (end_date_string);
4023         icalparameter_set_xname (param, EVOLUTION_END_DATE_PARAMETER);
4024         icalproperty_add_parameter (prop, param);
4025 }
4026
4027 #ifdef G_OS_WIN32
4028 #undef e_cal_recur_nth
4029 static
4030 #endif
4031 /**
4032  * e_cal_recur_nth:
4033  *
4034  * An array of 31 translated strings for each day of the month (i.e. "1st",
4035  * "2nd", and so on).
4036  */
4037 const char *e_cal_recur_nth[31] = {
4038         N_("1st"),
4039         N_("2nd"),
4040         N_("3rd"),
4041         N_("4th"),
4042         N_("5th"),
4043         N_("6th"),
4044         N_("7th"),
4045         N_("8th"),
4046         N_("9th"),
4047         N_("10th"),
4048         N_("11th"),
4049         N_("12th"),
4050         N_("13th"),
4051         N_("14th"),
4052         N_("15th"),
4053         N_("16th"),
4054         N_("17th"),
4055         N_("18th"),
4056         N_("19th"),
4057         N_("20th"),
4058         N_("21st"),
4059         N_("22nd"),
4060         N_("23rd"),
4061         N_("24th"),
4062         N_("25th"),
4063         N_("26th"),
4064         N_("27th"),
4065         N_("28th"),
4066         N_("29th"),
4067         N_("30th"),
4068         N_("31st")
4069 };
4070
4071 #ifdef G_OS_WIN32
4072
4073 /**
4074  * e_cal_get_recur_nth:
4075  *
4076  * Returns an array of 31 translated strings for each day of the month
4077  * (i.e. "1st", "2nd", and so on).
4078  *
4079  * Returns: a pointer to an array of strings.  This array is static, do not free it.
4080  */
4081 const char **
4082 e_cal_get_recur_nth (void)
4083 {
4084         return e_cal_recur_nth;
4085 }
4086
4087 #endif