1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Evolution calendar recurrence rule functions
5 * Copyright (C) 2000 Ximian, Inc.
7 * Author: Damon Chaplin <damon@ximian.com>
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.
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.
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.
30 #include <glib/gi18n-lib.h>
31 #include "e-cal-recur.h"
32 #include "e-cal-time-util.h"
36 * Introduction to The Recurrence Generation Functions:
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.
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.
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.
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.
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.
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.
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
93 * FIXME: I think there are a few errors in this code:
95 * 1) I'm not sure it should be generating events in parallel like it says
96 * above. That needs to be checked.
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
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
114 #define MAX_YEAR 2037
116 /* Define this for some debugging output. */
118 #define CAL_OBJ_DEBUG 1
121 /* We will use icalrecurrencetype instead of this eventually. */
123 icalrecurrencetype_frequency freq;
127 /* Specifies the end of the recurrence, inclusive. No occurrences are
128 generated after this date. If it is 0, the event recurs forever. */
131 /* WKST property - the week start day: 0 = Monday to 6 = Sunday. */
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. */
139 /* For BYMONTH modifier. A list of GINT_TO_POINTERs, 0-11. */
142 /* For BYWEEKNO modifier. A list of GINT_TO_POINTERs, [+-]1-53. */
145 /* For BYYEARDAY modifier. A list of GINT_TO_POINTERs, [+-]1-366. */
148 /* For BYMONTHDAY modifier. A list of GINT_TO_POINTERs, [+-]1-31. */
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. */
156 /* For BYHOUR modifier. A list of GINT_TO_POINTERs, 0-23. */
159 /* For BYMINUTE modifier. A list of GINT_TO_POINTERs, 0-59. */
162 /* For BYSECOND modifier. A list of GINT_TO_POINTERs, 0-60. */
165 /* For BYSETPOS modifier. A list of GINT_TO_POINTERs, +ve or -ve. */
169 /* This is what we use to pass to all the filter functions. */
170 typedef struct _RecurData RecurData;
172 ECalRecurrence *recur;
174 /* This is used for the WEEKLY frequency. It is the offset from the
178 /* This is used for fast lookup in BYMONTH filtering. */
181 /* This is used for fast lookup in BYYEARDAY filtering. */
182 guint8 yeardays[367], neg_yeardays[367]; /* Days are 1 - 366. */
184 /* This is used for fast lookup in BYMONTHDAY filtering. */
185 guint8 monthdays[32], neg_monthdays[32]; /* Days are 1 to 31. */
187 /* This is used for fast lookup in BYDAY filtering. */
190 /* This is used for fast lookup in BYHOUR filtering. */
193 /* This is used for fast lookup in BYMINUTE filtering. */
196 /* This is used for fast lookup in BYSECOND filtering. */
200 /* This is what we use to represent a date & time. */
201 typedef struct _CalObjTime CalObjTime;
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
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 {
223 ECalComponentPeriod *period;
226 /* The paramter we use to store the enddate in RRULE and EXRULE properties. */
227 #define EVOLUTION_END_DATE_PARAMETER "X-EVOLUTION-ENDDATE"
229 typedef gboolean (*CalObjFindStartFn) (CalObjTime *event_start,
230 CalObjTime *event_end,
231 RecurData *recur_data,
232 CalObjTime *interval_start,
233 CalObjTime *interval_end,
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,
242 typedef struct _ECalRecurVTable ECalRecurVTable;
243 struct _ECalRecurVTable {
244 CalObjFindStartFn find_start_position;
245 CalObjFindNextFn find_next_position;
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;
258 /* This is used to specify which parts of the CalObjTime to compare in
259 cal_obj_time_compare(). */
267 } CalObjTimeComparison;
269 static void e_cal_recur_generate_instances_of_rule (ECalComponent *comp,
273 ECalRecurInstanceFn cb,
275 ECalRecurResolveTimezoneFn tz_cb,
277 icaltimezone *default_timezone);
279 static ECalRecurrence * e_cal_recur_from_icalproperty (icalproperty *prop,
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);
287 static gboolean cal_object_get_rdate_end (CalObjTime *occ,
288 GArray *rdate_periods);
289 static void cal_object_compute_duration (CalObjTime *start,
294 static gboolean generate_instances_for_chunk (ECalComponent *comp,
301 gboolean single_rule,
302 CalObjTime *event_start,
303 time_t interval_start,
304 CalObjTime *chunk_start,
305 CalObjTime *chunk_end,
307 gint duration_seconds,
308 gboolean convert_end_date,
309 ECalRecurInstanceFn cb,
312 static GArray* cal_obj_expand_recurrence (CalObjTime *event_start,
314 ECalRecurrence *recur,
315 CalObjTime *interval_start,
316 CalObjTime *interval_end,
319 static GArray* cal_obj_generate_set_yearly (RecurData *recur_data,
320 ECalRecurVTable *vtable,
322 static GArray* cal_obj_generate_set_monthly (RecurData *recur_data,
323 ECalRecurVTable *vtable,
325 static GArray* cal_obj_generate_set_default (RecurData *recur_data,
326 ECalRecurVTable *vtable,
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,
337 static void cal_obj_remove_duplicates_and_invalid_dates (GArray *occs);
338 static void cal_obj_remove_exceptions (GArray *occs,
340 static GArray* cal_obj_bysetpos_filter (ECalRecurrence *recur,
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,
350 static gboolean cal_obj_yearly_find_next_position (CalObjTime *cotime,
351 CalObjTime *event_end,
352 RecurData *recur_data,
353 CalObjTime *interval_end);
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,
361 static gboolean cal_obj_monthly_find_next_position (CalObjTime *cotime,
362 CalObjTime *event_end,
363 RecurData *recur_data,
364 CalObjTime *interval_end);
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,
372 static gboolean cal_obj_weekly_find_next_position (CalObjTime *cotime,
373 CalObjTime *event_end,
374 RecurData *recur_data,
375 CalObjTime *interval_end);
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,
383 static gboolean cal_obj_daily_find_next_position (CalObjTime *cotime,
384 CalObjTime *event_end,
385 RecurData *recur_data,
386 CalObjTime *interval_end);
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,
394 static gboolean cal_obj_hourly_find_next_position (CalObjTime *cotime,
395 CalObjTime *event_end,
396 RecurData *recur_data,
397 CalObjTime *interval_end);
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,
405 static gboolean cal_obj_minutely_find_next_position (CalObjTime *cotime,
406 CalObjTime *event_end,
407 RecurData *recur_data,
408 CalObjTime *interval_end);
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,
416 static gboolean cal_obj_secondly_find_next_position (CalObjTime *cotime,
417 CalObjTime *event_end,
418 RecurData *recur_data,
419 CalObjTime *interval_end);
421 static GArray* cal_obj_bymonth_expand (RecurData *recur_data,
423 static GArray* cal_obj_bymonth_filter (RecurData *recur_data,
425 static GArray* cal_obj_byweekno_expand (RecurData *recur_data,
428 /* This isn't used at present. */
429 static GArray* cal_obj_byweekno_filter (RecurData *recur_data,
432 static GArray* cal_obj_byyearday_expand (RecurData *recur_data,
434 static GArray* cal_obj_byyearday_filter (RecurData *recur_data,
436 static GArray* cal_obj_bymonthday_expand (RecurData *recur_data,
438 static GArray* cal_obj_bymonthday_filter (RecurData *recur_data,
440 static GArray* cal_obj_byday_expand_yearly (RecurData *recur_data,
442 static GArray* cal_obj_byday_expand_monthly (RecurData *recur_data,
444 static GArray* cal_obj_byday_expand_weekly (RecurData *recur_data,
446 static GArray* cal_obj_byday_filter (RecurData *recur_data,
448 static GArray* cal_obj_byhour_expand (RecurData *recur_data,
450 static GArray* cal_obj_byhour_filter (RecurData *recur_data,
452 static GArray* cal_obj_byminute_expand (RecurData *recur_data,
454 static GArray* cal_obj_byminute_filter (RecurData *recur_data,
456 static GArray* cal_obj_bysecond_expand (RecurData *recur_data,
458 static GArray* cal_obj_bysecond_filter (RecurData *recur_data,
461 static void cal_obj_time_add_months (CalObjTime *cotime,
463 static void cal_obj_time_add_days (CalObjTime *cotime,
465 static void cal_obj_time_add_hours (CalObjTime *cotime,
467 static void cal_obj_time_add_minutes (CalObjTime *cotime,
469 static void cal_obj_time_add_seconds (CalObjTime *cotime,
471 static gint cal_obj_time_compare (CalObjTime *cotime1,
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,
483 static gint cal_obj_date_only_compare_func (const void *arg1,
488 static gboolean e_cal_recur_ensure_end_dates (ECalComponent *comp,
490 ECalRecurResolveTimezoneFn tz_cb,
491 gpointer tz_cb_data);
492 static gboolean e_cal_recur_ensure_rule_end_date (ECalComponent *comp,
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,
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,
509 static char* cal_obj_time_to_string (CalObjTime *cotime);
513 static ECalRecurVTable cal_obj_yearly_vtable = {
514 cal_obj_yearly_find_start_position,
515 cal_obj_yearly_find_next_position,
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
527 static ECalRecurVTable cal_obj_monthly_vtable = {
528 cal_obj_monthly_find_start_position,
529 cal_obj_monthly_find_next_position,
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
541 static ECalRecurVTable cal_obj_weekly_vtable = {
542 cal_obj_weekly_find_start_position,
543 cal_obj_weekly_find_next_position,
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
555 static ECalRecurVTable cal_obj_daily_vtable = {
556 cal_obj_daily_find_start_position,
557 cal_obj_daily_find_next_position,
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
569 static ECalRecurVTable cal_obj_hourly_vtable = {
570 cal_obj_hourly_find_start_position,
571 cal_obj_hourly_find_next_position,
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
583 static ECalRecurVTable cal_obj_minutely_vtable = {
584 cal_obj_minutely_find_start_position,
585 cal_obj_minutely_find_next_position,
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
597 static ECalRecurVTable cal_obj_secondly_vtable = {
598 cal_obj_secondly_find_start_position,
599 cal_obj_secondly_find_next_position,
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
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
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
628 * If the callback routine returns FALSE the occurrence generation stops.
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.
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.
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).
641 e_cal_recur_generate_instances (ECalComponent *comp,
644 ECalRecurInstanceFn cb,
646 ECalRecurResolveTimezoneFn tz_cb,
648 icaltimezone *default_timezone)
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));
655 e_cal_recur_generate_instances_of_rule (comp, NULL, start, end,
656 cb, cb_data, tz_cb, tz_cb_data,
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.
666 * If the callback routine returns FALSE the occurrence generation stops.
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.
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.
676 e_cal_recur_generate_instances_of_rule (ECalComponent *comp,
680 ECalRecurInstanceFn cb,
682 ECalRecurResolveTimezoneFn tz_cb,
684 icaltimezone *default_timezone)
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;
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);
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. */
706 e_cal_component_get_dtstart (comp, &dtstart);
707 e_cal_component_get_dtend (comp, &dtend);
709 if (!dtstart.value) {
710 g_message ("e_cal_recur_generate_instances_of_rule(): bogus "
711 "component, does not have DTSTART. Skipping...");
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);
721 start_zone = default_timezone;
723 start_zone = default_timezone;
725 /* Flag that we need to convert the saved ENDDATE property
726 to the default timezone. */
727 convert_end_date = TRUE;
730 dtstart_time = icaltime_as_timet_with_zone (*dtstart.value,
733 start = dtstart_time;
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);
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;
753 if (dtstart.value->is_date) {
754 icaltime_adjust (dtend.value, 1, 0, 0, 0);
758 if (dtend.tzid && !dtend.value->is_date) {
759 end_zone = (*tz_cb) (dtend.tzid, tz_cb_data);
761 end_zone = default_timezone;
763 end_zone = default_timezone;
766 /* We don't do this any more, since Outlook assumes that the DTEND
767 date is not included. */
769 /* If DTEND is a DATE value, we add 1 day to it so that it includes
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);
778 dtend_time = icaltime_as_timet_with_zone (*dtend.value, end_zone);
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);
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);
797 /* If a specific recurrence rule is being used, set up a simple list,
798 else get the recurrence rules from the component. */
805 } else if (e_cal_component_is_instance (comp)) {
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);
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);
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);
826 cal_object_time_from_time (&interval_end, end - 1, start_zone);
828 cal_object_time_from_time (&event_start, dtstart_time, start_zone);
829 cal_object_time_from_time (&event_end, dtend_time, start_zone);
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
835 cal_object_compute_duration (&event_start, &event_end,
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);
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;
858 chunk_start = interval_start;
859 chunk_start.year = year;
861 chunk_end = interval_end;
862 chunk_end.year = year;
864 if (year != interval_start.year) {
865 chunk_start.month = 0;
867 chunk_start.hour = 0;
868 chunk_start.minute = 0;
869 chunk_start.second = 0;
871 if (end == -1 || year != interval_end.year) {
872 chunk_end.month = 11;
875 chunk_end.minute = 59;
876 chunk_end.second = 61;
877 chunk_end.flags = FALSE;
880 if (!generate_instances_for_chunk (comp, dtstart_time,
887 &chunk_start, &chunk_end,
895 e_cal_component_free_period_list (rdates);
896 e_cal_component_free_exdate_list (exdates);
900 e_cal_component_free_datetime (&dtstart);
901 e_cal_component_free_datetime (&dtend);
904 /* Builds a list of GINT_TO_POINTER() elements out of a short array from a
905 * struct icalrecurrencetype.
908 array_to_list (short *array, int max_elements)
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);
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
930 * Converts an #icalproperty to a #ECalRecurrence. This should be
931 * freed using the e_cal_recur_free() function.
933 * Return value: #ECalRecurrence structure.
935 static ECalRecurrence *
936 e_cal_recur_from_icalproperty (icalproperty *prop, gboolean exception,
937 icaltimezone *zone, gboolean convert_end_date)
939 struct icalrecurrencetype ir;
941 gint max_elements, i;
944 g_return_val_if_fail (prop != NULL, NULL);
946 r = g_new (ECalRecurrence, 1);
949 ir = icalproperty_get_exrule (prop);
951 ir = icalproperty_get_rrule (prop);
954 r->interval = ir.interval;
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);
962 if (icaltime_is_null_time (ir.until)) {
963 /* If neither COUNT or UNTIL is set, the event
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
972 ir.until.minute = 59;
973 ir.until.second = 59;
974 ir.until.is_date = FALSE;
976 r->enddate = icaltime_as_timet_with_zone (ir.until,
979 g_print (" until: %li - %s", r->enddate, ctime (&r->enddate));
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,
991 r->week_start_day = e_cal_recur_ical_weekday_to_weekday (ir.week_start);
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);
1001 r->byweekno = array_to_list (ir.by_week_no,
1002 sizeof (ir.by_week_no) / sizeof (ir.by_week_no[0]));
1004 r->byyearday = array_to_list (ir.by_year_day,
1005 sizeof (ir.by_year_day) / sizeof (ir.by_year_day[0]));
1007 r->bymonthday = array_to_list (ir.by_month_day,
1008 sizeof (ir.by_month_day) / sizeof (ir.by_month_day[0]));
1010 /* FIXME: libical only supports 8 values, out of possible 107 * 7. */
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;
1017 day = icalrecurrencetype_day_day_of_week (ir.by_day[i]);
1018 weeknum = icalrecurrencetype_day_position (ir.by_day[i]);
1020 weekday = e_cal_recur_ical_weekday_to_weekday (day);
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));
1028 r->byhour = array_to_list (ir.by_hour,
1029 sizeof (ir.by_hour) / sizeof (ir.by_hour[0]));
1031 r->byminute = array_to_list (ir.by_minute,
1032 sizeof (ir.by_minute) / sizeof (ir.by_minute[0]));
1034 r->bysecond = array_to_list (ir.by_second,
1035 sizeof (ir.by_second) / sizeof (ir.by_second[0]));
1037 r->bysetpos = array_to_list (ir.by_set_pos,
1038 sizeof (ir.by_set_pos) / sizeof (ir.by_set_pos[0]));
1045 e_cal_recur_ical_weekday_to_weekday (enum icalrecurrencetype_weekday day)
1050 case ICAL_NO_WEEKDAY: /* Monday is the default in RFC2445. */
1051 case ICAL_MONDAY_WEEKDAY:
1054 case ICAL_TUESDAY_WEEKDAY:
1057 case ICAL_WEDNESDAY_WEEKDAY:
1060 case ICAL_THURSDAY_WEEKDAY:
1063 case ICAL_FRIDAY_WEEKDAY:
1066 case ICAL_SATURDAY_WEEKDAY:
1069 case ICAL_SUNDAY_WEEKDAY:
1073 g_warning ("e_cal_recur_ical_weekday_to_weekday(): Unknown week day %d",
1084 * @r: A #ECalRecurrence structure.
1086 * Frees a #ECalRecurrence structure.
1089 e_cal_recur_free (ECalRecurrence *r)
1091 g_return_if_fail (r != NULL);
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);
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.
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
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.
1120 generate_instances_for_chunk (ECalComponent *comp,
1121 time_t comp_dtstart,
1127 gboolean single_rule,
1128 CalObjTime *event_start,
1129 time_t interval_start,
1130 CalObjTime *chunk_start,
1131 CalObjTime *chunk_end,
1133 gint duration_seconds,
1134 gboolean convert_end_date,
1135 ECalRecurInstanceFn cb,
1138 GArray *occs, *ex_occs, *tmp_occs, *rdate_periods;
1139 CalObjTime cotime, *occ;
1142 time_t start_time, end_time;
1143 struct icaltimetype start_tt, end_tt;
1144 gboolean cb_status = TRUE, rule_finished, finished = TRUE;
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",
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);
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));
1163 /* The original DTSTART property is included in the occurrence set,
1164 but not if we are just generating occurrences for a 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
1169 if (cal_obj_time_compare_func (event_start, chunk_end) >= 0)
1171 else if (cal_obj_time_compare_func (event_start, chunk_start) >= 0)
1172 g_array_append_vals (occs, event_start, 1);
1175 /* Expand each of the recurrence rules. */
1176 for (elem = rrules; elem; elem = elem->next) {
1181 r = e_cal_recur_from_icalproperty (prop, FALSE, zone,
1184 tmp_occs = cal_obj_expand_recurrence (event_start, zone, r,
1188 e_cal_recur_free (r);
1190 /* If any of the rules return FALSE for finished, we know we
1191 have to carry on so we set finished to FALSE. */
1195 g_array_append_vals (occs, tmp_occs->data, tmp_occs->len);
1196 g_array_free (tmp_occs, TRUE);
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;
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;
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) {
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)
1231 if (p->type != E_CAL_COMPONENT_PERIOD_DATETIME
1232 || p->u.end.second != -1) {
1233 cotime.flags = TRUE;
1235 rdate.start = cotime;
1237 g_array_append_val (rdate_periods, rdate);
1240 g_array_append_val (occs, cotime);
1243 /* Expand each of the exception rules. */
1244 for (elem = exrules; elem; elem = elem->next) {
1249 r = e_cal_recur_from_icalproperty (prop, FALSE, zone,
1252 tmp_occs = cal_obj_expand_recurrence (event_start, zone, r,
1256 e_cal_recur_free (r);
1258 g_array_append_vals (ex_occs, tmp_occs->data, tmp_occs->len);
1259 g_array_free (tmp_occs, TRUE);
1262 /* Add on specific exception dates. */
1263 for (elem = exdates; elem; elem = elem->next) {
1264 ECalComponentDateTime *cdt;
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;
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) {
1282 cotime.flags = TRUE;
1284 cotime.hour = cdt->value->hour;
1285 cotime.minute = cdt->value->minute;
1286 cotime.second = cdt->value->second;
1287 cotime.flags = FALSE;
1290 g_array_append_val (ex_occs, cotime);
1294 /* Sort all the arrays. */
1295 cal_obj_sort_occurrences (occs);
1296 cal_obj_sort_occurrences (ex_occs);
1298 qsort (rdate_periods->data, rdate_periods->len,
1299 sizeof (CalObjRecurrenceDate), cal_obj_time_compare_func);
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);
1305 /* Call the callback for each occurrence. If it returns 0 we break
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);
1312 g_print ("Checking occurrence: %s\n",
1313 cal_obj_time_to_string (occ));
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);
1324 if (start_time == -1) {
1325 g_warning ("time_t out of range");
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) {
1339 g_print (" start time invalid\n");
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,
1354 cal_obj_time_add_days (occ, duration_days);
1355 cal_obj_time_add_seconds (occ, duration_seconds);
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);
1367 if (end_time == -1) {
1368 g_warning ("time_t out of range");
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) {
1377 g_print (" end time invalid\n");
1382 cb_status = (*cb) (comp, start_time, end_time, cb_data);
1387 g_array_free (occs, TRUE);
1388 g_array_free (ex_occs, TRUE);
1389 g_array_free (rdate_periods, TRUE);
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;
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. */
1402 cal_object_get_rdate_end (CalObjTime *occ,
1403 GArray *rdate_periods)
1405 CalObjRecurrenceDate *rdate = NULL;
1406 ECalComponentPeriod *p;
1407 gint lower, upper, middle, cmp = 0;
1410 upper = rdate_periods->len;
1412 while (lower < upper) {
1413 middle = (lower + upper) >> 1;
1415 rdate = &g_array_index (rdate_periods, CalObjRecurrenceDate,
1418 cmp = cal_obj_time_compare_func (occ, &rdate->start);
1428 /* This should never happen. */
1430 g_warning ("Recurrence date not found");
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;
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);
1459 cal_object_compute_duration (CalObjTime *start,
1464 GDate start_date, end_date;
1465 gint start_seconds, end_seconds;
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,
1471 g_date_set_dmy (&end_date, end->day, end->month + 1,
1474 *days = g_date_get_julian (&end_date) - g_date_get_julian (&start_date);
1475 start_seconds = start->hour * 3600 + start->minute * 60
1477 end_seconds = end->hour * 3600 + end->minute * 60 + end->second;
1479 *seconds = end_seconds - start_seconds;
1482 *seconds += 24 * 60 * 60;
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.*/
1495 cal_obj_expand_recurrence (CalObjTime *event_start,
1497 ECalRecurrence *recur,
1498 CalObjTime *interval_start,
1499 CalObjTime *interval_end,
1502 ECalRecurVTable *vtable;
1503 CalObjTime *event_end = NULL, event_end_cotime;
1504 RecurData recur_data;
1505 CalObjTime occ, *cotime;
1506 GArray *all_occs, *occs;
1509 /* This is the resulting array of CalObjTime elements. */
1510 all_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1514 vtable = cal_obj_get_vtable (recur->freq);
1518 /* Calculate some useful data such as some fast lookup tables. */
1519 cal_obj_initialize_recur_data (&recur_data, recur, event_start);
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;
1527 /* If the enddate is before the requested interval return. */
1528 if (cal_obj_time_compare_func (event_end, interval_start) < 0)
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)
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,
1542 interval_start, interval_end,
1546 /* Loop until the event ends or we go past the end of the required
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,
1555 case ICAL_MONTHLY_RECURRENCE:
1556 occs = cal_obj_generate_set_monthly (&recur_data,
1560 occs = cal_obj_generate_set_default (&recur_data,
1565 /* Sort the occurrences and remove duplicates. */
1566 cal_obj_sort_occurrences (occs);
1567 cal_obj_remove_duplicates_and_invalid_dates (occs);
1569 /* Apply the BYSETPOS property. */
1570 occs = cal_obj_bysetpos_filter (recur, occs);
1572 /* Remove any occs after event_end. */
1573 len = occs->len - 1;
1576 cotime = &g_array_index (occs, CalObjTime,
1578 if (cal_obj_time_compare_func (cotime,
1585 /* Add the occurrences onto the main array. */
1587 g_array_append_vals (all_occs, occs->data, len + 1);
1589 g_array_free (occs, TRUE);
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))
1602 cal_obj_generate_set_yearly (RecurData *recur_data,
1603 ECalRecurVTable *vtable,
1606 ECalRecurrence *recur = recur_data->recur;
1607 GArray *occs_arrays[4], *occs, *occs2;
1608 gint num_occs_arrays = 0, i;
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.
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.
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
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. */
1630 if (recur->bymonth) {
1631 occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1632 g_array_append_vals (occs, occ, 1);
1634 occs = (*vtable->bymonth_filter) (recur_data, occs);
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);
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,
1650 /* Add the 2 resulting arrays together. */
1651 g_array_append_vals (occs, occs2->data, occs2->len);
1652 g_array_free (occs2, TRUE);
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);
1660 occs_arrays[num_occs_arrays++] = occs;
1663 if (recur->byweekno) {
1664 occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1665 g_array_append_vals (occs, occ, 1);
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);
1672 occs_arrays[num_occs_arrays++] = occs;
1675 if (recur->byyearday) {
1676 occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1677 g_array_append_vals (occs, occ, 1);
1679 occs = (*vtable->byyearday_filter) (recur_data, occs);
1681 occs_arrays[num_occs_arrays++] = occs;
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);
1690 occs = (*vtable->bymonthday_filter) (recur_data, occs);
1692 occs_arrays[num_occs_arrays++] = occs;
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);
1701 occs = (*vtable->byday_filter) (recur_data, occs);
1703 occs_arrays[num_occs_arrays++] = occs;
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);
1716 occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1717 g_array_append_vals (occs, occ, 1);
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);
1730 cal_obj_generate_set_monthly (RecurData *recur_data,
1731 ECalRecurVTable *vtable,
1734 GArray *occs, *occs2;
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);
1740 occs = (*vtable->bymonth_filter) (recur_data, occs);
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);
1750 occs = (*vtable->bymonthday_filter) (recur_data, occs);
1751 occs2 = (*vtable->byday_filter) (recur_data, occs2);
1753 g_array_append_vals (occs, occs2->data, occs2->len);
1754 g_array_free (occs2, TRUE);
1756 occs = (*vtable->bymonthday_filter) (recur_data, occs);
1757 occs = (*vtable->byday_filter) (recur_data, occs);
1760 occs = (*vtable->byhour_filter) (recur_data, occs);
1761 occs = (*vtable->byminute_filter) (recur_data, occs);
1762 occs = (*vtable->bysecond_filter) (recur_data, occs);
1769 cal_obj_generate_set_default (RecurData *recur_data,
1770 ECalRecurVTable *vtable,
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,
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);
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);
1794 occs = (*vtable->byhour_filter) (recur_data, occs);
1795 occs = (*vtable->byminute_filter) (recur_data, occs);
1796 occs = (*vtable->bysecond_filter) (recur_data, occs);
1803 /* Returns the function table corresponding to the recurrence frequency. */
1804 static ECalRecurVTable* cal_obj_get_vtable (icalrecurrencetype_frequency recur_type)
1806 ECalRecurVTable* vtable;
1808 switch (recur_type) {
1809 case ICAL_YEARLY_RECURRENCE:
1810 vtable = &cal_obj_yearly_vtable;
1812 case ICAL_MONTHLY_RECURRENCE:
1813 vtable = &cal_obj_monthly_vtable;
1815 case ICAL_WEEKLY_RECURRENCE:
1816 vtable = &cal_obj_weekly_vtable;
1818 case ICAL_DAILY_RECURRENCE:
1819 vtable = &cal_obj_daily_vtable;
1821 case ICAL_HOURLY_RECURRENCE:
1822 vtable = &cal_obj_hourly_vtable;
1824 case ICAL_MINUTELY_RECURRENCE:
1825 vtable = &cal_obj_minutely_vtable;
1827 case ICAL_SECONDLY_RECURRENCE:
1828 vtable = &cal_obj_secondly_vtable;
1831 g_warning ("Unknown recurrence frequency");
1839 /* This creates a number of fast lookup tables used when filtering with the
1840 modifier properties BYMONTH, BYYEARDAY etc. */
1842 cal_obj_initialize_recur_data (RecurData *recur_data,
1843 ECalRecurrence *recur,
1844 CalObjTime *event_start)
1847 gint month, yearday, monthday, weekday, week_num, hour, minute, second;
1849 /* Clear the entire RecurData. */
1850 memset (recur_data, 0, sizeof (RecurData));
1852 recur_data->recur = recur;
1854 /* Set the weekday, used for the WEEKLY frequency and the BYWEEKNO
1856 recur_data->weekday_offset = cal_obj_time_weekday_offset (event_start,
1859 /* Create an array of months from bymonths for fast lookup. */
1860 elem = recur->bymonth;
1862 month = GPOINTER_TO_INT (elem->data);
1863 recur_data->months[month] = 1;
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;
1872 yearday = GPOINTER_TO_INT (elem->data);
1874 recur_data->yeardays[yearday] = 1;
1876 recur_data->neg_yeardays[-yearday] = 1;
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;
1885 monthday = GPOINTER_TO_INT (elem->data);
1887 recur_data->monthdays[monthday] = 1;
1889 recur_data->neg_monthdays[-monthday] = 1;
1893 /* Create an array of weekdays from byday for fast lookup. */
1894 elem = recur->byday;
1896 weekday = GPOINTER_TO_INT (elem->data);
1898 /* The week number is not used when filtering. */
1899 week_num = GPOINTER_TO_INT (elem->data);
1902 recur_data->weekdays[weekday] = 1;
1905 /* Create an array of hours from byhour for fast lookup. */
1906 elem = recur->byhour;
1908 hour = GPOINTER_TO_INT (elem->data);
1909 recur_data->hours[hour] = 1;
1913 /* Create an array of minutes from byminutes for fast lookup. */
1914 elem = recur->byminute;
1916 minute = GPOINTER_TO_INT (elem->data);
1917 recur_data->minutes[minute] = 1;
1921 /* Create an array of seconds from byseconds for fast lookup. */
1922 elem = recur->bysecond;
1924 second = GPOINTER_TO_INT (elem->data);
1925 recur_data->seconds[second] = 1;
1932 cal_obj_sort_occurrences (GArray *occs)
1934 qsort (occs->data, occs->len, sizeof (CalObjTime),
1935 cal_obj_time_compare_func);
1940 cal_obj_remove_duplicates_and_invalid_dates (GArray *occs)
1942 static const int days_in_month[12] = {
1943 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
1946 CalObjTime *occ, *prev_occ = NULL;
1947 gint len, i, j = 0, year, month, days;
1951 for (i = 0; i < len; i++) {
1952 occ = &g_array_index (occs, CalObjTime, i);
1955 if (prev_occ && cal_obj_time_compare_func (occ,
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
1965 || year % 400 == 0)))
1968 if (occ->day > days) {
1969 /* move occurrence to the last day of the month */
1975 g_array_index (occs, CalObjTime, j)
1976 = g_array_index (occs, CalObjTime, i);
1983 g_array_set_size (occs, j);
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. */
1990 cal_obj_remove_exceptions (GArray *occs,
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;
2001 occs_len = occs->len;
2002 ex_occs_len = ex_occs->len;
2004 if (ex_occs_len > 0)
2005 ex_occ = &g_array_index (ex_occs, CalObjTime, ex_index);
2007 for (i = 0; i < occs_len; i++) {
2008 occ = &g_array_index (occs, CalObjTime, i);
2011 /* If the occurrence is a duplicate of the previous one, skip
2014 && cal_obj_time_compare_func (occ, prev_occ) == 0) {
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,
2025 last_occ_kept->flags = TRUE;
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;
2034 /* Step through the exceptions until we come
2035 to one that matches or follows this
2038 /* If the exception is an EXDATE with
2039 a DATE value, we only have to
2040 compare the date. */
2042 cmp = cal_obj_date_only_compare_func (ex_occ, occ);
2044 cmp = cal_obj_time_compare_func (ex_occ, occ);
2049 /* Move to the next exception, or set
2050 ex_occ to NULL when we reach the
2053 if (ex_index < ex_occs_len)
2054 ex_occ = &g_array_index (ex_occs, CalObjTime, ex_index);
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. */
2063 current_time_is_exception = TRUE;
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
2077 g_array_index (occs, CalObjTime, j)
2078 = g_array_index (occs, CalObjTime, i);
2085 g_array_set_size (occs, j);
2091 cal_obj_bysetpos_filter (ECalRecurrence *recur,
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)
2105 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2107 /* Iterate over the indices given in bysetpos, adding the corresponding
2108 element from occs to new_occs. */
2111 pos = GPOINTER_TO_INT (elem->data);
2113 /* Negative values count back from the end of the array. */
2116 /* Positive values need to be decremented since the array is
2121 if (pos >= 0 && pos < len) {
2122 occ = &g_array_index (occs, CalObjTime, pos);
2123 g_array_append_vals (new_occs, occ, 1);
2128 g_array_free (occs, TRUE);
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. */
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,
2147 *cotime = *event_start;
2149 /* Move on to the next interval, if the event starts before the
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;
2159 if ((event_end && cotime->year > event_end->year)
2160 || (interval_end && cotime->year > interval_end->year))
2168 cal_obj_yearly_find_next_position (CalObjTime *cotime,
2169 CalObjTime *event_end,
2170 RecurData *recur_data,
2171 CalObjTime *interval_end)
2173 /* NOTE: The day may now be invalid, e.g. 29th Feb. */
2174 cotime->year += recur_data->recur->interval;
2176 if ((event_end && cotime->year > event_end->year)
2177 || (interval_end && cotime->year > interval_end->year))
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,
2193 *cotime = *event_start;
2195 /* Move on to the next interval, if the event starts before the
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);
2206 if (event_end && cal_obj_time_compare (cotime, event_end,
2209 if (interval_end && cal_obj_time_compare (cotime, interval_end,
2218 cal_obj_monthly_find_next_position (CalObjTime *cotime,
2219 CalObjTime *event_end,
2220 RecurData *recur_data,
2221 CalObjTime *interval_end)
2223 /* NOTE: The day may now be invalid, e.g. 31st Sep. */
2224 cal_obj_time_add_months (cotime, recur_data->recur->interval);
2226 if (event_end && cal_obj_time_compare (cotime, event_end,
2229 if (interval_end && cal_obj_time_compare (cotime, interval_end,
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,
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;
2251 if (event_end && cal_obj_time_compare (event_end, interval_start,
2254 if (interval_end && cal_obj_time_compare (event_start, interval_end,
2258 *cotime = *event_start;
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);
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;
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;
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);
2287 week_start = *cotime;
2288 cal_obj_time_add_days (&week_start, -recur_data->weekday_offset);
2290 if (event_end && cal_obj_time_compare (&week_start, event_end,
2293 if (interval_end && cal_obj_time_compare (&week_start, interval_end,
2302 cal_obj_weekly_find_next_position (CalObjTime *cotime,
2303 CalObjTime *event_end,
2304 RecurData *recur_data,
2305 CalObjTime *interval_end)
2307 CalObjTime week_start;
2309 cal_obj_time_add_days (cotime, recur_data->recur->interval * 7);
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);
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));
2321 if (event_end && cal_obj_time_compare (&week_start, event_end,
2324 if (interval_end && cal_obj_time_compare (&week_start, interval_end,
2326 #ifdef CAL_OBJ_DEBUG
2327 g_print ("Interval end reached: %s\n",
2328 cal_obj_time_to_string (interval_end));
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,
2345 GDate event_start_date, interval_start_date;
2346 guint32 event_start_julian, interval_start_julian, days;
2348 if (interval_end && cal_obj_time_compare (event_start, interval_end,
2351 if (event_end && cal_obj_time_compare (event_end, interval_start,
2355 *cotime = *event_start;
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);
2366 event_start_julian = g_date_get_julian (&event_start_date);
2367 interval_start_julian = g_date_get_julian (&interval_start_date);
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);
2376 if (event_end && cal_obj_time_compare (cotime, event_end,
2379 if (interval_end && cal_obj_time_compare (cotime, interval_end,
2388 cal_obj_daily_find_next_position (CalObjTime *cotime,
2389 CalObjTime *event_end,
2390 RecurData *recur_data,
2391 CalObjTime *interval_end)
2393 cal_obj_time_add_days (cotime, recur_data->recur->interval);
2395 if (event_end && cal_obj_time_compare (cotime, event_end,
2398 if (interval_end && cal_obj_time_compare (cotime, interval_end,
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,
2414 GDate event_start_date, interval_start_date;
2415 guint32 event_start_julian, interval_start_julian, hours;
2417 if (interval_end && cal_obj_time_compare (event_start, interval_end,
2420 if (event_end && cal_obj_time_compare (event_end, interval_start,
2424 *cotime = *event_start;
2426 if (cal_obj_time_compare (event_start, interval_start,
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);
2438 event_start_julian = g_date_get_julian (&event_start_date);
2439 interval_start_julian = g_date_get_julian (&interval_start_date);
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);
2448 if (event_end && cal_obj_time_compare (cotime, event_end,
2451 if (interval_end && cal_obj_time_compare (cotime, interval_end,
2460 cal_obj_hourly_find_next_position (CalObjTime *cotime,
2461 CalObjTime *event_end,
2462 RecurData *recur_data,
2463 CalObjTime *interval_end)
2465 cal_obj_time_add_hours (cotime, recur_data->recur->interval);
2467 if (event_end && cal_obj_time_compare (cotime, event_end,
2470 if (interval_end && cal_obj_time_compare (cotime, interval_end,
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,
2486 GDate event_start_date, interval_start_date;
2487 guint32 event_start_julian, interval_start_julian, minutes;
2489 if (interval_end && cal_obj_time_compare (event_start, interval_end,
2492 if (event_end && cal_obj_time_compare (event_end, interval_start,
2496 *cotime = *event_start;
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);
2510 event_start_julian = g_date_get_julian (&event_start_date);
2511 interval_start_julian = g_date_get_julian (&interval_start_date);
2513 minutes = (interval_start_julian - event_start_julian)
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);
2522 if (event_end && cal_obj_time_compare (cotime, event_end,
2525 if (interval_end && cal_obj_time_compare (cotime, interval_end,
2534 cal_obj_minutely_find_next_position (CalObjTime *cotime,
2535 CalObjTime *event_end,
2536 RecurData *recur_data,
2537 CalObjTime *interval_end)
2539 cal_obj_time_add_minutes (cotime, recur_data->recur->interval);
2541 if (event_end && cal_obj_time_compare (cotime, event_end,
2544 if (interval_end && cal_obj_time_compare (cotime, interval_end,
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,
2560 GDate event_start_date, interval_start_date;
2561 guint32 event_start_julian, interval_start_julian, seconds;
2563 if (interval_end && cal_obj_time_compare (event_start, interval_end,
2566 if (event_end && cal_obj_time_compare (event_end, interval_start,
2570 *cotime = *event_start;
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);
2584 event_start_julian = g_date_get_julian (&event_start_date);
2585 interval_start_julian = g_date_get_julian (&interval_start_date);
2587 seconds = (interval_start_julian - event_start_julian)
2589 seconds += (interval_start->hour - event_start->hour)
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);
2598 if (event_end && cal_obj_time_compare (cotime, event_end,
2599 CALOBJ_SECOND) >= 0)
2601 if (interval_end && cal_obj_time_compare (cotime, interval_end,
2602 CALOBJ_SECOND) >= 0)
2610 cal_obj_secondly_find_next_position (CalObjTime *cotime,
2611 CalObjTime *event_end,
2612 RecurData *recur_data,
2613 CalObjTime *interval_end)
2615 cal_obj_time_add_seconds (cotime, recur_data->recur->interval);
2617 if (event_end && cal_obj_time_compare (cotime, event_end,
2618 CALOBJ_SECOND) >= 0)
2620 if (interval_end && cal_obj_time_compare (cotime, interval_end,
2621 CALOBJ_SECOND) >= 0)
2631 /* If the BYMONTH rule is specified it expands each occurrence in occs, by
2632 using each of the months in the bymonth list. */
2634 cal_obj_bymonth_expand (RecurData *recur_data,
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)
2647 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2650 for (i = 0; i < len; i++) {
2651 occ = &g_array_index (occs, CalObjTime, i);
2653 elem = recur_data->recur->bymonth;
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);
2662 g_array_free (occs, TRUE);
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. */
2671 cal_obj_bymonth_filter (RecurData *recur_data,
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)
2683 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
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);
2692 g_array_free (occs, TRUE);
2700 cal_obj_byweekno_expand (RecurData *recur_data,
2704 CalObjTime *occ, year_start_cotime, year_end_cotime, cotime;
2706 gint len, i, weekno;
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)
2713 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2716 for (i = 0; i < len; i++) {
2717 occ = &g_array_index (occs, CalObjTime, i);
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,
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,
2733 /* Now iterate over the week numbers in byweekno, generating a
2734 new occurrence for each one. */
2735 elem = recur_data->recur->byweekno;
2737 weekno = GPOINTER_TO_INT (elem->data);
2739 cotime = year_start_cotime;
2740 cal_obj_time_add_days (&cotime,
2743 cotime = year_end_cotime;
2744 cal_obj_time_add_days (&cotime, weekno * 7);
2747 /* Skip occurrences if they fall outside the year. */
2748 if (cotime.year == occ->year)
2749 g_array_append_val (new_occs, cotime);
2754 g_array_free (occs, TRUE);
2761 /* This isn't used at present. */
2763 cal_obj_byweekno_filter (RecurData *recur_data,
2773 cal_obj_byyearday_expand (RecurData *recur_data,
2777 CalObjTime *occ, year_start_cotime, year_end_cotime, cotime;
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)
2786 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2789 for (i = 0; i < len; i++) {
2790 occ = &g_array_index (occs, CalObjTime, i);
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;
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;
2804 /* Now iterate over the day numbers in byyearday, generating a
2805 new occurrence for each one. */
2806 elem = recur_data->recur->byyearday;
2808 dayno = GPOINTER_TO_INT (elem->data);
2810 cotime = year_start_cotime;
2811 cal_obj_time_add_days (&cotime, dayno - 1);
2813 cotime = year_end_cotime;
2814 cal_obj_time_add_days (&cotime, dayno);
2817 /* Skip occurrences if they fall outside the year. */
2818 if (cotime.year == occ->year)
2819 g_array_append_val (new_occs, cotime);
2824 g_array_free (occs, TRUE);
2830 /* Note: occs must not contain invalid dates, e.g. 31st September. */
2832 cal_obj_byyearday_filter (RecurData *recur_data,
2837 gint yearday, len, i, days_in_year;
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)
2844 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
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);
2853 days_in_year = g_date_is_leap_year (occ->year)
2855 if (recur_data->neg_yeardays[days_in_year + 1
2857 g_array_append_vals (new_occs, occ, 1);
2861 g_array_free (occs, TRUE);
2869 cal_obj_bymonthday_expand (RecurData *recur_data,
2873 CalObjTime *occ, month_start_cotime, month_end_cotime, cotime;
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)
2882 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2885 for (i = 0; i < len; i++) {
2886 occ = &g_array_index (occs, CalObjTime, i);
2888 /* Find the day that would correspond to day 1. */
2889 month_start_cotime = *occ;
2890 month_start_cotime.day = 1;
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;
2898 /* Now iterate over the day numbers in bymonthday, generating a
2899 new occurrence for each one. */
2900 elem = recur_data->recur->bymonthday;
2902 dayno = GPOINTER_TO_INT (elem->data);
2904 cotime = month_start_cotime;
2905 cal_obj_time_add_days (&cotime, dayno - 1);
2907 cotime = month_end_cotime;
2908 cal_obj_time_add_days (&cotime, dayno);
2910 if (cotime.month == occ->month) {
2911 g_array_append_val (new_occs, cotime);
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);
2923 g_array_free (occs, TRUE);
2930 cal_obj_bymonthday_filter (RecurData *recur_data,
2935 gint len, i, days_in_month;
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)
2942 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
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);
2950 days_in_month = time_days_in_month (occ->year,
2952 if (recur_data->neg_monthdays[days_in_month + 1
2954 g_array_append_vals (new_occs, occ, 1);
2958 g_array_free (occs, TRUE);
2966 cal_obj_byday_expand_yearly (RecurData *recur_data,
2972 gint len, i, weekday, week_num;
2973 gint first_weekday, last_weekday, offset;
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)
2981 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2984 for (i = 0; i < len; i++) {
2985 occ = &g_array_index (occs, CalObjTime, i);
2987 elem = recur_data->recur->byday;
2989 weekday = GPOINTER_TO_INT (elem->data);
2991 week_num = GPOINTER_TO_INT (elem->data);
2995 if (week_num == 0) {
2996 /* Expand to every Mon/Tue/etc. in the year. */
2999 first_weekday = cal_obj_time_weekday (occ);
3000 offset = (weekday + 7 - first_weekday) % 7;
3001 cal_obj_time_add_days (occ, offset);
3003 while (occ->year == year) {
3004 g_array_append_vals (new_occs, occ, 1);
3005 cal_obj_time_add_days (occ, 7);
3008 } else if (week_num > 0) {
3009 /* Add the nth Mon/Tue/etc. in the year. */
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);
3020 /* Add the -nth Mon/Tue/etc. in the year. */
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);
3031 /* Reset the year, as we may have gone past the end. */
3036 g_array_free (occs, TRUE);
3043 cal_obj_byday_expand_monthly (RecurData *recur_data,
3049 gint len, i, weekday, week_num;
3050 gint first_weekday, last_weekday, offset;
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)
3059 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3062 for (i = 0; i < len; i++) {
3063 occ = &g_array_index (occs, CalObjTime, i);
3065 elem = recur_data->recur->byday;
3067 weekday = GPOINTER_TO_INT (elem->data);
3069 week_num = GPOINTER_TO_INT (elem->data);
3074 if (week_num == 0) {
3075 /* Expand to every Mon/Tue/etc. in the month.*/
3077 first_weekday = cal_obj_time_weekday (occ);
3078 offset = (weekday + 7 - first_weekday) % 7;
3079 cal_obj_time_add_days (occ, offset);
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);
3087 } else if (week_num > 0) {
3088 /* Add the nth Mon/Tue/etc. in the month. */
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);
3098 /* Add the -nth Mon/Tue/etc. in the month. */
3099 occ->day = time_days_in_month (occ->year,
3101 last_weekday = cal_obj_time_weekday (occ);
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;
3108 /* This adds on the weeks. */
3109 offset += (-week_num - 1) * 7;
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);
3116 /* Reset the year & month, as we may have gone past
3123 g_array_free (occs, TRUE);
3129 /* Note: occs must not contain invalid dates, e.g. 31st September. */
3131 cal_obj_byday_expand_weekly (RecurData *recur_data,
3137 gint len, i, weekday, week_num;
3138 gint weekday_offset, new_weekday_offset;
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)
3145 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3148 for (i = 0; i < len; i++) {
3149 occ = &g_array_index (occs, CalObjTime, i);
3151 elem = recur_data->recur->byday;
3153 weekday = GPOINTER_TO_INT (elem->data);
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);
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);
3169 g_array_free (occs, TRUE);
3175 /* Note: occs must not contain invalid dates, e.g. 31st September. */
3177 cal_obj_byday_filter (RecurData *recur_data,
3182 gint len, i, weekday;
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)
3189 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3192 for (i = 0; i < len; i++) {
3193 occ = &g_array_index (occs, CalObjTime, i);
3194 weekday = cal_obj_time_weekday (occ);
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);
3201 g_array_free (occs, TRUE);
3208 /* If the BYHOUR rule is specified it expands each occurrence in occs, by
3209 using each of the hours in the byhour list. */
3211 cal_obj_byhour_expand (RecurData *recur_data,
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)
3224 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3227 for (i = 0; i < len; i++) {
3228 occ = &g_array_index (occs, CalObjTime, i);
3230 elem = recur_data->recur->byhour;
3232 occ->hour = GPOINTER_TO_INT (elem->data);
3233 g_array_append_vals (new_occs, occ, 1);
3238 g_array_free (occs, TRUE);
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. */
3247 cal_obj_byhour_filter (RecurData *recur_data,
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)
3259 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
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);
3268 g_array_free (occs, TRUE);
3275 /* If the BYMINUTE rule is specified it expands each occurrence in occs, by
3276 using each of the minutes in the byminute list. */
3278 cal_obj_byminute_expand (RecurData *recur_data,
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)
3291 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3294 for (i = 0; i < len; i++) {
3295 occ = &g_array_index (occs, CalObjTime, i);
3297 elem = recur_data->recur->byminute;
3299 occ->minute = GPOINTER_TO_INT (elem->data);
3300 g_array_append_vals (new_occs, occ, 1);
3305 g_array_free (occs, TRUE);
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. */
3314 cal_obj_byminute_filter (RecurData *recur_data,
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)
3326 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
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);
3335 g_array_free (occs, TRUE);
3342 /* If the BYSECOND rule is specified it expands each occurrence in occs, by
3343 using each of the seconds in the bysecond list. */
3345 cal_obj_bysecond_expand (RecurData *recur_data,
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)
3358 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3361 for (i = 0; i < len; i++) {
3362 occ = &g_array_index (occs, CalObjTime, i);
3364 elem = recur_data->recur->bysecond;
3366 occ->second = GPOINTER_TO_INT (elem->data);
3367 g_array_append_vals (new_occs, occ, 1);
3372 g_array_free (occs, TRUE);
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. */
3381 cal_obj_bysecond_filter (RecurData *recur_data,
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)
3393 new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
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);
3402 g_array_free (occs, TRUE);
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. */
3415 cal_obj_time_add_months (CalObjTime *cotime,
3420 /* We use a guint to avoid overflow on the guint8. */
3421 month = cotime->month + months;
3422 cotime->month = month % 12;
3424 cotime->year += month / 12;
3427 if (cotime->month != 0) {
3428 cotime->month += 12;
3431 cotime->year += years;
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. */
3439 cal_obj_time_add_days (CalObjTime *cotime,
3442 gint day, days_in_month;
3444 /* We use a guint to avoid overflow on the guint8. */
3450 days_in_month = time_days_in_month (cotime->year,
3452 if (day <= days_in_month)
3456 if (cotime->month >= 12) {
3461 day -= days_in_month;
3464 cotime->day = (guint8) day;
3467 if (cotime->month == 0) {
3474 days_in_month = time_days_in_month (cotime->year,
3476 day += days_in_month;
3479 cotime->day = (guint8) day;
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
3488 cal_obj_time_add_hours (CalObjTime *cotime,
3493 /* We use a gint to avoid overflow on the guint8. */
3494 hour = cotime->hour + hours;
3495 cotime->hour = hour % 24;
3498 cal_obj_time_add_days (cotime, hour / 24);
3501 if (cotime->hour != 0) {
3505 cal_obj_time_add_days (cotime, days);
3510 /* Adds a positive or negative number of minutes to the given CalObjTime,
3511 updating the rest of the CalObjTime appropriately. */
3513 cal_obj_time_add_minutes (CalObjTime *cotime,
3518 /* We use a gint to avoid overflow on the guint8. */
3519 minute = cotime->minute + minutes;
3520 cotime->minute = minute % 60;
3523 cal_obj_time_add_hours (cotime, minute / 60);
3525 hours = minute / 60;
3526 if (cotime->minute != 0) {
3527 cotime->minute += 60;
3530 cal_obj_time_add_hours (cotime, hours);
3535 /* Adds a positive or negative number of seconds to the given CalObjTime,
3536 updating the rest of the CalObjTime appropriately. */
3538 cal_obj_time_add_seconds (CalObjTime *cotime,
3541 gint second, minutes;
3543 /* We use a gint to avoid overflow on the guint8. */
3544 second = cotime->second + seconds;
3545 cotime->second = second % 60;
3548 cal_obj_time_add_minutes (cotime, second / 60);
3550 minutes = second / 60;
3551 if (cotime->second != 0) {
3552 cotime->second += 60;
3555 cal_obj_time_add_minutes (cotime, minutes);
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. */
3565 cal_obj_time_compare (CalObjTime *cotime1,
3566 CalObjTime *cotime2,
3567 CalObjTimeComparison type)
3569 if (cotime1->year < cotime2->year)
3571 if (cotime1->year > cotime2->year)
3574 if (type == CALOBJ_YEAR)
3577 if (cotime1->month < cotime2->month)
3579 if (cotime1->month > cotime2->month)
3582 if (type == CALOBJ_MONTH)
3585 if (cotime1->day < cotime2->day)
3587 if (cotime1->day > cotime2->day)
3590 if (type == CALOBJ_DAY)
3593 if (cotime1->hour < cotime2->hour)
3595 if (cotime1->hour > cotime2->hour)
3598 if (type == CALOBJ_HOUR)
3601 if (cotime1->minute < cotime2->minute)
3603 if (cotime1->minute > cotime2->minute)
3606 if (type == CALOBJ_MINUTE)
3609 if (cotime1->second < cotime2->second)
3611 if (cotime1->second > cotime2->second)
3618 /* This is the same as the above function, but without the comparison type.
3619 It is used for qsort(). */
3621 cal_obj_time_compare_func (const void *arg1,
3624 CalObjTime *cotime1, *cotime2;
3627 cotime1 = (CalObjTime*) arg1;
3628 cotime2 = (CalObjTime*) arg2;
3630 if (cotime1->year < cotime2->year)
3632 else if (cotime1->year > cotime2->year)
3635 else if (cotime1->month < cotime2->month)
3637 else if (cotime1->month > cotime2->month)
3640 else if (cotime1->day < cotime2->day)
3642 else if (cotime1->day > cotime2->day)
3645 else if (cotime1->hour < cotime2->hour)
3647 else if (cotime1->hour > cotime2->hour)
3650 else if (cotime1->minute < cotime2->minute)
3652 else if (cotime1->minute > cotime2->minute)
3655 else if (cotime1->second < cotime2->second)
3657 else if (cotime1->second > cotime2->second)
3664 g_print ("%s - ", cal_obj_time_to_string (cotime1));
3665 g_print ("%s : %i\n", cal_obj_time_to_string (cotime2), retval);
3673 cal_obj_date_only_compare_func (const void *arg1,
3676 CalObjTime *cotime1, *cotime2;
3678 cotime1 = (CalObjTime*) arg1;
3679 cotime2 = (CalObjTime*) arg2;
3681 if (cotime1->year < cotime2->year)
3683 if (cotime1->year > cotime2->year)
3686 if (cotime1->month < cotime2->month)
3688 if (cotime1->month > cotime2->month)
3691 if (cotime1->day < cotime2->day)
3693 if (cotime1->day > cotime2->day)
3700 /* Returns the weekday of the given CalObjTime, from 0 (Mon) - 6 (Sun). */
3702 cal_obj_time_weekday (CalObjTime *cotime)
3707 g_date_clear (&date, 1);
3708 g_date_set_dmy (&date, cotime->day, cotime->month + 1, cotime->year);
3710 /* This results in a value of 0 (Monday) - 6 (Sunday). */
3711 weekday = g_date_get_weekday (&date) - 1;
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. */
3720 cal_obj_time_weekday_offset (CalObjTime *cotime,
3721 ECalRecurrence *recur)
3724 gint weekday, offset;
3726 g_date_clear (&date, 1);
3727 g_date_set_dmy (&date, cotime->day, cotime->month + 1, cotime->year);
3729 /* This results in a value of 0 (Monday) - 6 (Sunday). */
3730 weekday = g_date_get_weekday (&date) - 1;
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
3736 offset = (weekday + 7 - recur->week_start_day) % 7;
3742 /* Returns the day of the year of the given CalObjTime, from 1 - 366. */
3744 cal_obj_time_day_of_year (CalObjTime *cotime)
3748 g_date_clear (&date, 1);
3749 g_date_set_dmy (&date, cotime->day, cotime->month + 1, cotime->year);
3751 return g_date_get_day_of_year (&date);
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
3761 cal_obj_time_find_first_week (CalObjTime *cotime,
3762 RecurData *recur_data)
3765 gint weekday, week_start_day, first_full_week_start_offset, offset;
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;
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;
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;
3785 /* Now add the days to get to the event's weekday. */
3786 offset = first_full_week_start_offset + recur_data->weekday_offset;
3788 /* Now move the cotime to the appropriate day. */
3791 cal_obj_time_add_days (cotime, offset);
3796 cal_object_time_from_time (CalObjTime *cotime,
3800 struct icaltimetype tt;
3803 tt = icaltime_from_timet_with_zone (t, FALSE, zone);
3805 tt = icaltime_from_timet (t, FALSE);
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;
3817 /* Debugging function to convert a CalObjTime to a string. It uses a static
3818 buffer so beware. */
3819 #ifdef CAL_OBJ_DEBUG
3821 cal_obj_time_to_string (CalObjTime *cotime)
3823 static char buffer[20];
3824 char *weekdays[] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
3828 weekday = cal_obj_time_weekday (cotime);
3830 sprintf (buffer, "%s %02i/%02i/%04i %02i:%02i:%02i",
3832 cotime->day, cotime->month + 1, cotime->year,
3833 cotime->hour, cotime->minute, cotime->second);
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
3847 e_cal_recur_ensure_end_dates (ECalComponent *comp,
3849 ECalRecurResolveTimezoneFn tz_cb,
3850 gpointer tz_cb_data)
3852 GSList *rrules, *exrules, *elem;
3853 gboolean changed = FALSE;
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,
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,
3875 typedef struct _ECalRecurEnsureEndDateData ECalRecurEnsureEndDateData;
3876 struct _ECalRecurEnsureEndDateData {
3884 e_cal_recur_ensure_rule_end_date (ECalComponent *comp,
3888 ECalRecurResolveTimezoneFn tz_cb,
3889 gpointer tz_cb_data)
3891 struct icalrecurrencetype rule;
3892 ECalRecurEnsureEndDateData cb_data;
3895 rule = icalproperty_get_exrule (prop);
3897 rule = icalproperty_get_rrule (prop);
3899 /* If the rule doesn't use COUNT just return. */
3900 if (rule.count == 0)
3903 /* If refresh is FALSE, we check if the enddate is already set, and
3904 if it is we just return. */
3906 if (e_cal_recur_get_rule_end_date (prop, NULL) != -1)
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 ());
3923 /* Store the end date in the "X-EVOLUTION-ENDDATE" parameter of the
3925 e_cal_recur_set_rule_end_date (prop, cb_data.end_date);
3932 e_cal_recur_ensure_rule_end_date_cb (ECalComponent *comp,
3933 time_t instance_start,
3934 time_t instance_end,
3937 ECalRecurEnsureEndDateData *cb_data;
3939 cb_data = (ECalRecurEnsureEndDateData*) data;
3941 cb_data->instances++;
3943 if (cb_data->instances == cb_data->count) {
3944 cb_data->end_date = instance_start;
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. */
3957 e_cal_recur_get_rule_end_date (icalproperty *prop,
3958 icaltimezone *default_timezone)
3960 icalparameter *param;
3961 const char *xname, *xvalue;
3963 struct icaltimetype icaltime;
3966 param = icalproperty_get_first_parameter (prop, ICAL_X_PARAMETER);
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,
3974 icaltime = icalvalue_get_datetime (value);
3975 icalvalue_free (value);
3977 zone = default_timezone ? default_timezone :
3978 icaltimezone_get_utc_timezone ();
3979 return icaltime_as_timet_with_zone (icaltime,
3984 param = icalproperty_get_next_parameter (prop,
3993 e_cal_recur_set_rule_end_date (icalproperty *prop,
3996 icalparameter *param;
3998 icaltimezone *utc_zone;
3999 struct icaltimetype icaltime;
4000 const char *end_date_string, *xname;
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);
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);
4013 xname = icalparameter_get_xname (param);
4014 if (xname && !strcmp (xname, EVOLUTION_END_DATE_PARAMETER)) {
4015 icalparameter_set_x (param, end_date_string);
4018 param = icalproperty_get_next_parameter (prop, ICAL_X_PARAMETER);
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);
4028 #undef e_cal_recur_nth
4034 * An array of 31 translated strings for each day of the month (i.e. "1st",
4035 * "2nd", and so on).
4037 const char *e_cal_recur_nth[31] = {
4074 * e_cal_get_recur_nth:
4076 * Returns an array of 31 translated strings for each day of the month
4077 * (i.e. "1st", "2nd", and so on).
4079 * Returns: a pointer to an array of strings. This array is static, do not free it.
4082 e_cal_get_recur_nth (void)
4084 return e_cal_recur_nth;