changes: Bump to 3.8.0
[platform/upstream/evolution-data-server.git] / calendar / libecal / e-cal-util.c
1 /* Evolution calendar utilities and types
2  *
3  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4  *
5  * Author: Federico Mena-Quintero <federico@ximian.com>
6  *
7  * This library is free software you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation.
10  *
11  * This library is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  *for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <glib/gstdio.h>
24 #include <glib/gi18n-lib.h>
25
26 #include <libedataserver/libedataserver.h>
27
28 #include "e-cal-util.h"
29 #include "e-cal-system-timezone.h"
30
31 #define _TIME_MIN       ((time_t) 0)            /* Min valid time_t     */
32 #define _TIME_MAX       ((time_t) INT_MAX)
33
34 /**
35  * cal_obj_instance_list_free:
36  * @list: List of #CalObjInstance structures.
37  *
38  * Frees a list of #CalObjInstance structures.
39  **/
40 void
41 cal_obj_instance_list_free (GList *list)
42 {
43         CalObjInstance *i;
44         GList *l;
45
46         for (l = list; l; l = l->next) {
47                 i = l->data;
48
49                 if (i != NULL && i->uid != NULL) {
50                         g_free (i->uid);
51                         g_free (i);
52                 } else
53                         g_warn_if_reached ();
54         }
55
56         g_list_free (list);
57 }
58
59 /**
60  * cal_obj_uid_list_free:
61  * @list: List of strings with unique identifiers.
62  *
63  * Frees a list of unique identifiers for calendar objects.
64  **/
65 void
66 cal_obj_uid_list_free (GList *list)
67 {
68         g_list_foreach (list, (GFunc) g_free, NULL);
69         g_list_free (list);
70 }
71
72 /**
73  * e_cal_util_new_top_level:
74  *
75  * Creates a new VCALENDAR component.
76  *
77  * Returns: the newly created top level component.
78  */
79 icalcomponent *
80 e_cal_util_new_top_level (void)
81 {
82         icalcomponent *icalcomp;
83         icalproperty *prop;
84
85         icalcomp = icalcomponent_new (ICAL_VCALENDAR_COMPONENT);
86
87         /* RFC 2445, section 4.7.1 */
88         prop = icalproperty_new_calscale ("GREGORIAN");
89         icalcomponent_add_property (icalcomp, prop);
90
91        /* RFC 2445, section 4.7.3 */
92         prop = icalproperty_new_prodid ("-//Ximian//NONSGML Evolution Calendar//EN");
93         icalcomponent_add_property (icalcomp, prop);
94
95         /* RFC 2445, section 4.7.4.  This is the iCalendar spec version, *NOT*
96          * the product version!  Do not change this!
97          */
98         prop = icalproperty_new_version ("2.0");
99         icalcomponent_add_property (icalcomp, prop);
100
101         return icalcomp;
102 }
103
104 /**
105  * e_cal_util_new_component:
106  * @kind: Kind of the component to create.
107  *
108  * Creates a new #icalcomponent of the specified kind.
109  *
110  * Returns: the newly created component.
111  */
112 icalcomponent *
113 e_cal_util_new_component (icalcomponent_kind kind)
114 {
115         icalcomponent *comp;
116         struct icaltimetype dtstamp;
117         gchar *uid;
118
119         comp = icalcomponent_new (kind);
120         uid = e_cal_component_gen_uid ();
121         icalcomponent_set_uid (comp, uid);
122         g_free (uid);
123         dtstamp = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
124         icalcomponent_set_dtstamp (comp, dtstamp);
125
126         return comp;
127 }
128
129 static gchar *
130 read_line (const gchar *string)
131 {
132         GString *line_str = NULL;
133
134         for (; *string; string++) {
135                 if (!line_str)
136                         line_str = g_string_new ("");
137
138                 line_str = g_string_append_c (line_str, *string);
139                 if (*string == '\n')
140                         break;
141         }
142
143         return g_string_free (line_str, FALSE);
144 }
145
146 /**
147  * e_cal_util_parse_ics_string:
148  * @string: iCalendar string to be parsed.
149  *
150  * Parses an iCalendar string and returns a new #icalcomponent representing
151  * that string. Note that this function deals with multiple VCALENDAR's in the
152  * string, something that Mozilla used to do and which libical does not
153  * support.
154  *
155  * Returns: a newly created #icalcomponent or NULL if the string isn't a
156  * valid iCalendar string.
157  */
158 icalcomponent *
159 e_cal_util_parse_ics_string (const gchar *string)
160 {
161         GString *comp_str = NULL;
162         gchar *s;
163         icalcomponent *icalcomp = NULL;
164
165         g_return_val_if_fail (string != NULL, NULL);
166
167         /* Split string into separated VCALENDAR's, if more than one */
168         s = g_strstr_len (string, strlen (string), "BEGIN:VCALENDAR");
169
170         if (s == NULL)
171                 return icalparser_parse_string (string);
172
173         while (*s != '\0') {
174                 gchar *line = read_line (s);
175
176                 if (!comp_str)
177                         comp_str = g_string_new (line);
178                 else
179                         comp_str = g_string_append (comp_str, line);
180
181                 if (strncmp (line, "END:VCALENDAR", 13) == 0) {
182                         icalcomponent *tmp;
183
184                         tmp = icalparser_parse_string (comp_str->str);
185                         if (tmp && icalcomponent_isa (tmp) == ICAL_VCALENDAR_COMPONENT) {
186                                 if (icalcomp)
187                                         icalcomponent_merge_component (icalcomp, tmp);
188                                 else
189                                         icalcomp = tmp;
190                         } else {
191                                 g_warning (
192                                         "Could not merge the components, "
193                                         "the component is either invalid "
194                                         "or not a toplevel component \n");
195                         }
196
197                         g_string_free (comp_str, TRUE);
198                         comp_str = NULL;
199                 }
200
201                 s += strlen (line);
202
203                 g_free (line);
204         }
205
206         return icalcomp;
207 }
208
209 static gchar *
210 get_line_fn (gchar *buf,
211              gsize size,
212              gpointer file)
213 {
214         return fgets (buf, size, file);
215 }
216
217 /**
218  * e_cal_util_parse_ics_file:
219  * @filename: Name of the file to be parsed.
220  *
221  * Parses the given file, and, if it contains a valid iCalendar object,
222  * parse it and return a new #icalcomponent.
223  *
224  * Returns: a newly created #icalcomponent or NULL if the file doesn't
225  * contain a valid iCalendar object.
226  */
227 icalcomponent *
228 e_cal_util_parse_ics_file (const gchar *filename)
229 {
230         icalparser *parser;
231         icalcomponent *icalcomp;
232         FILE *file;
233
234         file = g_fopen (filename, "rb");
235         if (!file)
236                 return NULL;
237
238         parser = icalparser_new ();
239         icalparser_set_gen_data (parser, file);
240
241         icalcomp = icalparser_parse (parser, get_line_fn);
242         icalparser_free (parser);
243         fclose (file);
244
245         return icalcomp;
246 }
247
248 /* Computes the range of time in which recurrences should be generated for a
249  * component in order to compute alarm trigger times.
250  */
251 static void
252 compute_alarm_range (ECalComponent *comp,
253                      GList *alarm_uids,
254                      time_t start,
255                      time_t end,
256                      time_t *alarm_start,
257                      time_t *alarm_end)
258 {
259         GList *l;
260         time_t repeat_time;
261
262         *alarm_start = start;
263         *alarm_end = end;
264
265         repeat_time = 0;
266
267         for (l = alarm_uids; l; l = l->next) {
268                 const gchar *auid;
269                 ECalComponentAlarm *alarm;
270                 ECalComponentAlarmTrigger trigger;
271                 struct icaldurationtype *dur;
272                 time_t dur_time;
273                 ECalComponentAlarmRepeat repeat;
274
275                 auid = l->data;
276                 alarm = e_cal_component_get_alarm (comp, auid);
277                 g_return_if_fail (alarm != NULL);
278
279                 e_cal_component_alarm_get_trigger (alarm, &trigger);
280                 e_cal_component_alarm_get_repeat (alarm, &repeat);
281                 e_cal_component_alarm_free (alarm);
282
283                 switch (trigger.type) {
284                 case E_CAL_COMPONENT_ALARM_TRIGGER_NONE:
285                 case E_CAL_COMPONENT_ALARM_TRIGGER_ABSOLUTE:
286                         break;
287
288                 case E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START:
289                 case E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_END:
290                         dur = &trigger.u.rel_duration;
291                         dur_time = icaldurationtype_as_int (*dur);
292
293                         if (repeat.repetitions != 0) {
294                                 gint rdur;
295
296                                 rdur = repeat.repetitions *
297                                         icaldurationtype_as_int (repeat.duration);
298                                 repeat_time = MAX (repeat_time, rdur);
299                         }
300
301                         if (dur->is_neg)
302                                 /* If the duration is negative then dur_time
303                                  * will be negative as well; that is why we
304                                  * subtract to expand the range.
305                                  */
306                                 *alarm_end = MAX (*alarm_end, end - dur_time);
307                         else
308                                 *alarm_start = MIN (*alarm_start, start - dur_time);
309
310                         break;
311
312                 default:
313                         g_return_if_reached ();
314                 }
315         }
316
317         *alarm_start -= repeat_time;
318         g_warn_if_fail (*alarm_start <= *alarm_end);
319 }
320
321 /* Closure data to generate alarm occurrences */
322 struct alarm_occurrence_data {
323         /* These are the info we have */
324         GList *alarm_uids;
325         time_t start;
326         time_t end;
327         ECalComponentAlarmAction *omit;
328
329         /* This is what we compute */
330         GSList *triggers;
331         gint n_triggers;
332 };
333
334 static void
335 add_trigger (struct alarm_occurrence_data *aod,
336              const gchar *auid,
337              time_t trigger,
338              time_t occur_start,
339              time_t occur_end)
340 {
341         ECalComponentAlarmInstance *instance;
342
343         instance = g_new (ECalComponentAlarmInstance, 1);
344         instance->auid = g_strdup (auid);
345         instance->trigger = trigger;
346         instance->occur_start = occur_start;
347         instance->occur_end = occur_end;
348
349         aod->triggers = g_slist_prepend (aod->triggers, instance);
350         aod->n_triggers++;
351 }
352
353 /* Callback used from cal_recur_generate_instances(); generates triggers for all
354  * of a component's RELATIVE alarms.
355  */
356 static gboolean
357 add_alarm_occurrences_cb (ECalComponent *comp,
358                           time_t start,
359                           time_t end,
360                           gpointer data)
361 {
362         struct alarm_occurrence_data *aod;
363         GList *l;
364
365         aod = data;
366
367         for (l = aod->alarm_uids; l; l = l->next) {
368                 const gchar *auid;
369                 ECalComponentAlarm *alarm;
370                 ECalComponentAlarmAction action;
371                 ECalComponentAlarmTrigger trigger;
372                 ECalComponentAlarmRepeat repeat;
373                 struct icaldurationtype *dur;
374                 time_t dur_time;
375                 time_t occur_time, trigger_time;
376                 gint i;
377
378                 auid = l->data;
379                 alarm = e_cal_component_get_alarm (comp, auid);
380                 g_return_val_if_fail (alarm != NULL, FALSE);
381
382                 e_cal_component_alarm_get_action (alarm, &action);
383                 e_cal_component_alarm_get_trigger (alarm, &trigger);
384                 e_cal_component_alarm_get_repeat (alarm, &repeat);
385                 e_cal_component_alarm_free (alarm);
386
387                 for (i = 0; aod->omit[i] != -1; i++) {
388                         if (aod->omit[i] == action)
389                                 break;
390                 }
391                 if (aod->omit[i] != -1)
392                         continue;
393
394                 if (trigger.type != E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START
395                     && trigger.type != E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_END)
396                         continue;
397
398                 dur = &trigger.u.rel_duration;
399                 dur_time = icaldurationtype_as_int (*dur);
400
401                 if (trigger.type == E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START)
402                         occur_time = start;
403                 else
404                         occur_time = end;
405
406                 /* If dur->is_neg is true then dur_time will already be
407                  * negative.  So we do not need to test for dur->is_neg here; we
408                  * can simply add the dur_time value to the occur_time and get
409                  * the correct result.
410                  */
411
412                 trigger_time = occur_time + dur_time;
413
414                 /* Add repeating alarms */
415
416                 if (repeat.repetitions != 0) {
417                         gint i;
418                         time_t repeat_time;
419
420                         repeat_time = icaldurationtype_as_int (repeat.duration);
421
422                         for (i = 0; i < repeat.repetitions; i++) {
423                                 time_t t;
424
425                                 t = trigger_time + (i + 1) * repeat_time;
426
427                                 if (t >= aod->start && t < aod->end)
428                                         add_trigger (aod, auid, t, start, end);
429                         }
430                 }
431
432                 /* Add the trigger itself */
433
434                 if (trigger_time >= aod->start && trigger_time < aod->end)
435                         add_trigger (aod, auid, trigger_time, start, end);
436         }
437
438         return TRUE;
439 }
440
441 /* Generates the absolute triggers for a component */
442 static void
443 generate_absolute_triggers (ECalComponent *comp,
444                             struct alarm_occurrence_data *aod,
445                             ECalRecurResolveTimezoneFn resolve_tzid,
446                             gpointer user_data,
447                             icaltimezone *default_timezone)
448 {
449         GList *l;
450         ECalComponentDateTime dt_start, dt_end;
451
452         e_cal_component_get_dtstart (comp, &dt_start);
453         e_cal_component_get_dtend (comp, &dt_end);
454
455         for (l = aod->alarm_uids; l; l = l->next) {
456                 const gchar *auid;
457                 ECalComponentAlarm *alarm;
458                 ECalComponentAlarmAction action;
459                 ECalComponentAlarmRepeat repeat;
460                 ECalComponentAlarmTrigger trigger;
461                 time_t abs_time;
462                 time_t occur_start, occur_end;
463                 icaltimezone *zone;
464                 gint i;
465
466                 auid = l->data;
467                 alarm = e_cal_component_get_alarm (comp, auid);
468                 g_return_if_fail (alarm != NULL);
469
470                 e_cal_component_alarm_get_action (alarm, &action);
471                 e_cal_component_alarm_get_trigger (alarm, &trigger);
472                 e_cal_component_alarm_get_repeat (alarm, &repeat);
473                 e_cal_component_alarm_free (alarm);
474
475                 for (i = 0; aod->omit[i] != -1; i++) {
476                         if (aod->omit[i] == action)
477                                 break;
478                 }
479                 if (aod->omit[i] != -1)
480                         continue;
481
482                 if (trigger.type != E_CAL_COMPONENT_ALARM_TRIGGER_ABSOLUTE)
483                         continue;
484
485                 /* Absolute triggers are always in UTC;
486                  * see RFC 2445 section 4.8.6.3 */
487                 zone = icaltimezone_get_utc_timezone ();
488
489                 abs_time = icaltime_as_timet_with_zone (trigger.u.abs_time, zone);
490
491                 /* No particular occurrence, so just use the times from the
492                  * component */
493
494                 if (dt_start.value) {
495                         if (dt_start.tzid && !dt_start.value->is_date)
496                                 zone = (* resolve_tzid) (dt_start.tzid, user_data);
497                         else
498                                 zone = default_timezone;
499
500                         occur_start = icaltime_as_timet_with_zone (
501                                 *dt_start.value, zone);
502                 } else
503                         occur_start = -1;
504
505                 if (dt_end.value) {
506                         if (dt_end.tzid && !dt_end.value->is_date)
507                                 zone = (* resolve_tzid) (dt_end.tzid, user_data);
508                         else
509                                 zone = default_timezone;
510
511                         occur_end = icaltime_as_timet_with_zone (*dt_end.value, zone);
512                 } else
513                         occur_end = -1;
514
515                 /* Add repeating alarms */
516
517                 if (repeat.repetitions != 0) {
518                         gint i;
519                         time_t repeat_time;
520
521                         repeat_time = icaldurationtype_as_int (repeat.duration);
522
523                         for (i = 0; i < repeat.repetitions; i++) {
524                                 time_t t;
525
526                                 t = abs_time + (i + 1) * repeat_time;
527
528                                 if (t >= aod->start && t < aod->end)
529                                         add_trigger (
530                                                 aod, auid, t,
531                                                 occur_start, occur_end);
532                         }
533                 }
534
535                 /* Add the trigger itself */
536
537                 if (abs_time >= aod->start && abs_time < aod->end)
538                         add_trigger (aod, auid, abs_time, occur_start, occur_end);
539         }
540
541         e_cal_component_free_datetime (&dt_start);
542         e_cal_component_free_datetime (&dt_end);
543 }
544
545 /* Compares two alarm instances; called from g_slist_sort() */
546 static gint
547 compare_alarm_instance (gconstpointer a,
548                         gconstpointer b)
549 {
550         const ECalComponentAlarmInstance *aia, *aib;
551
552         aia = a;
553         aib = b;
554
555         if (aia->trigger < aib->trigger)
556                 return -1;
557         else if (aia->trigger > aib->trigger)
558                 return 1;
559         else
560                 return 0;
561 }
562
563 /**
564  * e_cal_util_generate_alarms_for_comp:
565  * @comp: The #ECalComponent to generate alarms from
566  * @start: Start time
567  * @end: End time
568  * @omit: Alarm types to omit
569  * @resolve_tzid: (closure user_data) (scope call): Callback for resolving
570  * timezones
571  * @user_data: (closure): Data to be passed to the resolve_tzid callback
572  * @default_timezone: The timezone used to resolve DATE and floating DATE-TIME
573  * values.
574  *
575  * Generates alarm instances for a calendar component.  Returns the instances
576  * structure, or %NULL if no alarm instances occurred in the specified time
577  * range.
578  *
579  * Returns: (allow-none) (transfer full): a list of all the alarms found for the
580  * given component in the given time range. The list of alarms should be freed
581  * by using e_cal_component_free_alarm_list().
582  */
583 ECalComponentAlarms *
584 e_cal_util_generate_alarms_for_comp (ECalComponent *comp,
585                                      time_t start,
586                                      time_t end,
587                                      ECalComponentAlarmAction *omit,
588                                      ECalRecurResolveTimezoneFn resolve_tzid,
589                                      gpointer user_data,
590                                      icaltimezone *default_timezone)
591 {
592         GList *alarm_uids;
593         time_t alarm_start, alarm_end;
594         struct alarm_occurrence_data aod;
595         ECalComponentAlarms *alarms;
596
597         if (!e_cal_component_has_alarms (comp))
598                 return NULL;
599
600         alarm_uids = e_cal_component_get_alarm_uids (comp);
601         compute_alarm_range (
602                 comp, alarm_uids, start, end, &alarm_start, &alarm_end);
603
604         aod.alarm_uids = alarm_uids;
605         aod.start = start;
606         aod.end = end;
607         aod.omit = omit;
608         aod.triggers = NULL;
609         aod.n_triggers = 0;
610
611         e_cal_recur_generate_instances (
612                 comp, alarm_start, alarm_end,
613                 add_alarm_occurrences_cb, &aod,
614                 resolve_tzid, user_data,
615                 default_timezone);
616
617         /* We add the ABSOLUTE triggers separately */
618         generate_absolute_triggers (
619                 comp, &aod, resolve_tzid, user_data, default_timezone);
620
621         cal_obj_uid_list_free (alarm_uids);
622
623         if (aod.n_triggers == 0)
624                 return NULL;
625
626         /* Create the component alarm instances structure */
627
628         alarms = g_new (ECalComponentAlarms, 1);
629         alarms->comp = comp;
630         g_object_ref (G_OBJECT (alarms->comp));
631         alarms->alarms = g_slist_sort (aod.triggers, compare_alarm_instance);
632
633         return alarms;
634 }
635
636 /**
637  * e_cal_util_generate_alarms_for_list:
638  * @comps: (element-type ECalComponent): List of #ECalComponent<!-- -->s
639  * @start: Start time
640  * @end: End time
641  * @omit: Alarm types to omit
642  * @comp_alarms: (out) (transfer full) (element-type ECalComponentAlarms): List
643  * to be returned
644  * @resolve_tzid: (closure user_data) (scope call): Callback for resolving
645  * timezones
646  * @user_data: (closure): Data to be passed to the resolve_tzid callback
647  * @default_timezone: The timezone used to resolve DATE and floating DATE-TIME
648  * values.
649  *
650  * Iterates through all the components in the @comps list and generates alarm
651  * instances for them; putting them in the @comp_alarms list.
652  *
653  * Returns: the number of elements it added to the list
654  */
655 gint
656 e_cal_util_generate_alarms_for_list (GList *comps,
657                                      time_t start,
658                                      time_t end,
659                                      ECalComponentAlarmAction *omit,
660                                      GSList **comp_alarms,
661                                      ECalRecurResolveTimezoneFn resolve_tzid,
662                                      gpointer user_data,
663                                      icaltimezone *default_timezone)
664 {
665         GList *l;
666         gint n;
667
668         n = 0;
669
670         for (l = comps; l; l = l->next) {
671                 ECalComponent *comp;
672                 ECalComponentAlarms *alarms;
673
674                 comp = E_CAL_COMPONENT (l->data);
675                 alarms = e_cal_util_generate_alarms_for_comp (
676                         comp, start, end, omit, resolve_tzid,
677                         user_data, default_timezone);
678
679                 if (alarms) {
680                         *comp_alarms = g_slist_prepend (*comp_alarms, alarms);
681                         n++;
682                 }
683         }
684
685         return n;
686 }
687
688 /**
689  * e_cal_util_priority_to_string:
690  * @priority: Priority value.
691  *
692  * Converts an iCalendar PRIORITY value to a translated string. Any unknown
693  * priority value (i.e. not 0-9) will be returned as "" (undefined).
694  *
695  * Returns: a string representing the PRIORITY value. This value is a
696  * constant, so it should never be freed.
697  */
698 const gchar *
699 e_cal_util_priority_to_string (gint priority)
700 {
701         const gchar *retval;
702
703         if (priority <= 0)
704                 retval = "";
705         else if (priority <= 4)
706                 retval = C_("Priority", "High");
707         else if (priority == 5)
708                 retval = C_("Priority", "Normal");
709         else if (priority <= 9)
710                 retval = C_("Priority", "Low");
711         else
712                 retval = "";
713
714         return retval;
715 }
716
717 /**
718  * e_cal_util_priority_from_string:
719  * @string: A string representing the PRIORITY value.
720  *
721  * Converts a translated priority string to an iCalendar priority value.
722  *
723  * Returns: the priority (0-9) or -1 if the priority string is not valid.
724 */
725 gint
726 e_cal_util_priority_from_string (const gchar *string)
727 {
728         gint priority;
729
730         /* An empty string is the same as 'None'. */
731         if (!string || !string[0] || !e_util_utf8_strcasecmp (string, C_("Priority", "Undefined")))
732                 priority = 0;
733         else if (!e_util_utf8_strcasecmp (string, C_("Priority", "High")))
734                 priority = 3;
735         else if (!e_util_utf8_strcasecmp (string, C_("Priority", "Normal")))
736                 priority = 5;
737         else if (!e_util_utf8_strcasecmp (string, C_("Priority", "Low")))
738                 priority = 7;
739         else
740                 priority = -1;
741
742         return priority;
743 }
744
745 /* callback for icalcomponent_foreach_tzid */
746 typedef struct {
747         icalcomponent *vcal_comp;
748         icalcomponent *icalcomp;
749 } ForeachTzidData;
750
751 static void
752 add_timezone_cb (icalparameter *param,
753                  gpointer data)
754 {
755         icaltimezone *tz;
756         const gchar *tzid;
757         icalcomponent *vtz_comp;
758         ForeachTzidData *f_data = (ForeachTzidData *) data;
759
760         tzid = icalparameter_get_tzid (param);
761         if (!tzid)
762                 return;
763
764         tz = icalcomponent_get_timezone (f_data->vcal_comp, tzid);
765         if (tz)
766                 return;
767
768         tz = icalcomponent_get_timezone (f_data->icalcomp, tzid);
769         if (!tz) {
770                 tz = icaltimezone_get_builtin_timezone_from_tzid (tzid);
771                 if (!tz)
772                         return;
773         }
774
775         vtz_comp = icaltimezone_get_component (tz);
776         if (!vtz_comp)
777                 return;
778
779         icalcomponent_add_component (
780                 f_data->vcal_comp,
781                 icalcomponent_new_clone (vtz_comp));
782 }
783
784 /**
785  * e_cal_util_add_timezones_from_component:
786  * @vcal_comp: A VCALENDAR component.
787  * @icalcomp: An iCalendar component, of any type.
788  *
789  * Adds VTIMEZONE components to a VCALENDAR for all tzid's
790  * in the given @icalcomp.
791  */
792 void
793 e_cal_util_add_timezones_from_component (icalcomponent *vcal_comp,
794                                          icalcomponent *icalcomp)
795 {
796         ForeachTzidData f_data;
797
798         g_return_if_fail (vcal_comp != NULL);
799         g_return_if_fail (icalcomp != NULL);
800
801         f_data.vcal_comp = vcal_comp;
802         f_data.icalcomp = icalcomp;
803         icalcomponent_foreach_tzid (icalcomp, add_timezone_cb, &f_data);
804 }
805
806 /**
807  * e_cal_util_component_is_instance:
808  * @icalcomp: An #icalcomponent.
809  *
810  * Checks whether an #icalcomponent is an instance of a recurring appointment.
811  *
812  * Returns: TRUE if it is an instance, FALSE if not.
813  */
814 gboolean
815 e_cal_util_component_is_instance (icalcomponent *icalcomp)
816 {
817         icalproperty *prop;
818
819         g_return_val_if_fail (icalcomp != NULL, FALSE);
820
821         prop = icalcomponent_get_first_property (
822                 icalcomp, ICAL_RECURRENCEID_PROPERTY);
823
824         return (prop != NULL);
825 }
826
827 /**
828  * e_cal_util_component_has_alarms:
829  * @icalcomp: An #icalcomponent.
830  *
831  * Checks whether an #icalcomponent has any alarm.
832  *
833  * Returns: TRUE if it has alarms, FALSE otherwise.
834  */
835 gboolean
836 e_cal_util_component_has_alarms (icalcomponent *icalcomp)
837 {
838         icalcomponent *alarm;
839
840         g_return_val_if_fail (icalcomp != NULL, FALSE);
841
842         alarm = icalcomponent_get_first_component (
843                 icalcomp, ICAL_VALARM_COMPONENT);
844
845         return (alarm != NULL);
846 }
847
848 /**
849  * e_cal_util_component_has_organizer:
850  * @icalcomp: An #icalcomponent.
851  *
852  * Checks whether an #icalcomponent has an organizer.
853  *
854  * Returns: TRUE if there is an organizer, FALSE if not.
855  */
856 gboolean
857 e_cal_util_component_has_organizer (icalcomponent *icalcomp)
858 {
859         icalproperty *prop;
860
861         g_return_val_if_fail (icalcomp != NULL, FALSE);
862
863         prop = icalcomponent_get_first_property (
864                 icalcomp, ICAL_ORGANIZER_PROPERTY);
865
866         return (prop != NULL);
867 }
868
869 /**
870  * e_cal_util_component_has_attendee:
871  * @icalcomp: An #icalcomponent.
872  *
873  * Checks if an #icalcomponent has any attendees.
874  *
875  * Returns: TRUE if there are attendees, FALSE if not.
876  */
877 gboolean
878 e_cal_util_component_has_attendee (icalcomponent *icalcomp)
879 {
880         icalproperty *prop;
881
882         g_return_val_if_fail (icalcomp != NULL, FALSE);
883
884         prop = icalcomponent_get_first_property (
885                 icalcomp, ICAL_ATTENDEE_PROPERTY);
886
887         return (prop != NULL);
888 }
889
890 /**
891  * e_cal_util_component_has_recurrences:
892  * @icalcomp: An #icalcomponent.
893  *
894  * Checks if an #icalcomponent has recurrence dates or rules.
895  *
896  * Returns: TRUE if there are recurrence dates/rules, FALSE if not.
897  */
898 gboolean
899 e_cal_util_component_has_recurrences (icalcomponent *icalcomp)
900 {
901         g_return_val_if_fail (icalcomp != NULL, FALSE);
902
903         return e_cal_util_component_has_rdates (icalcomp) ||
904                 e_cal_util_component_has_rrules (icalcomp);
905 }
906
907 /**
908  * e_cal_util_component_has_rdates:
909  * @icalcomp: An #icalcomponent.
910  *
911  * Checks if an #icalcomponent has recurrence dates.
912  *
913  * Returns: TRUE if there are recurrence dates, FALSE if not.
914  */
915 gboolean
916 e_cal_util_component_has_rdates (icalcomponent *icalcomp)
917 {
918         icalproperty *prop;
919
920         g_return_val_if_fail (icalcomp != NULL, FALSE);
921
922         prop = icalcomponent_get_first_property (
923                 icalcomp, ICAL_RDATE_PROPERTY);
924
925         return (prop != NULL);
926 }
927
928 /**
929  * e_cal_util_component_has_rrules:
930  * @icalcomp: An #icalcomponent.
931  *
932  * Checks if an #icalcomponent has recurrence rules.
933  *
934  * Returns: TRUE if there are recurrence rules, FALSE if not.
935  */
936 gboolean
937 e_cal_util_component_has_rrules (icalcomponent *icalcomp)
938 {
939         icalproperty *prop;
940
941         g_return_val_if_fail (icalcomp != NULL, FALSE);
942
943         prop = icalcomponent_get_first_property (
944                 icalcomp, ICAL_RRULE_PROPERTY);
945
946         return (prop != NULL);
947 }
948
949 /**
950  * e_cal_util_event_dates_match:
951  * @icalcomp1: An #icalcomponent.
952  * @icalcomp2: An #icalcomponent.
953  *
954  * Compare the dates of two #icalcomponent's to check if they match.
955  *
956  * Returns: TRUE if the dates of both components match, FALSE otherwise.
957  */
958 gboolean
959 e_cal_util_event_dates_match (icalcomponent *icalcomp1,
960                               icalcomponent *icalcomp2)
961 {
962         struct icaltimetype c1_dtstart, c1_dtend, c2_dtstart, c2_dtend;
963
964         g_return_val_if_fail (icalcomp1 != NULL, FALSE);
965         g_return_val_if_fail (icalcomp2 != NULL, FALSE);
966
967         c1_dtstart = icalcomponent_get_dtstart (icalcomp1);
968         c1_dtend = icalcomponent_get_dtend (icalcomp1);
969         c2_dtstart = icalcomponent_get_dtstart (icalcomp2);
970         c2_dtend = icalcomponent_get_dtend (icalcomp2);
971
972         /* if either value is NULL, they must both be NULL to match */
973         if (icaltime_is_valid_time (c1_dtstart) || icaltime_is_valid_time (c2_dtstart)) {
974                 if (!(icaltime_is_valid_time (c1_dtstart) && icaltime_is_valid_time (c2_dtstart)))
975                         return FALSE;
976         } else {
977                 if (icaltime_compare (c1_dtstart, c2_dtstart))
978                         return FALSE;
979         }
980
981         if (icaltime_is_valid_time (c1_dtend) || icaltime_is_valid_time (c2_dtend)) {
982                 if (!(icaltime_is_valid_time (c1_dtend) && icaltime_is_valid_time (c2_dtend)))
983                         return FALSE;
984         } else {
985                 if (icaltime_compare (c1_dtend, c2_dtend))
986                         return FALSE;
987         }
988
989         /* now match the timezones */
990         if (!(!c1_dtstart.zone && !c2_dtstart.zone) ||
991             (c1_dtstart.zone && c2_dtstart.zone &&
992              !strcmp (icaltimezone_get_tzid ((icaltimezone *) c1_dtstart.zone),
993                       icaltimezone_get_tzid ((icaltimezone *) c2_dtstart.zone))))
994                 return FALSE;
995
996         if (!(!c1_dtend.zone && !c2_dtend.zone) ||
997             (c1_dtend.zone && c2_dtend.zone &&
998              !strcmp (icaltimezone_get_tzid ((icaltimezone *) c1_dtend.zone),
999                       icaltimezone_get_tzid ((icaltimezone *) c2_dtend.zone))))
1000                 return FALSE;
1001
1002         return TRUE;
1003 }
1004
1005 /* Individual instances management */
1006
1007 struct instance_data {
1008         time_t start;
1009         gboolean found;
1010 };
1011
1012 static void
1013 check_instance (icalcomponent *comp,
1014                 struct icaltime_span *span,
1015                 gpointer data)
1016 {
1017         struct instance_data *instance = data;
1018
1019         if (span->start == instance->start)
1020                 instance->found = TRUE;
1021 }
1022
1023 /**
1024  * e_cal_util_construct_instance:
1025  * @icalcomp: A recurring #icalcomponent
1026  * @rid: The RECURRENCE-ID to construct a component for
1027  *
1028  * This checks that @rid indicates a valid recurrence of @icalcomp, and
1029  * if so, generates a copy of @comp containing a RECURRENCE-ID of @rid.
1030  *
1031  * Returns: the instance, or %NULL.
1032  **/
1033 icalcomponent *
1034 e_cal_util_construct_instance (icalcomponent *icalcomp,
1035                                struct icaltimetype rid)
1036 {
1037         struct instance_data instance;
1038         struct icaltimetype start, end;
1039
1040         g_return_val_if_fail (icalcomp != NULL, NULL);
1041
1042         /* Make sure this is really recurring */
1043         if (!icalcomponent_get_first_property (icalcomp, ICAL_RRULE_PROPERTY) &&
1044             !icalcomponent_get_first_property (icalcomp, ICAL_RDATE_PROPERTY))
1045                 return NULL;
1046
1047         /* Make sure the specified instance really exists */
1048         start = icaltime_convert_to_zone (rid, icaltimezone_get_utc_timezone ());
1049         end = start;
1050         icaltime_adjust (&end, 0, 0, 0, 1);
1051
1052         instance.start = icaltime_as_timet (start);
1053         instance.found = FALSE;
1054         icalcomponent_foreach_recurrence (icalcomp, start, end,
1055                                           check_instance, &instance);
1056         if (!instance.found)
1057                 return NULL;
1058
1059         /* Make the instance */
1060         icalcomp = icalcomponent_new_clone (icalcomp);
1061         icalcomponent_set_recurrenceid (icalcomp, rid);
1062
1063         return icalcomp;
1064 }
1065
1066 static inline gboolean
1067 time_matches_rid (struct icaltimetype itt,
1068                   struct icaltimetype rid,
1069                   ECalObjModType mod)
1070 {
1071         gint compare;
1072
1073         compare = icaltime_compare (itt, rid);
1074         if (compare == 0)
1075                 return TRUE;
1076         else if (compare < 0 && (mod & E_CAL_OBJ_MOD_THIS_AND_PRIOR))
1077                 return TRUE;
1078         else if (compare > 0 && (mod & E_CAL_OBJ_MOD_THIS_AND_FUTURE))
1079                 return TRUE;
1080
1081         return FALSE;
1082 }
1083
1084 /**
1085  * e_cal_util_remove_instances:
1086  * @icalcomp: A (recurring) #icalcomponent
1087  * @rid: The base RECURRENCE-ID to remove
1088  * @mod: How to interpret @rid
1089  *
1090  * Removes one or more instances from @comp according to @rid and @mod.
1091  *
1092  * FIXME: should probably have a return value indicating whether @icalcomp
1093  *        still has any instances
1094  **/
1095 void
1096 e_cal_util_remove_instances (icalcomponent *icalcomp,
1097                              struct icaltimetype rid,
1098                              ECalObjModType mod)
1099 {
1100         icalproperty *prop;
1101         struct icaltimetype itt, recur;
1102         struct icalrecurrencetype rule;
1103         icalrecur_iterator *iter;
1104
1105         g_return_if_fail (icalcomp != NULL);
1106         g_return_if_fail (mod != E_CAL_OBJ_MOD_ALL);
1107
1108         /* First remove RDATEs and EXDATEs in the indicated range. */
1109         for (prop = icalcomponent_get_first_property (icalcomp, ICAL_RDATE_PROPERTY);
1110              prop;
1111              prop = icalcomponent_get_next_property (icalcomp, ICAL_RDATE_PROPERTY)) {
1112                 struct icaldatetimeperiodtype period;
1113
1114                 period = icalproperty_get_rdate (prop);
1115                 if (time_matches_rid (period.time, rid, mod))
1116                         icalcomponent_remove_property (icalcomp, prop);
1117         }
1118         for (prop = icalcomponent_get_first_property (icalcomp, ICAL_EXDATE_PROPERTY);
1119              prop;
1120              prop = icalcomponent_get_next_property (icalcomp, ICAL_EXDATE_PROPERTY)) {
1121                 itt = icalproperty_get_exdate (prop);
1122                 if (time_matches_rid (itt, rid, mod))
1123                         icalcomponent_remove_property (icalcomp, prop);
1124         }
1125
1126         /* If we're only removing one instance, just add an EXDATE. */
1127         if (mod == E_CAL_OBJ_MOD_THIS) {
1128                 prop = icalproperty_new_exdate (rid);
1129                 icalcomponent_add_property (icalcomp, prop);
1130                 return;
1131         }
1132
1133         /* Otherwise, iterate through RRULEs */
1134         /* FIXME: this may generate duplicate EXRULEs */
1135         for (prop = icalcomponent_get_first_property (icalcomp, ICAL_RRULE_PROPERTY);
1136              prop;
1137              prop = icalcomponent_get_next_property (icalcomp, ICAL_RRULE_PROPERTY)) {
1138                 rule = icalproperty_get_rrule (prop);
1139
1140                 iter = icalrecur_iterator_new (rule, rid);
1141                 recur = icalrecur_iterator_next (iter);
1142
1143                 if (mod & E_CAL_OBJ_MOD_THIS_AND_FUTURE) {
1144                         /* If there is a recurrence on or after rid,
1145                          * use the UNTIL parameter to truncate the rule
1146                          * at rid.
1147                          */
1148                         if (!icaltime_is_null_time (recur)) {
1149                                 rule.count = 0;
1150                                 rule.until = rid;
1151                                 icaltime_adjust (&rule.until, 0, 0, 0, -1);
1152                                 icalproperty_set_rrule (prop, rule);
1153                         }
1154                 } else {
1155                         /* (If recur == rid, skip to the next occurrence) */
1156                         if (icaltime_compare (recur, rid) == 0)
1157                                 recur = icalrecur_iterator_next (iter);
1158
1159                         /* If there is a recurrence after rid, add
1160                          * an EXRULE to block instances up to rid.
1161                          * Otherwise, just remove the RRULE.
1162                          */
1163                         if (!icaltime_is_null_time (recur)) {
1164                                 rule.count = 0;
1165                                 /* iCalendar says we should just use rid
1166                                  * here, but Outlook/Exchange handle
1167                                  * UNTIL incorrectly.
1168                                  */
1169                                 rule.until = icaltime_add (
1170                                         rid, icalcomponent_get_duration (icalcomp));
1171                                 prop = icalproperty_new_exrule (rule);
1172                                 icalcomponent_add_property (icalcomp, prop);
1173                         } else
1174                                 icalcomponent_remove_property (icalcomp, prop);
1175                 }
1176
1177                 icalrecur_iterator_free (iter);
1178         }
1179 }
1180
1181 /**
1182  * e_cal_util_get_system_timezone_location:
1183  *
1184  * Fetches system timezone localtion string.
1185  *
1186  * Returns: (transfer full): system timezone location string, %NULL on an error.
1187  *
1188  * Since: 2.28
1189  **/
1190 gchar *
1191 e_cal_util_get_system_timezone_location (void)
1192 {
1193         return e_cal_system_timezone_get_location ();
1194 }
1195
1196 /**
1197  * e_cal_util_get_system_timezone:
1198  *
1199  * Fetches system timezone icaltimezone object.
1200  *
1201  * The returned pointer is part of the built-in timezones and should not be freed.
1202  *
1203  * Returns: (transfer none): The icaltimezone object of the system timezone, or %NULL on an error.
1204  *
1205  * Since: 2.28
1206  **/
1207 icaltimezone *
1208 e_cal_util_get_system_timezone (void)
1209 {
1210         gchar *location;
1211         icaltimezone *zone;
1212
1213         location = e_cal_system_timezone_get_location ();
1214         g_return_val_if_fail (location != NULL, NULL);
1215
1216         zone = icaltimezone_get_builtin_timezone (location);
1217
1218         g_free (location);
1219
1220         return zone;
1221 }
1222
1223 static time_t
1224 componenttime_to_utc_timet (const ECalComponentDateTime *dt_time,
1225                             ECalRecurResolveTimezoneFn tz_cb,
1226                             gpointer tz_cb_data,
1227                             const icaltimezone *default_zone)
1228 {
1229         time_t timet = -1;
1230         icaltimezone *zone = NULL;
1231
1232         g_return_val_if_fail (dt_time != NULL, -1);
1233
1234         if (dt_time->value) {
1235                 if (dt_time->tzid)
1236                         zone = tz_cb (dt_time->tzid, tz_cb_data);
1237
1238                 // zone = icaltimezone_get_utc_timezone ();
1239                 timet = icaltime_as_timet_with_zone (
1240                         *dt_time->value, zone ? zone : default_zone);
1241         }
1242
1243         return timet;
1244 }
1245
1246 /**
1247  * e_cal_util_get_component_occur_times:
1248  * @comp: an #ECalComponent
1249  * @start: (out): Location to store the start time
1250  * @end: (out): Location to store the end time
1251  * @tz_cb: (closure tz_cb_data) (scope call): The #ECalRecurResolveTimezoneFn to call
1252  * @tz_cb_data: (closure): User data to be passed to the @tz_cb callback
1253  * @default_timezone: The default timezone
1254  * @kind: the type of component, indicated with an icalcomponent_kind
1255  *
1256  * Find out when the component starts and stops, being careful about
1257  * recurrences.
1258  *
1259  * Since: 2.32
1260  **/
1261 void
1262 e_cal_util_get_component_occur_times (ECalComponent *comp,
1263                                       time_t *start,
1264                                       time_t *end,
1265                                       ECalRecurResolveTimezoneFn tz_cb,
1266                                       gpointer tz_cb_data,
1267                                       const icaltimezone *default_timezone,
1268                                       icalcomponent_kind kind)
1269 {
1270         struct icalrecurrencetype ir;
1271         ECalComponentDateTime dt_start, dt_end;
1272
1273         g_return_if_fail (comp != NULL);
1274         g_return_if_fail (start != NULL);
1275         g_return_if_fail (end != NULL);
1276
1277         e_cal_recur_ensure_end_dates (comp, FALSE, tz_cb, tz_cb_data);
1278
1279         /* Get dtstart of the component and convert it to UTC */
1280         e_cal_component_get_dtstart (comp, &dt_start);
1281
1282         if ((*start = componenttime_to_utc_timet (&dt_start, tz_cb, tz_cb_data, default_timezone)) == -1)
1283                 *start = _TIME_MIN;
1284
1285         e_cal_component_free_datetime (&dt_start);
1286
1287         /* find out end date of component */
1288         *end = _TIME_MAX;
1289
1290         if (kind == ICAL_VTODO_COMPONENT) {
1291                 /* max from COMPLETED and DUE properties */
1292                 struct icaltimetype *tt = NULL;
1293                 time_t completed_time = -1, due_time = -1, max_time;
1294                 ECalComponentDateTime dt_due;
1295
1296                 e_cal_component_get_completed (comp, &tt);
1297                 if (tt) {
1298                         /* COMPLETED must be in UTC. */
1299                         completed_time = icaltime_as_timet_with_zone (
1300                                 *tt, icaltimezone_get_utc_timezone ());
1301                         e_cal_component_free_icaltimetype (tt);
1302                 }
1303
1304                 e_cal_component_get_due (comp, &dt_due);
1305                 if (dt_due.value != NULL)
1306                         due_time = componenttime_to_utc_timet (
1307                                 &dt_due, tz_cb, tz_cb_data,
1308                                 default_timezone);
1309
1310                 e_cal_component_free_datetime (&dt_due);
1311
1312                 max_time = MAX (completed_time, due_time);
1313
1314                 if (max_time != -1)
1315                         *end = max_time;
1316
1317         } else {
1318                 /* ALARMS, EVENTS: DTEND and reccurences */
1319
1320                 if (e_cal_component_has_recurrences (comp)) {
1321                         GSList *rrules = NULL;
1322                         GSList *exrules = NULL;
1323                         GSList *elem;
1324                         GSList *rdates = NULL;
1325
1326                         /* Do the RRULEs, EXRULEs and RDATEs*/
1327                         e_cal_component_get_rrule_property_list (comp, &rrules);
1328                         e_cal_component_get_exrule_property_list (comp, &exrules);
1329                         e_cal_component_get_rdate_list (comp, &rdates);
1330
1331                         for (elem = rrules; elem; elem = elem->next) {
1332                                 time_t rule_end;
1333                                 icaltimezone *utc_zone;
1334                                 icalproperty *prop = elem->data;
1335                                 ir = icalproperty_get_rrule (prop);
1336
1337                                 utc_zone = icaltimezone_get_utc_timezone ();
1338                                 rule_end = e_cal_recur_obtain_enddate (
1339                                         &ir, prop, utc_zone, TRUE);
1340
1341                                 if (rule_end == -1) /* repeats forever */
1342                                         *end = _TIME_MAX;
1343                                 else if (rule_end > *end) /* new maximum */
1344                                         *end = rule_end;
1345                         }
1346
1347                         /* Do the EXRULEs. */
1348                         for (elem = exrules; elem; elem = elem->next) {
1349                                 icalproperty *prop = elem->data;
1350                                 time_t rule_end;
1351                                 icaltimezone *utc_zone;
1352                                 ir = icalproperty_get_exrule (prop);
1353
1354                                 utc_zone = icaltimezone_get_utc_timezone ();
1355                                 rule_end = e_cal_recur_obtain_enddate (
1356                                         &ir, prop, utc_zone, TRUE);
1357
1358                                 if (rule_end == -1) /* repeats forever */
1359                                         *end = _TIME_MAX;
1360                                 else if (rule_end > *end)
1361                                         *end = rule_end;
1362                         }
1363
1364                         /* Do the RDATEs */
1365                         for (elem = rdates; elem; elem = elem->next) {
1366                                 ECalComponentPeriod *p = elem->data;
1367                                 time_t rdate_end = _TIME_MAX;
1368
1369                                 /* FIXME: We currently assume RDATEs are in the same timezone
1370                                  * as DTSTART. We should get the RDATE timezone and convert
1371                                  * to the DTSTART timezone first. */
1372
1373                                 /* Check if the end date or duration is set, libical seems to set
1374                                  * second to -1 to denote an unset time */
1375                                 if (p->type != E_CAL_COMPONENT_PERIOD_DATETIME || p->u.end.second != -1)
1376                                         rdate_end = icaltime_as_timet (icaltime_add (p->start, p->u.duration));
1377                                 else
1378                                         rdate_end = icaltime_as_timet (p->u.end);
1379
1380                                 if (rdate_end == -1) /* repeats forever */
1381                                         *end = _TIME_MAX;
1382                                 else if (rdate_end > *end)
1383                                         *end = rdate_end;
1384                         }
1385
1386                         e_cal_component_free_period_list (rdates);
1387                 }
1388
1389                 /* Get dtend of the component and convert it to UTC */
1390                 e_cal_component_get_dtend (comp, &dt_end);
1391
1392                 if (dt_end.value) {
1393                         time_t dtend_time;
1394
1395                         dtend_time = componenttime_to_utc_timet (
1396                                 &dt_end, tz_cb, tz_cb_data, default_timezone);
1397
1398                         if (dtend_time == -1 || (dtend_time > *end))
1399                                 *end = dtend_time;
1400                 }
1401
1402                 e_cal_component_free_datetime (&dt_end);
1403         }
1404 }
1405