Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / calendar / libecal / e-cal-util.c
1 /* Evolution calendar utilities and types
2  *
3  * Copyright (C) 2000 Ximian, Inc.
4  * Copyright (C) 2000 Ximian, Inc.
5  *
6  * Author: Federico Mena-Quintero <federico@ximian.com>
7  *
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.
11  *
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.
16  *
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.
20  */
21
22 #include <config.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <glib.h>
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"
30
31 \f
32
33 /**
34  * cal_obj_instance_list_free:
35  * @list: List of #CalObjInstance structures.
36  *
37  * Frees a list of #CalObjInstance structures.
38  **/
39 void
40 cal_obj_instance_list_free (GList *list)
41 {
42         CalObjInstance *i;
43         GList *l;
44
45         for (l = list; l; l = l->next) {
46                 i = l->data;
47
48                 g_assert (i != NULL);
49                 g_assert (i->uid != NULL);
50
51                 g_free (i->uid);
52                 g_free (i);
53         }
54
55         g_list_free (list);
56 }
57
58 /**
59  * cal_obj_uid_list_free:
60  * @list: List of strings with unique identifiers.
61  *
62  * Frees a list of unique identifiers for calendar objects.
63  **/
64 void
65 cal_obj_uid_list_free (GList *list)
66 {
67         GList *l;
68
69         for (l = list; l; l = l->next) {
70                 char *uid;
71
72                 uid = l->data;
73
74                 g_assert (uid != NULL);
75                 g_free (uid);
76         }
77
78         g_list_free (list);
79 }
80
81 /**
82  * e_cal_util_new_top_level:
83  *
84  * Creates a new VCALENDAR component.
85  *
86  * Return value: the newly created top level component.
87  */
88 icalcomponent *
89 e_cal_util_new_top_level (void)
90 {
91         icalcomponent *icalcomp;
92         icalproperty *prop;
93
94         icalcomp = icalcomponent_new (ICAL_VCALENDAR_COMPONENT);
95
96         /* RFC 2445, section 4.7.1 */
97         prop = icalproperty_new_calscale ("GREGORIAN");
98         icalcomponent_add_property (icalcomp, prop);
99
100        /* RFC 2445, section 4.7.3 */
101         prop = icalproperty_new_prodid ("-//Ximian//NONSGML Evolution Calendar//EN");
102         icalcomponent_add_property (icalcomp, prop);
103
104         /* RFC 2445, section 4.7.4.  This is the iCalendar spec version, *NOT*
105          * the product version!  Do not change this!
106          */
107         prop = icalproperty_new_version ("2.0");
108         icalcomponent_add_property (icalcomp, prop);
109
110         return icalcomp;
111 }
112
113 /**
114  * e_cal_util_new_component:
115  * @kind: Kind of the component to create.
116  *
117  * Creates a new #icalcomponent of the specified kind.
118  *
119  * Return value: the newly created component.
120  */
121 icalcomponent *
122 e_cal_util_new_component (icalcomponent_kind kind)
123 {
124         icalcomponent *comp;
125         struct icaltimetype dtstamp;
126
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);
131
132         return comp;
133 }
134
135 static char *
136 read_line (const char *string)
137 {
138         char *line;
139         GString *line_str = NULL;
140
141         for (; *string; string++) {
142                 if (!line_str)
143                         line_str = g_string_new ("");
144
145                 line_str = g_string_append_c (line_str, *string);
146                 if (*string == '\n')
147                         break;
148         }
149
150         line = line_str->str;
151         g_string_free (line_str, FALSE);
152
153         return line;
154 }
155
156 /**
157  * e_cal_util_parse_ics_string:
158  * @string: iCalendar string to be parsed.
159  *
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
163  * support.
164  *
165  * Return value: an #icalcomponent.
166  */
167 icalcomponent *
168 e_cal_util_parse_ics_string (const char *string)
169 {
170         char *s;
171         icalcomponent *icalcomp = NULL;
172
173         g_return_val_if_fail (string != NULL, NULL);
174
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;
178
179                 while (*s) {
180                         char *line = read_line (s);
181                         if (line) {
182                                 if (!comp_str)
183                                         comp_str = g_string_new (line);
184                                 else
185                                         comp_str = g_string_append (comp_str, line);
186
187                                 if (!strncmp (line, "END:VCALENDAR", 13)) {
188                                         icalcomponent *tmp;
189
190                                         tmp = icalparser_parse_string (comp_str->str);
191                                         if (tmp && icalcomponent_isa (tmp) == ICAL_VCALENDAR_COMPONENT) {
192                                                 if (icalcomp)
193                                                         icalcomponent_merge_component (icalcomp, tmp);
194                                                 else
195                                                         icalcomp = tmp;
196                                         } else 
197                                                 g_warning ("Could not merge the components, the component is either invalid or not a toplevel component \n");
198
199                                         g_string_free (comp_str, TRUE);
200                                         comp_str = NULL;
201                                 }
202
203                                 s += strlen (line);
204
205                                 g_free (line);
206                         }
207                 }
208         } else
209                 icalcomp = icalparser_parse_string (string);
210
211         return icalcomp;
212 }
213
214 static char *
215 get_line_fn (char *buf, size_t size, void *file)
216 {
217         return fgets (buf, size, file);
218 }
219
220 /**
221  * e_cal_util_parse_ics_file:
222  * @filename: Name of the file to be parsed.
223  *
224  * Parses the given file, and, if it contains a valid iCalendar object,
225  * parse it and return a corresponding #icalcomponent.
226  *
227  * Return value: an #icalcomponent.
228  */
229 icalcomponent *
230 e_cal_util_parse_ics_file (const char *filename)
231 {
232         icalparser *parser;
233         icalcomponent *icalcomp;
234         FILE *file;
235
236         file = g_fopen (filename, "rb");
237         if (!file)
238                 return NULL;
239
240         parser = icalparser_new ();
241         icalparser_set_gen_data (parser, file);
242
243         icalcomp = icalparser_parse (parser, get_line_fn);
244         icalparser_free (parser);
245         fclose (file);
246
247         return icalcomp;
248 }
249
250 /* Computes the range of time in which recurrences should be generated for a
251  * component in order to compute alarm trigger times.
252  */
253 static void
254 compute_alarm_range (ECalComponent *comp, GList *alarm_uids, time_t start, time_t end,
255                      time_t *alarm_start, time_t *alarm_end)
256 {
257         GList *l;
258         time_t repeat_time;
259
260         *alarm_start = start;
261         *alarm_end = end;
262
263         repeat_time = 0;
264
265         for (l = alarm_uids; l; l = l->next) {
266                 const char *auid;
267                 ECalComponentAlarm *alarm;
268                 ECalComponentAlarmTrigger trigger;
269                 struct icaldurationtype *dur;
270                 time_t dur_time;
271                 ECalComponentAlarmRepeat repeat;
272
273                 auid = l->data;
274                 alarm = e_cal_component_get_alarm (comp, auid);
275                 g_assert (alarm != NULL);
276
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);
280
281                 switch (trigger.type) {
282                 case E_CAL_COMPONENT_ALARM_TRIGGER_NONE:
283                 case E_CAL_COMPONENT_ALARM_TRIGGER_ABSOLUTE:
284                         break;
285
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);
290
291                         if (repeat.repetitions != 0) {
292                                 int rdur;
293
294                                 rdur = repeat.repetitions * icaldurationtype_as_int (repeat.duration);
295                                 repeat_time = MAX (repeat_time, rdur);
296                         }
297
298                         if (dur->is_neg)
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.
302                                  */
303                                 *alarm_end = MAX (*alarm_end, end - dur_time);
304                         else
305                                 *alarm_start = MIN (*alarm_start, start - dur_time);
306
307                         break;
308
309                 default:
310                         g_assert_not_reached ();
311                 }
312         }
313
314         *alarm_start -= repeat_time;
315
316         g_assert (*alarm_start <= *alarm_end);
317 }
318
319 /* Closure data to generate alarm occurrences */
320 struct alarm_occurrence_data {
321         /* These are the info we have */
322         GList *alarm_uids;
323         time_t start;
324         time_t end;
325         ECalComponentAlarmAction *omit;
326         
327         /* This is what we compute */
328         GSList *triggers;
329         int n_triggers;
330 };
331
332 static void
333 add_trigger (struct alarm_occurrence_data *aod, const char *auid, time_t trigger,
334              time_t occur_start, time_t occur_end)
335 {
336         ECalComponentAlarmInstance *instance;
337
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;
343
344         aod->triggers = g_slist_prepend (aod->triggers, instance);
345         aod->n_triggers++;
346 }
347
348 /* Callback used from cal_recur_generate_instances(); generates triggers for all
349  * of a component's RELATIVE alarms.
350  */
351 static gboolean
352 add_alarm_occurrences_cb (ECalComponent *comp, time_t start, time_t end, gpointer data)
353 {
354         struct alarm_occurrence_data *aod;
355         GList *l;
356
357         aod = data;
358
359         for (l = aod->alarm_uids; l; l = l->next) {
360                 const char *auid;
361                 ECalComponentAlarm *alarm;
362                 ECalComponentAlarmAction action;
363                 ECalComponentAlarmTrigger trigger;
364                 ECalComponentAlarmRepeat repeat;
365                 struct icaldurationtype *dur;
366                 time_t dur_time;
367                 time_t occur_time, trigger_time;
368                 int i;
369                 
370                 auid = l->data;
371                 alarm = e_cal_component_get_alarm (comp, auid);
372                 g_assert (alarm != NULL);
373
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);
378
379                 for (i = 0; aod->omit[i] != -1; i++) {
380                         if (aod->omit[i] == action)
381                                 break;
382                 }
383                 if (aod->omit[i] != -1)
384                         continue;
385                 
386                 if (trigger.type != E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START
387                     && trigger.type != E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_END)
388                         continue;
389
390                 dur = &trigger.u.rel_duration;
391                 dur_time = icaldurationtype_as_int (*dur);
392
393                 if (trigger.type == E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START)
394                         occur_time = start;
395                 else
396                         occur_time = end;
397
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.
402                  */
403
404                 trigger_time = occur_time + dur_time;
405
406                 /* Add repeating alarms */
407
408                 if (repeat.repetitions != 0) {
409                         int i;
410                         time_t repeat_time;
411
412                         repeat_time = icaldurationtype_as_int (repeat.duration);
413
414                         for (i = 0; i < repeat.repetitions; i++) {
415                                 time_t t;
416
417                                 t = trigger_time + (i + 1) * repeat_time;
418
419                                 if (t >= aod->start && t < aod->end)
420                                         add_trigger (aod, auid, t, start, end);
421                         }
422                 }
423
424                 /* Add the trigger itself */
425
426                 if (trigger_time >= aod->start && trigger_time < aod->end)
427                         add_trigger (aod, auid, trigger_time, start, end);
428         }
429
430         return TRUE;
431 }
432
433 /* Generates the absolute triggers for a component */
434 static void
435 generate_absolute_triggers (ECalComponent *comp, struct alarm_occurrence_data *aod,
436                             ECalRecurResolveTimezoneFn resolve_tzid,
437                             gpointer user_data,
438                             icaltimezone *default_timezone)
439 {
440         GList *l;
441         ECalComponentDateTime dt_start, dt_end;
442
443         e_cal_component_get_dtstart (comp, &dt_start);
444         e_cal_component_get_dtend (comp, &dt_end);
445
446         for (l = aod->alarm_uids; l; l = l->next) {
447                 const char *auid;
448                 ECalComponentAlarm *alarm;
449                 ECalComponentAlarmAction action;
450                 ECalComponentAlarmRepeat repeat;
451                 ECalComponentAlarmTrigger trigger;
452                 time_t abs_time;
453                 time_t occur_start, occur_end;
454                 icaltimezone *zone;
455                 int i;
456                 
457                 auid = l->data;
458                 alarm = e_cal_component_get_alarm (comp, auid);
459                 g_assert (alarm != NULL);
460
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);
465
466                 for (i = 0; aod->omit[i] != -1; i++) {
467                         if (aod->omit[i] == action)
468                                 break;
469                 }
470                 if (aod->omit[i] != -1)
471                         continue;
472
473                 if (trigger.type != E_CAL_COMPONENT_ALARM_TRIGGER_ABSOLUTE)
474                         continue;
475
476                 /* Absolute triggers are always in UTC; see RFC 2445 section 4.8.6.3 */
477                 zone = icaltimezone_get_utc_timezone ();
478
479                 abs_time = icaltime_as_timet_with_zone (trigger.u.abs_time, zone);
480
481                 /* No particular occurrence, so just use the times from the component */
482
483                 if (dt_start.value) {
484                         if (dt_start.tzid && !dt_start.value->is_date)
485                                 zone = (* resolve_tzid) (dt_start.tzid, user_data);
486                         else
487                                 zone = default_timezone;
488
489                         occur_start = icaltime_as_timet_with_zone (*dt_start.value, zone);
490                 } else
491                         occur_start = -1;
492
493                 if (dt_end.value) {
494                         if (dt_end.tzid && !dt_end.value->is_date)
495                                 zone = (* resolve_tzid) (dt_end.tzid, user_data);
496                         else
497                                 zone = default_timezone;
498
499                         occur_end = icaltime_as_timet_with_zone (*dt_end.value, zone);
500                 } else
501                         occur_end = -1;
502
503                 /* Add repeating alarms */
504
505                 if (repeat.repetitions != 0) {
506                         int i;
507                         time_t repeat_time;
508
509                         repeat_time = icaldurationtype_as_int (repeat.duration);
510
511                         for (i = 0; i < repeat.repetitions; i++) {
512                                 time_t t;
513
514                                 t = abs_time + (i + 1) * repeat_time;
515
516                                 if (t >= aod->start && t < aod->end)
517                                         add_trigger (aod, auid, t, occur_start, occur_end);
518                         }
519                 }
520
521                 /* Add the trigger itself */
522
523                 if (abs_time >= aod->start && abs_time < aod->end)
524                         add_trigger (aod, auid, abs_time, occur_start, occur_end);
525         }
526
527         e_cal_component_free_datetime (&dt_start);
528         e_cal_component_free_datetime (&dt_end);
529 }
530
531 /* Compares two alarm instances; called from g_slist_sort() */
532 static gint
533 compare_alarm_instance (gconstpointer a, gconstpointer b)
534 {
535         const ECalComponentAlarmInstance *aia, *aib;
536
537         aia = a;
538         aib = b;
539
540         if (aia->trigger < aib->trigger)
541                 return -1;
542         else if (aia->trigger > aib->trigger)
543                 return 1;
544         else
545                 return 0;
546 }
547
548 /**
549  * e_cal_util_generate_alarms_for_comp
550  * @comp: The #ECalComponent to generate alarms from.
551  * @start: Start time.
552  * @end: End 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
557  * values.
558  *
559  * Generates alarm instances for a calendar component.  Returns the instances
560  * structure, or NULL if no alarm instances occurred in the specified time
561  * range.
562  *
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.
566  */
567 ECalComponentAlarms *
568 e_cal_util_generate_alarms_for_comp (ECalComponent *comp,
569                                      time_t start,
570                                      time_t end,
571                                      ECalComponentAlarmAction *omit,
572                                      ECalRecurResolveTimezoneFn resolve_tzid,
573                                      gpointer user_data,
574                                      icaltimezone *default_timezone)
575 {
576         GList *alarm_uids;
577         time_t alarm_start, alarm_end;
578         struct alarm_occurrence_data aod;
579         ECalComponentAlarms *alarms;
580
581         if (!e_cal_component_has_alarms (comp))
582                 return NULL;
583
584         alarm_uids = e_cal_component_get_alarm_uids (comp);
585         compute_alarm_range (comp, alarm_uids, start, end, &alarm_start, &alarm_end);
586
587         aod.alarm_uids = alarm_uids;
588         aod.start = start;
589         aod.end = end;
590         aod.omit = omit;
591         aod.triggers = NULL;
592         aod.n_triggers = 0;
593
594         e_cal_recur_generate_instances (comp, alarm_start, alarm_end,
595                                         add_alarm_occurrences_cb, &aod,
596                                         resolve_tzid, user_data,
597                                         default_timezone);
598
599         /* We add the ABSOLUTE triggers separately */
600         generate_absolute_triggers (comp, &aod, resolve_tzid, user_data, default_timezone);
601
602         if (aod.n_triggers == 0)
603                 return NULL;
604
605         /* Create the component alarm instances structure */
606
607         alarms = g_new (ECalComponentAlarms, 1);
608         alarms->comp = comp;
609         g_object_ref (G_OBJECT (alarms->comp));
610         alarms->alarms = g_slist_sort (aod.triggers, compare_alarm_instance);
611
612         return alarms;
613 }
614
615 /**
616  * e_cal_util_generate_alarms_for_list
617  * @comps: List of #ECalComponent's.
618  * @start: Start time.
619  * @end: End 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
625  * values.
626  *
627  * Iterates through all the components in the @comps list and generates alarm
628  * instances for them; putting them in the @comp_alarms list.
629  *
630  * Return value: the number of elements it added to the list.
631  */
632 int
633 e_cal_util_generate_alarms_for_list (GList *comps,
634                                      time_t start,
635                                      time_t end,
636                                      ECalComponentAlarmAction *omit,
637                                      GSList **comp_alarms,
638                                      ECalRecurResolveTimezoneFn resolve_tzid,
639                                      gpointer user_data,
640                                      icaltimezone *default_timezone)
641 {
642         GList *l;
643         int n;
644
645         n = 0;
646
647         for (l = comps; l; l = l->next) {
648                 ECalComponent *comp;
649                 ECalComponentAlarms *alarms;
650
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);
653
654                 if (alarms) {
655                         *comp_alarms = g_slist_prepend (*comp_alarms, alarms);
656                         n++;
657                 }
658         }
659
660         return n;
661 }
662
663
664 /**
665  * e_cal_util_priority_to_string:
666  * @priority: Priority value.
667  *
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).
670  *
671  * Return value: a string representing the PRIORITY value. This value is a
672  * constant, so it should never be freed.
673  */
674 char *
675 e_cal_util_priority_to_string (int priority)
676 {
677         char *retval;
678
679         if (priority <= 0)
680                 retval = "";
681         else if (priority <= 4)
682                 retval = _("High");
683         else if (priority == 5)
684                 retval = _("Normal");
685         else if (priority <= 9)
686                 retval = _("Low");
687         else
688                 retval = "";
689
690         return retval;
691 }
692
693
694 /**
695  * e_cal_util_priority_from_string:
696  * @string: A string representing the PRIORITY value.
697  *
698  * Converts a translated priority string to an iCalendar priority value.
699  *
700  * Return value: the priority (0-9) or -1 if the priority string is not valid.
701 */
702 int
703 e_cal_util_priority_from_string (const char *string)
704 {
705         int priority;
706
707         /* An empty string is the same as 'None'. */
708         if (!string || !string[0] || !e_util_utf8_strcasecmp (string, _("Undefined")))
709                 priority = 0;
710         else if (!e_util_utf8_strcasecmp (string, _("High")))
711                 priority = 3;
712         else if (!e_util_utf8_strcasecmp (string, _("Normal")))
713                 priority = 5;
714         else if (!e_util_utf8_strcasecmp (string, _("Low")))
715                 priority = 7;
716         else
717                 priority = -1;
718
719         return priority;
720 }
721
722 /* callback for icalcomponent_foreach_tzid */
723 typedef struct {
724         icalcomponent *vcal_comp;
725         icalcomponent *icalcomp;
726 } ForeachTzidData;
727
728 static void
729 add_timezone_cb (icalparameter *param, void *data)
730 {
731         icaltimezone *tz;
732         const char *tzid;
733         icalcomponent *vtz_comp;
734         ForeachTzidData *f_data = (ForeachTzidData *) data;
735
736         tzid = icalparameter_get_tzid (param);
737         if (!tzid)
738                 return;
739
740         tz = icalcomponent_get_timezone (f_data->vcal_comp, tzid);
741         if (tz)
742                 return;
743
744         tz = icalcomponent_get_timezone (f_data->icalcomp, tzid);
745         if (!tz) {
746                 tz = icaltimezone_get_builtin_timezone_from_tzid (tzid);
747                 if (!tz)
748                         return;
749         }
750
751         vtz_comp = icaltimezone_get_component (tz);
752         if (!vtz_comp)
753                 return;
754
755         icalcomponent_add_component (f_data->vcal_comp,
756                                      icalcomponent_new_clone (vtz_comp));
757 }
758
759 /**
760  * e_cal_util_add_timezones_from_component:
761  * @vcal_comp: A VCALENDAR component.
762  * @icalcomp: An iCalendar component, of any type.
763  *
764  * Adds VTIMEZONE components to a VCALENDAR for all tzid's
765  * in the given @icalcomp.
766  */
767 void
768 e_cal_util_add_timezones_from_component (icalcomponent *vcal_comp,
769                                          icalcomponent *icalcomp)
770 {
771         ForeachTzidData f_data;
772
773         g_return_if_fail (vcal_comp != NULL);
774         g_return_if_fail (icalcomp != NULL);;
775
776         f_data.vcal_comp = vcal_comp;
777         f_data.icalcomp = icalcomp;
778         icalcomponent_foreach_tzid (icalcomp, add_timezone_cb, &f_data);
779 }
780
781 /**
782  * e_cal_util_component_is_instance:
783  * @icalcomp: An #icalcomponent.
784  *
785  * Checks whether an #icalcomponent is an instance of a recurring appointment or not.
786  *
787  * Return value: TRUE if it is an instance, FALSE if not.
788  */
789 gboolean
790 e_cal_util_component_is_instance (icalcomponent *icalcomp)
791 {
792         icalproperty *prop;
793
794         g_return_val_if_fail (icalcomp != NULL, FALSE);
795
796         prop = icalcomponent_get_first_property (icalcomp, ICAL_RECURRENCEID_PROPERTY);
797         return prop ? TRUE : FALSE;
798 }
799
800 /**
801  * e_cal_util_component_has_alarms:
802  * @icalcomp: An #icalcomponent.
803  *
804  * Checks whether an #icalcomponent has any alarm.
805  *
806  * Return value: TRUE if it has alarms, FALSE otherwise.
807  */
808 gboolean
809 e_cal_util_component_has_alarms (icalcomponent *icalcomp)
810 {
811         icalcomponent *alarm;
812
813         g_return_val_if_fail (icalcomp != NULL, FALSE);
814
815         alarm = icalcomponent_get_first_component (icalcomp, ICAL_VALARM_COMPONENT);
816         return alarm ? TRUE : FALSE;
817 }
818
819 /**
820  * e_cal_util_component_has_organizer:
821  * @icalcomp: An #icalcomponent.
822  *
823  * Checks whether an #icalcomponent has an organizer or not.
824  *
825  * Return value: TRUE if there is an organizer, FALSE if not.
826  */
827 gboolean
828 e_cal_util_component_has_organizer (icalcomponent *icalcomp)
829 {
830         icalproperty *prop;
831
832         g_return_val_if_fail (icalcomp != NULL, FALSE);
833
834         prop = icalcomponent_get_first_property (icalcomp, ICAL_ORGANIZER_PROPERTY);
835         return prop ? TRUE : FALSE;
836 }
837
838 /**
839  * e_cal_util_component_has_attendee:
840  * @icalcomp: An #icalcomponent.
841  *
842  * Checks if an #icalcomponent has any attendees.
843  * 
844  * Return value: TRUE if there are attendees, FALSE if not.
845  */
846 gboolean
847 e_cal_util_component_has_attendee (icalcomponent *icalcomp)
848 {
849         icalproperty *prop;
850
851         g_return_val_if_fail (icalcomp != NULL, FALSE);
852
853         prop = icalcomponent_get_first_property (icalcomp, ICAL_ATTENDEE_PROPERTY);
854
855         return prop ? TRUE : FALSE;
856 }
857
858 /**
859  * e_cal_util_component_has_recurrences:
860  * @icalcomp: An #icalcomponent.
861  *
862  * Checks if an #icalcomponent has recurrence dates or rules.
863  *
864  * Return value: TRUE if there are recurrence dates/rules, FALSE if not.
865  */
866 gboolean
867 e_cal_util_component_has_recurrences (icalcomponent *icalcomp)
868 {
869         g_return_val_if_fail (icalcomp != NULL, FALSE);
870
871         return e_cal_util_component_has_rdates (icalcomp) || e_cal_util_component_has_rrules (icalcomp);
872 }
873
874 /**
875  * e_cal_util_component_has_rdates:
876  * @icalcomp: An #icalcomponent.
877  *
878  * Checks if an #icalcomponent has recurrence dates.
879  *
880  * Return value: TRUE if there are recurrence dates, FALSE if not.
881  */
882 gboolean
883 e_cal_util_component_has_rdates (icalcomponent *icalcomp)
884 {
885         icalproperty *prop;
886
887         g_return_val_if_fail (icalcomp != NULL, FALSE);
888
889         prop = icalcomponent_get_first_property (icalcomp, ICAL_RDATE_PROPERTY);
890         return prop ? TRUE : FALSE;
891 }
892
893 /**
894  * e_cal_util_component_has_rrules:
895  * @icalcomp: An #icalcomponent.
896  *
897  * Checks if an #icalcomponent has recurrence rules.
898  *
899  * Return value: TRUE if there are recurrence rules, FALSE if not.
900  */
901 gboolean
902 e_cal_util_component_has_rrules (icalcomponent *icalcomp)
903 {
904         icalproperty *prop;
905
906         g_return_val_if_fail (icalcomp != NULL, FALSE);
907
908         prop = icalcomponent_get_first_property (icalcomp, ICAL_RRULE_PROPERTY);
909         return prop ? TRUE : FALSE;
910 }
911
912 /**
913  * e_cal_util_event_dates_match:
914  * @icalcomp1: An #icalcomponent.
915  * @icalcomp2: An #icalcomponent.
916  *
917  * Compare the dates of two #icalcomponent's to check if they match.
918  *
919  * Return value: TRUE if the dates of both components match, FALSE otherwise.
920  */
921 gboolean
922 e_cal_util_event_dates_match (icalcomponent *icalcomp1, icalcomponent *icalcomp2)
923 {
924         struct icaltimetype c1_dtstart, c1_dtend, c2_dtstart, c2_dtend;
925
926         g_return_val_if_fail (icalcomp1 != NULL, FALSE);
927         g_return_val_if_fail (icalcomp2 != NULL, FALSE);
928
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);
933
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)))
937                         return FALSE;
938         } else {
939                 if (icaltime_compare (c1_dtstart, c2_dtstart))
940                         return FALSE;
941         }
942
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)))
945                         return FALSE;
946         } else {
947                 if (icaltime_compare (c1_dtend, c2_dtend))
948                         return FALSE;
949         }
950
951         
952
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))))
958                 return FALSE;
959
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))))
964                 return FALSE;
965
966         return TRUE;
967 }
968
969 /* Individual instances management */
970
971 struct instance_data {
972         time_t start;
973         gboolean found;
974 };
975
976 static void
977 check_instance (icalcomponent *comp, struct icaltime_span *span, void *data)
978 {
979         struct instance_data *instance = data;
980
981         if (span->start == instance->start)
982                 instance->found = TRUE;
983 }
984
985 /**
986  * e_cal_util_construct_instance:
987  * @icalcomp: A recurring #icalcomponent
988  * @rid: The RECURRENCE-ID to construct a component for
989  *
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.
992  *
993  * Return value: the instance, or %NULL.
994  **/
995 icalcomponent *
996 e_cal_util_construct_instance (icalcomponent *icalcomp,
997                                struct icaltimetype rid)
998 {
999         struct instance_data instance;
1000         struct icaltimetype start, end;
1001
1002         g_return_val_if_fail (icalcomp != NULL, NULL);
1003
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))
1007                 return NULL;
1008
1009         /* Make sure the specified instance really exists */
1010         start = icaltime_convert_to_zone (rid, icaltimezone_get_utc_timezone ());
1011         end = start;
1012         icaltime_adjust (&end, 0, 0, 0, 1);
1013
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)
1019                 return NULL;
1020
1021         /* Make the instance */
1022         icalcomp = icalcomponent_new_clone (icalcomp);
1023         icalcomponent_set_recurrenceid (icalcomp, rid);
1024
1025         return icalcomp;
1026 }
1027
1028 static inline gboolean
1029 time_matches_rid (struct icaltimetype itt, struct icaltimetype rid, CalObjModType mod)
1030 {
1031         int compare;
1032
1033         compare = icaltime_compare (itt, rid);
1034         if (compare == 0)
1035                 return TRUE;
1036         else if (compare < 0 && (mod & CALOBJ_MOD_THISANDPRIOR))
1037                 return TRUE;
1038         else if (compare > 0 && (mod & CALOBJ_MOD_THISANDFUTURE))
1039                 return TRUE;
1040
1041         return FALSE;
1042 }
1043
1044 /**
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
1049  *
1050  * Removes one or more instances from @comp according to @rid and @mod.
1051  *
1052  * FIXME: should probably have a return value indicating whether or not
1053  * @icalcomp still has any instances
1054  **/
1055 void
1056 e_cal_util_remove_instances (icalcomponent *icalcomp,
1057                              struct icaltimetype rid,
1058                              CalObjModType mod)
1059 {
1060         icalproperty *prop;
1061         struct icaltimetype itt, recur;
1062         struct icalrecurrencetype rule;
1063         icalrecur_iterator *iter;
1064
1065         g_return_if_fail (icalcomp != NULL);
1066         g_return_if_fail (mod != CALOBJ_MOD_ALL);
1067
1068         /* First remove RDATEs and EXDATEs in the indicated range. */
1069         for (prop = icalcomponent_get_first_property (icalcomp, ICAL_RDATE_PROPERTY);
1070              prop;
1071              prop = icalcomponent_get_next_property (icalcomp, ICAL_RDATE_PROPERTY)) {
1072                 struct icaldatetimeperiodtype period;
1073
1074                 period = icalproperty_get_rdate (prop);
1075                 if (time_matches_rid (period.time, rid, mod))
1076                         icalcomponent_remove_property (icalcomp, prop);
1077         }
1078         for (prop = icalcomponent_get_first_property (icalcomp, ICAL_EXDATE_PROPERTY);
1079              prop;
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);
1084         }
1085
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);
1090                 return;
1091         }
1092
1093         /* Otherwise, iterate through RRULEs */
1094         /* FIXME: this may generate duplicate EXRULEs */
1095         for (prop = icalcomponent_get_first_property (icalcomp, ICAL_RRULE_PROPERTY);
1096              prop;
1097              prop = icalcomponent_get_next_property (icalcomp, ICAL_RRULE_PROPERTY)) {
1098                 rule = icalproperty_get_rrule (prop);
1099
1100                 iter = icalrecur_iterator_new (rule, rid);
1101                 recur = icalrecur_iterator_next (iter);
1102
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
1106                          * at rid.
1107                          */
1108                         if (!icaltime_is_null_time (recur)) {
1109                                 rule.count = 0;
1110                                 rule.until = rid;
1111                                 icaltime_adjust (&rule.until, 0, 0, 0, -1);
1112                                 icalproperty_set_rrule (prop, rule);
1113                         }
1114                 } else {
1115                         /* (If recur == rid, skip to the next occurrence) */
1116                         if (icaltime_compare (recur, rid) == 0)
1117                                 recur = icalrecur_iterator_next (iter);
1118
1119                         /* If there is a recurrence after rid, add
1120                          * an EXRULE to block instances up to rid.
1121                          * Otherwise, just remove the RRULE.
1122                          */
1123                         if (!icaltime_is_null_time (recur)) {
1124                                 rule.count = 0;
1125                                 /* iCalendar says we should just use rid
1126                                  * here, but Outlook/Exchange handle
1127                                  * UNTIL incorrectly.
1128                                  */
1129                                 rule.until = icaltime_add (rid, icalcomponent_get_duration (icalcomp));
1130                                 prop = icalproperty_new_exrule (rule);
1131                                 icalcomponent_add_property (icalcomp, prop);
1132                         } else
1133                                 icalcomponent_remove_property (icalcomp, prop);
1134                 }
1135
1136                 icalrecur_iterator_free (iter);
1137         }
1138 }