1 /* Evolution calendar utilities and types
3 * Copyright (C) 2000 Ximian, Inc.
4 * Copyright (C) 2000 Ximian, Inc.
6 * Author: Federico Mena-Quintero <federico@ximian.com>
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of version 2 of the GNU Lesser General Public
10 * License as published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26 #include <glib/gstdio.h>
27 #include <glib/gi18n-lib.h>
28 #include "libedataserver/e-data-server-util.h"
29 #include "e-cal-util.h"
34 * cal_obj_instance_list_free:
35 * @list: List of #CalObjInstance structures.
37 * Frees a list of #CalObjInstance structures.
40 cal_obj_instance_list_free (GList *list)
45 for (l = list; l; l = l->next) {
49 g_assert (i->uid != NULL);
59 * cal_obj_uid_list_free:
60 * @list: List of strings with unique identifiers.
62 * Frees a list of unique identifiers for calendar objects.
65 cal_obj_uid_list_free (GList *list)
69 for (l = list; l; l = l->next) {
74 g_assert (uid != NULL);
82 * e_cal_util_new_top_level:
84 * Creates a new VCALENDAR component.
86 * Return value: the newly created top level component.
89 e_cal_util_new_top_level (void)
91 icalcomponent *icalcomp;
94 icalcomp = icalcomponent_new (ICAL_VCALENDAR_COMPONENT);
96 /* RFC 2445, section 4.7.1 */
97 prop = icalproperty_new_calscale ("GREGORIAN");
98 icalcomponent_add_property (icalcomp, prop);
100 /* RFC 2445, section 4.7.3 */
101 prop = icalproperty_new_prodid ("-//Ximian//NONSGML Evolution Calendar//EN");
102 icalcomponent_add_property (icalcomp, prop);
104 /* RFC 2445, section 4.7.4. This is the iCalendar spec version, *NOT*
105 * the product version! Do not change this!
107 prop = icalproperty_new_version ("2.0");
108 icalcomponent_add_property (icalcomp, prop);
114 * e_cal_util_new_component:
115 * @kind: Kind of the component to create.
117 * Creates a new #icalcomponent of the specified kind.
119 * Return value: the newly created component.
122 e_cal_util_new_component (icalcomponent_kind kind)
125 struct icaltimetype dtstamp;
127 comp = icalcomponent_new (kind);
128 icalcomponent_set_uid (comp, e_cal_component_gen_uid ());
129 dtstamp = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
130 icalcomponent_set_dtstamp (comp, dtstamp);
136 read_line (const char *string)
139 GString *line_str = NULL;
141 for (; *string; string++) {
143 line_str = g_string_new ("");
145 line_str = g_string_append_c (line_str, *string);
150 line = line_str->str;
151 g_string_free (line_str, FALSE);
157 * e_cal_util_parse_ics_string:
158 * @string: iCalendar string to be parsed.
160 * Parses an iCalendar stirng and returns an #icalcomponent representing that
161 * string. Note that this function deals with multiple VCALENDAR's in the
162 * string, something that Mozilla used to do and which libical does not
165 * Return value: an #icalcomponent.
168 e_cal_util_parse_ics_string (const char *string)
171 icalcomponent *icalcomp = NULL;
173 g_return_val_if_fail (string != NULL, NULL);
175 /* Split string into separated VCALENDAR's, if more than one */
176 if ((s = g_strstr_len (string, strlen (string), "BEGIN:VCALENDAR"))) {
177 GString *comp_str = NULL;
180 char *line = read_line (s);
183 comp_str = g_string_new (line);
185 comp_str = g_string_append (comp_str, line);
187 if (!strncmp (line, "END:VCALENDAR", 13)) {
190 tmp = icalparser_parse_string (comp_str->str);
191 if (tmp && icalcomponent_isa (tmp) == ICAL_VCALENDAR_COMPONENT) {
193 icalcomponent_merge_component (icalcomp, tmp);
197 g_warning ("Could not merge the components, the component is either invalid or not a toplevel component \n");
199 g_string_free (comp_str, TRUE);
209 icalcomp = icalparser_parse_string (string);
215 get_line_fn (char *buf, size_t size, void *file)
217 return fgets (buf, size, file);
221 * e_cal_util_parse_ics_file:
222 * @filename: Name of the file to be parsed.
224 * Parses the given file, and, if it contains a valid iCalendar object,
225 * parse it and return a corresponding #icalcomponent.
227 * Return value: an #icalcomponent.
230 e_cal_util_parse_ics_file (const char *filename)
233 icalcomponent *icalcomp;
236 file = g_fopen (filename, "rb");
240 parser = icalparser_new ();
241 icalparser_set_gen_data (parser, file);
243 icalcomp = icalparser_parse (parser, get_line_fn);
244 icalparser_free (parser);
250 /* Computes the range of time in which recurrences should be generated for a
251 * component in order to compute alarm trigger times.
254 compute_alarm_range (ECalComponent *comp, GList *alarm_uids, time_t start, time_t end,
255 time_t *alarm_start, time_t *alarm_end)
260 *alarm_start = start;
265 for (l = alarm_uids; l; l = l->next) {
267 ECalComponentAlarm *alarm;
268 ECalComponentAlarmTrigger trigger;
269 struct icaldurationtype *dur;
271 ECalComponentAlarmRepeat repeat;
274 alarm = e_cal_component_get_alarm (comp, auid);
275 g_assert (alarm != NULL);
277 e_cal_component_alarm_get_trigger (alarm, &trigger);
278 e_cal_component_alarm_get_repeat (alarm, &repeat);
279 e_cal_component_alarm_free (alarm);
281 switch (trigger.type) {
282 case E_CAL_COMPONENT_ALARM_TRIGGER_NONE:
283 case E_CAL_COMPONENT_ALARM_TRIGGER_ABSOLUTE:
286 case E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START:
287 case E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_END:
288 dur = &trigger.u.rel_duration;
289 dur_time = icaldurationtype_as_int (*dur);
291 if (repeat.repetitions != 0) {
294 rdur = repeat.repetitions * icaldurationtype_as_int (repeat.duration);
295 repeat_time = MAX (repeat_time, rdur);
299 /* If the duration is negative then dur_time
300 * will be negative as well; that is why we
301 * subtract to expand the range.
303 *alarm_end = MAX (*alarm_end, end - dur_time);
305 *alarm_start = MIN (*alarm_start, start - dur_time);
310 g_assert_not_reached ();
314 *alarm_start -= repeat_time;
316 g_assert (*alarm_start <= *alarm_end);
319 /* Closure data to generate alarm occurrences */
320 struct alarm_occurrence_data {
321 /* These are the info we have */
325 ECalComponentAlarmAction *omit;
327 /* This is what we compute */
333 add_trigger (struct alarm_occurrence_data *aod, const char *auid, time_t trigger,
334 time_t occur_start, time_t occur_end)
336 ECalComponentAlarmInstance *instance;
338 instance = g_new (ECalComponentAlarmInstance, 1);
339 instance->auid = auid;
340 instance->trigger = trigger;
341 instance->occur_start = occur_start;
342 instance->occur_end = occur_end;
344 aod->triggers = g_slist_prepend (aod->triggers, instance);
348 /* Callback used from cal_recur_generate_instances(); generates triggers for all
349 * of a component's RELATIVE alarms.
352 add_alarm_occurrences_cb (ECalComponent *comp, time_t start, time_t end, gpointer data)
354 struct alarm_occurrence_data *aod;
359 for (l = aod->alarm_uids; l; l = l->next) {
361 ECalComponentAlarm *alarm;
362 ECalComponentAlarmAction action;
363 ECalComponentAlarmTrigger trigger;
364 ECalComponentAlarmRepeat repeat;
365 struct icaldurationtype *dur;
367 time_t occur_time, trigger_time;
371 alarm = e_cal_component_get_alarm (comp, auid);
372 g_assert (alarm != NULL);
374 e_cal_component_alarm_get_action (alarm, &action);
375 e_cal_component_alarm_get_trigger (alarm, &trigger);
376 e_cal_component_alarm_get_repeat (alarm, &repeat);
377 e_cal_component_alarm_free (alarm);
379 for (i = 0; aod->omit[i] != -1; i++) {
380 if (aod->omit[i] == action)
383 if (aod->omit[i] != -1)
386 if (trigger.type != E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START
387 && trigger.type != E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_END)
390 dur = &trigger.u.rel_duration;
391 dur_time = icaldurationtype_as_int (*dur);
393 if (trigger.type == E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START)
398 /* If dur->is_neg is true then dur_time will already be
399 * negative. So we do not need to test for dur->is_neg here; we
400 * can simply add the dur_time value to the occur_time and get
401 * the correct result.
404 trigger_time = occur_time + dur_time;
406 /* Add repeating alarms */
408 if (repeat.repetitions != 0) {
412 repeat_time = icaldurationtype_as_int (repeat.duration);
414 for (i = 0; i < repeat.repetitions; i++) {
417 t = trigger_time + (i + 1) * repeat_time;
419 if (t >= aod->start && t < aod->end)
420 add_trigger (aod, auid, t, start, end);
424 /* Add the trigger itself */
426 if (trigger_time >= aod->start && trigger_time < aod->end)
427 add_trigger (aod, auid, trigger_time, start, end);
433 /* Generates the absolute triggers for a component */
435 generate_absolute_triggers (ECalComponent *comp, struct alarm_occurrence_data *aod,
436 ECalRecurResolveTimezoneFn resolve_tzid,
438 icaltimezone *default_timezone)
441 ECalComponentDateTime dt_start, dt_end;
443 e_cal_component_get_dtstart (comp, &dt_start);
444 e_cal_component_get_dtend (comp, &dt_end);
446 for (l = aod->alarm_uids; l; l = l->next) {
448 ECalComponentAlarm *alarm;
449 ECalComponentAlarmAction action;
450 ECalComponentAlarmRepeat repeat;
451 ECalComponentAlarmTrigger trigger;
453 time_t occur_start, occur_end;
458 alarm = e_cal_component_get_alarm (comp, auid);
459 g_assert (alarm != NULL);
461 e_cal_component_alarm_get_action (alarm, &action);
462 e_cal_component_alarm_get_trigger (alarm, &trigger);
463 e_cal_component_alarm_get_repeat (alarm, &repeat);
464 e_cal_component_alarm_free (alarm);
466 for (i = 0; aod->omit[i] != -1; i++) {
467 if (aod->omit[i] == action)
470 if (aod->omit[i] != -1)
473 if (trigger.type != E_CAL_COMPONENT_ALARM_TRIGGER_ABSOLUTE)
476 /* Absolute triggers are always in UTC; see RFC 2445 section 4.8.6.3 */
477 zone = icaltimezone_get_utc_timezone ();
479 abs_time = icaltime_as_timet_with_zone (trigger.u.abs_time, zone);
481 /* No particular occurrence, so just use the times from the component */
483 if (dt_start.value) {
484 if (dt_start.tzid && !dt_start.value->is_date)
485 zone = (* resolve_tzid) (dt_start.tzid, user_data);
487 zone = default_timezone;
489 occur_start = icaltime_as_timet_with_zone (*dt_start.value, zone);
494 if (dt_end.tzid && !dt_end.value->is_date)
495 zone = (* resolve_tzid) (dt_end.tzid, user_data);
497 zone = default_timezone;
499 occur_end = icaltime_as_timet_with_zone (*dt_end.value, zone);
503 /* Add repeating alarms */
505 if (repeat.repetitions != 0) {
509 repeat_time = icaldurationtype_as_int (repeat.duration);
511 for (i = 0; i < repeat.repetitions; i++) {
514 t = abs_time + (i + 1) * repeat_time;
516 if (t >= aod->start && t < aod->end)
517 add_trigger (aod, auid, t, occur_start, occur_end);
521 /* Add the trigger itself */
523 if (abs_time >= aod->start && abs_time < aod->end)
524 add_trigger (aod, auid, abs_time, occur_start, occur_end);
527 e_cal_component_free_datetime (&dt_start);
528 e_cal_component_free_datetime (&dt_end);
531 /* Compares two alarm instances; called from g_slist_sort() */
533 compare_alarm_instance (gconstpointer a, gconstpointer b)
535 const ECalComponentAlarmInstance *aia, *aib;
540 if (aia->trigger < aib->trigger)
542 else if (aia->trigger > aib->trigger)
549 * e_cal_util_generate_alarms_for_comp
550 * @comp: The #ECalComponent to generate alarms from.
551 * @start: Start time.
553 * @omit: Alarm types to omit
554 * @resolve_tzid: Callback for resolving timezones
555 * @user_data: Data to be passed to the resolve_tzid callback
556 * @default_timezone: The timezone used to resolve DATE and floating DATE-TIME
559 * Generates alarm instances for a calendar component. Returns the instances
560 * structure, or NULL if no alarm instances occurred in the specified time
563 * Return value: a list of all the alarms found for the given component on
564 * the given time tange. The list of alarms should be freed by using the
565 * #e_cal_component_free_alarm_list function.
567 ECalComponentAlarms *
568 e_cal_util_generate_alarms_for_comp (ECalComponent *comp,
571 ECalComponentAlarmAction *omit,
572 ECalRecurResolveTimezoneFn resolve_tzid,
574 icaltimezone *default_timezone)
577 time_t alarm_start, alarm_end;
578 struct alarm_occurrence_data aod;
579 ECalComponentAlarms *alarms;
581 if (!e_cal_component_has_alarms (comp))
584 alarm_uids = e_cal_component_get_alarm_uids (comp);
585 compute_alarm_range (comp, alarm_uids, start, end, &alarm_start, &alarm_end);
587 aod.alarm_uids = alarm_uids;
594 e_cal_recur_generate_instances (comp, alarm_start, alarm_end,
595 add_alarm_occurrences_cb, &aod,
596 resolve_tzid, user_data,
599 /* We add the ABSOLUTE triggers separately */
600 generate_absolute_triggers (comp, &aod, resolve_tzid, user_data, default_timezone);
602 if (aod.n_triggers == 0)
605 /* Create the component alarm instances structure */
607 alarms = g_new (ECalComponentAlarms, 1);
609 g_object_ref (G_OBJECT (alarms->comp));
610 alarms->alarms = g_slist_sort (aod.triggers, compare_alarm_instance);
616 * e_cal_util_generate_alarms_for_list
617 * @comps: List of #ECalComponent's.
618 * @start: Start time.
620 * @omit: Alarm types to omit
621 * @comp_alarms: List to be returned
622 * @resolve_tzid: Callback for resolving timezones
623 * @user_data: Data to be passed to the resolve_tzid callback
624 * @default_timezone: The timezone used to resolve DATE and floating DATE-TIME
627 * Iterates through all the components in the @comps list and generates alarm
628 * instances for them; putting them in the @comp_alarms list.
630 * Return value: the number of elements it added to the list.
633 e_cal_util_generate_alarms_for_list (GList *comps,
636 ECalComponentAlarmAction *omit,
637 GSList **comp_alarms,
638 ECalRecurResolveTimezoneFn resolve_tzid,
640 icaltimezone *default_timezone)
647 for (l = comps; l; l = l->next) {
649 ECalComponentAlarms *alarms;
651 comp = E_CAL_COMPONENT (l->data);
652 alarms = e_cal_util_generate_alarms_for_comp (comp, start, end, omit, resolve_tzid, user_data, default_timezone);
655 *comp_alarms = g_slist_prepend (*comp_alarms, alarms);
665 * e_cal_util_priority_to_string:
666 * @priority: Priority value.
668 * Converts an iCalendar PRIORITY value to a translated string. Any unknown
669 * priority value (i.e. not 0-9) will be returned as "" (undefined).
671 * Return value: a string representing the PRIORITY value. This value is a
672 * constant, so it should never be freed.
675 e_cal_util_priority_to_string (int priority)
681 else if (priority <= 4)
683 else if (priority == 5)
684 retval = _("Normal");
685 else if (priority <= 9)
695 * e_cal_util_priority_from_string:
696 * @string: A string representing the PRIORITY value.
698 * Converts a translated priority string to an iCalendar priority value.
700 * Return value: the priority (0-9) or -1 if the priority string is not valid.
703 e_cal_util_priority_from_string (const char *string)
707 /* An empty string is the same as 'None'. */
708 if (!string || !string[0] || !e_util_utf8_strcasecmp (string, _("Undefined")))
710 else if (!e_util_utf8_strcasecmp (string, _("High")))
712 else if (!e_util_utf8_strcasecmp (string, _("Normal")))
714 else if (!e_util_utf8_strcasecmp (string, _("Low")))
722 /* callback for icalcomponent_foreach_tzid */
724 icalcomponent *vcal_comp;
725 icalcomponent *icalcomp;
729 add_timezone_cb (icalparameter *param, void *data)
733 icalcomponent *vtz_comp;
734 ForeachTzidData *f_data = (ForeachTzidData *) data;
736 tzid = icalparameter_get_tzid (param);
740 tz = icalcomponent_get_timezone (f_data->vcal_comp, tzid);
744 tz = icalcomponent_get_timezone (f_data->icalcomp, tzid);
746 tz = icaltimezone_get_builtin_timezone_from_tzid (tzid);
751 vtz_comp = icaltimezone_get_component (tz);
755 icalcomponent_add_component (f_data->vcal_comp,
756 icalcomponent_new_clone (vtz_comp));
760 * e_cal_util_add_timezones_from_component:
761 * @vcal_comp: A VCALENDAR component.
762 * @icalcomp: An iCalendar component, of any type.
764 * Adds VTIMEZONE components to a VCALENDAR for all tzid's
765 * in the given @icalcomp.
768 e_cal_util_add_timezones_from_component (icalcomponent *vcal_comp,
769 icalcomponent *icalcomp)
771 ForeachTzidData f_data;
773 g_return_if_fail (vcal_comp != NULL);
774 g_return_if_fail (icalcomp != NULL);;
776 f_data.vcal_comp = vcal_comp;
777 f_data.icalcomp = icalcomp;
778 icalcomponent_foreach_tzid (icalcomp, add_timezone_cb, &f_data);
782 * e_cal_util_component_is_instance:
783 * @icalcomp: An #icalcomponent.
785 * Checks whether an #icalcomponent is an instance of a recurring appointment or not.
787 * Return value: TRUE if it is an instance, FALSE if not.
790 e_cal_util_component_is_instance (icalcomponent *icalcomp)
794 g_return_val_if_fail (icalcomp != NULL, FALSE);
796 prop = icalcomponent_get_first_property (icalcomp, ICAL_RECURRENCEID_PROPERTY);
797 return prop ? TRUE : FALSE;
801 * e_cal_util_component_has_alarms:
802 * @icalcomp: An #icalcomponent.
804 * Checks whether an #icalcomponent has any alarm.
806 * Return value: TRUE if it has alarms, FALSE otherwise.
809 e_cal_util_component_has_alarms (icalcomponent *icalcomp)
811 icalcomponent *alarm;
813 g_return_val_if_fail (icalcomp != NULL, FALSE);
815 alarm = icalcomponent_get_first_component (icalcomp, ICAL_VALARM_COMPONENT);
816 return alarm ? TRUE : FALSE;
820 * e_cal_util_component_has_organizer:
821 * @icalcomp: An #icalcomponent.
823 * Checks whether an #icalcomponent has an organizer or not.
825 * Return value: TRUE if there is an organizer, FALSE if not.
828 e_cal_util_component_has_organizer (icalcomponent *icalcomp)
832 g_return_val_if_fail (icalcomp != NULL, FALSE);
834 prop = icalcomponent_get_first_property (icalcomp, ICAL_ORGANIZER_PROPERTY);
835 return prop ? TRUE : FALSE;
839 * e_cal_util_component_has_attendee:
840 * @icalcomp: An #icalcomponent.
842 * Checks if an #icalcomponent has any attendees.
844 * Return value: TRUE if there are attendees, FALSE if not.
847 e_cal_util_component_has_attendee (icalcomponent *icalcomp)
851 g_return_val_if_fail (icalcomp != NULL, FALSE);
853 prop = icalcomponent_get_first_property (icalcomp, ICAL_ATTENDEE_PROPERTY);
855 return prop ? TRUE : FALSE;
859 * e_cal_util_component_has_recurrences:
860 * @icalcomp: An #icalcomponent.
862 * Checks if an #icalcomponent has recurrence dates or rules.
864 * Return value: TRUE if there are recurrence dates/rules, FALSE if not.
867 e_cal_util_component_has_recurrences (icalcomponent *icalcomp)
869 g_return_val_if_fail (icalcomp != NULL, FALSE);
871 return e_cal_util_component_has_rdates (icalcomp) || e_cal_util_component_has_rrules (icalcomp);
875 * e_cal_util_component_has_rdates:
876 * @icalcomp: An #icalcomponent.
878 * Checks if an #icalcomponent has recurrence dates.
880 * Return value: TRUE if there are recurrence dates, FALSE if not.
883 e_cal_util_component_has_rdates (icalcomponent *icalcomp)
887 g_return_val_if_fail (icalcomp != NULL, FALSE);
889 prop = icalcomponent_get_first_property (icalcomp, ICAL_RDATE_PROPERTY);
890 return prop ? TRUE : FALSE;
894 * e_cal_util_component_has_rrules:
895 * @icalcomp: An #icalcomponent.
897 * Checks if an #icalcomponent has recurrence rules.
899 * Return value: TRUE if there are recurrence rules, FALSE if not.
902 e_cal_util_component_has_rrules (icalcomponent *icalcomp)
906 g_return_val_if_fail (icalcomp != NULL, FALSE);
908 prop = icalcomponent_get_first_property (icalcomp, ICAL_RRULE_PROPERTY);
909 return prop ? TRUE : FALSE;
913 * e_cal_util_event_dates_match:
914 * @icalcomp1: An #icalcomponent.
915 * @icalcomp2: An #icalcomponent.
917 * Compare the dates of two #icalcomponent's to check if they match.
919 * Return value: TRUE if the dates of both components match, FALSE otherwise.
922 e_cal_util_event_dates_match (icalcomponent *icalcomp1, icalcomponent *icalcomp2)
924 struct icaltimetype c1_dtstart, c1_dtend, c2_dtstart, c2_dtend;
926 g_return_val_if_fail (icalcomp1 != NULL, FALSE);
927 g_return_val_if_fail (icalcomp2 != NULL, FALSE);
929 c1_dtstart = icalcomponent_get_dtstart (icalcomp1);
930 c1_dtend = icalcomponent_get_dtend (icalcomp1);
931 c2_dtstart = icalcomponent_get_dtstart (icalcomp2);
932 c2_dtend = icalcomponent_get_dtend (icalcomp2);
934 /* if either value is NULL, they must both be NULL to match */
935 if (icaltime_is_valid_time (c1_dtstart) || icaltime_is_valid_time (c2_dtstart)) {
936 if (!(icaltime_is_valid_time (c1_dtstart) && icaltime_is_valid_time (c2_dtstart)))
939 if (icaltime_compare (c1_dtstart, c2_dtstart))
943 if (icaltime_is_valid_time (c1_dtend) || icaltime_is_valid_time (c2_dtend)) {
944 if (!(icaltime_is_valid_time (c1_dtend) && icaltime_is_valid_time (c2_dtend)))
947 if (icaltime_compare (c1_dtend, c2_dtend))
953 /* now match the timezones */
954 if (!(!c1_dtstart.zone && !c2_dtstart.zone) ||
955 (c1_dtstart.zone && c2_dtstart.zone &&
956 !strcmp (icaltimezone_get_tzid ((icaltimezone *) c1_dtstart.zone),
957 icaltimezone_get_tzid ((icaltimezone *) c2_dtstart.zone))))
960 if (!(!c1_dtend.zone && !c2_dtend.zone) ||
961 (c1_dtend.zone && c2_dtend.zone &&
962 !strcmp (icaltimezone_get_tzid ((icaltimezone *) c1_dtend.zone),
963 icaltimezone_get_tzid ((icaltimezone *) c2_dtend.zone))))
969 /* Individual instances management */
971 struct instance_data {
977 check_instance (icalcomponent *comp, struct icaltime_span *span, void *data)
979 struct instance_data *instance = data;
981 if (span->start == instance->start)
982 instance->found = TRUE;
986 * e_cal_util_construct_instance:
987 * @icalcomp: A recurring #icalcomponent
988 * @rid: The RECURRENCE-ID to construct a component for
990 * This checks that @rid indicates a valid recurrence of @icalcomp, and
991 * if so, generates a copy of @comp containing a RECURRENCE-ID of @rid.
993 * Return value: the instance, or %NULL.
996 e_cal_util_construct_instance (icalcomponent *icalcomp,
997 struct icaltimetype rid)
999 struct instance_data instance;
1000 struct icaltimetype start, end;
1002 g_return_val_if_fail (icalcomp != NULL, NULL);
1004 /* Make sure this is really recurring */
1005 if (!icalcomponent_get_first_property (icalcomp, ICAL_RRULE_PROPERTY) &&
1006 !icalcomponent_get_first_property (icalcomp, ICAL_RDATE_PROPERTY))
1009 /* Make sure the specified instance really exists */
1010 start = icaltime_convert_to_zone (rid, icaltimezone_get_utc_timezone ());
1012 icaltime_adjust (&end, 0, 0, 0, 1);
1014 instance.start = icaltime_as_timet (start);
1015 instance.found = FALSE;
1016 icalcomponent_foreach_recurrence (icalcomp, start, end,
1017 check_instance, &instance);
1018 if (!instance.found)
1021 /* Make the instance */
1022 icalcomp = icalcomponent_new_clone (icalcomp);
1023 icalcomponent_set_recurrenceid (icalcomp, rid);
1028 static inline gboolean
1029 time_matches_rid (struct icaltimetype itt, struct icaltimetype rid, CalObjModType mod)
1033 compare = icaltime_compare (itt, rid);
1036 else if (compare < 0 && (mod & CALOBJ_MOD_THISANDPRIOR))
1038 else if (compare > 0 && (mod & CALOBJ_MOD_THISANDFUTURE))
1045 * e_cal_util_remove_instances:
1046 * @icalcomp: A (recurring) #icalcomponent
1047 * @rid: The base RECURRENCE-ID to remove
1048 * @mod: How to interpret @rid
1050 * Removes one or more instances from @comp according to @rid and @mod.
1052 * FIXME: should probably have a return value indicating whether or not
1053 * @icalcomp still has any instances
1056 e_cal_util_remove_instances (icalcomponent *icalcomp,
1057 struct icaltimetype rid,
1061 struct icaltimetype itt, recur;
1062 struct icalrecurrencetype rule;
1063 icalrecur_iterator *iter;
1065 g_return_if_fail (icalcomp != NULL);
1066 g_return_if_fail (mod != CALOBJ_MOD_ALL);
1068 /* First remove RDATEs and EXDATEs in the indicated range. */
1069 for (prop = icalcomponent_get_first_property (icalcomp, ICAL_RDATE_PROPERTY);
1071 prop = icalcomponent_get_next_property (icalcomp, ICAL_RDATE_PROPERTY)) {
1072 struct icaldatetimeperiodtype period;
1074 period = icalproperty_get_rdate (prop);
1075 if (time_matches_rid (period.time, rid, mod))
1076 icalcomponent_remove_property (icalcomp, prop);
1078 for (prop = icalcomponent_get_first_property (icalcomp, ICAL_EXDATE_PROPERTY);
1080 prop = icalcomponent_get_next_property (icalcomp, ICAL_EXDATE_PROPERTY)) {
1081 itt = icalproperty_get_exdate (prop);
1082 if (time_matches_rid (itt, rid, mod))
1083 icalcomponent_remove_property (icalcomp, prop);
1086 /* If we're only removing one instance, just add an EXDATE. */
1087 if (mod == CALOBJ_MOD_THIS) {
1088 prop = icalproperty_new_exdate (rid);
1089 icalcomponent_add_property (icalcomp, prop);
1093 /* Otherwise, iterate through RRULEs */
1094 /* FIXME: this may generate duplicate EXRULEs */
1095 for (prop = icalcomponent_get_first_property (icalcomp, ICAL_RRULE_PROPERTY);
1097 prop = icalcomponent_get_next_property (icalcomp, ICAL_RRULE_PROPERTY)) {
1098 rule = icalproperty_get_rrule (prop);
1100 iter = icalrecur_iterator_new (rule, rid);
1101 recur = icalrecur_iterator_next (iter);
1103 if (mod & CALOBJ_MOD_THISANDFUTURE) {
1104 /* If there is a recurrence on or after rid,
1105 * use the UNTIL parameter to truncate the rule
1108 if (!icaltime_is_null_time (recur)) {
1111 icaltime_adjust (&rule.until, 0, 0, 0, -1);
1112 icalproperty_set_rrule (prop, rule);
1115 /* (If recur == rid, skip to the next occurrence) */
1116 if (icaltime_compare (recur, rid) == 0)
1117 recur = icalrecur_iterator_next (iter);
1119 /* If there is a recurrence after rid, add
1120 * an EXRULE to block instances up to rid.
1121 * Otherwise, just remove the RRULE.
1123 if (!icaltime_is_null_time (recur)) {
1125 /* iCalendar says we should just use rid
1126 * here, but Outlook/Exchange handle
1127 * UNTIL incorrectly.
1129 rule.until = icaltime_add (rid, icalcomponent_get_duration (icalcomp));
1130 prop = icalproperty_new_exrule (rule);
1131 icalcomponent_add_property (icalcomp, prop);
1133 icalcomponent_remove_property (icalcomp, prop);
1136 icalrecur_iterator_free (iter);