1 /* Evolution calendar utilities and types
3 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5 * Author: Federico Mena-Quintero <federico@ximian.com>
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.
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
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/>.
23 #include <glib/gstdio.h>
24 #include <glib/gi18n-lib.h>
26 #include <libedataserver/libedataserver.h>
28 #include "e-cal-util.h"
29 #include "e-cal-system-timezone.h"
31 #define _TIME_MIN ((time_t) 0) /* Min valid time_t */
32 #define _TIME_MAX ((time_t) INT_MAX)
35 * cal_obj_instance_list_free:
36 * @list: List of #CalObjInstance structures.
38 * Frees a list of #CalObjInstance structures.
41 cal_obj_instance_list_free (GList *list)
46 for (l = list; l; l = l->next) {
49 if (i != NULL && i->uid != NULL) {
60 * cal_obj_uid_list_free:
61 * @list: List of strings with unique identifiers.
63 * Frees a list of unique identifiers for calendar objects.
66 cal_obj_uid_list_free (GList *list)
68 g_list_foreach (list, (GFunc) g_free, NULL);
73 * e_cal_util_new_top_level:
75 * Creates a new VCALENDAR component.
77 * Returns: the newly created top level component.
80 e_cal_util_new_top_level (void)
82 icalcomponent *icalcomp;
85 icalcomp = icalcomponent_new (ICAL_VCALENDAR_COMPONENT);
87 /* RFC 2445, section 4.7.1 */
88 prop = icalproperty_new_calscale ("GREGORIAN");
89 icalcomponent_add_property (icalcomp, prop);
91 /* RFC 2445, section 4.7.3 */
92 prop = icalproperty_new_prodid ("-//Ximian//NONSGML Evolution Calendar//EN");
93 icalcomponent_add_property (icalcomp, prop);
95 /* RFC 2445, section 4.7.4. This is the iCalendar spec version, *NOT*
96 * the product version! Do not change this!
98 prop = icalproperty_new_version ("2.0");
99 icalcomponent_add_property (icalcomp, prop);
105 * e_cal_util_new_component:
106 * @kind: Kind of the component to create.
108 * Creates a new #icalcomponent of the specified kind.
110 * Returns: the newly created component.
113 e_cal_util_new_component (icalcomponent_kind kind)
116 struct icaltimetype dtstamp;
119 comp = icalcomponent_new (kind);
120 uid = e_cal_component_gen_uid ();
121 icalcomponent_set_uid (comp, uid);
123 dtstamp = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
124 icalcomponent_set_dtstamp (comp, dtstamp);
130 read_line (const gchar *string)
132 GString *line_str = NULL;
134 for (; *string; string++) {
136 line_str = g_string_new ("");
138 line_str = g_string_append_c (line_str, *string);
143 return g_string_free (line_str, FALSE);
147 * e_cal_util_parse_ics_string:
148 * @string: iCalendar string to be parsed.
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
155 * Returns: a newly created #icalcomponent or NULL if the string isn't a
156 * valid iCalendar string.
159 e_cal_util_parse_ics_string (const gchar *string)
161 GString *comp_str = NULL;
163 icalcomponent *icalcomp = NULL;
165 g_return_val_if_fail (string != NULL, NULL);
167 /* Split string into separated VCALENDAR's, if more than one */
168 s = g_strstr_len (string, strlen (string), "BEGIN:VCALENDAR");
171 return icalparser_parse_string (string);
174 gchar *line = read_line (s);
177 comp_str = g_string_new (line);
179 comp_str = g_string_append (comp_str, line);
181 if (strncmp (line, "END:VCALENDAR", 13) == 0) {
184 tmp = icalparser_parse_string (comp_str->str);
185 if (tmp && icalcomponent_isa (tmp) == ICAL_VCALENDAR_COMPONENT) {
187 icalcomponent_merge_component (icalcomp, tmp);
192 "Could not merge the components, "
193 "the component is either invalid "
194 "or not a toplevel component \n");
197 g_string_free (comp_str, TRUE);
210 get_line_fn (gchar *buf,
214 return fgets (buf, size, file);
218 * e_cal_util_parse_ics_file:
219 * @filename: Name of the file to be parsed.
221 * Parses the given file, and, if it contains a valid iCalendar object,
222 * parse it and return a new #icalcomponent.
224 * Returns: a newly created #icalcomponent or NULL if the file doesn't
225 * contain a valid iCalendar object.
228 e_cal_util_parse_ics_file (const gchar *filename)
231 icalcomponent *icalcomp;
234 file = g_fopen (filename, "rb");
238 parser = icalparser_new ();
239 icalparser_set_gen_data (parser, file);
241 icalcomp = icalparser_parse (parser, get_line_fn);
242 icalparser_free (parser);
248 /* Computes the range of time in which recurrences should be generated for a
249 * component in order to compute alarm trigger times.
252 compute_alarm_range (ECalComponent *comp,
262 *alarm_start = start;
267 for (l = alarm_uids; l; l = l->next) {
269 ECalComponentAlarm *alarm;
270 ECalComponentAlarmTrigger trigger;
271 struct icaldurationtype *dur;
273 ECalComponentAlarmRepeat repeat;
276 alarm = e_cal_component_get_alarm (comp, auid);
277 g_return_if_fail (alarm != NULL);
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);
283 switch (trigger.type) {
284 case E_CAL_COMPONENT_ALARM_TRIGGER_NONE:
285 case E_CAL_COMPONENT_ALARM_TRIGGER_ABSOLUTE:
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);
293 if (repeat.repetitions != 0) {
296 rdur = repeat.repetitions *
297 icaldurationtype_as_int (repeat.duration);
298 repeat_time = MAX (repeat_time, rdur);
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.
306 *alarm_end = MAX (*alarm_end, end - dur_time);
308 *alarm_start = MIN (*alarm_start, start - dur_time);
313 g_return_if_reached ();
317 *alarm_start -= repeat_time;
318 g_warn_if_fail (*alarm_start <= *alarm_end);
321 /* Closure data to generate alarm occurrences */
322 struct alarm_occurrence_data {
323 /* These are the info we have */
327 ECalComponentAlarmAction *omit;
329 /* This is what we compute */
335 add_trigger (struct alarm_occurrence_data *aod,
341 ECalComponentAlarmInstance *instance;
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;
349 aod->triggers = g_slist_prepend (aod->triggers, instance);
353 /* Callback used from cal_recur_generate_instances(); generates triggers for all
354 * of a component's RELATIVE alarms.
357 add_alarm_occurrences_cb (ECalComponent *comp,
362 struct alarm_occurrence_data *aod;
367 for (l = aod->alarm_uids; l; l = l->next) {
369 ECalComponentAlarm *alarm;
370 ECalComponentAlarmAction action;
371 ECalComponentAlarmTrigger trigger;
372 ECalComponentAlarmRepeat repeat;
373 struct icaldurationtype *dur;
375 time_t occur_time, trigger_time;
379 alarm = e_cal_component_get_alarm (comp, auid);
380 g_return_val_if_fail (alarm != NULL, FALSE);
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);
387 for (i = 0; aod->omit[i] != -1; i++) {
388 if (aod->omit[i] == action)
391 if (aod->omit[i] != -1)
394 if (trigger.type != E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START
395 && trigger.type != E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_END)
398 dur = &trigger.u.rel_duration;
399 dur_time = icaldurationtype_as_int (*dur);
401 if (trigger.type == E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START)
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.
412 trigger_time = occur_time + dur_time;
414 /* Add repeating alarms */
416 if (repeat.repetitions != 0) {
420 repeat_time = icaldurationtype_as_int (repeat.duration);
422 for (i = 0; i < repeat.repetitions; i++) {
425 t = trigger_time + (i + 1) * repeat_time;
427 if (t >= aod->start && t < aod->end)
428 add_trigger (aod, auid, t, start, end);
432 /* Add the trigger itself */
434 if (trigger_time >= aod->start && trigger_time < aod->end)
435 add_trigger (aod, auid, trigger_time, start, end);
441 /* Generates the absolute triggers for a component */
443 generate_absolute_triggers (ECalComponent *comp,
444 struct alarm_occurrence_data *aod,
445 ECalRecurResolveTimezoneFn resolve_tzid,
447 icaltimezone *default_timezone)
450 ECalComponentDateTime dt_start, dt_end;
452 e_cal_component_get_dtstart (comp, &dt_start);
453 e_cal_component_get_dtend (comp, &dt_end);
455 for (l = aod->alarm_uids; l; l = l->next) {
457 ECalComponentAlarm *alarm;
458 ECalComponentAlarmAction action;
459 ECalComponentAlarmRepeat repeat;
460 ECalComponentAlarmTrigger trigger;
462 time_t occur_start, occur_end;
467 alarm = e_cal_component_get_alarm (comp, auid);
468 g_return_if_fail (alarm != NULL);
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);
475 for (i = 0; aod->omit[i] != -1; i++) {
476 if (aod->omit[i] == action)
479 if (aod->omit[i] != -1)
482 if (trigger.type != E_CAL_COMPONENT_ALARM_TRIGGER_ABSOLUTE)
485 /* Absolute triggers are always in UTC;
486 * see RFC 2445 section 4.8.6.3 */
487 zone = icaltimezone_get_utc_timezone ();
489 abs_time = icaltime_as_timet_with_zone (trigger.u.abs_time, zone);
491 /* No particular occurrence, so just use the times from the
494 if (dt_start.value) {
495 if (dt_start.tzid && !dt_start.value->is_date)
496 zone = (* resolve_tzid) (dt_start.tzid, user_data);
498 zone = default_timezone;
500 occur_start = icaltime_as_timet_with_zone (
501 *dt_start.value, zone);
506 if (dt_end.tzid && !dt_end.value->is_date)
507 zone = (* resolve_tzid) (dt_end.tzid, user_data);
509 zone = default_timezone;
511 occur_end = icaltime_as_timet_with_zone (*dt_end.value, zone);
515 /* Add repeating alarms */
517 if (repeat.repetitions != 0) {
521 repeat_time = icaldurationtype_as_int (repeat.duration);
523 for (i = 0; i < repeat.repetitions; i++) {
526 t = abs_time + (i + 1) * repeat_time;
528 if (t >= aod->start && t < aod->end)
531 occur_start, occur_end);
535 /* Add the trigger itself */
537 if (abs_time >= aod->start && abs_time < aod->end)
538 add_trigger (aod, auid, abs_time, occur_start, occur_end);
541 e_cal_component_free_datetime (&dt_start);
542 e_cal_component_free_datetime (&dt_end);
545 /* Compares two alarm instances; called from g_slist_sort() */
547 compare_alarm_instance (gconstpointer a,
550 const ECalComponentAlarmInstance *aia, *aib;
555 if (aia->trigger < aib->trigger)
557 else if (aia->trigger > aib->trigger)
564 * e_cal_util_generate_alarms_for_comp:
565 * @comp: The #ECalComponent to generate alarms from
568 * @omit: Alarm types to omit
569 * @resolve_tzid: (closure user_data) (scope call): Callback for resolving
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
575 * Generates alarm instances for a calendar component. Returns the instances
576 * structure, or %NULL if no alarm instances occurred in the specified time
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().
583 ECalComponentAlarms *
584 e_cal_util_generate_alarms_for_comp (ECalComponent *comp,
587 ECalComponentAlarmAction *omit,
588 ECalRecurResolveTimezoneFn resolve_tzid,
590 icaltimezone *default_timezone)
593 time_t alarm_start, alarm_end;
594 struct alarm_occurrence_data aod;
595 ECalComponentAlarms *alarms;
597 if (!e_cal_component_has_alarms (comp))
600 alarm_uids = e_cal_component_get_alarm_uids (comp);
601 compute_alarm_range (
602 comp, alarm_uids, start, end, &alarm_start, &alarm_end);
604 aod.alarm_uids = alarm_uids;
611 e_cal_recur_generate_instances (
612 comp, alarm_start, alarm_end,
613 add_alarm_occurrences_cb, &aod,
614 resolve_tzid, user_data,
617 /* We add the ABSOLUTE triggers separately */
618 generate_absolute_triggers (
619 comp, &aod, resolve_tzid, user_data, default_timezone);
621 cal_obj_uid_list_free (alarm_uids);
623 if (aod.n_triggers == 0)
626 /* Create the component alarm instances structure */
628 alarms = g_new (ECalComponentAlarms, 1);
630 g_object_ref (G_OBJECT (alarms->comp));
631 alarms->alarms = g_slist_sort (aod.triggers, compare_alarm_instance);
637 * e_cal_util_generate_alarms_for_list:
638 * @comps: (element-type ECalComponent): List of #ECalComponent<!-- -->s
641 * @omit: Alarm types to omit
642 * @comp_alarms: (out) (transfer full) (element-type ECalComponentAlarms): List
644 * @resolve_tzid: (closure user_data) (scope call): Callback for resolving
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
650 * Iterates through all the components in the @comps list and generates alarm
651 * instances for them; putting them in the @comp_alarms list.
653 * Returns: the number of elements it added to the list
656 e_cal_util_generate_alarms_for_list (GList *comps,
659 ECalComponentAlarmAction *omit,
660 GSList **comp_alarms,
661 ECalRecurResolveTimezoneFn resolve_tzid,
663 icaltimezone *default_timezone)
670 for (l = comps; l; l = l->next) {
672 ECalComponentAlarms *alarms;
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);
680 *comp_alarms = g_slist_prepend (*comp_alarms, alarms);
689 * e_cal_util_priority_to_string:
690 * @priority: Priority value.
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).
695 * Returns: a string representing the PRIORITY value. This value is a
696 * constant, so it should never be freed.
699 e_cal_util_priority_to_string (gint priority)
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");
718 * e_cal_util_priority_from_string:
719 * @string: A string representing the PRIORITY value.
721 * Converts a translated priority string to an iCalendar priority value.
723 * Returns: the priority (0-9) or -1 if the priority string is not valid.
726 e_cal_util_priority_from_string (const gchar *string)
730 /* An empty string is the same as 'None'. */
731 if (!string || !string[0] || !e_util_utf8_strcasecmp (string, C_("Priority", "Undefined")))
733 else if (!e_util_utf8_strcasecmp (string, C_("Priority", "High")))
735 else if (!e_util_utf8_strcasecmp (string, C_("Priority", "Normal")))
737 else if (!e_util_utf8_strcasecmp (string, C_("Priority", "Low")))
745 /* callback for icalcomponent_foreach_tzid */
747 icalcomponent *vcal_comp;
748 icalcomponent *icalcomp;
752 add_timezone_cb (icalparameter *param,
757 icalcomponent *vtz_comp;
758 ForeachTzidData *f_data = (ForeachTzidData *) data;
760 tzid = icalparameter_get_tzid (param);
764 tz = icalcomponent_get_timezone (f_data->vcal_comp, tzid);
768 tz = icalcomponent_get_timezone (f_data->icalcomp, tzid);
770 tz = icaltimezone_get_builtin_timezone_from_tzid (tzid);
775 vtz_comp = icaltimezone_get_component (tz);
779 icalcomponent_add_component (
781 icalcomponent_new_clone (vtz_comp));
785 * e_cal_util_add_timezones_from_component:
786 * @vcal_comp: A VCALENDAR component.
787 * @icalcomp: An iCalendar component, of any type.
789 * Adds VTIMEZONE components to a VCALENDAR for all tzid's
790 * in the given @icalcomp.
793 e_cal_util_add_timezones_from_component (icalcomponent *vcal_comp,
794 icalcomponent *icalcomp)
796 ForeachTzidData f_data;
798 g_return_if_fail (vcal_comp != NULL);
799 g_return_if_fail (icalcomp != NULL);
801 f_data.vcal_comp = vcal_comp;
802 f_data.icalcomp = icalcomp;
803 icalcomponent_foreach_tzid (icalcomp, add_timezone_cb, &f_data);
807 * e_cal_util_component_is_instance:
808 * @icalcomp: An #icalcomponent.
810 * Checks whether an #icalcomponent is an instance of a recurring appointment.
812 * Returns: TRUE if it is an instance, FALSE if not.
815 e_cal_util_component_is_instance (icalcomponent *icalcomp)
819 g_return_val_if_fail (icalcomp != NULL, FALSE);
821 prop = icalcomponent_get_first_property (
822 icalcomp, ICAL_RECURRENCEID_PROPERTY);
824 return (prop != NULL);
828 * e_cal_util_component_has_alarms:
829 * @icalcomp: An #icalcomponent.
831 * Checks whether an #icalcomponent has any alarm.
833 * Returns: TRUE if it has alarms, FALSE otherwise.
836 e_cal_util_component_has_alarms (icalcomponent *icalcomp)
838 icalcomponent *alarm;
840 g_return_val_if_fail (icalcomp != NULL, FALSE);
842 alarm = icalcomponent_get_first_component (
843 icalcomp, ICAL_VALARM_COMPONENT);
845 return (alarm != NULL);
849 * e_cal_util_component_has_organizer:
850 * @icalcomp: An #icalcomponent.
852 * Checks whether an #icalcomponent has an organizer.
854 * Returns: TRUE if there is an organizer, FALSE if not.
857 e_cal_util_component_has_organizer (icalcomponent *icalcomp)
861 g_return_val_if_fail (icalcomp != NULL, FALSE);
863 prop = icalcomponent_get_first_property (
864 icalcomp, ICAL_ORGANIZER_PROPERTY);
866 return (prop != NULL);
870 * e_cal_util_component_has_attendee:
871 * @icalcomp: An #icalcomponent.
873 * Checks if an #icalcomponent has any attendees.
875 * Returns: TRUE if there are attendees, FALSE if not.
878 e_cal_util_component_has_attendee (icalcomponent *icalcomp)
882 g_return_val_if_fail (icalcomp != NULL, FALSE);
884 prop = icalcomponent_get_first_property (
885 icalcomp, ICAL_ATTENDEE_PROPERTY);
887 return (prop != NULL);
891 * e_cal_util_component_has_recurrences:
892 * @icalcomp: An #icalcomponent.
894 * Checks if an #icalcomponent has recurrence dates or rules.
896 * Returns: TRUE if there are recurrence dates/rules, FALSE if not.
899 e_cal_util_component_has_recurrences (icalcomponent *icalcomp)
901 g_return_val_if_fail (icalcomp != NULL, FALSE);
903 return e_cal_util_component_has_rdates (icalcomp) ||
904 e_cal_util_component_has_rrules (icalcomp);
908 * e_cal_util_component_has_rdates:
909 * @icalcomp: An #icalcomponent.
911 * Checks if an #icalcomponent has recurrence dates.
913 * Returns: TRUE if there are recurrence dates, FALSE if not.
916 e_cal_util_component_has_rdates (icalcomponent *icalcomp)
920 g_return_val_if_fail (icalcomp != NULL, FALSE);
922 prop = icalcomponent_get_first_property (
923 icalcomp, ICAL_RDATE_PROPERTY);
925 return (prop != NULL);
929 * e_cal_util_component_has_rrules:
930 * @icalcomp: An #icalcomponent.
932 * Checks if an #icalcomponent has recurrence rules.
934 * Returns: TRUE if there are recurrence rules, FALSE if not.
937 e_cal_util_component_has_rrules (icalcomponent *icalcomp)
941 g_return_val_if_fail (icalcomp != NULL, FALSE);
943 prop = icalcomponent_get_first_property (
944 icalcomp, ICAL_RRULE_PROPERTY);
946 return (prop != NULL);
950 * e_cal_util_event_dates_match:
951 * @icalcomp1: An #icalcomponent.
952 * @icalcomp2: An #icalcomponent.
954 * Compare the dates of two #icalcomponent's to check if they match.
956 * Returns: TRUE if the dates of both components match, FALSE otherwise.
959 e_cal_util_event_dates_match (icalcomponent *icalcomp1,
960 icalcomponent *icalcomp2)
962 struct icaltimetype c1_dtstart, c1_dtend, c2_dtstart, c2_dtend;
964 g_return_val_if_fail (icalcomp1 != NULL, FALSE);
965 g_return_val_if_fail (icalcomp2 != NULL, FALSE);
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);
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)))
977 if (icaltime_compare (c1_dtstart, c2_dtstart))
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)))
985 if (icaltime_compare (c1_dtend, c2_dtend))
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))))
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))))
1005 /* Individual instances management */
1007 struct instance_data {
1013 check_instance (icalcomponent *comp,
1014 struct icaltime_span *span,
1017 struct instance_data *instance = data;
1019 if (span->start == instance->start)
1020 instance->found = TRUE;
1024 * e_cal_util_construct_instance:
1025 * @icalcomp: A recurring #icalcomponent
1026 * @rid: The RECURRENCE-ID to construct a component for
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.
1031 * Returns: the instance, or %NULL.
1034 e_cal_util_construct_instance (icalcomponent *icalcomp,
1035 struct icaltimetype rid)
1037 struct instance_data instance;
1038 struct icaltimetype start, end;
1040 g_return_val_if_fail (icalcomp != NULL, NULL);
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))
1047 /* Make sure the specified instance really exists */
1048 start = icaltime_convert_to_zone (rid, icaltimezone_get_utc_timezone ());
1050 icaltime_adjust (&end, 0, 0, 0, 1);
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)
1059 /* Make the instance */
1060 icalcomp = icalcomponent_new_clone (icalcomp);
1061 icalcomponent_set_recurrenceid (icalcomp, rid);
1066 static inline gboolean
1067 time_matches_rid (struct icaltimetype itt,
1068 struct icaltimetype rid,
1073 compare = icaltime_compare (itt, rid);
1076 else if (compare < 0 && (mod & E_CAL_OBJ_MOD_THIS_AND_PRIOR))
1078 else if (compare > 0 && (mod & E_CAL_OBJ_MOD_THIS_AND_FUTURE))
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
1090 * Removes one or more instances from @comp according to @rid and @mod.
1092 * FIXME: should probably have a return value indicating whether @icalcomp
1093 * still has any instances
1096 e_cal_util_remove_instances (icalcomponent *icalcomp,
1097 struct icaltimetype rid,
1101 struct icaltimetype itt, recur;
1102 struct icalrecurrencetype rule;
1103 icalrecur_iterator *iter;
1105 g_return_if_fail (icalcomp != NULL);
1106 g_return_if_fail (mod != E_CAL_OBJ_MOD_ALL);
1108 /* First remove RDATEs and EXDATEs in the indicated range. */
1109 for (prop = icalcomponent_get_first_property (icalcomp, ICAL_RDATE_PROPERTY);
1111 prop = icalcomponent_get_next_property (icalcomp, ICAL_RDATE_PROPERTY)) {
1112 struct icaldatetimeperiodtype period;
1114 period = icalproperty_get_rdate (prop);
1115 if (time_matches_rid (period.time, rid, mod))
1116 icalcomponent_remove_property (icalcomp, prop);
1118 for (prop = icalcomponent_get_first_property (icalcomp, ICAL_EXDATE_PROPERTY);
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);
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);
1133 /* Otherwise, iterate through RRULEs */
1134 /* FIXME: this may generate duplicate EXRULEs */
1135 for (prop = icalcomponent_get_first_property (icalcomp, ICAL_RRULE_PROPERTY);
1137 prop = icalcomponent_get_next_property (icalcomp, ICAL_RRULE_PROPERTY)) {
1138 rule = icalproperty_get_rrule (prop);
1140 iter = icalrecur_iterator_new (rule, rid);
1141 recur = icalrecur_iterator_next (iter);
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
1148 if (!icaltime_is_null_time (recur)) {
1151 icaltime_adjust (&rule.until, 0, 0, 0, -1);
1152 icalproperty_set_rrule (prop, rule);
1155 /* (If recur == rid, skip to the next occurrence) */
1156 if (icaltime_compare (recur, rid) == 0)
1157 recur = icalrecur_iterator_next (iter);
1159 /* If there is a recurrence after rid, add
1160 * an EXRULE to block instances up to rid.
1161 * Otherwise, just remove the RRULE.
1163 if (!icaltime_is_null_time (recur)) {
1165 /* iCalendar says we should just use rid
1166 * here, but Outlook/Exchange handle
1167 * UNTIL incorrectly.
1169 rule.until = icaltime_add (
1170 rid, icalcomponent_get_duration (icalcomp));
1171 prop = icalproperty_new_exrule (rule);
1172 icalcomponent_add_property (icalcomp, prop);
1174 icalcomponent_remove_property (icalcomp, prop);
1177 icalrecur_iterator_free (iter);
1182 * e_cal_util_get_system_timezone_location:
1184 * Fetches system timezone localtion string.
1186 * Returns: (transfer full): system timezone location string, %NULL on an error.
1191 e_cal_util_get_system_timezone_location (void)
1193 return e_cal_system_timezone_get_location ();
1197 * e_cal_util_get_system_timezone:
1199 * Fetches system timezone icaltimezone object.
1201 * The returned pointer is part of the built-in timezones and should not be freed.
1203 * Returns: (transfer none): The icaltimezone object of the system timezone, or %NULL on an error.
1208 e_cal_util_get_system_timezone (void)
1213 location = e_cal_system_timezone_get_location ();
1214 g_return_val_if_fail (location != NULL, NULL);
1216 zone = icaltimezone_get_builtin_timezone (location);
1224 componenttime_to_utc_timet (const ECalComponentDateTime *dt_time,
1225 ECalRecurResolveTimezoneFn tz_cb,
1226 gpointer tz_cb_data,
1227 const icaltimezone *default_zone)
1230 icaltimezone *zone = NULL;
1232 g_return_val_if_fail (dt_time != NULL, -1);
1234 if (dt_time->value) {
1236 zone = tz_cb (dt_time->tzid, tz_cb_data);
1238 // zone = icaltimezone_get_utc_timezone ();
1239 timet = icaltime_as_timet_with_zone (
1240 *dt_time->value, zone ? zone : default_zone);
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
1256 * Find out when the component starts and stops, being careful about
1262 e_cal_util_get_component_occur_times (ECalComponent *comp,
1265 ECalRecurResolveTimezoneFn tz_cb,
1266 gpointer tz_cb_data,
1267 const icaltimezone *default_timezone,
1268 icalcomponent_kind kind)
1270 struct icalrecurrencetype ir;
1271 ECalComponentDateTime dt_start, dt_end;
1273 g_return_if_fail (comp != NULL);
1274 g_return_if_fail (start != NULL);
1275 g_return_if_fail (end != NULL);
1277 e_cal_recur_ensure_end_dates (comp, FALSE, tz_cb, tz_cb_data);
1279 /* Get dtstart of the component and convert it to UTC */
1280 e_cal_component_get_dtstart (comp, &dt_start);
1282 if ((*start = componenttime_to_utc_timet (&dt_start, tz_cb, tz_cb_data, default_timezone)) == -1)
1285 e_cal_component_free_datetime (&dt_start);
1287 /* find out end date of component */
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;
1296 e_cal_component_get_completed (comp, &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);
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,
1310 e_cal_component_free_datetime (&dt_due);
1312 max_time = MAX (completed_time, due_time);
1318 /* ALARMS, EVENTS: DTEND and reccurences */
1320 if (e_cal_component_has_recurrences (comp)) {
1321 GSList *rrules = NULL;
1322 GSList *exrules = NULL;
1324 GSList *rdates = NULL;
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);
1331 for (elem = rrules; elem; elem = elem->next) {
1333 icaltimezone *utc_zone;
1334 icalproperty *prop = elem->data;
1335 ir = icalproperty_get_rrule (prop);
1337 utc_zone = icaltimezone_get_utc_timezone ();
1338 rule_end = e_cal_recur_obtain_enddate (
1339 &ir, prop, utc_zone, TRUE);
1341 if (rule_end == -1) /* repeats forever */
1343 else if (rule_end > *end) /* new maximum */
1347 /* Do the EXRULEs. */
1348 for (elem = exrules; elem; elem = elem->next) {
1349 icalproperty *prop = elem->data;
1351 icaltimezone *utc_zone;
1352 ir = icalproperty_get_exrule (prop);
1354 utc_zone = icaltimezone_get_utc_timezone ();
1355 rule_end = e_cal_recur_obtain_enddate (
1356 &ir, prop, utc_zone, TRUE);
1358 if (rule_end == -1) /* repeats forever */
1360 else if (rule_end > *end)
1365 for (elem = rdates; elem; elem = elem->next) {
1366 ECalComponentPeriod *p = elem->data;
1367 time_t rdate_end = _TIME_MAX;
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. */
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));
1378 rdate_end = icaltime_as_timet (p->u.end);
1380 if (rdate_end == -1) /* repeats forever */
1382 else if (rdate_end > *end)
1386 e_cal_component_free_period_list (rdates);
1389 /* Get dtend of the component and convert it to UTC */
1390 e_cal_component_get_dtend (comp, &dt_end);
1395 dtend_time = componenttime_to_utc_timet (
1396 &dt_end, tz_cb, tz_cb_data, default_timezone);
1398 if (dtend_time == -1 || (dtend_time > *end))
1402 e_cal_component_free_datetime (&dt_end);