1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Evolution calendar recurrence rule functions
4  *
5  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.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 Lesser 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 library. If not, see <http://www.gnu.org/licenses/>.
18  *
19  * Authors: Damon Chaplin <damon@ximian.com>
20  */
21 
22 #include "evolution-data-server-config.h"
23 
24 #include <stdlib.h>
25 #include <string.h>
26 #include <glib/gi18n-lib.h>
27 
28 #include "e-cal-recur.h"
29 #include "e-cal-time-util.h"
30 #include "e-cal-client.h"
31 
32 static gint
e_timetype_compare(gconstpointer aa,gconstpointer bb)33 e_timetype_compare (gconstpointer aa,
34 		    gconstpointer bb)
35 {
36 	ICalTime *tta = (ICalTime *) aa;
37 	ICalTime *ttb = (ICalTime *) bb;
38 
39 	if (!tta || !ttb) {
40 		if (tta == ttb)
41 			return 0;
42 		if (!tta)
43 			return -1;
44 		return 1;
45 	}
46 
47 	if (i_cal_time_is_date (tta) || i_cal_time_is_date (ttb))
48 		return i_cal_time_compare_date_only (tta, ttb);
49 
50 	return i_cal_time_compare (tta, ttb);
51 }
52 
53 static gint
e_timetype_compare_without_date(gconstpointer a,gconstpointer b)54 e_timetype_compare_without_date (gconstpointer a,
55 				 gconstpointer b)
56 {
57 	ICalTime *tta = (ICalTime *) a;
58 	ICalTime *ttb = (ICalTime *) b;
59 
60 	if (!tta || !ttb) {
61 		if (tta == ttb)
62 			return 0;
63 		if (!tta)
64 			return -1;
65 		return 1;
66 	}
67 
68 	return i_cal_time_compare (tta, ttb);
69 }
70 
71 static guint
e_timetype_hash(gconstpointer v)72 e_timetype_hash (gconstpointer v)
73 {
74 	const ICalTime *ttv = v;
75 	ICalTime *tt;
76 	guint value;
77 
78 	if (!ttv)
79 		return 0;
80 
81 	tt = i_cal_time_convert_to_zone (ttv, i_cal_timezone_get_utc_timezone ());
82 
83 	value = (i_cal_time_get_year (tt) * 10000) +
84 		(i_cal_time_get_month (tt) * 100) +
85 		i_cal_time_get_day (tt);
86 
87 	g_clear_object (&tt);
88 
89 	return value;
90 }
91 
92 typedef struct _EInstanceTime {
93 	ICalTime *tt;
94 	gboolean duration_set;
95 	gint duration_days;
96 	gint duration_seconds;
97 } EInstanceTime;
98 
99 static EInstanceTime *
e_instance_time_new(const ICalTime * tt,const ICalDuration * pduration)100 e_instance_time_new (const ICalTime *tt,
101 		     const ICalDuration *pduration)
102 {
103 	EInstanceTime *it;
104 	ICalDuration *duration = (ICalDuration *) pduration;
105 
106 	if (!tt)
107 		return NULL;
108 
109 	it = g_slice_new0 (EInstanceTime);
110 
111 	it->tt = i_cal_time_clone (tt);
112 	it->duration_set = duration && !i_cal_duration_is_null_duration (duration);
113 
114 	if (it->duration_set) {
115 		gint64 dur;
116 
117 		g_warn_if_fail (!i_cal_duration_is_neg (duration));
118 
119 		dur = (gint64) (i_cal_duration_get_weeks (duration) * 7 + i_cal_duration_get_days (duration)) * (24 * 60 * 60) +
120 			(i_cal_duration_get_hours (duration) * 60 * 60) +
121 			(i_cal_duration_get_minutes (duration) * 60) +
122 			i_cal_duration_get_seconds (duration);
123 
124 		it->duration_days = dur / (24 * 60 * 60);
125 		it->duration_seconds = dur % (24 * 60 * 60);
126 	}
127 
128 	return it;
129 }
130 
131 static void
e_instance_time_free(gpointer ptr)132 e_instance_time_free (gpointer ptr)
133 {
134 	EInstanceTime *it = ptr;
135 
136 	if (it) {
137 		g_clear_object (&it->tt);
138 		g_slice_free (EInstanceTime, it);
139 	}
140 }
141 
142 static gint
e_instance_time_compare(gconstpointer a,gconstpointer b)143 e_instance_time_compare (gconstpointer a,
144 			 gconstpointer b)
145 {
146 	const EInstanceTime *ait = a;
147 	const EInstanceTime *bit = b;
148 
149 	if (!ait || !bit) {
150 		if (ait == bit)
151 			return 0;
152 
153 		if (!ait)
154 			return -1;
155 
156 		return 1;
157 	}
158 
159 	return e_timetype_compare (ait->tt, bit->tt);
160 }
161 
162 static guint
e_instance_time_hash(gconstpointer v)163 e_instance_time_hash (gconstpointer v)
164 {
165 	const EInstanceTime *it = v;
166 
167 	if (!it)
168 		return 0;
169 
170 	return e_timetype_hash (it->tt);
171 }
172 
173 static gboolean
e_instance_time_equal(gconstpointer v1,gconstpointer v2)174 e_instance_time_equal (gconstpointer v1,
175 		       gconstpointer v2)
176 {
177 	return e_instance_time_compare (v1, v2) == 0;
178 }
179 
180 static gint
e_instance_time_compare_ptr_array(gconstpointer a,gconstpointer b)181 e_instance_time_compare_ptr_array (gconstpointer a,
182 				   gconstpointer b)
183 {
184 	gconstpointer *aa = (gconstpointer *) a, *bb = (gconstpointer *) b;
185 
186 	return e_instance_time_compare (*aa, *bb);
187 }
188 
189 static gboolean
ensure_timezone(ICalComponent * comp,ICalTime * tt,ICalPropertyKind prop_kind,ICalProperty * prop,ECalRecurResolveTimezoneCb get_tz_callback,gpointer get_tz_callback_user_data,ICalTimezone * default_timezone,GHashTable ** pcached_zones,GCancellable * cancellable,GError ** error)190 ensure_timezone (ICalComponent *comp,
191 		 ICalTime *tt,
192 		 ICalPropertyKind prop_kind,
193 		 ICalProperty *prop,
194 		 ECalRecurResolveTimezoneCb get_tz_callback,
195 		 gpointer get_tz_callback_user_data,
196 		 ICalTimezone *default_timezone,
197 		 GHashTable **pcached_zones,
198 		 GCancellable *cancellable,
199 		 GError **error)
200 {
201 	ICalParameter *param;
202 	ICalTimezone *zone;
203 	const gchar *tzid;
204 	GError *local_error = NULL;
205 
206 	if (g_cancellable_set_error_if_cancelled (cancellable, error))
207 		return FALSE;
208 
209 	if (!tt)
210 		return TRUE;
211 
212 	/* Do not trust the 'zone' set on the structure, as it can come from
213 	   a different icalcomponent and cause use-after-free. */
214 	zone = i_cal_time_get_timezone (tt);
215 	if (zone != i_cal_timezone_get_utc_timezone ())
216 		i_cal_time_set_timezone (tt, NULL);
217 
218 	if (i_cal_time_is_utc (tt))
219 		return TRUE;
220 
221 	i_cal_time_set_timezone (tt, default_timezone);
222 
223 	if (i_cal_time_is_date (tt))
224 		return TRUE;
225 
226 	if (!prop)
227 		prop = i_cal_component_get_first_property (comp, prop_kind);
228 	else
229 		g_object_ref (prop);
230 
231 	/* DTEND can be computed from DTSTART and DURATION, thus use TZID from DTSTART,
232 	   in case DTEND is not present. */
233 	if (!prop && prop_kind == I_CAL_DTEND_PROPERTY)
234 		prop = i_cal_component_get_first_property (comp, I_CAL_DTSTART_PROPERTY);
235 
236 	if (!prop)
237 		return TRUE;
238 
239 	param = i_cal_property_get_first_parameter (prop, I_CAL_TZID_PARAMETER);
240 	if (!param) {
241 		g_object_unref (prop);
242 		return TRUE;
243 	}
244 
245 	tzid = i_cal_parameter_get_tzid (param);
246 	if (!tzid || !*tzid) {
247 		g_object_unref (param);
248 		g_object_unref (prop);
249 		return TRUE;
250 	}
251 
252 	if (get_tz_callback) {
253 		zone = NULL;
254 
255 		if (*pcached_zones)
256 			zone = g_hash_table_lookup (*pcached_zones, tzid);
257 
258 		if (!zone) {
259 			zone = get_tz_callback (tzid, get_tz_callback_user_data, cancellable, &local_error);
260 			if (zone) {
261 				if (!*pcached_zones)
262 					*pcached_zones = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
263 
264 				g_hash_table_insert (*pcached_zones, g_strdup (tzid), g_object_ref (zone));
265 			}
266 		}
267 
268 		i_cal_time_set_timezone (tt, zone);
269 	} else
270 		i_cal_time_set_timezone (tt, NULL);
271 
272 	zone = i_cal_time_get_timezone (tt);
273 	if (!zone)
274 		i_cal_time_set_timezone (tt, default_timezone);
275 
276 	g_object_unref (param);
277 	g_object_unref (prop);
278 
279 	/* Timezone not found is not a fatal error */
280 	if (g_error_matches (local_error, E_CAL_CLIENT_ERROR, E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND))
281 		g_clear_error (&local_error);
282 
283 	if (local_error) {
284 		g_propagate_error (error, local_error);
285 		return FALSE;
286 	}
287 
288 	return TRUE;
289 }
290 
291 static void
apply_duration(ICalTime * tt,ICalDuration * duration)292 apply_duration (ICalTime *tt,
293 		ICalDuration *duration)
294 {
295 	gint64 days, seconds;
296 
297 	g_return_if_fail (tt != NULL);
298 	g_return_if_fail (duration != NULL);
299 
300 	if (i_cal_duration_is_null_duration (duration))
301 		return;
302 
303 	days = (gint64) i_cal_duration_get_days (duration) + 7 * ((gint64) i_cal_duration_get_weeks (duration));
304 	seconds = ((gint64) i_cal_duration_get_hours (duration) * 60 * 60) +
305 		  ((gint64) i_cal_duration_get_minutes (duration) * 60) +
306 		  i_cal_duration_get_seconds (duration);
307 	days += seconds / (24 * 60 * 60);
308 	seconds = seconds % (24 * 60 * 60);
309 
310 	if (seconds != 0)
311 		i_cal_time_set_is_date (tt, FALSE);
312 
313 	i_cal_time_adjust (tt,
314 		(i_cal_duration_is_neg (duration) ? -1 : 1) * ((gint) days),
315 		0, 0,
316 		(i_cal_duration_is_neg (duration) ? -1 : 1) * ((gint) seconds));
317 }
318 
319 static gboolean
intersects_interval(const ICalTime * tt,const ICalDuration * duration,gint default_duration_days,gint default_duration_seconds,const ICalTime * interval_start,const ICalTime * interval_end)320 intersects_interval (const ICalTime *tt,
321 		     const ICalDuration *duration,
322 		     gint default_duration_days,
323 		     gint default_duration_seconds,
324 		     const ICalTime *interval_start,
325 		     const ICalTime *interval_end)
326 {
327 	ICalTime *ttstart, *ttend;
328 	gboolean res;
329 
330 	if (!tt || i_cal_time_is_null_time (tt) || !interval_start || !interval_end)
331 		return FALSE;
332 
333 	ttstart = i_cal_time_clone (tt);
334 	ttend = i_cal_time_clone (ttstart);
335 
336 	if (duration && !i_cal_duration_is_null_duration ((ICalDuration *) duration)) {
337 		apply_duration (ttend, (ICalDuration *) duration);
338 	} else if (default_duration_days || default_duration_seconds) {
339 		if (default_duration_seconds != 0) {
340 			i_cal_time_set_is_date (ttend, FALSE);
341 		}
342 
343 		i_cal_time_adjust (ttend, default_duration_days, 0, 0, default_duration_seconds);
344 	}
345 
346 	res = e_timetype_compare_without_date (ttstart, interval_end) < 0 &&
347 	      e_timetype_compare_without_date (interval_start, ttend) < 0;
348 
349 	g_clear_object (&ttstart);
350 	g_clear_object (&ttend);
351 
352 	return res;
353 }
354 
355 /**
356  * e_cal_recur_generate_instances_sync:
357  * @icalcomp: an #ICalComponent
358  * @interval_start: an interval start, for which generate instances
359  * @interval_end: an interval end, for which generate instances
360  * @callback: (scope call): a callback to be called for each instance
361  * @callback_user_data: (closure callback): user data for @callback
362  * @get_tz_callback: (scope call): a callback to call when resolving timezone
363  * @get_tz_callback_user_data: (closure get_tz_callback): user data for @get_tz_callback
364  * @default_timezone: a default #ICalTimezone
365  * @cancellable: a #GCancellable; can be %NULL
366  * @error: a #GError to set an error, if any
367  *
368  * Calls the given callback function for each occurrence of the event that
369  * intersects the range between the given @start and @end times (the end time is
370  * not included). Note that the occurrences may start before the given start
371  * time.
372  *
373  * If the callback routine returns FALSE the occurrence generation stops.
374  *
375  * The start and end times are required valid times, start before end time.
376  *
377  * The @get_tz_callback is used to resolve references to timezones. It is passed
378  * a TZID and should return the ICalTimezone * corresponding to that TZID. We need to
379  * do this as we access timezones in different ways on the client & server.
380  *
381  * The default_timezone argument is used for DTSTART or DTEND properties that
382  * are DATE values or do not have a TZID (i.e. floating times).
383  *
384  * Returns: %TRUE if successful (when all instances had been returned), %FALSE otherwise.
385  *
386  * Since: 3.20
387  **/
388 gboolean
e_cal_recur_generate_instances_sync(ICalComponent * icalcomp,ICalTime * interval_start,ICalTime * interval_end,ECalRecurInstanceCb callback,gpointer callback_user_data,ECalRecurResolveTimezoneCb get_tz_callback,gpointer get_tz_callback_user_data,ICalTimezone * default_timezone,GCancellable * cancellable,GError ** error)389 e_cal_recur_generate_instances_sync (ICalComponent *icalcomp,
390 				     ICalTime *interval_start,
391 				     ICalTime *interval_end,
392 				     ECalRecurInstanceCb callback,
393 				     gpointer callback_user_data,
394 				     ECalRecurResolveTimezoneCb get_tz_callback,
395 				     gpointer get_tz_callback_user_data,
396 				     ICalTimezone *default_timezone,
397 				     GCancellable *cancellable,
398 				     GError **error)
399 {
400 	ICalTime *dtstart, *dtend, *next;
401 	ICalProperty *prop;
402 	gint64 duration_days, duration_seconds;
403 	GHashTable *times, *cached_zones = NULL;
404 	gboolean success = TRUE;
405 
406 	g_return_val_if_fail (icalcomp != NULL, FALSE);
407 	g_return_val_if_fail (callback != NULL, FALSE);
408 	g_return_val_if_fail (interval_start != NULL, FALSE);
409 	g_return_val_if_fail (interval_end != NULL, FALSE);
410 	g_return_val_if_fail (i_cal_time_compare (interval_start, interval_end) < 0, FALSE);
411 
412 	if (g_cancellable_set_error_if_cancelled (cancellable, error))
413 		return FALSE;
414 
415 	times = g_hash_table_new_full (e_instance_time_hash, e_instance_time_equal, e_instance_time_free, NULL);
416 
417 	dtstart = i_cal_component_get_dtstart (icalcomp);
418 	success = ensure_timezone (icalcomp, dtstart, I_CAL_DTSTART_PROPERTY, NULL,
419 		get_tz_callback, get_tz_callback_user_data, default_timezone, &cached_zones, cancellable, error);
420 
421 	duration_seconds = 0;
422 	dtend = i_cal_component_get_dtend (icalcomp);
423 
424 	if (!dtend || i_cal_time_is_null_time (dtend)) {
425 		g_clear_object (&dtend);
426 
427 		dtend = i_cal_component_get_due (icalcomp);
428 		if (!dtend || i_cal_time_is_null_time (dtend)) {
429 			ICalDuration *comp_duration;
430 
431 			g_clear_object (&dtend);
432 
433 			comp_duration = i_cal_component_get_duration (icalcomp);
434 
435 			if (comp_duration && !i_cal_duration_is_null_duration (comp_duration)) {
436 				dtend = i_cal_time_clone (dtstart);
437 
438 				apply_duration (dtend, comp_duration);
439 			}
440 
441 			g_clear_object (&comp_duration);
442 		}
443 
444 		/* This can happen for tasks without start date */
445 		if (dtend && i_cal_time_is_null_time (dtstart) && !i_cal_time_is_null_time (dtend)) {
446 			g_clear_object (&dtstart);
447 			dtstart = i_cal_time_clone (dtend);
448 		}
449 
450 		/* If there is no DTEND, then if DTSTART is a DATE-TIME value
451 		 * we use the same time (so we have a single point in time).
452 		 * If DTSTART is a DATE value we add 1 day. */
453 		if (!dtend || i_cal_time_is_null_time (dtend)) {
454 			g_clear_object (&dtend);
455 			dtend = i_cal_time_clone (dtstart);
456 
457 			if (i_cal_time_is_date (dtend))
458 				i_cal_time_adjust (dtend, 1, 0, 0, 0);
459 		}
460 	} else {
461 		/* This can happen for tasks without start date */
462 		if (dtend && i_cal_time_is_null_time (dtstart) && !i_cal_time_is_null_time (dtend)) {
463 			g_clear_object (&dtstart);
464 			dtstart = i_cal_time_clone (dtend);
465 		}
466 
467 		/* If both DTSTART and DTEND are DATE values, and they are the
468 		 * same day, we add 1 day to DTEND. This means that most
469 		 * events created with the old Evolution behavior will still
470 		 * work OK. I'm not sure what Outlook does in this case. */
471 		if (i_cal_time_is_date (dtstart) && i_cal_time_is_date (dtend)) {
472 			if (i_cal_time_compare_date_only (dtstart, dtend) == 0) {
473 				i_cal_time_adjust (dtend, 1, 0, 0, 0);
474 			}
475 		}
476 	}
477 
478 	if (success && dtend && !i_cal_time_is_null_time (dtend)) {
479 		ICalTimezone *dtstart_zone, *dtend_zone;
480 
481 		success = ensure_timezone (icalcomp, dtend, I_CAL_DTEND_PROPERTY, NULL,
482 			get_tz_callback, get_tz_callback_user_data, default_timezone, &cached_zones, cancellable, error);
483 
484 		dtstart_zone = i_cal_time_get_timezone (dtstart);
485 		dtend_zone = i_cal_time_get_timezone (dtend);
486 
487 		duration_seconds = (gint64) i_cal_time_as_timet_with_zone (dtend, dtend_zone) -
488 			(gint64) i_cal_time_as_timet_with_zone (dtstart, dtstart_zone);
489 
490 		if (duration_seconds < 0)
491 			duration_seconds = 0;
492 	}
493 
494 	duration_days = duration_seconds / (24 * 60 * 60);
495 	duration_seconds = duration_seconds % (24 * 60 * 60);
496 
497 	if (success && intersects_interval (dtstart, NULL, duration_days, duration_seconds, interval_start, interval_end)) {
498 		g_hash_table_insert (times, e_instance_time_new (dtstart, NULL), NULL);
499 	}
500 
501 	/* If this is a detached instance, then use only the DTSTART value */
502 	if (success && !e_cal_util_component_has_property (icalcomp, I_CAL_RECURRENCEID_PROPERTY)) {
503 		ICalTimezone *dtstart_zone = i_cal_time_get_timezone (dtstart);
504 
505 		for (prop = i_cal_component_get_first_property (icalcomp, I_CAL_RRULE_PROPERTY);
506 		     prop && success;
507 		     g_object_unref (prop), prop = i_cal_component_get_next_property (icalcomp, I_CAL_RRULE_PROPERTY)) {
508 			ICalRecurrence *rrule = i_cal_property_get_rrule (prop);
509 			ICalRecurIterator *riter;
510 			ICalTime *rrule_until;
511 
512 			rrule_until = i_cal_recurrence_get_until (rrule);
513 
514 			if (rrule_until && !i_cal_time_is_null_time (rrule_until)) {
515 				success = ensure_timezone (icalcomp, rrule_until, 0, prop,
516 					get_tz_callback, get_tz_callback_user_data, dtstart_zone,
517 					&cached_zones, cancellable, error);
518 				if (!success) {
519 					g_clear_object (&rrule_until);
520 					g_clear_object (&rrule);
521 					break;
522 				}
523 			}
524 
525 			if (rrule_until && !i_cal_time_is_null_time (rrule_until) &&
526 			    i_cal_time_is_date (rrule_until) && !i_cal_time_is_date (dtstart)) {
527 				i_cal_time_adjust (rrule_until, 1, 0, 0, 0);
528 				i_cal_time_set_is_date (rrule_until, FALSE);
529 				i_cal_time_set_time (rrule_until, 0, 0, 0);
530 
531 				if (!i_cal_time_get_timezone (rrule_until) && !i_cal_time_is_utc (rrule_until))
532 					i_cal_time_set_timezone (rrule_until, dtstart_zone);
533 			}
534 
535 			if (rrule_until && !i_cal_time_is_null_time (rrule_until))
536 				i_cal_recurrence_set_until (rrule, rrule_until);
537 			g_clear_object (&rrule_until);
538 
539 			riter = i_cal_recur_iterator_new (rrule, dtstart);
540 			if (riter) {
541 				ICalTime *prev = NULL;
542 
543 				for (next = i_cal_recur_iterator_next (riter);
544 				     next && !i_cal_time_is_null_time (next) && i_cal_time_compare (next, interval_end) <= 0;
545 				     g_object_unref (next), next = i_cal_recur_iterator_next (riter)) {
546 					if (prev && !i_cal_time_is_null_time (prev) &&
547 					    i_cal_time_compare (next, prev) == 0)
548 						break;
549 
550 					g_clear_object (&prev);
551 					prev = i_cal_time_clone (next);
552 
553 					if (intersects_interval (next, NULL, duration_days, duration_seconds, interval_start, interval_end)) {
554 						g_hash_table_insert (times, e_instance_time_new (next, NULL), NULL);
555 					}
556 				}
557 
558 				g_clear_object (&riter);
559 				g_clear_object (&prev);
560 				g_clear_object (&next);
561 			}
562 
563 			g_clear_object (&rrule);
564 		}
565 
566 		g_clear_object (&prop);
567 
568 		for (prop = i_cal_component_get_first_property (icalcomp, I_CAL_RDATE_PROPERTY);
569 		     prop && success;
570 		     g_object_unref (prop), prop = i_cal_component_get_next_property (icalcomp, I_CAL_RDATE_PROPERTY)) {
571 			ICalDatetimeperiod *rdate = i_cal_property_get_rdate (prop);
572 			ICalTime *tt = NULL;
573 			ICalDuration *duration = i_cal_duration_new_null_duration ();
574 
575 			tt = i_cal_datetimeperiod_get_time (rdate);
576 			if (!tt || i_cal_time_is_null_time (tt)) {
577 				ICalPeriod *period;
578 
579 				g_clear_object (&tt);
580 				period = i_cal_datetimeperiod_get_period (rdate);
581 
582 				if (period && !i_cal_period_is_null_period (period)) {
583 					ICalTime *pend;
584 
585 					tt = i_cal_period_get_start (period);
586 					pend = i_cal_period_get_end (period);
587 
588 					if (pend && !i_cal_time_is_null_time (pend)) {
589 						time_t diff;
590 
591 						diff = i_cal_time_as_timet (pend) - i_cal_time_as_timet (tt);
592 						if (diff < 0) {
593 							i_cal_duration_set_is_neg (duration, TRUE);
594 							diff = diff * (-1);
595 						}
596 
597 						#define set_and_dec(member, num) G_STMT_START { \
598 							i_cal_duration_set_ ## member (duration, diff / (num)); \
599 							diff = diff % (num); \
600 							} G_STMT_END
601 						set_and_dec (weeks, 7 * 24 * 60 * 60);
602 						set_and_dec (days, 24 * 60 * 60);
603 						set_and_dec (hours, 60 * 60);
604 						set_and_dec (minutes, 60);
605 						set_and_dec (seconds, 1);
606 						#undef set_and_dec
607 
608 						g_warn_if_fail (diff == 0);
609 					} else {
610 						g_clear_object (&duration);
611 						duration = i_cal_period_get_duration (period);
612 					}
613 
614 					if (duration && !i_cal_duration_is_null_duration (duration) &&
615 					    tt && !i_cal_time_is_null_time (tt)) {
616 						if (i_cal_duration_is_neg (duration)) {
617 							apply_duration (tt, duration);
618 
619 							i_cal_duration_set_is_neg (duration, FALSE);
620 						}
621 					}
622 
623 					g_clear_object (&pend);
624 				}
625 
626 				g_clear_object (&period);
627 			}
628 
629 			if (tt && !i_cal_time_is_null_time (tt)) {
630 				success = ensure_timezone (icalcomp, tt, 0, prop,
631 					get_tz_callback, get_tz_callback_user_data, dtstart_zone,
632 					&cached_zones, cancellable, error);
633 				if (!success) {
634 					g_clear_object (&duration);
635 					g_clear_object (&rdate);
636 					g_clear_object (&tt);
637 					break;
638 				}
639 
640 				if (intersects_interval (tt, duration, duration_days, duration_seconds, interval_start, interval_end)) {
641 					g_hash_table_insert (times, e_instance_time_new (tt, duration), NULL);
642 				}
643 			}
644 
645 			g_clear_object (&duration);
646 			g_clear_object (&rdate);
647 			g_clear_object (&tt);
648 		}
649 
650 		g_clear_object (&prop);
651 
652 		for (prop = i_cal_component_get_first_property (icalcomp, I_CAL_EXRULE_PROPERTY);
653 		     prop && success;
654 		     g_object_unref (prop), prop = i_cal_component_get_next_property (icalcomp, I_CAL_EXRULE_PROPERTY)) {
655 			ICalRecurrence *exrule = i_cal_property_get_exrule (prop);
656 			ICalRecurIterator *riter;
657 			ICalTime *exrule_until;
658 
659 			exrule_until = i_cal_recurrence_get_until (exrule);
660 
661 			if (exrule_until && !i_cal_time_is_null_time (exrule_until)) {
662 				success = ensure_timezone (icalcomp, exrule_until, 0, prop,
663 					get_tz_callback, get_tz_callback_user_data, dtstart_zone,
664 					&cached_zones, cancellable, error);
665 				if (!success) {
666 					g_clear_object (&exrule_until);
667 					g_clear_object (&exrule);
668 					break;
669 				}
670 			}
671 
672 			if (exrule_until && !i_cal_time_is_null_time (exrule_until) &&
673 			    i_cal_time_is_date (exrule_until) && !i_cal_time_is_date (dtstart)) {
674 				i_cal_time_adjust (exrule_until, 1, 0, 0, 0);
675 				i_cal_time_set_is_date (exrule_until, FALSE);
676 				i_cal_time_set_time (exrule_until, 0, 0, 0);
677 
678 				if (!i_cal_time_get_timezone (exrule_until) && !i_cal_time_is_utc (exrule_until))
679 					i_cal_time_set_timezone (exrule_until, dtstart_zone);
680 			}
681 
682 			if (exrule_until && !i_cal_time_is_null_time (exrule_until))
683 				i_cal_recurrence_set_until (exrule, exrule_until);
684 			g_clear_object (&exrule_until);
685 
686 			riter = i_cal_recur_iterator_new (exrule, dtstart);
687 			if (riter) {
688 				ICalTime *prev = NULL;
689 
690 				for (next = i_cal_recur_iterator_next (riter);
691 				     next && !i_cal_time_is_null_time (next) && i_cal_time_compare (next, interval_end) <= 0;
692 				     g_object_unref (next), next = i_cal_recur_iterator_next (riter)) {
693 					if (prev && !i_cal_time_is_null_time (prev) &&
694 					    i_cal_time_compare (next, prev) == 0)
695 						break;
696 					g_clear_object (&prev);
697 					prev = i_cal_time_clone (next);
698 
699 					if (intersects_interval (next, NULL, duration_days, duration_seconds, interval_start, interval_end)) {
700 						EInstanceTime it;
701 
702 						it.tt = next;
703 						it.duration_set = FALSE;
704 
705 						g_hash_table_remove (times, &it);
706 					}
707 				}
708 
709 				g_clear_object (&riter);
710 				g_clear_object (&prev);
711 				g_clear_object (&next);
712 			}
713 
714 			g_clear_object (&exrule);
715 		}
716 
717 		g_clear_object (&prop);
718 
719 		for (prop = i_cal_component_get_first_property (icalcomp, I_CAL_EXDATE_PROPERTY);
720 		     prop && success;
721 		     g_object_unref (prop), prop = i_cal_component_get_next_property (icalcomp, I_CAL_EXDATE_PROPERTY)) {
722 			ICalTime *exdate = i_cal_property_get_exdate (prop);
723 
724 			if (!exdate || i_cal_time_is_null_time (exdate)) {
725 				g_clear_object (&exdate);
726 				continue;
727 			}
728 
729 			success = ensure_timezone (icalcomp, exdate, 0, prop,
730 				get_tz_callback, get_tz_callback_user_data, dtstart_zone,
731 				&cached_zones, cancellable, error);
732 			if (!success) {
733 				g_clear_object (&exdate);
734 				break;
735 			}
736 
737 			if (!i_cal_time_get_timezone (exdate) && !i_cal_time_is_utc (exdate))
738 				i_cal_time_set_timezone (exdate, dtstart_zone);
739 
740 			if (intersects_interval (exdate, NULL, duration_days, duration_seconds, interval_start, interval_end)) {
741 				EInstanceTime it;
742 
743 				it.tt = exdate;
744 				it.duration_set = FALSE;
745 
746 				g_hash_table_remove (times, &it);
747 			}
748 
749 			g_clear_object (&exdate);
750 		}
751 
752 		g_clear_object (&prop);
753 	}
754 
755 	if (success && g_hash_table_size (times) > 0) {
756 		GPtrArray *times_array;
757 		GHashTableIter hiter;
758 		gpointer key, value;
759 		gint ii;
760 
761 		times_array = g_ptr_array_sized_new (g_hash_table_size (times));
762 
763 		g_hash_table_iter_init (&hiter, times);
764 		while (g_hash_table_iter_next (&hiter, &key, &value)) {
765 			EInstanceTime *it = key;
766 
767 			if (!it)
768 				continue;
769 
770 			g_ptr_array_add (times_array, it);
771 		}
772 
773 		g_ptr_array_sort (times_array, e_instance_time_compare_ptr_array);
774 
775 		for (ii = 0; ii < times_array->len; ii++) {
776 			EInstanceTime *it = g_ptr_array_index (times_array, ii);
777 			ICalTime *endtt;
778 			gint dur_days = duration_days, dur_seconds = duration_seconds;
779 
780 			if (!it)
781 				continue;
782 
783 			endtt = i_cal_time_clone (it->tt);
784 
785 			if (it->duration_set) {
786 				dur_days = it->duration_days;
787 				dur_seconds = it->duration_seconds;
788 			}
789 
790 			if (dur_seconds != 0)
791 				i_cal_time_set_is_date (endtt, FALSE);
792 
793 			i_cal_time_adjust (endtt, dur_days, 0, 0, dur_seconds);
794 
795 			if (g_cancellable_set_error_if_cancelled (cancellable, error)) {
796 				success = FALSE;
797 				g_clear_object (&endtt);
798 				break;
799 			}
800 
801 			success = callback (icalcomp, it->tt, endtt, callback_user_data, cancellable, error);
802 
803 			g_clear_object (&endtt);
804 
805 			if (!success)
806 				break;
807 		}
808 
809 		g_ptr_array_free (times_array, TRUE);
810 	}
811 
812 	g_hash_table_destroy (times);
813 
814 	if (cached_zones)
815 		g_hash_table_destroy (cached_zones);
816 
817 	g_clear_object (&dtstart);
818 	g_clear_object (&dtend);
819 
820 	return success;
821 }
822 
823 /*
824  * Introduction to The Recurrence Generation Functions:
825  *
826  * Note: This is pretty complicated. See the iCalendar spec (RFC 2445) for
827  *       the specification of the recurrence rules and lots of examples
828  *	 (sections 4.3.10 & 4.8.5). We also want to support the older
829  *	 vCalendar spec, though this should be easy since it is basically a
830  *	 subset of iCalendar.
831  *
832  * o An iCalendar event can have any number of recurrence rules specifying
833  *   occurrences of the event, as well as dates & times of specific
834  *   occurrences. It can also have any number of recurrence rules and
835  *   specific dates & times specifying exceptions to the occurrences.
836  *   So we first merge all the occurrences generated, eliminating any
837  *   duplicates, then we generate all the exceptions and remove these to
838  *   form the final set of occurrences.
839  *
840  * o There are 7 frequencies of occurrences: YEARLY, MONTHLY, WEEKLY, DAILY,
841  *   HOURLY, MINUTELY & SECONDLY. The 'interval' property specifies the
842  *   multiples of the frequency which we step by. We generate a 'set' of
843  *   occurrences for each period defined by the frequency & interval.
844  *   So for a YEARLY frequency with an interval of 3, we generate a set of
845  *   occurrences for every 3rd year. We use complete years here -  any
846  *   generated occurrences that occur before the event's start (or after its
847  *   end) are just discarded.
848  *
849  * o There are 8 frequency modifiers: BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY,
850  *   BYDAY, BYHOUR, BYMINUTE & BYSECOND. These can either add extra occurrences
851  *   or filter out occurrences. For example 'FREQ=YEARLY;BYMONTH=1,2' produces
852  *   2 occurrences for each year rather than the default 1. And
853  *   'FREQ=DAILY;BYMONTH=1' filters out all occurrences except those in Jan.
854  *   If the modifier works on periods which are less than the recurrence
855  *   frequency, then extra occurrences are added, otherwise occurrences are
856  *   filtered. So we have 2 functions for each modifier - one to expand events
857  *   and the other to filter. We use a table of functions for each frequency
858  *   which points to the appropriate function to use for each modifier.
859  *
860  * o Any number of frequency modifiers can be used in a recurrence rule.
861  *   (Though the iCalendar spec says that BYWEEKNO can only be used in a YEARLY
862  *   rule, and some modifiers aren't appropriate for some frequencies - e.g.
863  *   BYMONTHDAY is not really useful in a WEEKLY frequency, and BYYEARDAY is
864  *   not useful in a MONTHLY or WEEKLY frequency).
865  *   The frequency modifiers are applied in the order given above. The first 5
866  *   modifier rules (BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY & BYDAY) all
867  *   produce the days on which the occurrences take place, and so we have to
868  *   compute some of these in parallel rather than sequentially, or we may end
869  *   up with too many days.
870  *
871  * o Note that some expansion functions may produce days which are invalid,
872  *   e.g. 31st September, 30th Feb. These invalid days are removed before the
873  *   BYHOUR, BYMINUTE & BYSECOND modifier functions are applied.
874  *
875  * o After the set of occurrences for the frequency interval are generated,
876  *   the BYSETPOS property is used to select which of the occurrences are
877  *   finally output. If BYSETPOS is not specified then all the occurrences are
878  *   output.
879  *
880  *
881  * FIXME: I think there are a few errors in this code:
882  *
883  *  1) I'm not sure it should be generating events in parallel like it says
884  *     above. That needs to be checked.
885  *
886  *  2) I didn't think about timezone changes when implementing this. I just
887  *     assumed all the occurrences of the event would be in local time.
888  *     But when clocks go back or forwards due to daylight-saving time, some
889  *     special handling may be needed, especially for the shorter frequencies.
890  *     e.g. for a MINUTELY frequency it should probably iterate over all the
891  *     minutes before and after clocks go back (i.e. some may be the same local
892  *     time but have different UTC offsets). For longer frequencies, if an
893  *     occurrence lands on the overlapping or non-existant time when clocks
894  *     go back/forward, then it may need to choose which of the times to use
895  *     or move the time forward or something. I'm not sure this is clear in the
896  *     spec.
897  */
898 
899 /* This is the maximum year we will go up to (inclusive). Since we use time_t
900  * values we can't go past 2037 anyway, and some of our VTIMEZONEs may stop
901  * at 2037 as well. */
902 #define MAX_YEAR	2037
903 
904 /* Define this for some debugging output. */
905 #if 0
906 #define CAL_OBJ_DEBUG	1
907 #endif
908 
909 /* We will use ICalRecurrence instead of this eventually. */
910 typedef struct {
911 	ICalRecurrenceFrequency freq;
912 
913 	gint            interval;
914 
915 	/* Specifies the end of the recurrence, inclusive. No occurrences are
916 	 * generated after this date. If it is 0, the event recurs forever. */
917 	time_t         enddate;
918 
919 	/* WKST property - the week start day: 0 = Monday to 6 = Sunday. */
920 	gint	       week_start_day;
921 
922 	/* NOTE: I've used GList's here, but it doesn't matter if we use
923 	 * other data structures like arrays. The code should be easy to
924 	 * change. So long as it is easy to see if the modifier is set. */
925 
926 	/* For BYMONTH modifier. A list of GINT_TO_POINTERs, 0-11. */
927 	GList	      *bymonth;
928 
929 	/* For BYWEEKNO modifier. A list of GINT_TO_POINTERs, [+-]1-53. */
930 	GList	      *byweekno;
931 
932 	/* For BYYEARDAY modifier. A list of GINT_TO_POINTERs, [+-]1-366. */
933 	GList	      *byyearday;
934 
935 	/* For BYMONTHDAY modifier. A list of GINT_TO_POINTERs, [+-]1-31. */
936 	GList	      *bymonthday;
937 
938 	/* For BYDAY modifier. A list of GINT_TO_POINTERs, in pairs.
939 	 * The first of each pair is the weekday, 0 = Monday to 6 = Sunday.
940 	 * The second of each pair is the week number[+-]0-53. */
941 	GList	      *byday;
942 
943 	/* For BYHOUR modifier. A list of GINT_TO_POINTERs, 0-23. */
944 	GList	      *byhour;
945 
946 	/* For BYMINUTE modifier. A list of GINT_TO_POINTERs, 0-59. */
947 	GList	      *byminute;
948 
949 	/* For BYSECOND modifier. A list of GINT_TO_POINTERs, 0-60. */
950 	GList	      *bysecond;
951 
952 	/* For BYSETPOS modifier. A list of GINT_TO_POINTERs, +ve or -ve. */
953 	GList	      *bysetpos;
954 } ECalRecurrence;
955 
956 /* This is what we use to pass to all the filter functions. */
957 typedef struct _RecurData RecurData;
958 struct _RecurData {
959 	ECalRecurrence *recur;
960 
961 	/* This is used for the WEEKLY frequency. It is the offset from the
962 	 * week_start_day. */
963 	gint weekday_offset;
964 
965 	/* This is used for fast lookup in BYMONTH filtering. */
966 	guint8 months[12];
967 
968 	/* This is used for fast lookup in BYYEARDAY filtering. */
969 	guint8 yeardays[367], neg_yeardays[367]; /* Days are 1 - 366. */
970 
971 	/* This is used for fast lookup in BYMONTHDAY filtering. */
972 	guint8 monthdays[32], neg_monthdays[32]; /* Days are 1 to 31. */
973 
974 	/* This is used for fast lookup in BYDAY filtering. */
975 	guint8 weekdays[7];
976 
977 	/* This is used for fast lookup in BYHOUR filtering. */
978 	guint8 hours[24];
979 
980 	/* This is used for fast lookup in BYMINUTE filtering. */
981 	guint8 minutes[60];
982 
983 	/* This is used for fast lookup in BYSECOND filtering. */
984 	guint8 seconds[62];
985 };
986 
987 /* This is what we use to represent a date & time. */
988 typedef struct _CalObjTime CalObjTime;
989 struct _CalObjTime {
990 	guint16 year;
991 	guint8 month;		/* 0 - 11 */
992 	guint8 day;		/* 1 - 31 */
993 	guint8 hour;		/* 0 - 23 */
994 	guint8 minute;		/* 0 - 59 */
995 	guint8 second;		/* 0 - 59 (maybe up to 61 for leap seconds) */
996 	guint8 flags;		/* The meaning of this depends on where the
997 				 * CalObjTime is used. In most cases this is
998 				 * set to TRUE to indicate that this is an
999 				 * RDATE with an end or a duration set.
1000 				 * In the exceptions code, this is set to TRUE
1001 				 * to indicate that this is an EXDATE with a
1002 				 * DATE value. */
1003 };
1004 
1005 /* This is what we use to represent specific recurrence dates.
1006  * Note that we assume it starts with a CalObjTime when sorting. */
1007 typedef struct _CalObjRecurrenceDate CalObjRecurrenceDate;
1008 struct _CalObjRecurrenceDate {
1009 	CalObjTime start;
1010 	ECalComponentPeriod *period;
1011 };
1012 
1013 typedef gboolean (*CalObjFindStartFn) (CalObjTime *event_start,
1014 				       CalObjTime *event_end,
1015 				       RecurData  *recur_data,
1016 				       CalObjTime *interval_start,
1017 				       CalObjTime *interval_end,
1018 				       CalObjTime *cotime);
1019 typedef gboolean (*CalObjFindNextFn)  (CalObjTime *cotime,
1020 				       CalObjTime *event_end,
1021 				       RecurData  *recur_data,
1022 				       CalObjTime *interval_end);
1023 typedef GArray *	 (*CalObjFilterFn)    (RecurData  *recur_data,
1024 				       GArray     *occs);
1025 
1026 typedef struct _ECalRecurVTable ECalRecurVTable;
1027 struct _ECalRecurVTable {
1028 	CalObjFindStartFn find_start_position;
1029 	CalObjFindNextFn find_next_position;
1030 
1031 	CalObjFilterFn bymonth_filter;
1032 	CalObjFilterFn byweekno_filter;
1033 	CalObjFilterFn byyearday_filter;
1034 	CalObjFilterFn bymonthday_filter;
1035 	CalObjFilterFn byday_filter;
1036 	CalObjFilterFn byhour_filter;
1037 	CalObjFilterFn byminute_filter;
1038 	CalObjFilterFn bysecond_filter;
1039 };
1040 
1041 /* This is used to specify which parts of the CalObjTime to compare in
1042  * cal_obj_time_compare (). */
1043 typedef enum {
1044 	CALOBJ_YEAR,
1045 	CALOBJ_MONTH,
1046 	CALOBJ_DAY,
1047 	CALOBJ_HOUR,
1048 	CALOBJ_MINUTE,
1049 	CALOBJ_SECOND
1050 } CalObjTimeComparison;
1051 
1052 static void e_cal_recur_generate_instances_of_rule (ECalComponent	*comp,
1053 						  ICalProperty	*prop,
1054 						  time_t	 start,
1055 						  time_t	 end,
1056 						  ECalRecurInstanceCb cb,
1057 						  gpointer       cb_data,
1058 						  ECalRecurResolveTimezoneCb  tz_cb,
1059 						  gpointer	 tz_cb_data,
1060 						  ICalTimezone	*default_timezone);
1061 
1062 static ECalRecurrence * e_cal_recur_from_icalproperty (ICalProperty *prop,
1063 						    gboolean exception,
1064 						    ICalTimezone *zone,
1065 						    gboolean convert_end_date);
1066 static gint e_cal_recur_ical_weekday_to_weekday	(ICalRecurrenceWeekday day);
1067 static void	e_cal_recur_free			(ECalRecurrence	*r);
1068 
1069 static gboolean cal_object_get_rdate_end	(CalObjTime	*occ,
1070 						 GArray		*rdate_periods,
1071 						 ICalTimezone	*zone);
1072 static void	cal_object_compute_duration	(CalObjTime	*start,
1073 						 CalObjTime	*end,
1074 						 gint		*days,
1075 						 gint		*seconds);
1076 
1077 static gboolean generate_instances_for_chunk	(ECalComponent		*comp,
1078 						 time_t			 comp_dtstart,
1079 						 ICalTimezone		*zone,
1080 						 GSList			*rrules,
1081 						 GSList			*rdates,
1082 						 GSList			*exrules,
1083 						 GSList			*exdates,
1084 						 gboolean		 single_rule,
1085 						 CalObjTime		*event_start,
1086 						 time_t			 interval_start,
1087 						 CalObjTime		*chunk_start,
1088 						 CalObjTime		*chunk_end,
1089 						 gint			 duration_days,
1090 						 gint			 duration_seconds,
1091 						 gboolean		 convert_end_date,
1092 						 ECalRecurInstanceCb	 cb,
1093 						 gpointer		 cb_data);
1094 
1095 static GArray * cal_obj_expand_recurrence	(CalObjTime	  *event_start,
1096 						 ICalTimezone	  *zone,
1097 						 ECalRecurrence	  *recur,
1098 						 CalObjTime	  *interval_start,
1099 						 CalObjTime	  *interval_end,
1100 						 gboolean	  *finished);
1101 
1102 static GArray *	cal_obj_generate_set_yearly	(RecurData	*recur_data,
1103 						 ECalRecurVTable *vtable,
1104 						 CalObjTime	*occ);
1105 static GArray *	cal_obj_generate_set_monthly	(RecurData	*recur_data,
1106 						 ECalRecurVTable *vtable,
1107 						 CalObjTime	*occ);
1108 static GArray *	cal_obj_generate_set_default	(RecurData	*recur_data,
1109 						 ECalRecurVTable *vtable,
1110 						 CalObjTime	*occ);
1111 
1112 static gboolean cal_obj_get_vtable		(ECalRecurrence *recur,
1113 						 ECalRecurVTable *out_vtable);
1114 static void	cal_obj_initialize_recur_data	(RecurData	*recur_data,
1115 						 ECalRecurrence	*recur,
1116 						 CalObjTime	*event_start);
1117 static void	cal_obj_sort_occurrences	(GArray		*occs);
1118 static gint	cal_obj_time_compare_func	(const void	*arg1,
1119 						 const void	*arg2);
1120 static void	cal_obj_remove_duplicates_and_invalid_dates (GArray	*occs);
1121 static void	cal_obj_remove_exceptions	(GArray		*occs,
1122 						 GArray		*ex_occs);
1123 static GArray *	cal_obj_bysetpos_filter		(ECalRecurrence	*recur,
1124 						 GArray		*occs);
1125 
1126 static gboolean cal_obj_yearly_find_start_position (CalObjTime *event_start,
1127 						    CalObjTime *event_end,
1128 						    RecurData  *recur_data,
1129 						    CalObjTime *interval_start,
1130 						    CalObjTime *interval_end,
1131 						    CalObjTime *cotime);
1132 static gboolean cal_obj_yearly_find_next_position  (CalObjTime *cotime,
1133 						    CalObjTime *event_end,
1134 						    RecurData  *recur_data,
1135 						    CalObjTime *interval_end);
1136 
1137 static gboolean cal_obj_monthly_find_start_position (CalObjTime *event_start,
1138 						     CalObjTime *event_end,
1139 						     RecurData  *recur_data,
1140 						     CalObjTime *interval_start,
1141 						     CalObjTime *interval_end,
1142 						     CalObjTime *cotime);
1143 static gboolean cal_obj_monthly_find_next_position  (CalObjTime *cotime,
1144 						     CalObjTime *event_end,
1145 						     RecurData  *recur_data,
1146 						     CalObjTime *interval_end);
1147 
1148 static gboolean cal_obj_weekly_find_start_position (CalObjTime *event_start,
1149 						    CalObjTime *event_end,
1150 						    RecurData  *recur_data,
1151 						    CalObjTime *interval_start,
1152 						    CalObjTime *interval_end,
1153 						    CalObjTime *cotime);
1154 static gboolean cal_obj_weekly_find_next_position  (CalObjTime *cotime,
1155 						    CalObjTime *event_end,
1156 						    RecurData  *recur_data,
1157 						    CalObjTime *interval_end);
1158 
1159 static gboolean cal_obj_daily_find_start_position  (CalObjTime *event_start,
1160 						    CalObjTime *event_end,
1161 						    RecurData  *recur_data,
1162 						    CalObjTime *interval_start,
1163 						    CalObjTime *interval_end,
1164 						    CalObjTime *cotime);
1165 static gboolean cal_obj_daily_find_next_position   (CalObjTime *cotime,
1166 						    CalObjTime *event_end,
1167 						    RecurData  *recur_data,
1168 						    CalObjTime *interval_end);
1169 
1170 static gboolean cal_obj_hourly_find_start_position (CalObjTime *event_start,
1171 						    CalObjTime *event_end,
1172 						    RecurData  *recur_data,
1173 						    CalObjTime *interval_start,
1174 						    CalObjTime *interval_end,
1175 						    CalObjTime *cotime);
1176 static gboolean cal_obj_hourly_find_next_position  (CalObjTime *cotime,
1177 						    CalObjTime *event_end,
1178 						    RecurData  *recur_data,
1179 						    CalObjTime *interval_end);
1180 
1181 static gboolean cal_obj_minutely_find_start_position (CalObjTime *event_start,
1182 						      CalObjTime *event_end,
1183 						      RecurData  *recur_data,
1184 						      CalObjTime *interval_start,
1185 						      CalObjTime *interval_end,
1186 						      CalObjTime *cotime);
1187 static gboolean cal_obj_minutely_find_next_position  (CalObjTime *cotime,
1188 						      CalObjTime *event_end,
1189 						      RecurData  *recur_data,
1190 						      CalObjTime *interval_end);
1191 
1192 static gboolean cal_obj_secondly_find_start_position (CalObjTime *event_start,
1193 						      CalObjTime *event_end,
1194 						      RecurData  *recur_data,
1195 						      CalObjTime *interval_start,
1196 						      CalObjTime *interval_end,
1197 						      CalObjTime *cotime);
1198 static gboolean cal_obj_secondly_find_next_position  (CalObjTime *cotime,
1199 						      CalObjTime *event_end,
1200 						      RecurData  *recur_data,
1201 						      CalObjTime *interval_end);
1202 
1203 static GArray * cal_obj_bymonth_expand		(RecurData  *recur_data,
1204 						 GArray     *occs);
1205 static GArray * cal_obj_bymonth_filter		(RecurData  *recur_data,
1206 						 GArray     *occs);
1207 static GArray * cal_obj_byweekno_expand		(RecurData  *recur_data,
1208 						 GArray     *occs);
1209 #if 0
1210 /* This isn't used at present. */
1211 static GArray * cal_obj_byweekno_filter		(RecurData  *recur_data,
1212 						 GArray     *occs);
1213 #endif
1214 static GArray * cal_obj_byyearday_expand		(RecurData  *recur_data,
1215 						 GArray     *occs);
1216 static GArray * cal_obj_byyearday_filter		(RecurData  *recur_data,
1217 						 GArray     *occs);
1218 static GArray * cal_obj_bymonthday_expand	(RecurData  *recur_data,
1219 						 GArray     *occs);
1220 static GArray * cal_obj_bymonthday_filter	(RecurData  *recur_data,
1221 						 GArray     *occs);
1222 static GArray * cal_obj_byday_expand_yearly	(RecurData  *recur_data,
1223 						 GArray     *occs);
1224 static GArray * cal_obj_byday_expand_monthly	(RecurData  *recur_data,
1225 						 GArray     *occs);
1226 static GArray * cal_obj_byday_expand_weekly	(RecurData  *recur_data,
1227 						 GArray     *occs);
1228 static GArray * cal_obj_byday_filter		(RecurData  *recur_data,
1229 						 GArray     *occs);
1230 static GArray * cal_obj_byhour_expand		(RecurData  *recur_data,
1231 						 GArray     *occs);
1232 static GArray * cal_obj_byhour_filter		(RecurData  *recur_data,
1233 						 GArray     *occs);
1234 static GArray * cal_obj_byminute_expand		(RecurData  *recur_data,
1235 						 GArray     *occs);
1236 static GArray * cal_obj_byminute_filter		(RecurData  *recur_data,
1237 						 GArray     *occs);
1238 static GArray * cal_obj_bysecond_expand		(RecurData  *recur_data,
1239 						 GArray     *occs);
1240 static GArray * cal_obj_bysecond_filter		(RecurData  *recur_data,
1241 						 GArray     *occs);
1242 
1243 static void cal_obj_time_add_months		(CalObjTime *cotime,
1244 						 gint	     months);
1245 static void cal_obj_time_add_days		(CalObjTime *cotime,
1246 						 gint	     days);
1247 static void cal_obj_time_add_hours		(CalObjTime *cotime,
1248 						 gint	     hours);
1249 static void cal_obj_time_add_minutes		(CalObjTime *cotime,
1250 						 gint	     minutes);
1251 static void cal_obj_time_add_seconds		(CalObjTime *cotime,
1252 						 gint	     seconds);
1253 static gint cal_obj_time_compare		(CalObjTime *cotime1,
1254 						 CalObjTime *cotime2,
1255 						 CalObjTimeComparison type);
1256 static gint cal_obj_time_weekday		(CalObjTime *cotime);
1257 static gint cal_obj_time_weekday_offset		(CalObjTime *cotime,
1258 						 ECalRecurrence *recur);
1259 static gint cal_obj_time_day_of_year		(CalObjTime *cotime);
1260 static void cal_obj_time_find_first_week	(CalObjTime *cotime,
1261 						 RecurData  *recur_data);
1262 static void cal_object_time_from_time		(CalObjTime *cotime,
1263 						 time_t      t,
1264 						 ICalTimezone *zone);
1265 static gint cal_obj_date_only_compare_func	(gconstpointer arg1,
1266 						 gconstpointer arg2);
1267 static gboolean e_cal_recur_ensure_rule_end_date	(ECalComponent	*comp,
1268 						 ICalProperty	*prop,
1269 						 gboolean	 exception,
1270 						 gboolean	 refresh,
1271 						 ECalRecurResolveTimezoneCb tz_cb,
1272 						 gpointer	 tz_cb_data,
1273 						 GCancellable *cancellable,
1274 						 GError **error);
1275 static gboolean e_cal_recur_ensure_rule_end_date_cb	(ICalComponent	*comp,
1276 							 ICalTime *instance_start,
1277 							 ICalTime *instance_end,
1278 							 gpointer user_data,
1279 							 GCancellable *cancellable,
1280 							 GError **error);
1281 static time_t e_cal_recur_get_rule_end_date	(ICalProperty	*prop,
1282 						 ICalTimezone	*default_timezone);
1283 static void e_cal_recur_set_rule_end_date	(ICalProperty	*prop,
1284 						 time_t		 end_date);
1285 
1286 #ifdef CAL_OBJ_DEBUG
1287 static const gchar * cal_obj_time_to_string		(CalObjTime	*cotime);
1288 #endif
1289 
1290 static ECalRecurVTable cal_obj_yearly_vtable = {
1291 	cal_obj_yearly_find_start_position,
1292 	cal_obj_yearly_find_next_position,
1293 
1294 	cal_obj_bymonth_expand,
1295 	cal_obj_byweekno_expand,
1296 	cal_obj_byyearday_expand,
1297 	cal_obj_bymonthday_expand,
1298 	cal_obj_byday_expand_yearly,
1299 	cal_obj_byhour_expand,
1300 	cal_obj_byminute_expand,
1301 	cal_obj_bysecond_expand
1302 };
1303 
1304 static ECalRecurVTable cal_obj_monthly_vtable = {
1305 	cal_obj_monthly_find_start_position,
1306 	cal_obj_monthly_find_next_position,
1307 
1308 	cal_obj_bymonth_filter,
1309 	NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
1310 	NULL, /* BYYEARDAY is not useful in a MONTHLY frequency. */
1311 	cal_obj_bymonthday_filter,
1312 	cal_obj_byday_expand_monthly,
1313 	cal_obj_byhour_expand,
1314 	cal_obj_byminute_expand,
1315 	cal_obj_bysecond_expand
1316 };
1317 
1318 static ECalRecurVTable cal_obj_weekly_vtable = {
1319 	cal_obj_weekly_find_start_position,
1320 	cal_obj_weekly_find_next_position,
1321 
1322 	cal_obj_bymonth_filter,
1323 	NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
1324 	NULL, /* BYYEARDAY is not useful in a WEEKLY frequency. */
1325 	NULL, /* BYMONTHDAY is not useful in a WEEKLY frequency. */
1326 	cal_obj_byday_expand_weekly,
1327 	cal_obj_byhour_expand,
1328 	cal_obj_byminute_expand,
1329 	cal_obj_bysecond_expand
1330 };
1331 
1332 static ECalRecurVTable cal_obj_daily_vtable = {
1333 	cal_obj_daily_find_start_position,
1334 	cal_obj_daily_find_next_position,
1335 
1336 	cal_obj_bymonth_filter,
1337 	NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
1338 	cal_obj_byyearday_filter,
1339 	cal_obj_bymonthday_filter,
1340 	cal_obj_byday_filter,
1341 	cal_obj_byhour_expand,
1342 	cal_obj_byminute_expand,
1343 	cal_obj_bysecond_expand
1344 };
1345 
1346 static ECalRecurVTable cal_obj_hourly_vtable = {
1347 	cal_obj_hourly_find_start_position,
1348 	cal_obj_hourly_find_next_position,
1349 
1350 	cal_obj_bymonth_filter,
1351 	NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
1352 	cal_obj_byyearday_filter,
1353 	cal_obj_bymonthday_filter,
1354 	cal_obj_byday_filter,
1355 	cal_obj_byhour_filter,
1356 	cal_obj_byminute_expand,
1357 	cal_obj_bysecond_expand
1358 };
1359 
1360 static ECalRecurVTable cal_obj_minutely_vtable = {
1361 	cal_obj_minutely_find_start_position,
1362 	cal_obj_minutely_find_next_position,
1363 
1364 	cal_obj_bymonth_filter,
1365 	NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
1366 	cal_obj_byyearday_filter,
1367 	cal_obj_bymonthday_filter,
1368 	cal_obj_byday_filter,
1369 	cal_obj_byhour_filter,
1370 	cal_obj_byminute_filter,
1371 	cal_obj_bysecond_expand
1372 };
1373 
1374 static ECalRecurVTable cal_obj_secondly_vtable = {
1375 	cal_obj_secondly_find_start_position,
1376 	cal_obj_secondly_find_next_position,
1377 
1378 	cal_obj_bymonth_filter,
1379 	NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
1380 	cal_obj_byyearday_filter,
1381 	cal_obj_bymonthday_filter,
1382 	cal_obj_byday_filter,
1383 	cal_obj_byhour_filter,
1384 	cal_obj_byminute_filter,
1385 	cal_obj_bysecond_filter
1386 };
1387 
1388 static gboolean
call_instance_callback(ECalRecurInstanceCb cb,ECalComponent * comp,time_t dtstart_time,time_t dtend_time,gpointer cb_data)1389 call_instance_callback (ECalRecurInstanceCb cb,
1390 			ECalComponent *comp,
1391 			time_t dtstart_time,
1392 			time_t dtend_time,
1393 			gpointer cb_data)
1394 {
1395 	ICalTime *start, *end;
1396 	ICalTimezone *utc_zone;
1397 	gboolean res;
1398 
1399 	g_return_val_if_fail (cb != NULL, FALSE);
1400 
1401 	utc_zone = i_cal_timezone_get_utc_timezone ();
1402 	start = i_cal_time_new_from_timet_with_zone (dtstart_time, FALSE, utc_zone);
1403 	end = i_cal_time_new_from_timet_with_zone (dtend_time, FALSE, utc_zone);
1404 
1405 	res = cb (e_cal_component_get_icalcomponent (comp), start, end, cb_data, NULL, NULL);
1406 
1407 	g_clear_object (&start);
1408 	g_clear_object (&end);
1409 
1410 	return res;
1411 }
1412 
1413 /*
1414  * Calls the given callback function for each occurrence of the given
1415  * recurrence rule between the given start and end times. If the rule is NULL
1416  * it uses all the rules from the component.
1417  *
1418  * If the callback routine returns FALSE the occurrence generation stops.
1419  *
1420  * The use of the specific rule is for determining the end of a rule when
1421  * COUNT is set. The callback will count instances and store the enddate
1422  * when COUNT is reached.
1423  *
1424  * Both start and end can be -1, in which case we start at the events first
1425  * instance and continue until it ends, or forever if it has no enddate.
1426  */
1427 static void
e_cal_recur_generate_instances_of_rule(ECalComponent * comp,ICalProperty * prop,time_t start,time_t end,ECalRecurInstanceCb cb,gpointer cb_data,ECalRecurResolveTimezoneCb tz_cb,gpointer tz_cb_data,ICalTimezone * default_timezone)1428 e_cal_recur_generate_instances_of_rule (ECalComponent *comp,
1429                                         ICalProperty *prop,
1430                                         time_t start,
1431                                         time_t end,
1432                                         ECalRecurInstanceCb cb,
1433                                         gpointer cb_data,
1434                                         ECalRecurResolveTimezoneCb tz_cb,
1435                                         gpointer tz_cb_data,
1436                                         ICalTimezone *default_timezone)
1437 {
1438 	ECalComponentDateTime *dtstart, *dtend;
1439 	time_t dtstart_time, dtend_time;
1440 	GSList *rrules = NULL, *rdates = NULL;
1441 	GSList *exrules = NULL, *exdates = NULL;
1442 	CalObjTime interval_start, interval_end, event_start, event_end;
1443 	CalObjTime chunk_start, chunk_end;
1444 	gint days, seconds, year;
1445 	gboolean single_rule, convert_end_date = FALSE;
1446 	ICalTimezone *start_zone = NULL, *end_zone = NULL;
1447 	ICalTime *dtstarttt, *dtendtt;
1448 
1449 	g_return_if_fail (comp != NULL);
1450 	g_return_if_fail (cb != NULL);
1451 	g_return_if_fail (tz_cb != NULL);
1452 	g_return_if_fail (start >= -1);
1453 	g_return_if_fail (end >= -1);
1454 
1455 	/* Get dtstart, dtend, recurrences, and exceptions. Note that
1456 	 * cal_component_get_dtend () will convert a DURATION property to a
1457 	 * DTEND so we don't need to worry about that. */
1458 
1459 	dtstart = e_cal_component_get_dtstart (comp);
1460 	dtend = e_cal_component_get_dtend (comp);
1461 
1462 	dtstarttt = (dtstart && e_cal_component_datetime_get_value (dtstart)) ?
1463 		e_cal_component_datetime_get_value (dtstart) : NULL;
1464 	dtendtt = (dtend && e_cal_component_datetime_get_value (dtend)) ?
1465 		e_cal_component_datetime_get_value (dtend) : NULL;
1466 
1467 	if (!dtstart || !dtstarttt) {
1468 		g_message (
1469 			"e_cal_recur_generate_instances_of_rule(): bogus "
1470 			"component, does not have DTSTART.  Skipping...");
1471 		goto out;
1472 	}
1473 
1474 	/* For DATE-TIME values with a TZID, we use the supplied callback to
1475 	 * resolve the TZID. For DATE values and DATE-TIME values without a
1476 	 * TZID (i.e. floating times) we use the default timezone. */
1477 	if (e_cal_component_datetime_get_tzid (dtstart) && !i_cal_time_is_date (dtstarttt)) {
1478 		start_zone = (*tz_cb) (e_cal_component_datetime_get_tzid (dtstart), tz_cb_data, NULL, NULL);
1479 		if (!start_zone)
1480 			start_zone = default_timezone;
1481 	} else {
1482 		start_zone = default_timezone;
1483 
1484 		/* Flag that we need to convert the saved ENDDATE property
1485 		 * to the default timezone. */
1486 		convert_end_date = TRUE;
1487 	}
1488 
1489 	if (start_zone)
1490 		g_object_ref (start_zone);
1491 
1492 	dtstart_time = i_cal_time_as_timet_with_zone (dtstarttt, start_zone);
1493 	if (start == -1)
1494 		start = dtstart_time;
1495 
1496 	if (dtendtt) {
1497 		/* If both DTSTART and DTEND are DATE values, and they are the
1498 		 * same day, we add 1 day to DTEND. This means that most
1499 		 * events created with the old Evolution behavior will still
1500 		 * work OK. I'm not sure what Outlook does in this case. */
1501 		if (i_cal_time_is_date (dtstarttt) && i_cal_time_is_date (dtendtt)) {
1502 			if (i_cal_time_compare_date_only (dtstarttt, dtendtt) == 0) {
1503 				i_cal_time_adjust (dtendtt, 1, 0, 0, 0);
1504 			}
1505 		}
1506 	} else {
1507 		/* If there is no DTEND, then if DTSTART is a DATE-TIME value
1508 		 * we use the same time (so we have a single point in time).
1509 		 * If DTSTART is a DATE value we add 1 day. */
1510 		e_cal_component_datetime_free (dtend);
1511 
1512 		dtend = e_cal_component_datetime_copy (dtstart);
1513 		dtendtt = (dtend && e_cal_component_datetime_get_value (dtend)) ?
1514 			e_cal_component_datetime_get_value (dtend) : NULL;
1515 
1516 		g_warn_if_fail (dtendtt != NULL);
1517 
1518 		if (i_cal_time_is_date (dtstarttt)) {
1519 			i_cal_time_adjust (dtendtt, 1, 0, 0, 0);
1520 		}
1521 	}
1522 
1523 	if (e_cal_component_datetime_get_tzid (dtend) && dtendtt && !i_cal_time_is_date (dtendtt)) {
1524 		end_zone = (*tz_cb) (e_cal_component_datetime_get_tzid (dtend), tz_cb_data, NULL, NULL);
1525 		if (!end_zone)
1526 			end_zone = default_timezone;
1527 	} else {
1528 		end_zone = default_timezone;
1529 	}
1530 
1531 	if (end_zone)
1532 		g_object_ref (end_zone);
1533 
1534 	/* We don't do this any more, since Outlook assumes that the DTEND
1535 	 * date is not included. */
1536 #if 0
1537 	/* If DTEND is a DATE value, we add 1 day to it so that it includes
1538 	 * the entire day. */
1539 	if (i_cal_time_is_date (dtendtt)) {
1540 		i_cal_time_set_time (dtendtt, 0, 0, 0);
1541 		i_cal_time_adjust (dtendtt, 1, 0, 0, 0);
1542 	}
1543 #endif
1544 	dtend_time = i_cal_time_as_timet_with_zone (dtendtt, end_zone);
1545 
1546 	/* If there is no recurrence, just call the callback if the event
1547 	 * intersects the given interval. */
1548 	if (!(e_cal_component_has_recurrences (comp)
1549 	      || e_cal_component_has_exceptions (comp))) {
1550 		if (e_cal_component_get_vtype (comp) == E_CAL_COMPONENT_JOURNAL) {
1551 			ICalTime *start_t = i_cal_time_new_from_timet_with_zone (start, FALSE, default_timezone);
1552 			ICalTime *end_t = i_cal_time_new_from_timet_with_zone (end, FALSE, default_timezone);
1553 
1554 			if ((i_cal_time_compare_date_only (dtstarttt, start_t) >= 0) && ((i_cal_time_compare_date_only (dtstarttt, end_t) < 0)))
1555 				call_instance_callback (cb, comp, dtstart_time, dtend_time, cb_data);
1556 
1557 			g_clear_object (&start_t);
1558 			g_clear_object (&end_t);
1559 		} else if  ((end == -1 || dtstart_time < end) && dtend_time > start) {
1560 			call_instance_callback (cb, comp, dtstart_time, dtend_time, cb_data);
1561 		}
1562 
1563 		goto out;
1564 	}
1565 
1566 	/* If a specific recurrence rule is being used, set up a simple list,
1567 	 * else get the recurrence rules from the component. */
1568 	if (prop) {
1569 		single_rule = TRUE;
1570 
1571 		rrules = g_slist_prepend (NULL, g_object_ref (prop));
1572 	} else if (e_cal_component_is_instance (comp)) {
1573 		single_rule = FALSE;
1574 	} else {
1575 		single_rule = FALSE;
1576 
1577 		/* Make sure all the enddates for the rules are set. */
1578 		e_cal_recur_ensure_end_dates (comp, FALSE, tz_cb, tz_cb_data, NULL, NULL);
1579 
1580 		rrules = e_cal_component_get_rrule_properties (comp);
1581 		rdates = e_cal_component_get_rdates (comp);
1582 		exrules = e_cal_component_get_exrule_properties (comp);
1583 		exdates = e_cal_component_get_exdates (comp);
1584 	}
1585 
1586 	/* Convert the interval start & end to CalObjTime. Note that if end
1587 	 * is -1 interval_end won't be set, so don't use it!
1588 	 * Also note that we use end - 1 since we want the interval to be
1589 	 * inclusive as it makes the code simpler. We do all calculation
1590 	 * in the timezone of the DTSTART. */
1591 	cal_object_time_from_time (&interval_start, start, start_zone);
1592 	if (end != -1)
1593 		cal_object_time_from_time (&interval_end, end - 1, start_zone);
1594 
1595 	cal_object_time_from_time (&event_start, dtstart_time, start_zone);
1596 	cal_object_time_from_time (&event_end, dtend_time, start_zone);
1597 
1598 	/* Calculate the duration of the event, which we use for all
1599 	 * occurrences. We can't just subtract start from end since that may
1600 	 * be affected by daylight-saving time. So we want a value of days
1601 	 * + seconds. */
1602 	cal_object_compute_duration (
1603 		&event_start, &event_end,
1604 		&days, &seconds);
1605 
1606 	/* Take off the duration from interval_start, so we get occurrences
1607 	 * that start just before the start time but overlap it. But only do
1608 	 * that if the interval is after the event's start time. */
1609 	if (start > dtstart_time) {
1610 		cal_obj_time_add_days (&interval_start, -days);
1611 		cal_obj_time_add_seconds (&interval_start, -seconds);
1612 	}
1613 
1614 	/* Expand the recurrence for each year between start & end, or until
1615 	 * the callback returns 0 if end is 0. We do a year at a time to
1616 	 * give the callback function a chance to break out of the loop, and
1617 	 * so we don't get into problems with infinite recurrences. Since we
1618 	 * have to work on complete sets of occurrences, if there is a yearly
1619 	 * frequency it wouldn't make sense to break it into smaller chunks,
1620 	 * since we would then be calculating the same sets several times.
1621 	 * Though this does mean that we sometimes do a lot more work than
1622 	 * is necessary, e.g. if COUNT is set to something quite low. */
1623 	for (year = interval_start.year;
1624 	     (end == -1 || year <= interval_end.year) && year <= MAX_YEAR;
1625 	     year++) {
1626 		chunk_start = interval_start;
1627 		chunk_start.year = year;
1628 		if (end != -1)
1629 			chunk_end = interval_end;
1630 		chunk_end.year = year;
1631 
1632 		if (year != interval_start.year) {
1633 			chunk_start.month = 0;
1634 			chunk_start.day = 1;
1635 			chunk_start.hour = 0;
1636 			chunk_start.minute = 0;
1637 			chunk_start.second = 0;
1638 		}
1639 		if (end == -1 || year != interval_end.year) {
1640 			chunk_end.month = 11;
1641 			chunk_end.day = 31;
1642 			chunk_end.hour = 23;
1643 			chunk_end.minute = 59;
1644 			chunk_end.second = 61;
1645 			chunk_end.flags = FALSE;
1646 		}
1647 
1648 		if (!generate_instances_for_chunk (comp, dtstart_time,
1649 						   start_zone,
1650 						   rrules, rdates,
1651 						   exrules, exdates,
1652 						   single_rule,
1653 						   &event_start,
1654 						   start,
1655 						   &chunk_start, &chunk_end,
1656 						   days, seconds,
1657 						   convert_end_date,
1658 						   cb, cb_data))
1659 			break;
1660 	}
1661 
1662 	g_slist_free_full (rdates, e_cal_component_period_free);
1663 	g_slist_free_full (exdates, e_cal_component_datetime_free);
1664 	g_slist_free_full (rrules, g_object_unref);
1665 	g_slist_free_full (exrules, g_object_unref);
1666 
1667  out:
1668 	e_cal_component_datetime_free (dtstart);
1669 	e_cal_component_datetime_free (dtend);
1670 	g_clear_object (&start_zone);
1671 	g_clear_object (&end_zone);
1672 }
1673 
1674 /* Builds a list of GINT_TO_POINTER() elements out of a short array from an
1675  * ICalRecurrence array of gshort.
1676  */
1677 static GList *
array_to_list_and_free(GArray * array)1678 array_to_list_and_free (GArray *array)
1679 {
1680 	GList *lst;
1681 	gint ii;
1682 
1683 	if (!array)
1684 		return NULL;
1685 
1686 	lst = NULL;
1687 
1688 	for (ii = 0; ii < array->len; ii++) {
1689 		gshort value = g_array_index (array, gshort, ii);
1690 
1691 		if (value == I_CAL_RECURRENCE_ARRAY_MAX)
1692 			break;
1693 
1694 		lst = g_list_prepend (lst, GINT_TO_POINTER ((gint) value));
1695 	}
1696 
1697 	g_array_unref (array);
1698 
1699 	return g_list_reverse (lst);
1700 }
1701 
1702 /**
1703  * e_cal_recur_get_enddate:
1704  * @ir: RRULE or EXRULE recurrence
1705  * @prop: An RRULE or EXRULE #ICalProperty.
1706  * @zone: The DTSTART timezone, used for converting the UNTIL property if it
1707  *    is given as a DATE value.
1708  * @convert_end_date: TRUE if the saved end date needs to be converted to the
1709  *    given @zone timezone. This is needed if the DTSTART is a DATE or floating
1710  *    time.
1711  *
1712  * Finds out end time of (reccurent) event.
1713  *
1714  * Returns: End timepoint of given event
1715  *
1716  * Since: 2.32
1717  */
1718 time_t
e_cal_recur_obtain_enddate(ICalRecurrence * ir,ICalProperty * prop,ICalTimezone * zone,gboolean convert_end_date)1719 e_cal_recur_obtain_enddate (ICalRecurrence *ir,
1720 			    ICalProperty *prop,
1721 			    ICalTimezone *zone,
1722 			    gboolean convert_end_date)
1723 {
1724 	time_t enddate = -1;
1725 
1726 	g_return_val_if_fail (prop != NULL, 0);
1727 	g_return_val_if_fail (ir != NULL, 0);
1728 
1729 	if (i_cal_recurrence_get_count (ir) != 0) {
1730 		/* If COUNT is set, we use the pre-calculated enddate.
1731 		Note that this can be 0 if the RULE doesn't actually
1732 		generate COUNT instances. */
1733 		enddate = e_cal_recur_get_rule_end_date (prop, convert_end_date ? zone : NULL);
1734 	} else {
1735 		ICalTime *until;
1736 
1737 		until = i_cal_recurrence_get_until (ir);
1738 
1739 		if (!until || i_cal_time_is_null_time (until)) {
1740 			/* If neither COUNT or UNTIL is set, the event
1741 			recurs forever. */
1742 		} else if (i_cal_time_is_date (until)) {
1743 			/* If UNTIL is a DATE, we stop at the end of
1744 			the day, in local time (with the DTSTART timezone).
1745 			Note that UNTIL is inclusive so we stop before
1746 			midnight. */
1747 			i_cal_time_set_time (until, 23, 59, 59);
1748 			i_cal_time_set_is_date (until, FALSE);
1749 
1750 			enddate = i_cal_time_as_timet_with_zone (until, zone);
1751 		} else {
1752 			/* If UNTIL is a DATE-TIME, it must be in UTC. */
1753 			enddate = i_cal_time_as_timet_with_zone (until, i_cal_timezone_get_utc_timezone ());
1754 		}
1755 
1756 		g_clear_object (&until);
1757 	}
1758 
1759 	return enddate;
1760 }
1761 
1762 /*
1763  * e_cal_recur_from_icalproperty:
1764  * @prop: An RRULE or EXRULE #ICalProperty.
1765  * @exception: TRUE if this is an EXRULE rather than an RRULE.
1766  * @zone: The DTSTART timezone, used for converting the UNTIL property if it
1767  * is given as a DATE value.
1768  * @convert_end_date: TRUE if the saved end date needs to be converted to the
1769  * given @zone timezone. This is needed if the DTSTART is a DATE or floating
1770  * time.
1771  *
1772  * Converts an #ICalProperty to an #ECalRecurrence.  This should be
1773  * freed using the e_cal_recur_free() function.
1774  *
1775  * Returns: #ECalRecurrence structure.
1776  */
1777 static ECalRecurrence *
e_cal_recur_from_icalproperty(ICalProperty * prop,gboolean exception,ICalTimezone * zone,gboolean convert_end_date)1778 e_cal_recur_from_icalproperty (ICalProperty *prop,
1779 			       gboolean exception,
1780 			       ICalTimezone *zone,
1781 			       gboolean convert_end_date)
1782 {
1783 	ICalRecurrence *ir;
1784 	ECalRecurrence *r;
1785 	GArray *array;
1786 	gint ii;
1787 	GList *elem;
1788 
1789 	g_return_val_if_fail (prop != NULL, NULL);
1790 
1791 	r = g_slice_new0 (ECalRecurrence);
1792 
1793 	if (exception) {
1794 		ir = i_cal_property_get_exrule (prop);
1795 	} else {
1796 		ir = i_cal_property_get_rrule (prop);
1797 	}
1798 
1799 	r->freq = i_cal_recurrence_get_freq (ir);
1800 
1801 	if (G_UNLIKELY (i_cal_recurrence_get_interval (ir) < 1)) {
1802 		gchar *str;
1803 
1804 		str = i_cal_recurrence_to_string (ir);
1805 		g_warning (
1806 			"Invalid interval in rule %s - using 1\n",
1807 			str);
1808 		g_free (str);
1809 		r->interval = 1;
1810 	} else {
1811 		r->interval = i_cal_recurrence_get_interval (ir);
1812 	}
1813 
1814 	r->enddate = e_cal_recur_obtain_enddate (ir, prop, zone, convert_end_date);
1815 	r->week_start_day = e_cal_recur_ical_weekday_to_weekday (i_cal_recurrence_get_week_start (ir));
1816 
1817 	r->bymonth = array_to_list_and_free (i_cal_recurrence_get_by_month_array (ir));
1818 	for (elem = r->bymonth; elem; elem = elem->next) {
1819 		/* We need to convert from 1-12 to 0-11, i.e. subtract 1. */
1820 		gint month = GPOINTER_TO_INT (elem->data) - 1;
1821 		elem->data = GINT_TO_POINTER (month);
1822 	}
1823 
1824 	r->byweekno = array_to_list_and_free (i_cal_recurrence_get_by_week_no_array (ir));
1825 
1826 	r->byyearday = array_to_list_and_free (i_cal_recurrence_get_by_year_day_array (ir));
1827 
1828 	r->bymonthday = array_to_list_and_free (i_cal_recurrence_get_by_month_day_array (ir));
1829 
1830 	/* FIXME: libical only supports 8 values, out of possible 107 * 7. */
1831 	r->byday = NULL;
1832 	array = i_cal_recurrence_get_by_day_array (ir);
1833 	for (ii = 0; array && ii < array->len; ii++) {
1834 		gshort value = g_array_index (array, gshort, ii);
1835 		ICalRecurrenceWeekday day;
1836 		gint weeknum, weekday;
1837 
1838 		if (value == I_CAL_RECURRENCE_ARRAY_MAX)
1839 			break;
1840 
1841 		day = i_cal_recurrence_day_day_of_week (value);
1842 		weeknum = i_cal_recurrence_day_position (value);
1843 		weekday = e_cal_recur_ical_weekday_to_weekday (day);
1844 
1845 		r->byday = g_list_prepend (
1846 			r->byday,
1847 			GINT_TO_POINTER (weeknum));
1848 		r->byday = g_list_prepend (
1849 			r->byday,
1850 			GINT_TO_POINTER (weekday));
1851 	}
1852 	if (array)
1853 		g_array_unref (array);
1854 
1855 	r->byhour = array_to_list_and_free (i_cal_recurrence_get_by_hour_array (ir));
1856 
1857 	r->byminute = array_to_list_and_free (i_cal_recurrence_get_by_minute_array (ir));
1858 
1859 	r->bysecond = array_to_list_and_free (i_cal_recurrence_get_by_second_array (ir));
1860 
1861 	r->bysetpos = array_to_list_and_free (i_cal_recurrence_get_by_set_pos_array (ir));
1862 
1863 	g_clear_object (&ir);
1864 
1865 	return r;
1866 }
1867 
1868 static gint
e_cal_recur_ical_weekday_to_weekday(ICalRecurrenceWeekday day)1869 e_cal_recur_ical_weekday_to_weekday (ICalRecurrenceWeekday day)
1870 {
1871 	gint weekday;
1872 
1873 	switch (day) {
1874 	case I_CAL_NO_WEEKDAY:		/* Monday is the default in RFC2445. */
1875 	case I_CAL_MONDAY_WEEKDAY:
1876 		weekday = 0;
1877 		break;
1878 	case I_CAL_TUESDAY_WEEKDAY:
1879 		weekday = 1;
1880 		break;
1881 	case I_CAL_WEDNESDAY_WEEKDAY:
1882 		weekday = 2;
1883 		break;
1884 	case I_CAL_THURSDAY_WEEKDAY:
1885 		weekday = 3;
1886 		break;
1887 	case I_CAL_FRIDAY_WEEKDAY:
1888 		weekday = 4;
1889 		break;
1890 	case I_CAL_SATURDAY_WEEKDAY:
1891 		weekday = 5;
1892 		break;
1893 	case I_CAL_SUNDAY_WEEKDAY:
1894 		weekday = 6;
1895 		break;
1896 	default:
1897 		g_warning (
1898 			"e_cal_recur_ical_weekday_to_weekday(): Unknown week day %d",
1899 			day);
1900 		weekday = 0;
1901 	}
1902 
1903 	return weekday;
1904 }
1905 
1906 /**
1907  * e_cal_recur_free:
1908  * @r: An #ECalRecurrence structure.
1909  *
1910  * Frees an #ECalRecurrence structure.
1911  **/
1912 static void
e_cal_recur_free(ECalRecurrence * r)1913 e_cal_recur_free (ECalRecurrence *r)
1914 {
1915 	g_return_if_fail (r != NULL);
1916 
1917 	g_list_free (r->bymonth);
1918 	g_list_free (r->byweekno);
1919 	g_list_free (r->byyearday);
1920 	g_list_free (r->bymonthday);
1921 	g_list_free (r->byday);
1922 	g_list_free (r->byhour);
1923 	g_list_free (r->byminute);
1924 	g_list_free (r->bysecond);
1925 	g_list_free (r->bysetpos);
1926 
1927 	g_slice_free (ECalRecurrence, r);
1928 }
1929 
1930 /* Generates one year's worth of recurrence instances.  Returns TRUE if all the
1931  * callback invocations returned TRUE, or FALSE when any one of them returns
1932  * FALSE, i.e. meaning that the instance generation should be stopped.
1933  *
1934  * This should only output instances whose start time is between chunk_start
1935  * and chunk_end (inclusive), or we may generate duplicates when we do the next
1936  * chunk. (This applies mainly to weekly recurrences, since weeks can span 2
1937  * years.)
1938  *
1939  * It should also only output instances that are on or after the event's
1940  * DTSTART property and that intersect the required interval, between
1941  * interval_start and interval_end.
1942  */
1943 static gboolean
generate_instances_for_chunk(ECalComponent * comp,time_t comp_dtstart,ICalTimezone * zone,GSList * rrules,GSList * rdates,GSList * exrules,GSList * exdates,gboolean single_rule,CalObjTime * event_start,time_t interval_start,CalObjTime * chunk_start,CalObjTime * chunk_end,gint duration_days,gint duration_seconds,gboolean convert_end_date,ECalRecurInstanceCb cb,gpointer cb_data)1944 generate_instances_for_chunk (ECalComponent *comp,
1945                               time_t comp_dtstart,
1946                               ICalTimezone *zone,
1947                               GSList *rrules, /* ICalProperty * */
1948                               GSList *rdates,
1949                               GSList *exrules,
1950                               GSList *exdates,
1951                               gboolean single_rule,
1952                               CalObjTime *event_start,
1953                               time_t interval_start,
1954                               CalObjTime *chunk_start,
1955                               CalObjTime *chunk_end,
1956                               gint duration_days,
1957                               gint duration_seconds,
1958                               gboolean convert_end_date,
1959                               ECalRecurInstanceCb cb,
1960                               gpointer cb_data)
1961 {
1962 	GArray *occs, *ex_occs, *tmp_occs, *rdate_periods;
1963 	CalObjTime cotime, *occ;
1964 	GSList *elem;
1965 	gint i;
1966 	time_t start_time, end_time;
1967 	gboolean cb_status = TRUE, rule_finished, finished = TRUE;
1968 
1969 #if 0
1970 	g_print (
1971 		"In generate_instances_for_chunk rrules: %p\n"
1972 		"  %i/%i/%i %02i:%02i:%02i - %i/%i/%i %02i:%02i:%02i\n",
1973 		rrules,
1974 		chunk_start->day, chunk_start->month + 1,
1975 		chunk_start->year, chunk_start->hour,
1976 		chunk_start->minute, chunk_start->second,
1977 		chunk_end->day, chunk_end->month + 1,
1978 		chunk_end->year, chunk_end->hour,
1979 		chunk_end->minute, chunk_end->second);
1980 #endif
1981 
1982 	occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1983 	ex_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
1984 	rdate_periods = g_array_new (
1985 		FALSE, FALSE,
1986 		sizeof (CalObjRecurrenceDate));
1987 
1988 	/* The original DTSTART property is included in the occurrence set,
1989 	 * but not if we are just generating occurrences for a single rule. */
1990 	if (!single_rule) {
1991 		/* We add it if it is in this chunk. If it is after this chunk
1992 		 * we set finished to FALSE, since we know we aren't finished
1993 		 * yet. */
1994 		if (cal_obj_time_compare_func (event_start, chunk_end) >= 0)
1995 			finished = FALSE;
1996 		else if (cal_obj_time_compare_func (event_start, chunk_start) >= 0)
1997 			g_array_append_vals (occs, event_start, 1);
1998 	}
1999 
2000 	/* Expand each of the recurrence rules. */
2001 	for (elem = rrules; elem; elem = elem->next) {
2002 		ICalProperty *prop;
2003 		ECalRecurrence *r;
2004 
2005 		prop = elem->data;
2006 		r = e_cal_recur_from_icalproperty (
2007 			prop, FALSE, zone,
2008 			convert_end_date);
2009 
2010 		tmp_occs = cal_obj_expand_recurrence (
2011 			event_start, zone, r,
2012 			chunk_start,
2013 			chunk_end,
2014 			&rule_finished);
2015 		e_cal_recur_free (r);
2016 
2017 		/* If any of the rules return FALSE for finished, we know we
2018 		 * have to carry on so we set finished to FALSE. */
2019 		if (!rule_finished)
2020 			finished = FALSE;
2021 
2022 		g_array_append_vals (occs, tmp_occs->data, tmp_occs->len);
2023 		g_array_free (tmp_occs, TRUE);
2024 	}
2025 
2026 	/* Add on specific RDATE occurrence dates. If they have an end time
2027 	 * or duration set, flag them as RDATEs, and store a pointer to the
2028 	 * period in the rdate_periods array. Otherwise we can just treat them
2029 	 * as normal occurrences. */
2030 	for (elem = rdates; elem; elem = elem->next) {
2031 		ECalComponentPeriod *period = elem->data;
2032 		CalObjRecurrenceDate rdate;
2033 		ICalTime *tt;
2034 
2035 		tt = e_cal_component_period_get_start (period);
2036 		if (!tt)
2037 			continue;
2038 
2039 		tt = i_cal_time_clone (tt);
2040 		i_cal_time_convert_to_zone_inplace (tt, zone);
2041 		cotime.year = i_cal_time_get_year (tt);
2042 		cotime.month = i_cal_time_get_month (tt) - 1;
2043 		cotime.day = i_cal_time_get_day (tt);
2044 		cotime.hour = i_cal_time_get_hour (tt);
2045 		cotime.minute = i_cal_time_get_minute (tt);
2046 		cotime.second = i_cal_time_get_second (tt);
2047 		cotime.flags = FALSE;
2048 
2049 		g_clear_object (&tt);
2050 
2051 		/* If the rdate is after the current chunk we set finished
2052 		 * to FALSE, and we skip it. */
2053 		if (cal_obj_time_compare_func (&cotime, chunk_end) >= 0) {
2054 			finished = FALSE;
2055 			continue;
2056 		}
2057 
2058 		/* Check if the end date or duration is set. If it is we need
2059 		 * to store it so we can get it later. (libical seems to set
2060 		 * second to -1 to denote an unset time. See icalvalue.c)
2061 		 * FIXME. */
2062 		if (e_cal_component_period_get_kind (period) != E_CAL_COMPONENT_PERIOD_DATETIME ||
2063 		    !e_cal_component_period_get_end (period) ||
2064 		    i_cal_time_get_second (e_cal_component_period_get_end (period)) != -1) {
2065 			cotime.flags = TRUE;
2066 
2067 			rdate.start = cotime;
2068 			rdate.period = period;
2069 			g_array_append_val (rdate_periods, rdate);
2070 		}
2071 
2072 		g_array_append_val (occs, cotime);
2073 	}
2074 
2075 	/* Expand each of the exception rules. */
2076 	for (elem = exrules; elem; elem = elem->next) {
2077 		ICalProperty *prop;
2078 		ECalRecurrence *r;
2079 
2080 		prop = elem->data;
2081 		r = e_cal_recur_from_icalproperty (
2082 			prop, FALSE, zone,
2083 			convert_end_date);
2084 
2085 		tmp_occs = cal_obj_expand_recurrence (
2086 			event_start, zone, r,
2087 			chunk_start,
2088 			chunk_end,
2089 			&rule_finished);
2090 		e_cal_recur_free (r);
2091 
2092 		g_array_append_vals (ex_occs, tmp_occs->data, tmp_occs->len);
2093 		g_array_free (tmp_occs, TRUE);
2094 	}
2095 
2096 	/* Add on specific exception dates. */
2097 	for (elem = exdates; elem; elem = elem->next) {
2098 		ECalComponentDateTime *cdt;
2099 		ICalTime *tt;
2100 
2101 		cdt = elem->data;
2102 
2103 		if (!e_cal_component_datetime_get_value (cdt))
2104 			continue;
2105 
2106 		tt = e_cal_component_datetime_get_value (cdt);
2107 		if (!tt)
2108 			continue;
2109 
2110 		tt = i_cal_time_clone (tt);
2111 		i_cal_time_convert_to_zone_inplace (tt, zone);
2112 
2113 		cotime.year = i_cal_time_get_year (tt);
2114 		cotime.month = i_cal_time_get_month (tt) - 1;
2115 		cotime.day = i_cal_time_get_day (tt);
2116 
2117 		/* If the EXDATE has a DATE value, set the time to the start
2118 		 * of the day and set flags to TRUE so we know to skip all
2119 		 * occurrences on that date. */
2120 		if (i_cal_time_is_date (tt)) {
2121 			cotime.hour = 0;
2122 			cotime.minute = 0;
2123 			cotime.second = 0;
2124 			cotime.flags = TRUE;
2125 		} else {
2126 			cotime.hour = i_cal_time_get_hour (tt);
2127 			cotime.minute = i_cal_time_get_minute (tt);
2128 			cotime.second = i_cal_time_get_second (tt);
2129 			cotime.flags = FALSE;
2130 		}
2131 
2132 		g_array_append_val (ex_occs, cotime);
2133 
2134 		g_clear_object (&tt);
2135 	}
2136 
2137 	/* Sort all the arrays. */
2138 	cal_obj_sort_occurrences (occs);
2139 	cal_obj_sort_occurrences (ex_occs);
2140 
2141 	if (rdate_periods->data && rdate_periods->len) {
2142 		qsort (rdate_periods->data, rdate_periods->len,
2143 			sizeof (CalObjRecurrenceDate), cal_obj_time_compare_func);
2144 	}
2145 
2146 	/* Create the final array, by removing the exceptions from the
2147 	 * occurrences, and removing any duplicates. */
2148 	cal_obj_remove_exceptions (occs, ex_occs);
2149 
2150 	/* Call the callback for each occurrence. If it returns 0 we break
2151 	 * out of the loop. */
2152 	for (i = 0; i < occs->len; i++) {
2153 		ICalTime *start_tt, *end_tt;
2154 
2155 		/* Convert each CalObjTime into a start & end time_t, and
2156 		 * check it is within the bounds of the event & interval. */
2157 		occ = &g_array_index (occs, CalObjTime, i);
2158 #if 0
2159 		g_print (
2160 			"Checking occurrence: %s\n",
2161 			cal_obj_time_to_string (occ));
2162 #endif
2163 		start_tt = i_cal_time_new_null_time ();
2164 		i_cal_time_set_date (start_tt, occ->year, occ->month + 1, occ->day);
2165 		i_cal_time_set_time (start_tt, occ->hour, occ->minute, occ->second);
2166 		start_time = i_cal_time_as_timet_with_zone (start_tt, zone);
2167 		g_clear_object (&start_tt);
2168 
2169 		if (start_time == -1) {
2170 			g_warning ("time_t out of range");
2171 			finished = TRUE;
2172 			break;
2173 		}
2174 
2175 		/* Check to ensure that the start time is at or after the
2176 		 * event's DTSTART time, and that it is inside the chunk that
2177 		 * we are currently working on. (Note that the chunk_end time
2178 		 * is never after the interval end time, so this also tests
2179 		 * that we don't go past the end of the required interval). */
2180 		if (start_time < comp_dtstart
2181 		    || cal_obj_time_compare_func (occ, chunk_start) < 0
2182 		    || cal_obj_time_compare_func (occ, chunk_end) > 0) {
2183 #if 0
2184 			g_print ("  start time invalid\n");
2185 #endif
2186 			continue;
2187 		}
2188 
2189 		if (occ->flags) {
2190 			/* If it is an RDATE, we see if the end date or
2191 			 * duration was set. If not, we use the same duration
2192 			 * as the original occurrence. */
2193 			if (!cal_object_get_rdate_end (occ, rdate_periods, zone)) {
2194 				cal_obj_time_add_days (occ, duration_days);
2195 				cal_obj_time_add_seconds (
2196 					occ,
2197 					duration_seconds);
2198 			}
2199 		} else {
2200 			cal_obj_time_add_days (occ, duration_days);
2201 			cal_obj_time_add_seconds (occ, duration_seconds);
2202 		}
2203 
2204 		end_tt = i_cal_time_new_null_time ();
2205 		i_cal_time_set_date (end_tt, occ->year, occ->month + 1, occ->day);
2206 		i_cal_time_set_time (end_tt, occ->hour, occ->minute, occ->second);
2207 		end_time = i_cal_time_as_timet_with_zone (end_tt, zone);
2208 		g_clear_object (&end_tt);
2209 
2210 		if (end_time == -1) {
2211 			g_warning ("time_t out of range");
2212 			finished = TRUE;
2213 			break;
2214 		}
2215 
2216 		/* Check that the end time is after the interval start, so we
2217 		 * know that it intersects the required interval. */
2218 		if (end_time <= interval_start) {
2219 #if 0
2220 			g_print ("  end time invalid\n");
2221 #endif
2222 			continue;
2223 		}
2224 
2225 		cb_status = call_instance_callback (cb, comp, start_time, end_time, cb_data);
2226 		if (!cb_status)
2227 			break;
2228 	}
2229 
2230 	g_array_free (occs, TRUE);
2231 	g_array_free (ex_occs, TRUE);
2232 	g_array_free (rdate_periods, TRUE);
2233 
2234 	/* We return TRUE (i.e. carry on) only if the callback has always
2235 	 * returned TRUE and we know that we have more occurrences to generate
2236 	 * (i.e. finished is FALSE). */
2237 	return cb_status && !finished;
2238 }
2239 
2240 /* This looks up the occurrence time in the sorted rdate_periods array, and
2241  * tries to compute the end time of the occurrence. If no end time or duration
2242  * is set it returns FALSE and the default duration will be used. */
2243 static gboolean
cal_object_get_rdate_end(CalObjTime * occ,GArray * rdate_periods,ICalTimezone * zone)2244 cal_object_get_rdate_end (CalObjTime *occ,
2245 			  GArray *rdate_periods,
2246 			  ICalTimezone *zone)
2247 {
2248 	CalObjRecurrenceDate *rdate = NULL;
2249 	ECalComponentPeriod *period;
2250 	gint lower, upper, middle, cmp = 0;
2251 
2252 	lower = 0;
2253 	upper = rdate_periods->len;
2254 
2255 	while (lower < upper) {
2256 		middle = (lower + upper) >> 1;
2257 
2258 		rdate = &g_array_index (rdate_periods, CalObjRecurrenceDate,
2259 					middle);
2260 
2261 		cmp = cal_obj_time_compare_func (occ, &rdate->start);
2262 
2263 		if (cmp == 0)
2264 			break;
2265 		else if (cmp < 0)
2266 			upper = middle;
2267 		else
2268 			lower = middle + 1;
2269 	}
2270 
2271 	/* This should never happen. */
2272 	if (cmp == 0 || !rdate) {
2273 		#ifdef CAL_OBJ_DEBUG
2274 		g_debug ("%s: Recurrence date %s not found", G_STRFUNC, cal_obj_time_to_string (cc));
2275 		#endif
2276 		return FALSE;
2277 	}
2278 
2279 	period = rdate->period;
2280 	if (e_cal_component_period_get_kind (period) == E_CAL_COMPONENT_PERIOD_DATETIME) {
2281 		ICalTime *end;
2282 
2283 		end = e_cal_component_period_get_end (period);
2284 		g_warn_if_fail (end != NULL);
2285 		if (end) {
2286 			ICalTime *tt;
2287 
2288 			tt = i_cal_time_convert_to_zone (end, zone);
2289 
2290 			occ->year = i_cal_time_get_year (tt);
2291 			occ->month = i_cal_time_get_month (tt) - 1;
2292 			occ->day = i_cal_time_get_day (tt);
2293 			occ->hour = i_cal_time_get_hour (tt);
2294 			occ->minute = i_cal_time_get_minute (tt);
2295 			occ->second = i_cal_time_get_second (tt);
2296 			occ->flags = FALSE;
2297 
2298 			g_clear_object (&tt);
2299 		}
2300 	} else {
2301 		ICalDuration *duration;
2302 
2303 		duration = e_cal_component_period_get_duration (period);
2304 
2305 		cal_obj_time_add_days (
2306 			occ,
2307 			i_cal_duration_get_weeks (duration) * 7 +
2308 			i_cal_duration_get_days (duration));
2309 		cal_obj_time_add_hours (occ, i_cal_duration_get_hours (duration));
2310 		cal_obj_time_add_minutes (occ, i_cal_duration_get_minutes (duration));
2311 		cal_obj_time_add_seconds (occ, i_cal_duration_get_seconds (duration));
2312 	}
2313 
2314 	return TRUE;
2315 }
2316 
2317 static void
cal_object_compute_duration(CalObjTime * start,CalObjTime * end,gint * days,gint * seconds)2318 cal_object_compute_duration (CalObjTime *start,
2319                              CalObjTime *end,
2320                              gint *days,
2321                              gint *seconds)
2322 {
2323 	GDate start_date, end_date;
2324 	gint start_seconds, end_seconds;
2325 
2326 	g_date_clear (&start_date, 1);
2327 	g_date_clear (&end_date, 1);
2328 	g_date_set_dmy (
2329 		&start_date, start->day, start->month + 1, start->year);
2330 	g_date_set_dmy (
2331 		&end_date, end->day, end->month + 1, end->year);
2332 
2333 	*days = g_date_get_julian (&end_date) - g_date_get_julian (&start_date);
2334 	start_seconds = start->hour * 3600 + start->minute * 60
2335 		+ start->second;
2336 	end_seconds = end->hour * 3600 + end->minute * 60 + end->second;
2337 
2338 	*seconds = end_seconds - start_seconds;
2339 	if (*seconds < 0) {
2340 		*days = *days - 1;
2341 		*seconds += 24 * 60 * 60;
2342 	}
2343 }
2344 
2345 /* Returns an unsorted GArray of CalObjTime's resulting from expanding the
2346  * given recurrence rule within the given interval. Note that it doesn't
2347  * clip the generated occurrences to the interval, i.e. if the interval
2348  * starts part way through the year this function still returns all the
2349  * occurrences for the year. Clipping is done later.
2350  * The finished flag is set to FALSE if there are more occurrences to generate
2351  * after the given interval.*/
2352 static GArray *
cal_obj_expand_recurrence(CalObjTime * event_start,ICalTimezone * zone,ECalRecurrence * recur,CalObjTime * interval_start,CalObjTime * interval_end,gboolean * finished)2353 cal_obj_expand_recurrence (CalObjTime *event_start,
2354                            ICalTimezone *zone,
2355                            ECalRecurrence *recur,
2356                            CalObjTime *interval_start,
2357                            CalObjTime *interval_end,
2358                            gboolean *finished)
2359 {
2360 	ECalRecurVTable vtable;
2361 	CalObjTime *event_end = NULL, event_end_cotime;
2362 	RecurData recur_data;
2363 	CalObjTime occ, *cotime;
2364 	GArray *all_occs, *occs;
2365 	gint len;
2366 
2367 	/* This is the resulting array of CalObjTime elements. */
2368 	all_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2369 
2370 	*finished = TRUE;
2371 
2372 	if (!cal_obj_get_vtable (recur, &vtable))
2373 		return all_occs;
2374 
2375 	/* Calculate some useful data such as some fast lookup tables. */
2376 	cal_obj_initialize_recur_data (&recur_data, recur, event_start);
2377 
2378 	/* Compute the event_end, if the recur's enddate is set. */
2379 	if (recur->enddate > 0) {
2380 		cal_object_time_from_time (
2381 			&event_end_cotime,
2382 			recur->enddate, zone);
2383 		event_end = &event_end_cotime;
2384 
2385 		/* If the enddate is before the requested interval return. */
2386 		if (cal_obj_time_compare_func (event_end, interval_start) < 0)
2387 			return all_occs;
2388 	}
2389 
2390 	/* Set finished to FALSE if we know there will be more occurrences to
2391 	 * do after this interval. */
2392 	if (!interval_end || !event_end
2393 	    || cal_obj_time_compare_func (event_end, interval_end) > 0)
2394 		*finished = FALSE;
2395 
2396 	/* Get the first period based on the frequency and the interval that
2397 	 * intersects the interval between start and end. */
2398 	if ((*vtable.find_start_position) (event_start, event_end,
2399 					    &recur_data,
2400 					    interval_start, interval_end,
2401 					    &occ))
2402 		return all_occs;
2403 
2404 	/* Loop until the event ends or we go past the end of the required
2405 	 * interval. */
2406 	for (;;) {
2407 		/* Generate the set of occurrences for this period. */
2408 		switch (recur->freq) {
2409 		case I_CAL_YEARLY_RECURRENCE:
2410 			occs = cal_obj_generate_set_yearly (
2411 				&recur_data,
2412 				&vtable, &occ);
2413 			break;
2414 		case I_CAL_MONTHLY_RECURRENCE:
2415 			occs = cal_obj_generate_set_monthly (
2416 				&recur_data,
2417 				&vtable, &occ);
2418 			break;
2419 		default:
2420 			occs = cal_obj_generate_set_default (
2421 				&recur_data,
2422 				&vtable, &occ);
2423 			break;
2424 		}
2425 
2426 		/* Sort the occurrences and remove duplicates. */
2427 		cal_obj_sort_occurrences (occs);
2428 		cal_obj_remove_duplicates_and_invalid_dates (occs);
2429 
2430 		/* Apply the BYSETPOS property. */
2431 		occs = cal_obj_bysetpos_filter (recur, occs);
2432 
2433 		/* Remove any occs after event_end. */
2434 		len = occs->len - 1;
2435 		if (event_end) {
2436 			while (len >= 0) {
2437 				cotime = &g_array_index (occs, CalObjTime,
2438 							 len);
2439 				if (cal_obj_time_compare_func (cotime,
2440 							       event_end) <= 0)
2441 					break;
2442 				len--;
2443 			}
2444 		}
2445 
2446 		/* Add the occurrences onto the main array. */
2447 		if (len >= 0)
2448 			g_array_append_vals (all_occs, occs->data, len + 1);
2449 
2450 		g_array_free (occs, TRUE);
2451 
2452 		/* Skip to the next period, or exit the loop if finished. */
2453 		if ((*vtable.find_next_position) (&occ, event_end,
2454 						   &recur_data, interval_end))
2455 			break;
2456 	}
2457 
2458 	return all_occs;
2459 }
2460 
2461 static GArray *
cal_obj_generate_set_yearly(RecurData * recur_data,ECalRecurVTable * vtable,CalObjTime * occ)2462 cal_obj_generate_set_yearly (RecurData *recur_data,
2463                              ECalRecurVTable *vtable,
2464                              CalObjTime *occ)
2465 {
2466 	ECalRecurrence *recur = recur_data->recur;
2467 	GArray *occs_arrays[4], *occs, *occs2;
2468 	gint num_occs_arrays = 0, i;
2469 
2470 	/* This is a bit complicated, since the iCalendar spec says that
2471 	 * several BYxxx modifiers can be used simultaneously. So we have to
2472 	 * be quite careful when determining the days of the occurrences.
2473 	 * The BYHOUR, BYMINUTE & BYSECOND modifiers are no problem at all.
2474 	 *
2475 	 * The modifiers we have to worry about are: BYMONTH, BYWEEKNO,
2476 	 * BYYEARDAY, BYMONTHDAY & BYDAY. We can't do these sequentially
2477 	 * since each filter will mess up the results of the previous one.
2478 	 * But they aren't all completely independant, e.g. BYMONTHDAY and
2479 	 * BYDAY are related to BYMONTH, and BYDAY is related to BYWEEKNO.
2480 	 *
2481 	 * BYDAY & BYMONTHDAY can also be applied independently, which makes
2482 	 * it worse. So we assume that if BYMONTH or BYWEEKNO is used, then
2483 	 * the BYDAY modifier applies to those, else it is applied
2484 	 * independantly.
2485 	 *
2486 	 * We expand the occurrences in parallel into the occs_arrays[] array,
2487 	 * and then merge them all into one GArray before expanding BYHOUR,
2488 	 * BYMINUTE & BYSECOND. */
2489 
2490 	if (recur->bymonth) {
2491 		occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2492 		g_array_append_vals (occs, occ, 1);
2493 
2494 		occs = (*vtable->bymonth_filter) (recur_data, occs);
2495 
2496 		/* If BYMONTHDAY & BYDAY are both set we need to expand them
2497 		 * in parallel and add the results. */
2498 		if (recur->bymonthday && recur->byday) {
2499 			CalObjTime *prev_occ = NULL;
2500 			GArray *new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2501 
2502 			/* Copy the occs array. */
2503 			occs2 = g_array_new (
2504 				FALSE, FALSE,
2505 				sizeof (CalObjTime));
2506 			g_array_append_vals (occs2, occs->data, occs->len);
2507 
2508 			occs = (*vtable->bymonthday_filter) (recur_data, occs);
2509 			/* Note that we explicitly call the monthly version
2510 			 * of the BYDAY expansion filter. */
2511 			occs2 = cal_obj_byday_expand_monthly (
2512 				recur_data,
2513 				occs2);
2514 
2515 			/* Add only intersection of those two arrays. */
2516 			g_array_append_vals (occs, occs2->data, occs2->len);
2517 			cal_obj_sort_occurrences (occs);
2518 			for (i = 0; i < occs->len; i++) {
2519 				CalObjTime *act_occ = &g_array_index (occs, CalObjTime, i);
2520 
2521 				if (prev_occ && cal_obj_time_compare_func (act_occ, prev_occ) == 0) {
2522 					prev_occ = NULL;
2523 					g_array_append_vals (new_occs, act_occ, 1);
2524 				} else {
2525 					prev_occ = act_occ;
2526 				}
2527 			}
2528 
2529 			g_array_free (occs, TRUE);
2530 			g_array_free (occs2, TRUE);
2531 
2532 			occs = new_occs;
2533 		} else {
2534 			occs = (*vtable->bymonthday_filter) (recur_data, occs);
2535 			/* Note that we explicitly call the monthly version
2536 			 * of the BYDAY expansion filter. */
2537 			occs = cal_obj_byday_expand_monthly (recur_data, occs);
2538 		}
2539 
2540 		occs_arrays[num_occs_arrays++] = occs;
2541 	}
2542 
2543 	if (recur->byweekno) {
2544 		occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2545 		g_array_append_vals (occs, occ, 1);
2546 
2547 		occs = (*vtable->byweekno_filter) (recur_data, occs);
2548 		/* Note that we explicitly call the weekly version of the
2549 		 * BYDAY expansion filter. */
2550 		occs = cal_obj_byday_expand_weekly (recur_data, occs);
2551 
2552 		occs_arrays[num_occs_arrays++] = occs;
2553 	}
2554 
2555 	if (recur->byyearday) {
2556 		occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2557 		g_array_append_vals (occs, occ, 1);
2558 
2559 		occs = (*vtable->byyearday_filter) (recur_data, occs);
2560 
2561 		occs_arrays[num_occs_arrays++] = occs;
2562 	}
2563 
2564 	/* If BYMONTHDAY is set, and BYMONTH is not set, we need to
2565 	 * expand BYMONTHDAY independantly. */
2566 	if (recur->bymonthday && !recur->bymonth) {
2567 		occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2568 		g_array_append_vals (occs, occ, 1);
2569 
2570 		occs = (*vtable->bymonthday_filter) (recur_data, occs);
2571 
2572 		occs_arrays[num_occs_arrays++] = occs;
2573 	}
2574 
2575 	/* If BYDAY is set, and BYMONTH and BYWEEKNO are not set, we need to
2576 	 * expand BYDAY independantly. */
2577 	if (recur->byday && !recur->bymonth && !recur->byweekno) {
2578 		occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2579 		g_array_append_vals (occs, occ, 1);
2580 
2581 		occs = (*vtable->byday_filter) (recur_data, occs);
2582 
2583 		occs_arrays[num_occs_arrays++] = occs;
2584 	}
2585 
2586 	/* Add all the arrays together. If no filters were used we just
2587 	 * create an array with one element. */
2588 	if (num_occs_arrays > 0) {
2589 		occs = occs_arrays[0];
2590 		for (i = 1; i < num_occs_arrays; i++) {
2591 			occs2 = occs_arrays[i];
2592 			g_array_append_vals (occs, occs2->data, occs2->len);
2593 			g_array_free (occs2, TRUE);
2594 		}
2595 	} else {
2596 		occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2597 		g_array_append_vals (occs, occ, 1);
2598 	}
2599 
2600 	/* Now expand BYHOUR, BYMINUTE & BYSECOND. */
2601 	occs = (*vtable->byhour_filter) (recur_data, occs);
2602 	occs = (*vtable->byminute_filter) (recur_data, occs);
2603 	occs = (*vtable->bysecond_filter) (recur_data, occs);
2604 
2605 	return occs;
2606 }
2607 
2608 static GArray *
cal_obj_generate_set_monthly(RecurData * recur_data,ECalRecurVTable * vtable,CalObjTime * occ)2609 cal_obj_generate_set_monthly (RecurData *recur_data,
2610                               ECalRecurVTable *vtable,
2611                               CalObjTime *occ)
2612 {
2613 	GArray *occs;
2614 
2615 	/* We start with just the one time in each set. */
2616 	occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2617 	g_array_append_vals (occs, occ, 1);
2618 
2619 	occs = (*vtable->bymonth_filter) (recur_data, occs);
2620 
2621 	/* We need to combine the output of BYMONTHDAY & BYDAY, by doing them
2622 	 * in parallel rather than sequentially. If we did them sequentially
2623 	 * then we would lose the occurrences generated by BYMONTHDAY, and
2624 	 * instead have repetitions of the occurrences from BYDAY. */
2625 	if (recur_data->recur->bymonthday && recur_data->recur->byday) {
2626 		occs = (*vtable->byday_filter) (recur_data, occs);
2627 		occs = (*vtable->bymonthday_filter) (recur_data, occs);
2628 	} else {
2629 		occs = (*vtable->bymonthday_filter) (recur_data, occs);
2630 		occs = (*vtable->byday_filter) (recur_data, occs);
2631 	}
2632 
2633 	occs = (*vtable->byhour_filter) (recur_data, occs);
2634 	occs = (*vtable->byminute_filter) (recur_data, occs);
2635 	occs = (*vtable->bysecond_filter) (recur_data, occs);
2636 
2637 	return occs;
2638 }
2639 
2640 static GArray *
cal_obj_generate_set_default(RecurData * recur_data,ECalRecurVTable * vtable,CalObjTime * occ)2641 cal_obj_generate_set_default (RecurData *recur_data,
2642                               ECalRecurVTable *vtable,
2643                               CalObjTime *occ)
2644 {
2645 	GArray *occs;
2646 
2647 #if 0
2648 	g_print (
2649 		"Generating set for %i/%i/%i %02i:%02i:%02i\n",
2650 		occ->day, occ->month + 1, occ->year, occ->hour, occ->minute,
2651 		occ->second);
2652 #endif
2653 
2654 	/* We start with just the one time in the set. */
2655 	occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2656 	g_array_append_vals (occs, occ, 1);
2657 
2658 	occs = (*vtable->bymonth_filter) (recur_data, occs);
2659 	if (vtable->byweekno_filter)
2660 		occs = (*vtable->byweekno_filter) (recur_data, occs);
2661 	if (vtable->byyearday_filter)
2662 		occs = (*vtable->byyearday_filter) (recur_data, occs);
2663 	if (vtable->bymonthday_filter)
2664 		occs = (*vtable->bymonthday_filter) (recur_data, occs);
2665 	occs = (*vtable->byday_filter) (recur_data, occs);
2666 
2667 	occs = (*vtable->byhour_filter) (recur_data, occs);
2668 	occs = (*vtable->byminute_filter) (recur_data, occs);
2669 	occs = (*vtable->bysecond_filter) (recur_data, occs);
2670 
2671 	return occs;
2672 }
2673 
2674 /* Returns the function table corresponding to the recurrence frequency. */
2675 static gboolean
cal_obj_get_vtable(ECalRecurrence * recur,ECalRecurVTable * out_vtable)2676 cal_obj_get_vtable (ECalRecurrence *recur,
2677 		    ECalRecurVTable *out_vtable)
2678 {
2679 	gboolean valid = TRUE;
2680 
2681 	switch (recur->freq) {
2682 	case I_CAL_YEARLY_RECURRENCE:
2683 		*out_vtable = cal_obj_yearly_vtable;
2684 		break;
2685 	case I_CAL_MONTHLY_RECURRENCE:
2686 		*out_vtable = cal_obj_monthly_vtable;
2687 		if (recur->bymonthday && recur->byday)
2688 			out_vtable->bymonthday_filter = cal_obj_bymonthday_filter;
2689 		else
2690 			out_vtable->bymonthday_filter = cal_obj_bymonthday_expand;
2691 		break;
2692 	case I_CAL_WEEKLY_RECURRENCE:
2693 		*out_vtable = cal_obj_weekly_vtable;
2694 		break;
2695 	case I_CAL_DAILY_RECURRENCE:
2696 		*out_vtable = cal_obj_daily_vtable;
2697 		break;
2698 	case I_CAL_HOURLY_RECURRENCE:
2699 		*out_vtable = cal_obj_hourly_vtable;
2700 		break;
2701 	case I_CAL_MINUTELY_RECURRENCE:
2702 		*out_vtable = cal_obj_minutely_vtable;
2703 		break;
2704 	case I_CAL_SECONDLY_RECURRENCE:
2705 		*out_vtable = cal_obj_secondly_vtable;
2706 		break;
2707 	default:
2708 		g_warning ("Unknown recurrence frequency");
2709 		valid = FALSE;
2710 	}
2711 
2712 	return valid;
2713 }
2714 
2715 /* This creates a number of fast lookup tables used when filtering with the
2716  * modifier properties BYMONTH, BYYEARDAY etc. */
2717 static void
cal_obj_initialize_recur_data(RecurData * recur_data,ECalRecurrence * recur,CalObjTime * event_start)2718 cal_obj_initialize_recur_data (RecurData *recur_data,
2719                                ECalRecurrence *recur,
2720                                CalObjTime *event_start)
2721 {
2722 	GList *elem;
2723 	gint month, yearday, monthday, weekday, hour, minute, second;
2724 
2725 	/* Clear the entire RecurData. */
2726 	memset (recur_data, 0, sizeof (RecurData));
2727 
2728 	recur_data->recur = recur;
2729 
2730 	/* Set the weekday, used for the WEEKLY frequency and the BYWEEKNO
2731 	 * modifier. */
2732 	recur_data->weekday_offset = cal_obj_time_weekday_offset (
2733 		event_start,
2734 		recur);
2735 
2736 	/* Create an array of months from bymonths for fast lookup. */
2737 	elem = recur->bymonth;
2738 	while (elem) {
2739 		month = GPOINTER_TO_INT (elem->data);
2740 		recur_data->months[month] = 1;
2741 		elem = elem->next;
2742 	}
2743 
2744 	/* Create an array of yeardays from byyearday for fast lookup.
2745 	 * We create a second array to handle the negative values. The first
2746 	 * element there corresponds to the last day of the year. */
2747 	elem = recur->byyearday;
2748 	while (elem) {
2749 		yearday = GPOINTER_TO_INT (elem->data);
2750 		if (yearday >= 0)
2751 			recur_data->yeardays[yearday] = 1;
2752 		else
2753 			recur_data->neg_yeardays[-yearday] = 1;
2754 		elem = elem->next;
2755 	}
2756 
2757 	/* Create an array of monthdays from bymonthday for fast lookup.
2758 	 * We create a second array to handle the negative values. The first
2759 	 * element there corresponds to the last day of the month. */
2760 	elem = recur->bymonthday;
2761 	while (elem) {
2762 		monthday = GPOINTER_TO_INT (elem->data);
2763 		if (monthday >= 0)
2764 			recur_data->monthdays[monthday] = 1;
2765 		else
2766 			recur_data->neg_monthdays[-monthday] = 1;
2767 		elem = elem->next;
2768 	}
2769 
2770 	/* Create an array of weekdays from byday for fast lookup. */
2771 	elem = recur->byday;
2772 	while (elem) {
2773 		weekday = GPOINTER_TO_INT (elem->data);
2774 		recur_data->weekdays[weekday] = 1;
2775 		elem = elem->next;
2776 		/* the second element is week_num, skip it */
2777 		if (elem)
2778 			elem = elem->next;
2779 	}
2780 
2781 	/* Create an array of hours from byhour for fast lookup. */
2782 	elem = recur->byhour;
2783 	while (elem) {
2784 		hour = GPOINTER_TO_INT (elem->data);
2785 		recur_data->hours[hour] = 1;
2786 		elem = elem->next;
2787 	}
2788 
2789 	/* Create an array of minutes from byminutes for fast lookup. */
2790 	elem = recur->byminute;
2791 	while (elem) {
2792 		minute = GPOINTER_TO_INT (elem->data);
2793 		recur_data->minutes[minute] = 1;
2794 		elem = elem->next;
2795 	}
2796 
2797 	/* Create an array of seconds from byseconds for fast lookup. */
2798 	elem = recur->bysecond;
2799 	while (elem) {
2800 		second = GPOINTER_TO_INT (elem->data);
2801 		recur_data->seconds[second] = 1;
2802 		elem = elem->next;
2803 	}
2804 }
2805 
2806 static void
cal_obj_sort_occurrences(GArray * occs)2807 cal_obj_sort_occurrences (GArray *occs)
2808 {
2809 	if (occs->data && occs->len) {
2810 		qsort (occs->data, occs->len, sizeof (CalObjTime),
2811 			cal_obj_time_compare_func);
2812 	}
2813 }
2814 
2815 static void
cal_obj_remove_duplicates_and_invalid_dates(GArray * occs)2816 cal_obj_remove_duplicates_and_invalid_dates (GArray *occs)
2817 {
2818 	static const gint days_in_month[12] = {
2819 		31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
2820 	};
2821 
2822 	CalObjTime *occ, *prev_occ = NULL;
2823 	gint len, i, j = 0, year, month, days;
2824 	gboolean keep_occ;
2825 
2826 	len = occs->len;
2827 	for (i = 0; i < len; i++) {
2828 		occ = &g_array_index (occs, CalObjTime, i);
2829 		keep_occ = TRUE;
2830 
2831 		if (prev_occ && cal_obj_time_compare_func (occ,
2832 							   prev_occ) == 0)
2833 			keep_occ = FALSE;
2834 
2835 		year = occ->year;
2836 		month = occ->month;
2837 		days = days_in_month[occ->month];
2838 		/* If it is february and a leap year, add a day. */
2839 		if (month == 1 && (year % 4 == 0
2840 				   && (year % 100 != 0
2841 				       || year % 400 == 0)))
2842 			days++;
2843 
2844 		if (occ->day > days) {
2845 			/* move occurrence to the last day of the month */
2846 			occ->day = days;
2847 		}
2848 
2849 		if (keep_occ) {
2850 			if (i != j)
2851 				g_array_index (occs, CalObjTime, j)
2852  = g_array_index (occs, CalObjTime, i);
2853 			j++;
2854 		}
2855 
2856 		prev_occ = occ;
2857 	}
2858 
2859 	g_array_set_size (occs, j);
2860 }
2861 
2862 /* Removes the exceptions from the ex_occs array from the occurrences in the
2863  * occs array, and removes any duplicates. Both arrays are sorted. */
2864 static void
cal_obj_remove_exceptions(GArray * occs,GArray * ex_occs)2865 cal_obj_remove_exceptions (GArray *occs,
2866                            GArray *ex_occs)
2867 {
2868 	CalObjTime *occ, *prev_occ = NULL, *ex_occ = NULL, *last_occ_kept;
2869 	gint i, j = 0, cmp, ex_index, occs_len, ex_occs_len;
2870 	gboolean keep_occ, current_time_is_exception = FALSE;
2871 
2872 	if (occs->len == 0)
2873 		return;
2874 
2875 	ex_index = 0;
2876 	occs_len = occs->len;
2877 	ex_occs_len = ex_occs->len;
2878 
2879 	if (ex_occs_len > 0)
2880 		ex_occ = &g_array_index (ex_occs, CalObjTime, ex_index);
2881 
2882 	for (i = 0; i < occs_len; i++) {
2883 		occ = &g_array_index (occs, CalObjTime, i);
2884 		keep_occ = TRUE;
2885 
2886 		/* If the occurrence is a duplicate of the previous one, skip
2887 		 * it. */
2888 		if (prev_occ
2889 		    && cal_obj_time_compare_func (occ, prev_occ) == 0) {
2890 			keep_occ = FALSE;
2891 
2892 			/* If this occurrence is an RDATE with an end or
2893 			 * duration set, and the previous occurrence in the
2894 			 * array was kept, set the RDATE flag of the last one,
2895 			 * so we still use the end date or duration. */
2896 			if (occ->flags && !current_time_is_exception) {
2897 				last_occ_kept = &g_array_index (occs,
2898 								CalObjTime,
2899 								j - 1);
2900 				last_occ_kept->flags = TRUE;
2901 			}
2902 		} else {
2903 			/* We've found a new occurrence time. Reset the flag
2904 			 * to indicate that it hasn't been found in the
2905 			 * exceptions array (yet). */
2906 			current_time_is_exception = FALSE;
2907 
2908 			if (ex_occ) {
2909 				/* Step through the exceptions until we come
2910 				 * to one that matches or follows this
2911 				 * occurrence. */
2912 				while (ex_occ) {
2913 					/* If the exception is an EXDATE with
2914 					 * a DATE value, we only have to
2915 					 * compare the date. */
2916 					if (ex_occ->flags)
2917 						cmp = cal_obj_date_only_compare_func (ex_occ, occ);
2918 					else
2919 						cmp = cal_obj_time_compare_func (ex_occ, occ);
2920 
2921 					if (cmp > 0)
2922 						break;
2923 
2924 					/* Move to the next exception, or set
2925 					 * ex_occ to NULL when we reach the
2926 					 * end of array. */
2927 					ex_index++;
2928 					if (ex_index < ex_occs_len)
2929 						ex_occ = &g_array_index (ex_occs, CalObjTime, ex_index);
2930 					else
2931 						ex_occ = NULL;
2932 
2933 					/* If the exception did match this
2934 					 * occurrence we remove it, and set the
2935 					 * flag to indicate that the current
2936 					 * time is an exception. */
2937 					if (cmp == 0) {
2938 						current_time_is_exception = TRUE;
2939 						keep_occ = FALSE;
2940 						break;
2941 					}
2942 				}
2943 			}
2944 		}
2945 
2946 		if (keep_occ) {
2947 			/* We are keeping this occurrence, so we move it to
2948 			 * the next free space, unless its position hasn't
2949 			 * changed (i.e. all previous occurrences were also
2950 			 * kept). */
2951 			if (i != j)
2952 				g_array_index (occs, CalObjTime, j)
2953  = g_array_index (occs, CalObjTime, i);
2954 			j++;
2955 		}
2956 
2957 		prev_occ = occ;
2958 	}
2959 
2960 	g_array_set_size (occs, j);
2961 }
2962 
2963 static GArray *
cal_obj_bysetpos_filter(ECalRecurrence * recur,GArray * occs)2964 cal_obj_bysetpos_filter (ECalRecurrence *recur,
2965                          GArray *occs)
2966 {
2967 	GArray *new_occs;
2968 	CalObjTime *occ;
2969 	GList *elem;
2970 	gint len, pos;
2971 
2972 	/* If BYSETPOS has not been specified, or the array is empty, just
2973 	 * return the array. */
2974 	elem = recur->bysetpos;
2975 	if (!elem || occs->len == 0)
2976 		return occs;
2977 
2978 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
2979 
2980 	/* Iterate over the indices given in bysetpos, adding the corresponding
2981 	 * element from occs to new_occs. */
2982 	len = occs->len;
2983 	while (elem) {
2984 		pos = GPOINTER_TO_INT (elem->data);
2985 
2986 		/* Negative values count back from the end of the array. */
2987 		if (pos < 0)
2988 			pos += len;
2989 		/* Positive values need to be decremented since the array is
2990 		 * 0 - based. */
2991 		else
2992 			pos--;
2993 
2994 		if (pos >= 0 && pos < len) {
2995 			occ = &g_array_index (occs, CalObjTime, pos);
2996 			g_array_append_vals (new_occs, occ, 1);
2997 		}
2998 		elem = elem->next;
2999 	}
3000 
3001 	g_array_free (occs, TRUE);
3002 
3003 	return new_occs;
3004 }
3005 
3006 /* Finds the first year from the event_start, counting in multiples of the
3007  * recurrence interval, that intersects the given interval. It returns TRUE
3008  * if there is no intersection. */
3009 static gboolean
cal_obj_yearly_find_start_position(CalObjTime * event_start,CalObjTime * event_end,RecurData * recur_data,CalObjTime * interval_start,CalObjTime * interval_end,CalObjTime * cotime)3010 cal_obj_yearly_find_start_position (CalObjTime *event_start,
3011                                     CalObjTime *event_end,
3012                                     RecurData *recur_data,
3013                                     CalObjTime *interval_start,
3014                                     CalObjTime *interval_end,
3015                                     CalObjTime *cotime)
3016 {
3017 	*cotime = *event_start;
3018 
3019 	/* Move on to the next interval, if the event starts before the
3020 	 * given interval. */
3021 	if (cotime->year < interval_start->year) {
3022 		gint years = interval_start->year - cotime->year
3023 			+ recur_data->recur->interval - 1;
3024 		years -= years % recur_data->recur->interval;
3025 		/* NOTE: The day may now be invalid, e.g. 29th Feb. */
3026 		cotime->year += years;
3027 	}
3028 
3029 	if ((event_end && cotime->year > event_end->year)
3030 	    || (interval_end && cotime->year > interval_end->year))
3031 		return TRUE;
3032 
3033 	return FALSE;
3034 }
3035 
3036 static gboolean
cal_obj_yearly_find_next_position(CalObjTime * cotime,CalObjTime * event_end,RecurData * recur_data,CalObjTime * interval_end)3037 cal_obj_yearly_find_next_position (CalObjTime *cotime,
3038                                    CalObjTime *event_end,
3039                                    RecurData *recur_data,
3040                                    CalObjTime *interval_end)
3041 {
3042 	/* NOTE: The day may now be invalid, e.g. 29th Feb. */
3043 	cotime->year += recur_data->recur->interval;
3044 
3045 	if ((event_end && cotime->year > event_end->year)
3046 	    || (interval_end && cotime->year > interval_end->year))
3047 		return TRUE;
3048 
3049 	return FALSE;
3050 }
3051 
3052 static gboolean
cal_obj_monthly_find_start_position(CalObjTime * event_start,CalObjTime * event_end,RecurData * recur_data,CalObjTime * interval_start,CalObjTime * interval_end,CalObjTime * cotime)3053 cal_obj_monthly_find_start_position (CalObjTime *event_start,
3054                                      CalObjTime *event_end,
3055                                      RecurData *recur_data,
3056                                      CalObjTime *interval_start,
3057                                      CalObjTime *interval_end,
3058                                      CalObjTime *cotime)
3059 {
3060 	*cotime = *event_start;
3061 
3062 	/* Move on to the next interval, if the event starts before the
3063 	 * given interval. */
3064 	if (cal_obj_time_compare (cotime, interval_start, CALOBJ_MONTH) < 0) {
3065 		gint months = (interval_start->year - cotime->year) * 12
3066 			+ interval_start->month - cotime->month
3067 			+ recur_data->recur->interval - 1;
3068 		months -= months % recur_data->recur->interval;
3069 		/* NOTE: The day may now be invalid, e.g. 31st Sep. */
3070 		cal_obj_time_add_months (cotime, months);
3071 	}
3072 
3073 	if (event_end && cal_obj_time_compare (cotime, event_end,
3074 					       CALOBJ_MONTH) > 0)
3075 		return TRUE;
3076 	if (interval_end && cal_obj_time_compare (cotime, interval_end,
3077 						  CALOBJ_MONTH) > 0)
3078 		return TRUE;
3079 
3080 	return FALSE;
3081 }
3082 
3083 static gboolean
cal_obj_monthly_find_next_position(CalObjTime * cotime,CalObjTime * event_end,RecurData * recur_data,CalObjTime * interval_end)3084 cal_obj_monthly_find_next_position (CalObjTime *cotime,
3085                                     CalObjTime *event_end,
3086                                     RecurData *recur_data,
3087                                     CalObjTime *interval_end)
3088 {
3089 	/* NOTE: The day may now be invalid, e.g. 31st Sep. */
3090 	cal_obj_time_add_months (cotime, recur_data->recur->interval);
3091 
3092 	if (event_end && cal_obj_time_compare (cotime, event_end,
3093 					       CALOBJ_MONTH) > 0)
3094 		return TRUE;
3095 	if (interval_end && cal_obj_time_compare (cotime, interval_end,
3096 						  CALOBJ_MONTH) > 0)
3097 		return TRUE;
3098 
3099 	return FALSE;
3100 }
3101 
3102 static gboolean
cal_obj_weekly_find_start_position(CalObjTime * event_start,CalObjTime * event_end,RecurData * recur_data,CalObjTime * interval_start,CalObjTime * interval_end,CalObjTime * cotime)3103 cal_obj_weekly_find_start_position (CalObjTime *event_start,
3104                                     CalObjTime *event_end,
3105                                     RecurData *recur_data,
3106                                     CalObjTime *interval_start,
3107                                     CalObjTime *interval_end,
3108                                     CalObjTime *cotime)
3109 {
3110 	GDate event_start_date, interval_start_date;
3111 	guint32 event_start_julian, interval_start_julian;
3112 	gint interval_start_weekday_offset;
3113 	CalObjTime week_start;
3114 
3115 	if (event_end && cal_obj_time_compare (event_end, interval_start,
3116 					       CALOBJ_DAY) < 0)
3117 		return TRUE;
3118 	if (interval_end && cal_obj_time_compare (event_start, interval_end,
3119 						  CALOBJ_DAY) > 0)
3120 		return TRUE;
3121 
3122 	*cotime = *event_start;
3123 
3124 	/* Convert the event start and interval start to GDates, so we can
3125 	 * easily find the number of days between them. */
3126 	g_date_clear (&event_start_date, 1);
3127 	g_date_set_dmy (
3128 		&event_start_date, event_start->day,
3129 		event_start->month + 1, event_start->year);
3130 	g_date_clear (&interval_start_date, 1);
3131 	g_date_set_dmy (
3132 		&interval_start_date, interval_start->day,
3133 		interval_start->month + 1, interval_start->year);
3134 
3135 	/* Calculate the start of the weeks corresponding to the event start
3136 	 * and interval start. */
3137 	event_start_julian = g_date_get_julian (&event_start_date);
3138 	event_start_julian -= recur_data->weekday_offset;
3139 
3140 	interval_start_julian = g_date_get_julian (&interval_start_date);
3141 	interval_start_weekday_offset = cal_obj_time_weekday_offset (interval_start, recur_data->recur);
3142 	interval_start_julian -= interval_start_weekday_offset;
3143 
3144 	/* We want to find the first full week using the recurrence interval
3145 	 * that intersects the given interval dates. */
3146 	if (event_start_julian < interval_start_julian) {
3147 		gint weeks = (interval_start_julian - event_start_julian) / 7;
3148 		weeks += recur_data->recur->interval - 1;
3149 		weeks -= weeks % recur_data->recur->interval;
3150 		cal_obj_time_add_days (cotime, weeks * 7);
3151 	}
3152 
3153 	week_start = *cotime;
3154 	cal_obj_time_add_days (&week_start, -recur_data->weekday_offset);
3155 
3156 	if (event_end && cal_obj_time_compare (&week_start, event_end,
3157 					       CALOBJ_DAY) > 0)
3158 		return TRUE;
3159 	if (interval_end && cal_obj_time_compare (&week_start, interval_end,
3160 						  CALOBJ_DAY) > 0)
3161 		return TRUE;
3162 
3163 	return FALSE;
3164 }
3165 
3166 static gboolean
cal_obj_weekly_find_next_position(CalObjTime * cotime,CalObjTime * event_end,RecurData * recur_data,CalObjTime * interval_end)3167 cal_obj_weekly_find_next_position (CalObjTime *cotime,
3168                                    CalObjTime *event_end,
3169                                    RecurData *recur_data,
3170                                    CalObjTime *interval_end)
3171 {
3172 	CalObjTime week_start;
3173 
3174 	cal_obj_time_add_days (cotime, recur_data->recur->interval * 7);
3175 
3176 	/* Return TRUE if the start of this week is after the event finishes
3177 	 * or is after the end of the required interval. */
3178 	week_start = *cotime;
3179 	cal_obj_time_add_days (&week_start, -recur_data->weekday_offset);
3180 
3181 #ifdef CAL_OBJ_DEBUG
3182 	g_print ("Next  day: %s\n", cal_obj_time_to_string (cotime));
3183 	g_print ("Week Start: %s\n", cal_obj_time_to_string (&week_start));
3184 #endif
3185 
3186 	if (event_end && cal_obj_time_compare (&week_start, event_end,
3187 					       CALOBJ_DAY) > 0)
3188 		return TRUE;
3189 	if (interval_end && cal_obj_time_compare (&week_start, interval_end,
3190 						  CALOBJ_DAY) > 0) {
3191 #ifdef CAL_OBJ_DEBUG
3192 		g_print (
3193 			"Interval end reached: %s\n",
3194 			cal_obj_time_to_string (interval_end));
3195 #endif
3196 		return TRUE;
3197 	}
3198 
3199 	return FALSE;
3200 }
3201 
3202 static gboolean
cal_obj_daily_find_start_position(CalObjTime * event_start,CalObjTime * event_end,RecurData * recur_data,CalObjTime * interval_start,CalObjTime * interval_end,CalObjTime * cotime)3203 cal_obj_daily_find_start_position (CalObjTime *event_start,
3204                                    CalObjTime *event_end,
3205                                    RecurData *recur_data,
3206                                    CalObjTime *interval_start,
3207                                    CalObjTime *interval_end,
3208                                    CalObjTime *cotime)
3209 {
3210 	GDate event_start_date, interval_start_date;
3211 	guint32 event_start_julian, interval_start_julian, days;
3212 
3213 	if (interval_end && cal_obj_time_compare (event_start, interval_end,
3214 						  CALOBJ_DAY) > 0)
3215 		return TRUE;
3216 	if (event_end && cal_obj_time_compare (event_end, interval_start,
3217 					       CALOBJ_DAY) < 0)
3218 		return TRUE;
3219 
3220 	*cotime = *event_start;
3221 
3222 	/* Convert the event start and interval start to GDates, so we can
3223 	 * easily find the number of days between them. */
3224 	g_date_clear (&event_start_date, 1);
3225 	g_date_set_dmy (
3226 		&event_start_date, event_start->day,
3227 		event_start->month + 1, event_start->year);
3228 	g_date_clear (&interval_start_date, 1);
3229 	g_date_set_dmy (
3230 		&interval_start_date, interval_start->day,
3231 		interval_start->month + 1, interval_start->year);
3232 
3233 	event_start_julian = g_date_get_julian (&event_start_date);
3234 	interval_start_julian = g_date_get_julian (&interval_start_date);
3235 
3236 	if (event_start_julian < interval_start_julian) {
3237 		days = interval_start_julian - event_start_julian
3238 			+ recur_data->recur->interval - 1;
3239 		days -= days % recur_data->recur->interval;
3240 		cal_obj_time_add_days (cotime, days);
3241 	}
3242 
3243 	if (event_end && cal_obj_time_compare (cotime, event_end, CALOBJ_DAY) > 0)
3244 		return TRUE;
3245 	if (interval_end && cal_obj_time_compare (cotime, interval_end, CALOBJ_DAY) > 0)
3246 		return TRUE;
3247 
3248 	return FALSE;
3249 }
3250 
3251 static gboolean
cal_obj_daily_find_next_position(CalObjTime * cotime,CalObjTime * event_end,RecurData * recur_data,CalObjTime * interval_end)3252 cal_obj_daily_find_next_position (CalObjTime *cotime,
3253                                   CalObjTime *event_end,
3254                                   RecurData *recur_data,
3255                                   CalObjTime *interval_end)
3256 {
3257 	cal_obj_time_add_days (cotime, recur_data->recur->interval);
3258 
3259 	if (event_end && cal_obj_time_compare (cotime, event_end,
3260 					       CALOBJ_DAY) > 0)
3261 		return TRUE;
3262 	if (interval_end && cal_obj_time_compare (cotime, interval_end,
3263 						  CALOBJ_DAY) > 0)
3264 		return TRUE;
3265 
3266 	return FALSE;
3267 }
3268 
3269 static gboolean
cal_obj_hourly_find_start_position(CalObjTime * event_start,CalObjTime * event_end,RecurData * recur_data,CalObjTime * interval_start,CalObjTime * interval_end,CalObjTime * cotime)3270 cal_obj_hourly_find_start_position (CalObjTime *event_start,
3271                                     CalObjTime *event_end,
3272                                     RecurData *recur_data,
3273                                     CalObjTime *interval_start,
3274                                     CalObjTime *interval_end,
3275                                     CalObjTime *cotime)
3276 {
3277 	GDate event_start_date, interval_start_date;
3278 	guint32 event_start_julian, interval_start_julian, hours;
3279 
3280 	if (interval_end && cal_obj_time_compare (event_start, interval_end,
3281 						  CALOBJ_HOUR) > 0)
3282 		return TRUE;
3283 	if (event_end && cal_obj_time_compare (event_end, interval_start,
3284 					       CALOBJ_HOUR) < 0)
3285 		return TRUE;
3286 
3287 	*cotime = *event_start;
3288 
3289 	if (cal_obj_time_compare (event_start, interval_start,
3290 				  CALOBJ_HOUR) < 0) {
3291 		/* Convert the event start and interval start to GDates, so we
3292 		 * can easily find the number of days between them. */
3293 		g_date_clear (&event_start_date, 1);
3294 		g_date_set_dmy (
3295 			&event_start_date, event_start->day,
3296 			event_start->month + 1, event_start->year);
3297 		g_date_clear (&interval_start_date, 1);
3298 		g_date_set_dmy (
3299 			&interval_start_date, interval_start->day,
3300 			interval_start->month + 1,
3301 			interval_start->year);
3302 
3303 		event_start_julian = g_date_get_julian (&event_start_date);
3304 		interval_start_julian = g_date_get_julian (&interval_start_date);
3305 
3306 		hours = (interval_start_julian - event_start_julian) * 24;
3307 		hours += interval_start->hour - event_start->hour;
3308 		hours += recur_data->recur->interval - 1;
3309 		hours -= hours % recur_data->recur->interval;
3310 		cal_obj_time_add_hours (cotime, hours);
3311 	}
3312 
3313 	if (event_end && cal_obj_time_compare (cotime, event_end,
3314 					       CALOBJ_HOUR) > 0)
3315 		return TRUE;
3316 	if (interval_end && cal_obj_time_compare (cotime, interval_end,
3317 						  CALOBJ_HOUR) > 0)
3318 		return TRUE;
3319 
3320 	return FALSE;
3321 }
3322 
3323 static gboolean
cal_obj_hourly_find_next_position(CalObjTime * cotime,CalObjTime * event_end,RecurData * recur_data,CalObjTime * interval_end)3324 cal_obj_hourly_find_next_position (CalObjTime *cotime,
3325                                    CalObjTime *event_end,
3326                                    RecurData *recur_data,
3327                                    CalObjTime *interval_end)
3328 {
3329 	cal_obj_time_add_hours (cotime, recur_data->recur->interval);
3330 
3331 	if (event_end && cal_obj_time_compare (cotime, event_end,
3332 					       CALOBJ_HOUR) > 0)
3333 		return TRUE;
3334 	if (interval_end && cal_obj_time_compare (cotime, interval_end,
3335 						  CALOBJ_HOUR) > 0)
3336 		return TRUE;
3337 
3338 	return FALSE;
3339 }
3340 
3341 static gboolean
cal_obj_minutely_find_start_position(CalObjTime * event_start,CalObjTime * event_end,RecurData * recur_data,CalObjTime * interval_start,CalObjTime * interval_end,CalObjTime * cotime)3342 cal_obj_minutely_find_start_position (CalObjTime *event_start,
3343                                       CalObjTime *event_end,
3344                                       RecurData *recur_data,
3345                                       CalObjTime *interval_start,
3346                                       CalObjTime *interval_end,
3347                                       CalObjTime *cotime)
3348 {
3349 	GDate event_start_date, interval_start_date;
3350 	guint32 event_start_julian, interval_start_julian, minutes;
3351 
3352 	if (interval_end && cal_obj_time_compare (event_start, interval_end,
3353 						  CALOBJ_MINUTE) > 0)
3354 		return TRUE;
3355 	if (event_end && cal_obj_time_compare (event_end, interval_start,
3356 					       CALOBJ_MINUTE) < 0)
3357 		return TRUE;
3358 
3359 	*cotime = *event_start;
3360 
3361 	if (cal_obj_time_compare (event_start, interval_start,
3362 				  CALOBJ_MINUTE) < 0) {
3363 		/* Convert the event start and interval start to GDates, so we
3364 		 * can easily find the number of days between them. */
3365 		g_date_clear (&event_start_date, 1);
3366 		g_date_set_dmy (
3367 			&event_start_date, event_start->day,
3368 			event_start->month + 1, event_start->year);
3369 		g_date_clear (&interval_start_date, 1);
3370 		g_date_set_dmy (
3371 			&interval_start_date, interval_start->day,
3372 			interval_start->month + 1,
3373 			interval_start->year);
3374 
3375 		event_start_julian = g_date_get_julian (&event_start_date);
3376 		interval_start_julian = g_date_get_julian (&interval_start_date);
3377 
3378 		minutes = (interval_start_julian - event_start_julian)
3379 			* 24 * 60;
3380 		minutes += (interval_start->hour - event_start->hour) * 24;
3381 		minutes += interval_start->minute - event_start->minute;
3382 		minutes += recur_data->recur->interval - 1;
3383 		minutes -= minutes % recur_data->recur->interval;
3384 		cal_obj_time_add_minutes (cotime, minutes);
3385 	}
3386 
3387 	if (event_end && cal_obj_time_compare (cotime, event_end,
3388 					       CALOBJ_MINUTE) > 0)
3389 		return TRUE;
3390 	if (interval_end && cal_obj_time_compare (cotime, interval_end,
3391 						  CALOBJ_MINUTE) > 0)
3392 		return TRUE;
3393 
3394 	return FALSE;
3395 }
3396 
3397 static gboolean
cal_obj_minutely_find_next_position(CalObjTime * cotime,CalObjTime * event_end,RecurData * recur_data,CalObjTime * interval_end)3398 cal_obj_minutely_find_next_position (CalObjTime *cotime,
3399                                      CalObjTime *event_end,
3400                                      RecurData *recur_data,
3401                                      CalObjTime *interval_end)
3402 {
3403 	cal_obj_time_add_minutes (cotime, recur_data->recur->interval);
3404 
3405 	if (event_end && cal_obj_time_compare (cotime, event_end,
3406 					       CALOBJ_MINUTE) > 0)
3407 		return TRUE;
3408 	if (interval_end && cal_obj_time_compare (cotime, interval_end,
3409 						  CALOBJ_MINUTE) > 0)
3410 		return TRUE;
3411 
3412 	return FALSE;
3413 }
3414 
3415 static gboolean
cal_obj_secondly_find_start_position(CalObjTime * event_start,CalObjTime * event_end,RecurData * recur_data,CalObjTime * interval_start,CalObjTime * interval_end,CalObjTime * cotime)3416 cal_obj_secondly_find_start_position (CalObjTime *event_start,
3417                                       CalObjTime *event_end,
3418                                       RecurData *recur_data,
3419                                       CalObjTime *interval_start,
3420                                       CalObjTime *interval_end,
3421                                       CalObjTime *cotime)
3422 {
3423 	GDate event_start_date, interval_start_date;
3424 	guint32 event_start_julian, interval_start_julian, seconds;
3425 
3426 	if (interval_end && cal_obj_time_compare (event_start, interval_end,
3427 						  CALOBJ_SECOND) > 0)
3428 		return TRUE;
3429 	if (event_end && cal_obj_time_compare (event_end, interval_start,
3430 					       CALOBJ_SECOND) < 0)
3431 		return TRUE;
3432 
3433 	*cotime = *event_start;
3434 
3435 	if (cal_obj_time_compare (event_start, interval_start,
3436 				  CALOBJ_SECOND) < 0) {
3437 		/* Convert the event start and interval start to GDates, so we
3438 		 * can easily find the number of days between them. */
3439 		g_date_clear (&event_start_date, 1);
3440 		g_date_set_dmy (
3441 			&event_start_date, event_start->day,
3442 			event_start->month + 1, event_start->year);
3443 		g_date_clear (&interval_start_date, 1);
3444 		g_date_set_dmy (
3445 			&interval_start_date, interval_start->day,
3446 			interval_start->month + 1,
3447 			interval_start->year);
3448 
3449 		event_start_julian = g_date_get_julian (&event_start_date);
3450 		interval_start_julian = g_date_get_julian (&interval_start_date);
3451 
3452 		seconds = (interval_start_julian - event_start_julian)
3453 			* 24 * 60 * 60;
3454 		seconds += (interval_start->hour - event_start->hour)
3455 			* 24 * 60;
3456 		seconds += (interval_start->minute - event_start->minute) * 60;
3457 		seconds += interval_start->second - event_start->second;
3458 		seconds += recur_data->recur->interval - 1;
3459 		seconds -= seconds % recur_data->recur->interval;
3460 		cal_obj_time_add_seconds (cotime, seconds);
3461 	}
3462 
3463 	if (event_end && cal_obj_time_compare (cotime, event_end,
3464 					       CALOBJ_SECOND) >= 0)
3465 		return TRUE;
3466 	if (interval_end && cal_obj_time_compare (cotime, interval_end,
3467 						  CALOBJ_SECOND) >= 0)
3468 		return TRUE;
3469 
3470 	return FALSE;
3471 }
3472 
3473 static gboolean
cal_obj_secondly_find_next_position(CalObjTime * cotime,CalObjTime * event_end,RecurData * recur_data,CalObjTime * interval_end)3474 cal_obj_secondly_find_next_position (CalObjTime *cotime,
3475                                      CalObjTime *event_end,
3476                                      RecurData *recur_data,
3477                                      CalObjTime *interval_end)
3478 {
3479 	cal_obj_time_add_seconds (cotime, recur_data->recur->interval);
3480 
3481 	if (event_end && cal_obj_time_compare (cotime, event_end,
3482 					       CALOBJ_SECOND) >= 0)
3483 		return TRUE;
3484 	if (interval_end && cal_obj_time_compare (cotime, interval_end,
3485 						  CALOBJ_SECOND) >= 0)
3486 		return TRUE;
3487 
3488 	return FALSE;
3489 }
3490 
3491 /* If the BYMONTH rule is specified it expands each occurrence in occs, by
3492  * using each of the months in the bymonth list. */
3493 static GArray *
cal_obj_bymonth_expand(RecurData * recur_data,GArray * occs)3494 cal_obj_bymonth_expand (RecurData *recur_data,
3495                         GArray *occs)
3496 {
3497 	GArray *new_occs;
3498 	CalObjTime *occ;
3499 	GList *elem;
3500 	gint len, i;
3501 
3502 	/* If BYMONTH has not been specified, or the array is empty, just
3503 	 * return the array. */
3504 	if (!recur_data->recur->bymonth || occs->len == 0)
3505 		return occs;
3506 
3507 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3508 
3509 	len = occs->len;
3510 	for (i = 0; i < len; i++) {
3511 		occ = &g_array_index (occs, CalObjTime, i);
3512 
3513 		elem = recur_data->recur->bymonth;
3514 		while (elem) {
3515 			/* NOTE: The day may now be invalid, e.g. 31st Feb. */
3516 			occ->month = GPOINTER_TO_INT (elem->data);
3517 			g_array_append_vals (new_occs, occ, 1);
3518 			elem = elem->next;
3519 		}
3520 	}
3521 
3522 	g_array_free (occs, TRUE);
3523 
3524 	return new_occs;
3525 }
3526 
3527 /* If the BYMONTH rule is specified it filters out all occurrences in occs
3528  * which do not match one of the months in the bymonth list. */
3529 static GArray *
cal_obj_bymonth_filter(RecurData * recur_data,GArray * occs)3530 cal_obj_bymonth_filter (RecurData *recur_data,
3531                         GArray *occs)
3532 {
3533 	GArray *new_occs;
3534 	CalObjTime *occ;
3535 	gint len, i;
3536 
3537 	/* If BYMONTH has not been specified, or the array is empty, just
3538 	 * return the array. */
3539 	if (!recur_data->recur->bymonth || occs->len == 0)
3540 		return occs;
3541 
3542 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3543 
3544 	len = occs->len;
3545 	for (i = 0; i < len; i++) {
3546 		occ = &g_array_index (occs, CalObjTime, i);
3547 		if (recur_data->months[occ->month])
3548 			g_array_append_vals (new_occs, occ, 1);
3549 	}
3550 
3551 	g_array_free (occs, TRUE);
3552 
3553 	return new_occs;
3554 }
3555 
3556 static GArray *
cal_obj_byweekno_expand(RecurData * recur_data,GArray * occs)3557 cal_obj_byweekno_expand (RecurData *recur_data,
3558                          GArray *occs)
3559 {
3560 	GArray *new_occs;
3561 	CalObjTime *occ, year_start_cotime, year_end_cotime, cotime;
3562 	GList *elem;
3563 	gint len, i, weekno;
3564 
3565 	/* If BYWEEKNO has not been specified, or the array is empty, just
3566 	 * return the array. */
3567 	if (!recur_data->recur->byweekno || occs->len == 0)
3568 		return occs;
3569 
3570 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3571 
3572 	len = occs->len;
3573 	for (i = 0; i < len; i++) {
3574 		occ = &g_array_index (occs, CalObjTime, i);
3575 
3576 		/* Find the day that would correspond to week 1 (note that
3577 		 * week 1 is the first week starting from the specified week
3578 		 * start day that has 4 days in the new year). */
3579 		year_start_cotime = *occ;
3580 		cal_obj_time_find_first_week (
3581 			&year_start_cotime,
3582 			recur_data);
3583 
3584 		/* Find the day that would correspond to week 1 of the next
3585 		 * year, which we use for -ve week numbers. */
3586 		year_end_cotime = *occ;
3587 		year_end_cotime.year++;
3588 		cal_obj_time_find_first_week (
3589 			&year_end_cotime,
3590 			recur_data);
3591 
3592 		/* Now iterate over the week numbers in byweekno, generating a
3593 		 * new occurrence for each one. */
3594 		elem = recur_data->recur->byweekno;
3595 		while (elem) {
3596 			weekno = GPOINTER_TO_INT (elem->data);
3597 			if (weekno > 0) {
3598 				cotime = year_start_cotime;
3599 				cal_obj_time_add_days (
3600 					&cotime,
3601 					(weekno - 1) * 7);
3602 			} else {
3603 				cotime = year_end_cotime;
3604 				cal_obj_time_add_days (&cotime, weekno * 7);
3605 			}
3606 
3607 			/* Skip occurrences if they fall outside the year. */
3608 			if (cotime.year == occ->year)
3609 				g_array_append_val (new_occs, cotime);
3610 			elem = elem->next;
3611 		}
3612 	}
3613 
3614 	g_array_free (occs, TRUE);
3615 
3616 	return new_occs;
3617 }
3618 
3619 static GArray *
cal_obj_byyearday_expand(RecurData * recur_data,GArray * occs)3620 cal_obj_byyearday_expand (RecurData *recur_data,
3621                           GArray *occs)
3622 {
3623 	GArray *new_occs;
3624 	CalObjTime *occ, year_start_cotime, year_end_cotime, cotime;
3625 	GList *elem;
3626 	gint len, i, dayno;
3627 
3628 	/* If BYYEARDAY has not been specified, or the array is empty, just
3629 	 * return the array. */
3630 	if (!recur_data->recur->byyearday || occs->len == 0)
3631 		return occs;
3632 
3633 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3634 
3635 	len = occs->len;
3636 	for (i = 0; i < len; i++) {
3637 		occ = &g_array_index (occs, CalObjTime, i);
3638 
3639 		/* Find the day that would correspond to day 1. */
3640 		year_start_cotime = *occ;
3641 		year_start_cotime.month = 0;
3642 		year_start_cotime.day = 1;
3643 
3644 		/* Find the day that would correspond to day 1 of the next
3645 		 * year, which we use for -ve day numbers. */
3646 		year_end_cotime = *occ;
3647 		year_end_cotime.year++;
3648 		year_end_cotime.month = 0;
3649 		year_end_cotime.day = 1;
3650 
3651 		/* Now iterate over the day numbers in byyearday, generating a
3652 		 * new occurrence for each one. */
3653 		elem = recur_data->recur->byyearday;
3654 		while (elem) {
3655 			dayno = GPOINTER_TO_INT (elem->data);
3656 			if (dayno > 0) {
3657 				cotime = year_start_cotime;
3658 				cal_obj_time_add_days (&cotime, dayno - 1);
3659 			} else {
3660 				cotime = year_end_cotime;
3661 				cal_obj_time_add_days (&cotime, dayno);
3662 			}
3663 
3664 			/* Skip occurrences if they fall outside the year. */
3665 			if (cotime.year == occ->year)
3666 				g_array_append_val (new_occs, cotime);
3667 			elem = elem->next;
3668 		}
3669 	}
3670 
3671 	g_array_free (occs, TRUE);
3672 
3673 	return new_occs;
3674 }
3675 
3676 /* Note: occs must not contain invalid dates, e.g. 31st September. */
3677 static GArray *
cal_obj_byyearday_filter(RecurData * recur_data,GArray * occs)3678 cal_obj_byyearday_filter (RecurData *recur_data,
3679                           GArray *occs)
3680 {
3681 	GArray *new_occs;
3682 	CalObjTime *occ;
3683 	gint yearday, len, i, days_in_year;
3684 
3685 	/* If BYYEARDAY has not been specified, or the array is empty, just
3686 	 * return the array. */
3687 	if (!recur_data->recur->byyearday || occs->len == 0)
3688 		return occs;
3689 
3690 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3691 
3692 	len = occs->len;
3693 	for (i = 0; i < len; i++) {
3694 		occ = &g_array_index (occs, CalObjTime, i);
3695 		yearday = cal_obj_time_day_of_year (occ);
3696 		if (recur_data->yeardays[yearday]) {
3697 			g_array_append_vals (new_occs, occ, 1);
3698 		} else {
3699 			days_in_year = g_date_is_leap_year (occ->year)
3700 				? 366 : 365;
3701 			if (recur_data->neg_yeardays[days_in_year + 1
3702 						    - yearday])
3703 				g_array_append_vals (new_occs, occ, 1);
3704 		}
3705 	}
3706 
3707 	g_array_free (occs, TRUE);
3708 
3709 	return new_occs;
3710 }
3711 
3712 static GArray *
cal_obj_bymonthday_expand(RecurData * recur_data,GArray * occs)3713 cal_obj_bymonthday_expand (RecurData *recur_data,
3714                            GArray *occs)
3715 {
3716 	GArray *new_occs;
3717 	CalObjTime *occ, month_start_cotime, month_end_cotime, cotime;
3718 	GList *elem;
3719 	gint len, i, dayno;
3720 
3721 	/* If BYMONTHDAY has not been specified, or the array is empty, just
3722 	 * return the array. */
3723 	if (!recur_data->recur->bymonthday || occs->len == 0)
3724 		return occs;
3725 
3726 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3727 
3728 	len = occs->len;
3729 	for (i = 0; i < len; i++) {
3730 		occ = &g_array_index (occs, CalObjTime, i);
3731 
3732 		/* Find the day that would correspond to day 1. */
3733 		month_start_cotime = *occ;
3734 		month_start_cotime.day = 1;
3735 
3736 		/* Find the day that would correspond to day 1 of the next
3737 		 * month, which we use for -ve day numbers. */
3738 		month_end_cotime = *occ;
3739 		month_end_cotime.month++;
3740 		month_end_cotime.day = 1;
3741 
3742 		/* Now iterate over the day numbers in bymonthday, generating a
3743 		 * new occurrence for each one. */
3744 		elem = recur_data->recur->bymonthday;
3745 		while (elem) {
3746 			dayno = GPOINTER_TO_INT (elem->data);
3747 			if (dayno > 0) {
3748 				cotime = month_start_cotime;
3749 				cal_obj_time_add_days (&cotime, dayno - 1);
3750 			} else {
3751 				cotime = month_end_cotime;
3752 				cal_obj_time_add_days (&cotime, dayno);
3753 			}
3754 			if (cotime.month == occ->month) {
3755 				g_array_append_val (new_occs, cotime);
3756 			} else {
3757 				/* set to last day in month */
3758 				cotime.month = occ->month;
3759 				cotime.day = time_days_in_month (occ->year, occ->month);
3760 				g_array_append_val (new_occs, cotime);
3761 			}
3762 
3763 			elem = elem->next;
3764 		}
3765 	}
3766 
3767 	g_array_free (occs, TRUE);
3768 
3769 	return new_occs;
3770 }
3771 
3772 static GArray *
cal_obj_bymonthday_filter(RecurData * recur_data,GArray * occs)3773 cal_obj_bymonthday_filter (RecurData *recur_data,
3774                            GArray *occs)
3775 {
3776 	GArray *new_occs;
3777 	CalObjTime *occ;
3778 	gint len, i, days_in_month;
3779 
3780 	/* If BYMONTHDAY has not been specified, or the array is empty, just
3781 	 * return the array. */
3782 	if (!recur_data->recur->bymonthday || occs->len == 0)
3783 		return occs;
3784 
3785 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3786 
3787 	len = occs->len;
3788 	for (i = 0; i < len; i++) {
3789 		occ = &g_array_index (occs, CalObjTime, i);
3790 		if (recur_data->monthdays[occ->day]) {
3791 			g_array_append_vals (new_occs, occ, 1);
3792 		} else {
3793 			days_in_month = time_days_in_month (
3794 				occ->year,
3795 				occ->month);
3796 			if (recur_data->neg_monthdays[days_in_month + 1 - occ->day])
3797 				g_array_append_vals (new_occs, occ, 1);
3798 		}
3799 	}
3800 
3801 	g_array_free (occs, TRUE);
3802 
3803 	return new_occs;
3804 }
3805 
3806 static GArray *
cal_obj_byday_expand_yearly(RecurData * recur_data,GArray * occs)3807 cal_obj_byday_expand_yearly (RecurData *recur_data,
3808                              GArray *occs)
3809 {
3810 	GArray *new_occs;
3811 	CalObjTime *occ;
3812 	GList *elem;
3813 	gint len, i, weekday, week_num;
3814 	gint first_weekday, last_weekday, offset;
3815 	guint16 year;
3816 
3817 	/* If BYDAY has not been specified, or the array is empty, just
3818 	 * return the array. */
3819 	if (!recur_data->recur->byday || occs->len == 0)
3820 		return occs;
3821 
3822 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3823 
3824 	len = occs->len;
3825 	for (i = 0; i < len; i++) {
3826 		occ = &g_array_index (occs, CalObjTime, i);
3827 
3828 		elem = recur_data->recur->byday;
3829 		while (elem) {
3830 			weekday = GPOINTER_TO_INT (elem->data);
3831 			elem = elem->next;
3832 			week_num = GPOINTER_TO_INT (elem->data);
3833 			elem = elem->next;
3834 
3835 			year = occ->year;
3836 			if (week_num == 0) {
3837 				/* Expand to every Mon/Tue/etc. in the year. */
3838 				occ->month = 0;
3839 				occ->day = 1;
3840 				first_weekday = cal_obj_time_weekday (occ);
3841 				offset = (weekday + 7 - first_weekday) % 7;
3842 				cal_obj_time_add_days (occ, offset);
3843 
3844 				while (occ->year == year) {
3845 					g_array_append_vals (new_occs, occ, 1);
3846 					cal_obj_time_add_days (occ, 7);
3847 				}
3848 
3849 			} else if (week_num > 0) {
3850 				/* Add the nth Mon/Tue/etc. in the year. */
3851 				occ->month = 0;
3852 				occ->day = 1;
3853 				first_weekday = cal_obj_time_weekday (occ);
3854 				offset = (weekday + 7 - first_weekday) % 7;
3855 				offset += (week_num - 1) * 7;
3856 				cal_obj_time_add_days (occ, offset);
3857 				if (occ->year == year)
3858 					g_array_append_vals (new_occs, occ, 1);
3859 
3860 			} else {
3861 				/* Add the -nth Mon/Tue/etc. in the year. */
3862 				occ->month = 11;
3863 				occ->day = 31;
3864 				last_weekday = cal_obj_time_weekday (occ);
3865 				offset = (last_weekday + 7 - weekday) % 7;
3866 				offset += (week_num - 1) * 7;
3867 				cal_obj_time_add_days (occ, -offset);
3868 				if (occ->year == year)
3869 					g_array_append_vals (new_occs, occ, 1);
3870 			}
3871 
3872 			/* Reset the year, as we may have gone past the end. */
3873 			occ->year = year;
3874 		}
3875 	}
3876 
3877 	g_array_free (occs, TRUE);
3878 
3879 	return new_occs;
3880 }
3881 
3882 static GArray *
cal_obj_byday_expand_monthly(RecurData * recur_data,GArray * occs)3883 cal_obj_byday_expand_monthly (RecurData *recur_data,
3884                               GArray *occs)
3885 {
3886 	GArray *new_occs;
3887 	CalObjTime *occ;
3888 	GList *elem;
3889 	gint len, i, weekday, week_num;
3890 	gint first_weekday, last_weekday, offset;
3891 	guint16 year;
3892 	guint8 month;
3893 
3894 	/* If BYDAY has not been specified, or the array is empty, just
3895 	 * return the array. */
3896 	if (!recur_data->recur->byday || occs->len == 0)
3897 		return occs;
3898 
3899 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3900 
3901 	len = occs->len;
3902 	for (i = 0; i < len; i++) {
3903 		occ = &g_array_index (occs, CalObjTime, i);
3904 
3905 		elem = recur_data->recur->byday;
3906 		while (elem) {
3907 			weekday = GPOINTER_TO_INT (elem->data);
3908 			elem = elem->next;
3909 			week_num = GPOINTER_TO_INT (elem->data);
3910 			elem = elem->next;
3911 
3912 			year = occ->year;
3913 			month = occ->month;
3914 			if (week_num == 0) {
3915 				/* Expand to every Mon/Tue/etc. in the month.*/
3916 				occ->day = 1;
3917 				first_weekday = cal_obj_time_weekday (occ);
3918 				offset = (weekday + 7 - first_weekday) % 7;
3919 				cal_obj_time_add_days (occ, offset);
3920 
3921 				while (occ->year == year
3922 				       && occ->month == month) {
3923 					g_array_append_vals (new_occs, occ, 1);
3924 					cal_obj_time_add_days (occ, 7);
3925 				}
3926 
3927 			} else if (week_num > 0) {
3928 				/* Add the nth Mon/Tue/etc. in the month. */
3929 				occ->day = 1;
3930 				first_weekday = cal_obj_time_weekday (occ);
3931 				offset = (weekday + 7 - first_weekday) % 7;
3932 				offset += (week_num - 1) * 7;
3933 				cal_obj_time_add_days (occ, offset);
3934 				if (occ->year == year && occ->month == month)
3935 					g_array_append_vals (new_occs, occ, 1);
3936 
3937 			} else {
3938 				/* Add the -nth Mon/Tue/etc. in the month. */
3939 				occ->day = time_days_in_month (
3940 					occ->year,
3941 					occ->month);
3942 				last_weekday = cal_obj_time_weekday (occ);
3943 
3944 				/* This calculates the number of days to step
3945 				 * backwards from the last day of the month
3946 				 * to the weekday we want. */
3947 				offset = (last_weekday + 7 - weekday) % 7;
3948 
3949 				/* This adds on the weeks. */
3950 				offset += (-week_num - 1) * 7;
3951 
3952 				cal_obj_time_add_days (occ, -offset);
3953 				if (occ->year == year && occ->month == month)
3954 					g_array_append_vals (new_occs, occ, 1);
3955 			}
3956 
3957 			/* Reset the year & month, as we may have gone past
3958 			 * the end. */
3959 			occ->year = year;
3960 			occ->month = month;
3961 		}
3962 	}
3963 
3964 	g_array_free (occs, TRUE);
3965 
3966 	return new_occs;
3967 }
3968 
3969 /* Note: occs must not contain invalid dates, e.g. 31st September. */
3970 static GArray *
cal_obj_byday_expand_weekly(RecurData * recur_data,GArray * occs)3971 cal_obj_byday_expand_weekly (RecurData *recur_data,
3972                              GArray *occs)
3973 {
3974 	GArray *new_occs;
3975 	CalObjTime *occ;
3976 	GList *elem;
3977 	gint len, i, weekday;
3978 	gint weekday_offset, new_weekday_offset;
3979 
3980 	/* If BYDAY has not been specified, or the array is empty, just
3981 	 * return the array. */
3982 	if (!recur_data->recur->byday || occs->len == 0)
3983 		return occs;
3984 
3985 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
3986 
3987 	len = occs->len;
3988 	for (i = 0; i < len; i++) {
3989 		occ = &g_array_index (occs, CalObjTime, i);
3990 
3991 		elem = recur_data->recur->byday;
3992 		while (elem) {
3993 			weekday = GPOINTER_TO_INT (elem->data);
3994 			elem = elem->next;
3995 
3996 			/* FIXME: Currently we just ignore this, but maybe we
3997 			 * should skip all elements where week_num != 0.
3998 			 * The spec isn't clear about this. */
3999 			/*week_num = GPOINTER_TO_INT (elem->data);*/
4000 			elem = elem->next;
4001 
4002 			weekday_offset = cal_obj_time_weekday_offset (occ, recur_data->recur);
4003 			new_weekday_offset = (weekday + 7 - recur_data->recur->week_start_day) % 7;
4004 			cal_obj_time_add_days (occ, new_weekday_offset - weekday_offset);
4005 			g_array_append_vals (new_occs, occ, 1);
4006 		}
4007 	}
4008 
4009 	g_array_free (occs, TRUE);
4010 
4011 	return new_occs;
4012 }
4013 
4014 /* Note: occs must not contain invalid dates, e.g. 31st September. */
4015 static GArray *
cal_obj_byday_filter(RecurData * recur_data,GArray * occs)4016 cal_obj_byday_filter (RecurData *recur_data,
4017                       GArray *occs)
4018 {
4019 	GArray *new_occs;
4020 	CalObjTime *occ;
4021 	gint len, i, weekday;
4022 
4023 	/* If BYDAY has not been specified, or the array is empty, just
4024 	 * return the array. */
4025 	if (!recur_data->recur->byday || occs->len == 0)
4026 		return occs;
4027 
4028 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
4029 
4030 	len = occs->len;
4031 	for (i = 0; i < len; i++) {
4032 		occ = &g_array_index (occs, CalObjTime, i);
4033 		weekday = cal_obj_time_weekday (occ);
4034 
4035 		/* See if the weekday on its own is set. */
4036 		if (recur_data->weekdays[weekday])
4037 			g_array_append_vals (new_occs, occ, 1);
4038 	}
4039 
4040 	g_array_free (occs, TRUE);
4041 
4042 	return new_occs;
4043 }
4044 
4045 /* If the BYHOUR rule is specified it expands each occurrence in occs, by
4046  * using each of the hours in the byhour list. */
4047 static GArray *
cal_obj_byhour_expand(RecurData * recur_data,GArray * occs)4048 cal_obj_byhour_expand (RecurData *recur_data,
4049                        GArray *occs)
4050 {
4051 	GArray *new_occs;
4052 	CalObjTime *occ;
4053 	GList *elem;
4054 	gint len, i;
4055 
4056 	/* If BYHOUR has not been specified, or the array is empty, just
4057 	 * return the array. */
4058 	if (!recur_data->recur->byhour || occs->len == 0)
4059 		return occs;
4060 
4061 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
4062 
4063 	len = occs->len;
4064 	for (i = 0; i < len; i++) {
4065 		occ = &g_array_index (occs, CalObjTime, i);
4066 
4067 		elem = recur_data->recur->byhour;
4068 		while (elem) {
4069 			occ->hour = GPOINTER_TO_INT (elem->data);
4070 			g_array_append_vals (new_occs, occ, 1);
4071 			elem = elem->next;
4072 		}
4073 	}
4074 
4075 	g_array_free (occs, TRUE);
4076 
4077 	return new_occs;
4078 }
4079 
4080 /* If the BYHOUR rule is specified it filters out all occurrences in occs
4081  * which do not match one of the hours in the byhour list. */
4082 static GArray *
cal_obj_byhour_filter(RecurData * recur_data,GArray * occs)4083 cal_obj_byhour_filter (RecurData *recur_data,
4084                        GArray *occs)
4085 {
4086 	GArray *new_occs;
4087 	CalObjTime *occ;
4088 	gint len, i;
4089 
4090 	/* If BYHOUR has not been specified, or the array is empty, just
4091 	 * return the array. */
4092 	if (!recur_data->recur->byhour || occs->len == 0)
4093 		return occs;
4094 
4095 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
4096 
4097 	len = occs->len;
4098 	for (i = 0; i < len; i++) {
4099 		occ = &g_array_index (occs, CalObjTime, i);
4100 		if (recur_data->hours[occ->hour])
4101 			g_array_append_vals (new_occs, occ, 1);
4102 	}
4103 
4104 	g_array_free (occs, TRUE);
4105 
4106 	return new_occs;
4107 }
4108 
4109 /* If the BYMINUTE rule is specified it expands each occurrence in occs, by
4110  * using each of the minutes in the byminute list. */
4111 static GArray *
cal_obj_byminute_expand(RecurData * recur_data,GArray * occs)4112 cal_obj_byminute_expand (RecurData *recur_data,
4113                          GArray *occs)
4114 {
4115 	GArray *new_occs;
4116 	CalObjTime *occ;
4117 	GList *elem;
4118 	gint len, i;
4119 
4120 	/* If BYMINUTE has not been specified, or the array is empty, just
4121 	 * return the array. */
4122 	if (!recur_data->recur->byminute || occs->len == 0)
4123 		return occs;
4124 
4125 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
4126 
4127 	len = occs->len;
4128 	for (i = 0; i < len; i++) {
4129 		occ = &g_array_index (occs, CalObjTime, i);
4130 
4131 		elem = recur_data->recur->byminute;
4132 		while (elem) {
4133 			occ->minute = GPOINTER_TO_INT (elem->data);
4134 			g_array_append_vals (new_occs, occ, 1);
4135 			elem = elem->next;
4136 		}
4137 	}
4138 
4139 	g_array_free (occs, TRUE);
4140 
4141 	return new_occs;
4142 }
4143 
4144 /* If the BYMINUTE rule is specified it filters out all occurrences in occs
4145  * which do not match one of the minutes in the byminute list. */
4146 static GArray *
cal_obj_byminute_filter(RecurData * recur_data,GArray * occs)4147 cal_obj_byminute_filter (RecurData *recur_data,
4148                          GArray *occs)
4149 {
4150 	GArray *new_occs;
4151 	CalObjTime *occ;
4152 	gint len, i;
4153 
4154 	/* If BYMINUTE has not been specified, or the array is empty, just
4155 	 * return the array. */
4156 	if (!recur_data->recur->byminute || occs->len == 0)
4157 		return occs;
4158 
4159 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
4160 
4161 	len = occs->len;
4162 	for (i = 0; i < len; i++) {
4163 		occ = &g_array_index (occs, CalObjTime, i);
4164 		if (recur_data->minutes[occ->minute])
4165 			g_array_append_vals (new_occs, occ, 1);
4166 	}
4167 
4168 	g_array_free (occs, TRUE);
4169 
4170 	return new_occs;
4171 }
4172 
4173 /* If the BYSECOND rule is specified it expands each occurrence in occs, by
4174  * using each of the seconds in the bysecond list. */
4175 static GArray *
cal_obj_bysecond_expand(RecurData * recur_data,GArray * occs)4176 cal_obj_bysecond_expand (RecurData *recur_data,
4177                          GArray *occs)
4178 {
4179 	GArray *new_occs;
4180 	CalObjTime *occ;
4181 	GList *elem;
4182 	gint len, i;
4183 
4184 	/* If BYSECOND has not been specified, or the array is empty, just
4185 	 * return the array. */
4186 	if (!recur_data->recur->bysecond || occs->len == 0)
4187 		return occs;
4188 
4189 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
4190 
4191 	len = occs->len;
4192 	for (i = 0; i < len; i++) {
4193 		occ = &g_array_index (occs, CalObjTime, i);
4194 
4195 		elem = recur_data->recur->bysecond;
4196 		while (elem) {
4197 			occ->second = GPOINTER_TO_INT (elem->data);
4198 			g_array_append_vals (new_occs, occ, 1);
4199 			elem = elem->next;
4200 		}
4201 	}
4202 
4203 	g_array_free (occs, TRUE);
4204 
4205 	return new_occs;
4206 }
4207 
4208 /* If the BYSECOND rule is specified it filters out all occurrences in occs
4209  * which do not match one of the seconds in the bysecond list. */
4210 static GArray *
cal_obj_bysecond_filter(RecurData * recur_data,GArray * occs)4211 cal_obj_bysecond_filter (RecurData *recur_data,
4212                          GArray *occs)
4213 {
4214 	GArray *new_occs;
4215 	CalObjTime *occ;
4216 	gint len, i;
4217 
4218 	/* If BYSECOND has not been specified, or the array is empty, just
4219 	 * return the array. */
4220 	if (!recur_data->recur->bysecond || occs->len == 0)
4221 		return occs;
4222 
4223 	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
4224 
4225 	len = occs->len;
4226 	for (i = 0; i < len; i++) {
4227 		occ = &g_array_index (occs, CalObjTime, i);
4228 		if (recur_data->seconds[occ->second])
4229 			g_array_append_vals (new_occs, occ, 1);
4230 	}
4231 
4232 	g_array_free (occs, TRUE);
4233 
4234 	return new_occs;
4235 }
4236 
4237 /* Adds a positive or negative number of months to the given CalObjTime,
4238  * updating the year appropriately so we end up with a valid month.
4239  * Note that the day may be invalid, e.g. 30th Feb. */
4240 static void
cal_obj_time_add_months(CalObjTime * cotime,gint months)4241 cal_obj_time_add_months (CalObjTime *cotime,
4242                          gint months)
4243 {
4244 	guint month, years;
4245 
4246 	/* We use a guint to avoid overflow on the guint8. */
4247 	month = cotime->month + months;
4248 	cotime->month = month % 12;
4249 	if (month > 0) {
4250 		cotime->year += month / 12;
4251 	} else {
4252 		years = month / 12;
4253 		if (cotime->month != 0) {
4254 			cotime->month += 12;
4255 			years -= 1;
4256 		}
4257 		cotime->year += years;
4258 	}
4259 }
4260 
4261 /* Adds a positive or negative number of days to the given CalObjTime,
4262  * updating the month and year appropriately so we end up with a valid day. */
4263 static void
cal_obj_time_add_days(CalObjTime * cotime,gint days)4264 cal_obj_time_add_days (CalObjTime *cotime,
4265                        gint days)
4266 {
4267 	gint day, days_in_month;
4268 
4269 	/* We use a guint to avoid overflow on the guint8. */
4270 	day = cotime->day;
4271 	day += days;
4272 
4273 	if (days >= 0) {
4274 		for (;;) {
4275 			days_in_month = time_days_in_month (
4276 				cotime->year,
4277 				cotime->month);
4278 			if (day <= days_in_month)
4279 				break;
4280 
4281 			cotime->month++;
4282 			if (cotime->month >= 12) {
4283 				cotime->year++;
4284 				cotime->month = 0;
4285 			}
4286 
4287 			day -= days_in_month;
4288 		}
4289 
4290 		cotime->day = (guint8) day;
4291 	} else {
4292 		while (day <= 0) {
4293 			if (cotime->month == 0) {
4294 				cotime->year--;
4295 				cotime->month = 11;
4296 			} else {
4297 				cotime->month--;
4298 			}
4299 
4300 			days_in_month = time_days_in_month (
4301 				cotime->year,
4302 				cotime->month);
4303 			day += days_in_month;
4304 		}
4305 
4306 		cotime->day = (guint8) day;
4307 	}
4308 }
4309 
4310 /* Adds a positive or negative number of hours to the given CalObjTime,
4311  * updating the day, month & year appropriately so we end up with a valid
4312  * time. */
4313 static void
cal_obj_time_add_hours(CalObjTime * cotime,gint hours)4314 cal_obj_time_add_hours (CalObjTime *cotime,
4315                         gint hours)
4316 {
4317 	gint hour, days;
4318 
4319 	/* We use a gint to avoid overflow on the guint8. */
4320 	hour = cotime->hour + hours;
4321 	cotime->hour = hour % 24;
4322 	if (hour >= 0) {
4323 		if (hour >= 24)
4324 			cal_obj_time_add_days (cotime, hour / 24);
4325 	} else {
4326 		days = hour / 24;
4327 		if (cotime->hour != 0) {
4328 			cotime->hour += 24;
4329 			days -= 1;
4330 		}
4331 		cal_obj_time_add_days (cotime, days);
4332 	}
4333 }
4334 
4335 /* Adds a positive or negative number of minutes to the given CalObjTime,
4336  * updating the rest of the CalObjTime appropriately. */
4337 static void
cal_obj_time_add_minutes(CalObjTime * cotime,gint minutes)4338 cal_obj_time_add_minutes (CalObjTime *cotime,
4339                           gint minutes)
4340 {
4341 	gint minute, hours;
4342 
4343 	/* We use a gint to avoid overflow on the guint8. */
4344 	minute = cotime->minute + minutes;
4345 	cotime->minute = minute % 60;
4346 	if (minute >= 0) {
4347 		if (minute >= 60)
4348 			cal_obj_time_add_hours (cotime, minute / 60);
4349 	} else {
4350 		hours = minute / 60;
4351 		if (cotime->minute != 0) {
4352 			cotime->minute += 60;
4353 			hours -= 1;
4354 		}
4355 		cal_obj_time_add_hours (cotime, hours);
4356 	}
4357 }
4358 
4359 /* Adds a positive or negative number of seconds to the given CalObjTime,
4360  * updating the rest of the CalObjTime appropriately. */
4361 static void
cal_obj_time_add_seconds(CalObjTime * cotime,gint seconds)4362 cal_obj_time_add_seconds (CalObjTime *cotime,
4363                           gint seconds)
4364 {
4365 	gint second, minutes;
4366 
4367 	/* We use a gint to avoid overflow on the guint8. */
4368 	second = cotime->second + seconds;
4369 	cotime->second = second % 60;
4370 	if (second >= 0) {
4371 		if (second >= 60)
4372 			cal_obj_time_add_minutes (cotime, second / 60);
4373 	} else {
4374 		minutes = second / 60;
4375 		if (cotime->second != 0) {
4376 			cotime->second += 60;
4377 			minutes -= 1;
4378 		}
4379 		cal_obj_time_add_minutes (cotime, minutes);
4380 	}
4381 }
4382 
4383 /* Compares 2 CalObjTimes. Returns -1 if the cotime1 is before cotime2, 0 if
4384  * they are the same, or 1 if cotime1 is after cotime2. The comparison type
4385  * specifies which parts of the times we are interested in, e.g. if CALOBJ_DAY
4386  * is used we only want to know if the days are different. */
4387 static gint
cal_obj_time_compare(CalObjTime * cotime1,CalObjTime * cotime2,CalObjTimeComparison type)4388 cal_obj_time_compare (CalObjTime *cotime1,
4389                       CalObjTime *cotime2,
4390                       CalObjTimeComparison type)
4391 {
4392 	if (cotime1->year < cotime2->year)
4393 		return -1;
4394 	if (cotime1->year > cotime2->year)
4395 		return 1;
4396 
4397 	if (type == CALOBJ_YEAR)
4398 		return 0;
4399 
4400 	if (cotime1->month < cotime2->month)
4401 		return -1;
4402 	if (cotime1->month > cotime2->month)
4403 		return 1;
4404 
4405 	if (type == CALOBJ_MONTH)
4406 		return 0;
4407 
4408 	if (cotime1->day < cotime2->day)
4409 		return -1;
4410 	if (cotime1->day > cotime2->day)
4411 		return 1;
4412 
4413 	if (type == CALOBJ_DAY)
4414 		return 0;
4415 
4416 	if (cotime1->hour < cotime2->hour)
4417 		return -1;
4418 	if (cotime1->hour > cotime2->hour)
4419 		return 1;
4420 
4421 	if (type == CALOBJ_HOUR)
4422 		return 0;
4423 
4424 	if (cotime1->minute < cotime2->minute)
4425 		return -1;
4426 	if (cotime1->minute > cotime2->minute)
4427 		return 1;
4428 
4429 	if (type == CALOBJ_MINUTE)
4430 		return 0;
4431 
4432 	if (cotime1->second < cotime2->second)
4433 		return -1;
4434 	if (cotime1->second > cotime2->second)
4435 		return 1;
4436 
4437 	return 0;
4438 }
4439 
4440 /* This is the same as the above function, but without the comparison type.
4441  * It is used for qsort (). */
4442 static gint
cal_obj_time_compare_func(gconstpointer arg1,gconstpointer arg2)4443 cal_obj_time_compare_func (gconstpointer arg1,
4444                            gconstpointer arg2)
4445 {
4446 	CalObjTime *cotime1, *cotime2;
4447 	gint retval;
4448 
4449 	cotime1 = (CalObjTime *) arg1;
4450 	cotime2 = (CalObjTime *) arg2;
4451 
4452 	if (!cotime1 || !cotime2) {
4453 		if (cotime1 == cotime2)
4454 			return 0;
4455 
4456 		return cotime2 ? -1 : 1;
4457 	}
4458 
4459 	if (cotime1->year < cotime2->year)
4460 		retval = -1;
4461 	else if (cotime1->year > cotime2->year)
4462 		retval = 1;
4463 
4464 	else if (cotime1->month < cotime2->month)
4465 		retval = -1;
4466 	else if (cotime1->month > cotime2->month)
4467 		retval = 1;
4468 
4469 	else if (cotime1->day < cotime2->day)
4470 		retval = -1;
4471 	else if (cotime1->day > cotime2->day)
4472 		retval = 1;
4473 
4474 	else if (cotime1->hour < cotime2->hour)
4475 		retval = -1;
4476 	else if (cotime1->hour > cotime2->hour)
4477 		retval = 1;
4478 
4479 	else if (cotime1->minute < cotime2->minute)
4480 		retval = -1;
4481 	else if (cotime1->minute > cotime2->minute)
4482 		retval = 1;
4483 
4484 	else if (cotime1->second < cotime2->second)
4485 		retval = -1;
4486 	else if (cotime1->second > cotime2->second)
4487 		retval = 1;
4488 
4489 	else
4490 		retval = 0;
4491 
4492 #if 0
4493 	g_print ("%s - ", cal_obj_time_to_string (cotime1));
4494 	g_print ("%s : %i\n", cal_obj_time_to_string (cotime2), retval);
4495 #endif
4496 
4497 	return retval;
4498 }
4499 
4500 static gint
cal_obj_date_only_compare_func(gconstpointer arg1,gconstpointer arg2)4501 cal_obj_date_only_compare_func (gconstpointer arg1,
4502                                 gconstpointer arg2)
4503 {
4504 	CalObjTime *cotime1, *cotime2;
4505 
4506 	cotime1 = (CalObjTime *) arg1;
4507 	cotime2 = (CalObjTime *) arg2;
4508 
4509 	if (cotime1->year < cotime2->year)
4510 		return -1;
4511 	if (cotime1->year > cotime2->year)
4512 		return 1;
4513 
4514 	if (cotime1->month < cotime2->month)
4515 		return -1;
4516 	if (cotime1->month > cotime2->month)
4517 		return 1;
4518 
4519 	if (cotime1->day < cotime2->day)
4520 		return -1;
4521 	if (cotime1->day > cotime2->day)
4522 		return 1;
4523 
4524 	return 0;
4525 }
4526 
4527 /* Returns the weekday of the given CalObjTime, from 0 (Mon) - 6 (Sun). */
4528 static gint
cal_obj_time_weekday(CalObjTime * cotime)4529 cal_obj_time_weekday (CalObjTime *cotime)
4530 {
4531 	GDate date;
4532 	gint weekday;
4533 
4534 	g_date_clear (&date, 1);
4535 	g_date_set_dmy (&date, cotime->day, cotime->month + 1, cotime->year);
4536 
4537 	/* This results in a value of 0 (Monday) - 6 (Sunday). */
4538 	weekday = g_date_get_weekday (&date) - 1;
4539 
4540 	return weekday;
4541 }
4542 
4543 /* Returns the weekday of the given CalObjTime, from 0 - 6. The week start
4544  * day is Monday by default, but can be set in the recurrence rule. */
4545 static gint
cal_obj_time_weekday_offset(CalObjTime * cotime,ECalRecurrence * recur)4546 cal_obj_time_weekday_offset (CalObjTime *cotime,
4547                              ECalRecurrence *recur)
4548 {
4549 	GDate date;
4550 	gint weekday, offset;
4551 
4552 	g_date_clear (&date, 1);
4553 	g_date_set_dmy (&date, cotime->day, cotime->month + 1, cotime->year);
4554 
4555 	/* This results in a value of 0 (Monday) - 6 (Sunday). */
4556 	weekday = g_date_get_weekday (&date) - 1;
4557 
4558 	/* This calculates the offset of our day from the start of the week.
4559 	 * We just add on a week (to avoid any possible negative values) and
4560 	 * then subtract the specified week start day, then convert it into a
4561 	 * value from 0-6. */
4562 	offset = (weekday + 7 - recur->week_start_day) % 7;
4563 
4564 	return offset;
4565 }
4566 
4567 /* Returns the day of the year of the given CalObjTime, from 1 - 366. */
4568 static gint
cal_obj_time_day_of_year(CalObjTime * cotime)4569 cal_obj_time_day_of_year (CalObjTime *cotime)
4570 {
4571 	GDate date;
4572 
4573 	g_date_clear (&date, 1);
4574 	g_date_set_dmy (&date, cotime->day, cotime->month + 1, cotime->year);
4575 
4576 	return g_date_get_day_of_year (&date);
4577 }
4578 
4579 /* Finds the first week in the given CalObjTime's year, using the same weekday
4580  * as the event start day (i.e. from the RecurData).
4581  * The first week of the year is the first week starting from the specified
4582  * week start day that has 4 days in the new year. It may be in the previous
4583  * year. */
4584 static void
cal_obj_time_find_first_week(CalObjTime * cotime,RecurData * recur_data)4585 cal_obj_time_find_first_week (CalObjTime *cotime,
4586                               RecurData *recur_data)
4587 {
4588 	GDate date;
4589 	gint weekday, week_start_day, first_full_week_start_offset, offset;
4590 
4591 	/* Find out the weekday of the 1st of the year, 0 (Mon) - 6 (Sun). */
4592 	g_date_clear (&date, 1);
4593 	g_date_set_dmy (&date, 1, 1, cotime->year);
4594 	weekday = g_date_get_weekday (&date) - 1;
4595 
4596 	/* Calculate the first day of the year that starts a new week, i.e. the
4597 	 * first week_start_day after weekday, using 0 = 1st Jan.
4598 	 * e.g. if the 1st Jan is a Tuesday (1) and week_start_day is a
4599 	 * Monday (0), the result will be (0 + 7 - 1) % 7 = 6 (7th Jan). */
4600 	week_start_day = recur_data->recur->week_start_day;
4601 	first_full_week_start_offset = (week_start_day + 7 - weekday) % 7;
4602 
4603 	/* Now see if we have to move backwards 1 week, i.e. if the week
4604 	 * starts on or after Jan 5th (since the previous week has 4 days in
4605 	 * this year and so will be the first week of the year). */
4606 	if (first_full_week_start_offset >= 4)
4607 		first_full_week_start_offset -= 7;
4608 
4609 	/* Now add the days to get to the event's weekday. */
4610 	offset = first_full_week_start_offset + recur_data->weekday_offset;
4611 
4612 	/* Now move the cotime to the appropriate day. */
4613 	cotime->month = 0;
4614 	cotime->day = 1;
4615 	cal_obj_time_add_days (cotime, offset);
4616 }
4617 
4618 static void
cal_object_time_from_time(CalObjTime * cotime,time_t t,ICalTimezone * zone)4619 cal_object_time_from_time (CalObjTime *cotime,
4620                            time_t t,
4621                            ICalTimezone *zone)
4622 {
4623 	ICalTime *tt;
4624 
4625 	tt = i_cal_time_new_from_timet_with_zone (t, FALSE, zone);
4626 
4627 	cotime->year = i_cal_time_get_year (tt);
4628 	cotime->month = i_cal_time_get_month (tt) - 1;
4629 	cotime->day = i_cal_time_get_day (tt);
4630 	cotime->hour = i_cal_time_get_hour (tt);
4631 	cotime->minute = i_cal_time_get_minute (tt);
4632 	cotime->second = i_cal_time_get_second (tt);
4633 	cotime->flags = FALSE;
4634 
4635 	g_clear_object (&tt);
4636 }
4637 
4638 /* Debugging function to convert a CalObjTime to a string. It uses a static
4639  * buffer so beware. */
4640 #ifdef CAL_OBJ_DEBUG
4641 static const gchar *
cal_obj_time_to_string(CalObjTime * cotime)4642 cal_obj_time_to_string (CalObjTime *cotime)
4643 {
4644 	static gchar buffer[50];
4645 	gchar *weekdays[] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
4646 			     "   " };
4647 	gint weekday;
4648 
4649 	weekday = cal_obj_time_weekday (cotime);
4650 
4651 	g_snprintf (
4652 		buffer, sizeof (buffer),
4653 		"%s %02i/%02i/%04i %02i:%02i:%02i",
4654 		weekdays[weekday],
4655 		cotime->day, cotime->month + 1, cotime->year,
4656 		cotime->hour, cotime->minute, cotime->second);
4657 
4658 	return buffer;
4659 }
4660 #endif
4661 
4662 /**
4663  * e_cal_recur_ensure_end_dates:
4664  * @comp: an #ECalComponent
4665  * @refresh: %TRUE to recalculate all end dates
4666  * @tz_cb: (scope call): function to call to resolve timezones
4667  * @tz_cb_data: (closure tz_cb_data): user data to pass to @tz_cb
4668  * @cancellable: optional #GCancellable object, or %NULL
4669  * @error: return location for a #GError, or %NULL
4670  *
4671  * This recalculates the end dates for recurrence & exception rules which use
4672  * the COUNT property. If @refresh is %TRUE it will recalculate all enddates
4673  * for rules which use COUNT. If @refresh is %FALSE, it will only calculate
4674  * the enddate if it hasn't already been set. It returns %TRUE if the component
4675  * was changed, i.e. if the component should be saved at some point.
4676  * We store the enddate in the %E_CAL_EVOLUTION_ENDDATE_PARAMETER parameter of the RRULE
4677  * or EXRULE.
4678  *
4679  * Returns: %TRUE if the component was changed, %FALSE otherwise
4680  *
4681  * Since: 2.32
4682  **/
4683 gboolean
e_cal_recur_ensure_end_dates(ECalComponent * comp,gboolean refresh,ECalRecurResolveTimezoneCb tz_cb,gpointer tz_cb_data,GCancellable * cancellable,GError ** error)4684 e_cal_recur_ensure_end_dates (ECalComponent *comp,
4685 			      gboolean refresh,
4686 			      ECalRecurResolveTimezoneCb tz_cb,
4687 			      gpointer tz_cb_data,
4688 			      GCancellable *cancellable,
4689 			      GError **error)
4690 {
4691 	GSList *rrules, *exrules, *elem;
4692 	gboolean changed = FALSE;
4693 
4694 	/* Do the RRULEs. */
4695 	rrules = e_cal_component_get_rrule_properties (comp);
4696 	for (elem = rrules; elem && !g_cancellable_is_cancelled (cancellable); elem = elem->next) {
4697 		changed |= e_cal_recur_ensure_rule_end_date (comp, elem->data,
4698 			FALSE, refresh, tz_cb, tz_cb_data, cancellable, error);
4699 	}
4700 
4701 	/* Do the EXRULEs. */
4702 	exrules = e_cal_component_get_exrule_properties (comp);
4703 	for (elem = exrules; elem && !g_cancellable_is_cancelled (cancellable); elem = elem->next) {
4704 		changed |= e_cal_recur_ensure_rule_end_date (comp, elem->data,
4705 			TRUE, refresh, tz_cb, tz_cb_data, cancellable, error);
4706 	}
4707 
4708 	g_slist_free_full (rrules, g_object_unref);
4709 	g_slist_free_full (exrules, g_object_unref);
4710 
4711 	return changed;
4712 }
4713 
4714 typedef struct _ECalRecurEnsureEndDateData ECalRecurEnsureEndDateData;
4715 struct _ECalRecurEnsureEndDateData {
4716 	gint count;
4717 	gint instances;
4718 	time_t end_date;
4719 };
4720 
4721 static gboolean
e_cal_recur_ensure_rule_end_date(ECalComponent * comp,ICalProperty * prop,gboolean exception,gboolean refresh,ECalRecurResolveTimezoneCb tz_cb,gpointer tz_cb_data,GCancellable * cancellable,GError ** error)4722 e_cal_recur_ensure_rule_end_date (ECalComponent *comp,
4723                                   ICalProperty *prop,
4724                                   gboolean exception,
4725                                   gboolean refresh,
4726                                   ECalRecurResolveTimezoneCb tz_cb,
4727                                   gpointer tz_cb_data,
4728 				  GCancellable *cancellable,
4729 				  GError **error)
4730 {
4731 	ICalRecurrence *rule;
4732 	ECalRecurEnsureEndDateData cb_data;
4733 
4734 	if (!prop)
4735 		return FALSE;
4736 
4737 	if (exception)
4738 		rule = i_cal_property_get_exrule (prop);
4739 	else
4740 		rule = i_cal_property_get_rrule (prop);
4741 
4742 	/* If the rule doesn't use COUNT just return. */
4743 	if (!rule || !i_cal_recurrence_get_count (rule)) {
4744 		g_clear_object (&rule);
4745 		return FALSE;
4746 	}
4747 
4748 	/* If refresh is FALSE, we check if the enddate is already set, and
4749 	 * if it is we just return. */
4750 	if (!refresh) {
4751 		if (e_cal_recur_get_rule_end_date (prop, NULL) != -1) {
4752 			g_clear_object (&rule);
4753 			return FALSE;
4754 		}
4755 	} else {
4756 		/* Remove the property parameter, thus it'll not influence the regeneration */
4757 		i_cal_property_remove_parameter_by_name (prop, E_CAL_EVOLUTION_ENDDATE_PARAMETER);
4758 	}
4759 
4760 	/* Calculate the end date. Note that we initialize end_date to 0, so
4761 	 * if the RULE doesn't generate COUNT instances we save a time_t of 0.
4762 	 * Also note that we use the UTC timezone as the default timezone.
4763 	 * In get_end_date () if the DTSTART is a DATE or floating time, we will
4764 	 * convert the ENDDATE to the current timezone. */
4765 	cb_data.count = i_cal_recurrence_get_count (rule);
4766 	cb_data.instances = 0;
4767 	cb_data.end_date = 0;
4768 	e_cal_recur_generate_instances_of_rule (
4769 		comp, prop, -1, -1,
4770 		e_cal_recur_ensure_rule_end_date_cb,
4771 		&cb_data, tz_cb, tz_cb_data,
4772 		i_cal_timezone_get_utc_timezone ());
4773 
4774 	/* Store the end date in the E_CAL_EVOLUTION_ENDDATE_PARAMETER parameter of the rule. */
4775 	e_cal_recur_set_rule_end_date (prop, cb_data.end_date);
4776 
4777 	g_clear_object (&rule);
4778 
4779 	return TRUE;
4780 }
4781 
4782 static gboolean
e_cal_recur_ensure_rule_end_date_cb(ICalComponent * comp,ICalTime * instance_start,ICalTime * instance_end,gpointer user_data,GCancellable * cancellable,GError ** error)4783 e_cal_recur_ensure_rule_end_date_cb (ICalComponent *comp,
4784 				     ICalTime *instance_start,
4785 				     ICalTime *instance_end,
4786 				     gpointer user_data,
4787 				     GCancellable *cancellable,
4788 				     GError **error)
4789 {
4790 	ECalRecurEnsureEndDateData *cb_data = user_data;
4791 
4792 	cb_data->instances++;
4793 
4794 	if (cb_data->instances == cb_data->count) {
4795 		cb_data->end_date = i_cal_time_as_timet (instance_start);
4796 		return FALSE;
4797 	}
4798 
4799 	return TRUE;
4800 }
4801 
4802 /* If default_timezone is set, the saved ENDDATE parameter is assumed to be
4803  * in that timezone. This is used when the DTSTART is a DATE or floating
4804  * value, since the RRULE end date will change depending on the timezone that
4805  * it is evaluated in. */
4806 static time_t
e_cal_recur_get_rule_end_date(ICalProperty * prop,ICalTimezone * default_timezone)4807 e_cal_recur_get_rule_end_date (ICalProperty *prop,
4808                                ICalTimezone *default_timezone)
4809 {
4810 	ICalParameter *param;
4811 	const gchar *xname, *xvalue;
4812 
4813 	for (param = i_cal_property_get_first_parameter (prop, I_CAL_X_PARAMETER);
4814 	     param;
4815 	     g_object_unref (param), param = i_cal_property_get_next_parameter (prop, I_CAL_X_PARAMETER)) {
4816 		xname = i_cal_parameter_get_xname (param);
4817 		if (xname && !strcmp (xname, E_CAL_EVOLUTION_ENDDATE_PARAMETER)) {
4818 			ICalValue *value;
4819 
4820 			xvalue = i_cal_parameter_get_x (param);
4821 			value = i_cal_value_new_from_string (I_CAL_DATETIME_VALUE, xvalue);
4822 			if (value) {
4823 				ICalTime *tt;
4824 				ICalTimezone *zone;
4825 				time_t res;
4826 
4827 				tt = i_cal_value_get_datetime (value);
4828 				g_object_unref (value);
4829 
4830 				zone = default_timezone;
4831 				if (!zone)
4832 					zone = i_cal_timezone_get_utc_timezone ();
4833 
4834 				res = i_cal_time_as_timet_with_zone (tt, zone);
4835 
4836 				g_object_unref (param);
4837 				g_clear_object (&tt);
4838 
4839 				return res;
4840 			}
4841 		}
4842 	}
4843 
4844 	return -1;
4845 }
4846 
4847 static void
e_cal_recur_set_rule_end_date(ICalProperty * prop,time_t end_date)4848 e_cal_recur_set_rule_end_date (ICalProperty *prop,
4849                                time_t end_date)
4850 {
4851 	ICalParameter *param;
4852 	ICalValue *value;
4853 	ICalTimezone *utc_zone;
4854 	ICalTime *icaltime;
4855 	const gchar *xname;
4856 	gchar *end_date_string;
4857 
4858 	/* We save the value as a UTC DATE-TIME. */
4859 	utc_zone = i_cal_timezone_get_utc_timezone ();
4860 	icaltime = i_cal_time_new_from_timet_with_zone (end_date, FALSE, utc_zone);
4861 	value = i_cal_value_new_datetime (icaltime);
4862 	end_date_string = i_cal_value_as_ical_string (value);
4863 	g_object_unref (value);
4864 	g_object_unref (icaltime);
4865 
4866 	/* If we already have an X-EVOLUTION-ENDDATE parameter, set the value
4867 	 * to the new date-time. */
4868 	for (param = i_cal_property_get_first_parameter (prop, I_CAL_X_PARAMETER);
4869 	     param;
4870 	     g_object_unref (param), param = i_cal_property_get_next_parameter (prop, I_CAL_X_PARAMETER)) {
4871 		xname = i_cal_parameter_get_xname (param);
4872 		if (xname && !strcmp (xname, E_CAL_EVOLUTION_ENDDATE_PARAMETER)) {
4873 			i_cal_parameter_set_x (param, end_date_string);
4874 			g_free (end_date_string);
4875 			g_object_unref (param);
4876 			return;
4877 		}
4878 	}
4879 
4880 	/* Create a new X-EVOLUTION-ENDDATE and add it to the property. */
4881 	param = i_cal_parameter_new_x (end_date_string);
4882 	i_cal_parameter_set_xname (param, E_CAL_EVOLUTION_ENDDATE_PARAMETER);
4883 	i_cal_property_take_parameter (prop, param);
4884 
4885 	g_free (end_date_string);
4886 }
4887 
4888 static const gchar *e_cal_recur_nth[31] = {
4889 	N_("1st"),
4890 	N_("2nd"),
4891 	N_("3rd"),
4892 	N_("4th"),
4893 	N_("5th"),
4894 	N_("6th"),
4895 	N_("7th"),
4896 	N_("8th"),
4897 	N_("9th"),
4898 	N_("10th"),
4899 	N_("11th"),
4900 	N_("12th"),
4901 	N_("13th"),
4902 	N_("14th"),
4903 	N_("15th"),
4904 	N_("16th"),
4905 	N_("17th"),
4906 	N_("18th"),
4907 	N_("19th"),
4908 	N_("20th"),
4909 	N_("21st"),
4910 	N_("22nd"),
4911 	N_("23rd"),
4912 	N_("24th"),
4913 	N_("25th"),
4914 	N_("26th"),
4915 	N_("27th"),
4916 	N_("28th"),
4917 	N_("29th"),
4918 	N_("30th"),
4919 	N_("31st")
4920 };
4921 
4922 /**
4923  * e_cal_recur_get_localized_nth:
4924  * @nth: the nth index, counting from zero
4925  *
4926  * Returns: Localized text for the nth position, counting from zero, which means
4927  *    for '0' it'll return "1st", for '1' it'll return "2nd" and so on, up to 30,
4928  *    when it'll return "31st".
4929  *
4930  * Since: 3.28
4931  **/
4932 const gchar *
e_cal_recur_get_localized_nth(gint nth)4933 e_cal_recur_get_localized_nth (gint nth)
4934 {
4935 	g_return_val_if_fail (nth >= 0 && nth < G_N_ELEMENTS (e_cal_recur_nth), NULL);
4936 
4937 	return _(e_cal_recur_nth[nth]);
4938 }
4939 
4940 static gint
cal_comp_util_recurrence_count_by_xxx_and_free(GArray * array)4941 cal_comp_util_recurrence_count_by_xxx_and_free (GArray *array) /* gshort */
4942 {
4943 	gint ii;
4944 
4945 	if (!array)
4946 		return 0;
4947 
4948 	for (ii = 0; ii < array->len; ii++) {
4949 		if (g_array_index (array, gshort, ii) == I_CAL_RECURRENCE_ARRAY_MAX)
4950 			break;
4951 	}
4952 
4953 	g_array_unref (array);
4954 
4955 	return ii;
4956 }
4957 
4958 /**
4959  * e_cal_recur_describe_recurrence_ex:
4960  * @icalcomp: an #ICalComponent
4961  * @week_start_day: a day when the week starts
4962  * @flags: bit-or of #ECalRecurDescribeRecurrenceFlags
4963  * @datetime_fmt_func: (nullable) (scope call): formatting function for date/time value
4964  *
4965  * Describes some simple types of recurrences in a human-readable and localized way.
4966  * The @flags influence the output format and what to do when the @icalcomp
4967  * contains more complicated recurrence, some which the function cannot describe.
4968  *
4969  * The @week_start_day is used for weekly recurrences, to start the list of selected
4970  * days at that day.
4971  *
4972  * If @datetime_fmt_func is %NULL, the e_time_format_date_and_time() is used
4973  * to format data/time value.
4974  *
4975  * Free the returned string with g_free(), when no longer needed.
4976  *
4977  * Returns: (nullable) (transfer full): a newly allocated string, which
4978  *    describes the recurrence of the @icalcomp, or #NULL, when the @icalcomp
4979  *    doesn't recur or the recurrence is too complicated to describe, also
4980  *    according to given @flags.
4981  *
4982  * Since: 3.38
4983  **/
4984 gchar *
e_cal_recur_describe_recurrence_ex(ICalComponent * icalcomp,GDateWeekday week_start_day,guint32 flags,ECalRecurFormatDateTimeFunc datetime_fmt_func)4985 e_cal_recur_describe_recurrence_ex (ICalComponent *icalcomp,
4986 				    GDateWeekday week_start_day,
4987 				    guint32 flags,
4988 				    ECalRecurFormatDateTimeFunc datetime_fmt_func)
4989 {
4990 	gchar *prefix = NULL, *mid = NULL, *suffix = NULL, *result = NULL;
4991 	ICalProperty *prop;
4992 	ICalRecurrence *rrule = NULL;
4993 	ICalTime *until = NULL, *dtstart = NULL;
4994 	gint n_by_second, n_by_minute, n_by_hour;
4995 	gint n_by_day, n_by_month_day, n_by_year_day;
4996 	gint n_by_week_no, n_by_month, n_by_set_pos;
4997 	gboolean prefixed, fallback;
4998 
4999 	g_return_val_if_fail (I_CAL_IS_COMPONENT (icalcomp), NULL);
5000 
5001 	prefixed = (flags & E_CAL_RECUR_DESCRIBE_RECURRENCE_FLAG_PREFIXED) != 0;
5002 	fallback = (flags & E_CAL_RECUR_DESCRIBE_RECURRENCE_FLAG_FALLBACK) != 0;
5003 
5004 	prop = i_cal_component_get_first_property (icalcomp, I_CAL_RRULE_PROPERTY);
5005 	if (!prop)
5006 		return NULL;
5007 
5008 	switch (i_cal_component_isa (icalcomp)) {
5009 	case I_CAL_VEVENT_COMPONENT:
5010 	case I_CAL_VTODO_COMPONENT:
5011 	case I_CAL_VJOURNAL_COMPONENT:
5012 		break;
5013 	default:
5014 		g_object_unref (prop);
5015 		return NULL;
5016 	}
5017 
5018 	if (i_cal_component_count_properties (icalcomp, I_CAL_RRULE_PROPERTY) != 1 ||
5019 	    i_cal_component_count_properties (icalcomp, I_CAL_RDATE_PROPERTY) != 0 ||
5020 	    i_cal_component_count_properties (icalcomp, I_CAL_EXRULE_PROPERTY) != 0)
5021 		goto custom;
5022 
5023 	rrule = i_cal_property_get_rrule (prop);
5024 
5025 	switch (i_cal_recurrence_get_freq (rrule)) {
5026 	case I_CAL_DAILY_RECURRENCE:
5027 	case I_CAL_WEEKLY_RECURRENCE:
5028 	case I_CAL_MONTHLY_RECURRENCE:
5029 	case I_CAL_YEARLY_RECURRENCE:
5030 		break;
5031 	default:
5032 		goto custom;
5033 
5034 	}
5035 
5036 	until = i_cal_recurrence_get_until (rrule);
5037 	dtstart = i_cal_component_get_dtstart (icalcomp);
5038 
5039 	n_by_second = cal_comp_util_recurrence_count_by_xxx_and_free (i_cal_recurrence_get_by_second_array (rrule));
5040 	n_by_minute = cal_comp_util_recurrence_count_by_xxx_and_free (i_cal_recurrence_get_by_minute_array (rrule));
5041 	n_by_hour = cal_comp_util_recurrence_count_by_xxx_and_free (i_cal_recurrence_get_by_hour_array (rrule));
5042 	n_by_day = cal_comp_util_recurrence_count_by_xxx_and_free (i_cal_recurrence_get_by_day_array (rrule));
5043 	n_by_month_day = cal_comp_util_recurrence_count_by_xxx_and_free (i_cal_recurrence_get_by_month_day_array (rrule));
5044 	n_by_year_day = cal_comp_util_recurrence_count_by_xxx_and_free (i_cal_recurrence_get_by_year_day_array (rrule));
5045 	n_by_week_no = cal_comp_util_recurrence_count_by_xxx_and_free (i_cal_recurrence_get_by_week_no_array (rrule));
5046 	n_by_month = cal_comp_util_recurrence_count_by_xxx_and_free (i_cal_recurrence_get_by_month_array (rrule));
5047 	n_by_set_pos = cal_comp_util_recurrence_count_by_xxx_and_free (i_cal_recurrence_get_by_set_pos_array (rrule));
5048 
5049 	if (n_by_second != 0 ||
5050 	    n_by_minute != 0 ||
5051 	    n_by_hour != 0)
5052 		goto custom;
5053 
5054 	switch (i_cal_recurrence_get_freq (rrule)) {
5055 	case I_CAL_DAILY_RECURRENCE:
5056 		if (n_by_day != 0 ||
5057 		    n_by_month_day != 0 ||
5058 		    n_by_year_day != 0 ||
5059 		    n_by_week_no != 0 ||
5060 		    n_by_month != 0 ||
5061 		    n_by_set_pos != 0)
5062 			goto custom;
5063 
5064 		if (i_cal_recurrence_get_interval (rrule) > 0) {
5065 			if (!i_cal_recurrence_get_count (rrule) && (!until || !i_cal_time_get_year (until))) {
5066 				if (prefixed) {
5067 					result = g_strdup_printf (
5068 						g_dngettext (GETTEXT_PACKAGE,
5069 							"every day forever",
5070 							"every %d days forever",
5071 							i_cal_recurrence_get_interval (rrule)), i_cal_recurrence_get_interval (rrule));
5072 				} else {
5073 					result = g_strdup_printf (
5074 						g_dngettext (GETTEXT_PACKAGE,
5075 							"Every day forever",
5076 							"Every %d days forever",
5077 							i_cal_recurrence_get_interval (rrule)), i_cal_recurrence_get_interval (rrule));
5078 				}
5079 			} else {
5080 				if (prefixed) {
5081 					prefix = g_strdup_printf (
5082 						g_dngettext (GETTEXT_PACKAGE,
5083 							"every day",
5084 							"every %d days",
5085 							i_cal_recurrence_get_interval (rrule)), i_cal_recurrence_get_interval (rrule));
5086 				} else {
5087 					prefix = g_strdup_printf (
5088 						g_dngettext (GETTEXT_PACKAGE,
5089 							"Every day",
5090 							"Every %d days",
5091 							i_cal_recurrence_get_interval (rrule)), i_cal_recurrence_get_interval (rrule));
5092 				}
5093 			}
5094 		}
5095 		break;
5096 
5097 	case I_CAL_WEEKLY_RECURRENCE: {
5098 		gint ii, ndays;
5099 		guint8 day_mask;
5100 		gint day_shift = week_start_day;
5101 
5102 		/* Sunday is at bit 0 in the day_mask */
5103 		if (day_shift < 0 || day_shift >= G_DATE_SUNDAY)
5104 			day_shift = 0;
5105 
5106 		if (n_by_month_day != 0 ||
5107 		    n_by_year_day != 0 ||
5108 		    n_by_week_no != 0 ||
5109 		    n_by_month != 0 ||
5110 		    n_by_set_pos != 0)
5111 			goto custom;
5112 
5113 		day_mask = 0;
5114 
5115 		for (ii = 0; ii < 8 && i_cal_recurrence_get_by_day (rrule, ii) != I_CAL_RECURRENCE_ARRAY_MAX; ii++) {
5116 			ICalRecurrenceWeekday weekday;
5117 			gint pos;
5118 
5119 			weekday = i_cal_recurrence_day_day_of_week (i_cal_recurrence_get_by_day (rrule, ii));
5120 			pos = i_cal_recurrence_day_position (i_cal_recurrence_get_by_day (rrule, ii));
5121 
5122 			if (pos != 0)
5123 				goto custom;
5124 
5125 			switch (weekday) {
5126 			case I_CAL_SUNDAY_WEEKDAY:
5127 				day_mask |= 1 << 0;
5128 				break;
5129 
5130 			case I_CAL_MONDAY_WEEKDAY:
5131 				day_mask |= 1 << 1;
5132 				break;
5133 
5134 			case I_CAL_TUESDAY_WEEKDAY:
5135 				day_mask |= 1 << 2;
5136 				break;
5137 
5138 			case I_CAL_WEDNESDAY_WEEKDAY:
5139 				day_mask |= 1 << 3;
5140 				break;
5141 
5142 			case I_CAL_THURSDAY_WEEKDAY:
5143 				day_mask |= 1 << 4;
5144 				break;
5145 
5146 			case I_CAL_FRIDAY_WEEKDAY:
5147 				day_mask |= 1 << 5;
5148 				break;
5149 
5150 			case I_CAL_SATURDAY_WEEKDAY:
5151 				day_mask |= 1 << 6;
5152 				break;
5153 
5154 			default:
5155 				break;
5156 			}
5157 		}
5158 
5159 		if (ii == 0) {
5160 			ii = i_cal_time_day_of_week (dtstart);
5161 			if (ii >= 1)
5162 				day_mask |= 1 << (ii - 1);
5163 		}
5164 
5165 		ndays = 0;
5166 
5167 		for (ii = 0; ii < 7; ii++) {
5168 			if ((day_mask & (1 << ii)) != 0)
5169 				ndays++;
5170 		}
5171 
5172 		if (prefixed) {
5173 			prefix = g_strdup_printf (
5174 				g_dngettext (GETTEXT_PACKAGE,
5175 					"every week",
5176 					"every %d weeks",
5177 					i_cal_recurrence_get_interval (rrule)), i_cal_recurrence_get_interval (rrule));
5178 		} else {
5179 			prefix = g_strdup_printf (
5180 				g_dngettext (GETTEXT_PACKAGE,
5181 					"Every week",
5182 					"Every %d weeks",
5183 					i_cal_recurrence_get_interval (rrule)), i_cal_recurrence_get_interval (rrule));
5184 		}
5185 
5186 		for (ii = 0; ii < 7 && ndays; ii++) {
5187 			gint bit = ((ii + day_shift) % 7);
5188 
5189 			if ((day_mask & (1 << bit)) != 0) {
5190 				/* Translators: This is used to merge set of week day names in a recurrence, which can contain any
5191 				   of the week day names. The string always starts with "on DAYNAME", then it can continue either
5192 				   with ", DAYNAME" or " and DAYNAME", thus it can be something like "on Monday and Tuesday"
5193 				   or "on Monday, Wednesday and Friday" or simply "on Saturday". The '%1$s' is replaced with
5194 				   the previously gathered text, while the '%2$s' is replaced with the text to append. */
5195 				#define merge_fmt C_("recur-description-dayname", "%1$s%2$s")
5196 				#define merge_string(_on_str, _merge_str, _and_str) G_STMT_START {\
5197 					if (mid) { \
5198 						gchar *tmp; \
5199 						if (ndays == 1) \
5200 							tmp = g_strdup_printf (merge_fmt, mid, _and_str); \
5201 						else \
5202 							tmp = g_strdup_printf (merge_fmt, mid, _merge_str); \
5203 						g_free (mid); \
5204 						mid = tmp; \
5205 					} else { \
5206 						mid = g_strdup (_on_str); \
5207 					} \
5208 					ndays--; \
5209 					} G_STMT_END
5210 				switch (bit) {
5211 				case 0:
5212 					merge_string (C_("recur-description", "on Sunday"),
5213 						      C_("recur-description", ", Sunday"),
5214 						      C_("recur-description", " and Sunday"));
5215 					break;
5216 				case 1:
5217 					merge_string (C_("recur-description", "on Monday"),
5218 						      C_("recur-description", ", Monday"),
5219 						      C_("recur-description", " and Monday"));
5220 					break;
5221 				case 2:
5222 					merge_string (C_("recur-description", "on Tuesday"),
5223 						      C_("recur-description", ", Tuesday"),
5224 						      C_("recur-description", " and Tuesday"));
5225 					break;
5226 				case 3:
5227 					merge_string (C_("recur-description", "on Wednesday"),
5228 						      C_("recur-description", ", Wednesday"),
5229 						      C_("recur-description", " and Wednesday"));
5230 					break;
5231 				case 4:
5232 					merge_string (C_("recur-description", "on Thursday"),
5233 						      C_("recur-description", ", Thursday"),
5234 						      C_("recur-description", " and Thursday"));
5235 					break;
5236 				case 5:
5237 					merge_string (C_("recur-description", "on Friday"),
5238 						      C_("recur-description", ", Friday"),
5239 						      C_("recur-description", " and Friday"));
5240 					break;
5241 				case 6:
5242 					merge_string (C_("recur-description", "on Saturday"),
5243 						      C_("recur-description", ", Saturday"),
5244 						      C_("recur-description", " and Saturday"));
5245 					break;
5246 				default:
5247 					g_warn_if_reached ();
5248 					break;
5249 				}
5250 				#undef merge_string
5251 				#undef merge_fmt
5252 			}
5253 		}
5254 		break;
5255 	}
5256 
5257 	case I_CAL_MONTHLY_RECURRENCE: {
5258 		enum month_num_options {
5259 			MONTH_NUM_INVALID = -1,
5260 			MONTH_NUM_FIRST,
5261 			MONTH_NUM_SECOND,
5262 			MONTH_NUM_THIRD,
5263 			MONTH_NUM_FOURTH,
5264 			MONTH_NUM_FIFTH,
5265 			MONTH_NUM_LAST,
5266 			MONTH_NUM_DAY,
5267 			MONTH_NUM_OTHER
5268 		};
5269 
5270 		enum month_day_options {
5271 			MONTH_DAY_NTH,
5272 			MONTH_DAY_MON,
5273 			MONTH_DAY_TUE,
5274 			MONTH_DAY_WED,
5275 			MONTH_DAY_THU,
5276 			MONTH_DAY_FRI,
5277 			MONTH_DAY_SAT,
5278 			MONTH_DAY_SUN
5279 		};
5280 
5281 		gint month_index = 1;
5282 		enum month_day_options month_day;
5283 		enum month_num_options month_num;
5284 
5285 		if (n_by_year_day != 0 ||
5286 		    n_by_week_no != 0 ||
5287 		    n_by_month != 0 ||
5288 		    n_by_set_pos > 1)
5289 			goto custom;
5290 
5291 		if (n_by_month_day == 1) {
5292 			gint nth;
5293 
5294 			if (n_by_set_pos != 0)
5295 				goto custom;
5296 
5297 			nth = i_cal_recurrence_get_by_month_day (rrule, 0);
5298 			if (nth < 1 && nth != -1)
5299 				goto custom;
5300 
5301 			if (nth == -1) {
5302 				month_index = i_cal_time_get_day (dtstart);
5303 				month_num = MONTH_NUM_LAST;
5304 			} else {
5305 				month_index = nth;
5306 				month_num = MONTH_NUM_DAY;
5307 			}
5308 			month_day = MONTH_DAY_NTH;
5309 
5310 		} else if (n_by_day == 1) {
5311 			ICalRecurrenceWeekday weekday;
5312 			gint pos;
5313 
5314 			/* Outlook 2000 uses BYDAY=TU;BYSETPOS=2, and will not
5315 			 * accept BYDAY=2TU. So we now use the same as Outlook
5316 			 * by default. */
5317 
5318 			weekday = i_cal_recurrence_day_day_of_week (i_cal_recurrence_get_by_day (rrule, 0));
5319 			pos = i_cal_recurrence_day_position (i_cal_recurrence_get_by_day (rrule, 0));
5320 
5321 			if (pos == 0) {
5322 				if (n_by_set_pos != 1)
5323 					goto custom;
5324 				pos = i_cal_recurrence_get_by_set_pos (rrule, 0);
5325 			} else if (pos < 0) {
5326 				goto custom;
5327 			}
5328 
5329 			switch (weekday) {
5330 			case I_CAL_MONDAY_WEEKDAY:
5331 				month_day = MONTH_DAY_MON;
5332 				break;
5333 
5334 			case I_CAL_TUESDAY_WEEKDAY:
5335 				month_day = MONTH_DAY_TUE;
5336 				break;
5337 
5338 			case I_CAL_WEDNESDAY_WEEKDAY:
5339 				month_day = MONTH_DAY_WED;
5340 				break;
5341 
5342 			case I_CAL_THURSDAY_WEEKDAY:
5343 				month_day = MONTH_DAY_THU;
5344 				break;
5345 
5346 			case I_CAL_FRIDAY_WEEKDAY:
5347 				month_day = MONTH_DAY_FRI;
5348 				break;
5349 
5350 			case I_CAL_SATURDAY_WEEKDAY:
5351 				month_day = MONTH_DAY_SAT;
5352 				break;
5353 
5354 			case I_CAL_SUNDAY_WEEKDAY:
5355 				month_day = MONTH_DAY_SUN;
5356 				break;
5357 
5358 			default:
5359 				goto custom;
5360 			}
5361 
5362 			if (pos == -1)
5363 				month_num = MONTH_NUM_LAST;
5364 			else
5365 				month_num = pos - 1;
5366 		} else {
5367 			goto custom;
5368 		}
5369 
5370 		if (prefixed) {
5371 			prefix = g_strdup_printf (
5372 				g_dngettext (GETTEXT_PACKAGE,
5373 					"every month",
5374 					"every %d months",
5375 					i_cal_recurrence_get_interval (rrule)), i_cal_recurrence_get_interval (rrule));
5376 		} else {
5377 			prefix = g_strdup_printf (
5378 				g_dngettext (GETTEXT_PACKAGE,
5379 					"Every month",
5380 					"Every %d months",
5381 					i_cal_recurrence_get_interval (rrule)), i_cal_recurrence_get_interval (rrule));
5382 		}
5383 
5384 		switch (month_day) {
5385 		case MONTH_DAY_NTH:
5386 			if (month_num == MONTH_NUM_LAST) {
5387 				switch (month_index) {
5388 				case 0:
5389 					mid = g_strdup (C_("recur-description", "on the last Sunday"));
5390 					break;
5391 				case 1:
5392 					mid = g_strdup (C_("recur-description", "on the last Monday"));
5393 					break;
5394 				case 2:
5395 					mid = g_strdup (C_("recur-description", "on the last Tuesday"));
5396 					break;
5397 				case 3:
5398 					mid = g_strdup (C_("recur-description", "on the last Wednesday"));
5399 					break;
5400 				case 4:
5401 					mid = g_strdup (C_("recur-description", "on the last Thursday"));
5402 					break;
5403 				case 5:
5404 					mid = g_strdup (C_("recur-description", "on the last Friday"));
5405 					break;
5406 				case 6:
5407 					mid = g_strdup (C_("recur-description", "on the last Saturday"));
5408 					break;
5409 				default:
5410 					g_warning ("%s: What is month_index:%d for the last day?", G_STRFUNC, month_index);
5411 					break;
5412 				}
5413 			} else { /* month_num = MONTH_NUM_DAY; */
5414 				switch (month_index) {
5415 				case 1:
5416 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5417 					mid = g_strdup (C_("recur-description", "on the 1st day"));
5418 					break;
5419 				case 2:
5420 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5421 					mid = g_strdup (C_("recur-description", "on the 2nd day"));
5422 					break;
5423 				case 3:
5424 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5425 					mid = g_strdup (C_("recur-description", "on the 3rd day"));
5426 					break;
5427 				case 4:
5428 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5429 					mid = g_strdup (C_("recur-description", "on the 4th day"));
5430 					break;
5431 				case 5:
5432 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5433 					mid = g_strdup (C_("recur-description", "on the 5th day"));
5434 					break;
5435 				case 6:
5436 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5437 					mid = g_strdup (C_("recur-description", "on the 6th day"));
5438 					break;
5439 				case 7:
5440 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5441 					mid = g_strdup (C_("recur-description", "on the 7th day"));
5442 					break;
5443 				case 8:
5444 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5445 					mid = g_strdup (C_("recur-description", "on the 8th day"));
5446 					break;
5447 				case 9:
5448 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5449 					mid = g_strdup (C_("recur-description", "on the 9th day"));
5450 					break;
5451 				case 10:
5452 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5453 					mid = g_strdup (C_("recur-description", "on the 10th day"));
5454 					break;
5455 				case 11:
5456 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5457 					mid = g_strdup (C_("recur-description", "on the 11th day"));
5458 					break;
5459 				case 12:
5460 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5461 					mid = g_strdup (C_("recur-description", "on the 12th day"));
5462 					break;
5463 				case 13:
5464 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5465 					mid = g_strdup (C_("recur-description", "on the 13th day"));
5466 					break;
5467 				case 14:
5468 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5469 					mid = g_strdup (C_("recur-description", "on the 14th day"));
5470 					break;
5471 				case 15:
5472 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5473 					mid = g_strdup (C_("recur-description", "on the 15th day"));
5474 					break;
5475 				case 16:
5476 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5477 					mid = g_strdup (C_("recur-description", "on the 16th day"));
5478 					break;
5479 				case 17:
5480 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5481 					mid = g_strdup (C_("recur-description", "on the 17th day"));
5482 					break;
5483 				case 18:
5484 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5485 					mid = g_strdup (C_("recur-description", "on the 18th day"));
5486 					break;
5487 				case 19:
5488 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5489 					mid = g_strdup (C_("recur-description", "on the 19th day"));
5490 					break;
5491 				case 20:
5492 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5493 					mid = g_strdup (C_("recur-description", "on the 20th day"));
5494 					break;
5495 				case 21:
5496 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5497 					mid = g_strdup (C_("recur-description", "on the 21st day"));
5498 					break;
5499 				case 22:
5500 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5501 					mid = g_strdup (C_("recur-description", "on the 22nd day"));
5502 					break;
5503 				case 23:
5504 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5505 					mid = g_strdup (C_("recur-description", "on the 23rd day"));
5506 					break;
5507 				case 24:
5508 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5509 					mid = g_strdup (C_("recur-description", "on the 24th day"));
5510 					break;
5511 				case 25:
5512 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5513 					mid = g_strdup (C_("recur-description", "on the 25th day"));
5514 					break;
5515 				case 26:
5516 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5517 					mid = g_strdup (C_("recur-description", "on the 26th day"));
5518 					break;
5519 				case 27:
5520 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5521 					mid = g_strdup (C_("recur-description", "on the 27th day"));
5522 					break;
5523 				case 28:
5524 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5525 					mid = g_strdup (C_("recur-description", "on the 28th day"));
5526 					break;
5527 				case 29:
5528 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5529 					mid = g_strdup (C_("recur-description", "on the 29th day"));
5530 					break;
5531 				case 30:
5532 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5533 					mid = g_strdup (C_("recur-description", "on the 30th day"));
5534 					break;
5535 				case 31:
5536 					/* Translators: This is added to a monthly recurrence, forming something like "Every month on the Xth day" */
5537 					mid = g_strdup (C_("recur-description", "on the 31st day"));
5538 					break;
5539 				}
5540 			}
5541 			break;
5542 		case MONTH_DAY_MON:
5543 			switch (month_num) {
5544 			case MONTH_NUM_FIRST:
5545 				mid = g_strdup (C_("recur-description", "on the first Monday"));
5546 				break;
5547 			case MONTH_NUM_SECOND:
5548 				mid = g_strdup (C_("recur-description", "on the second Monday"));
5549 				break;
5550 			case MONTH_NUM_THIRD:
5551 				mid = g_strdup (C_("recur-description", "on the third Monday"));
5552 				break;
5553 			case MONTH_NUM_FOURTH:
5554 				mid = g_strdup (C_("recur-description", "on the fourth Monday"));
5555 				break;
5556 			case MONTH_NUM_FIFTH:
5557 				mid = g_strdup (C_("recur-description", "on the fifth Monday"));
5558 				break;
5559 			case MONTH_NUM_LAST:
5560 				mid = g_strdup (C_("recur-description", "on the last Monday"));
5561 				break;
5562 			default:
5563 				g_warning ("%s: What is month_num:%d for month_day:%d?", G_STRFUNC, month_num, month_day);
5564 				break;
5565 			}
5566 			break;
5567 		case MONTH_DAY_TUE:
5568 			switch (month_num) {
5569 			case MONTH_NUM_FIRST:
5570 				mid = g_strdup (C_("recur-description", "on the first Tuesday"));
5571 				break;
5572 			case MONTH_NUM_SECOND:
5573 				mid = g_strdup (C_("recur-description", "on the second Tuesday"));
5574 				break;
5575 			case MONTH_NUM_THIRD:
5576 				mid = g_strdup (C_("recur-description", "on the third Tuesday"));
5577 				break;
5578 			case MONTH_NUM_FOURTH:
5579 				mid = g_strdup (C_("recur-description", "on the fourth Tuesday"));
5580 				break;
5581 			case MONTH_NUM_FIFTH:
5582 				mid = g_strdup (C_("recur-description", "on the fifth Tuesday"));
5583 				break;
5584 			case MONTH_NUM_LAST:
5585 				mid = g_strdup (C_("recur-description", "on the last Tuesday"));
5586 				break;
5587 			default:
5588 				g_warning ("%s: What is month_num:%d for month_day:%d?", G_STRFUNC, month_num, month_day);
5589 				break;
5590 			}
5591 			break;
5592 		case MONTH_DAY_WED:
5593 			switch (month_num) {
5594 			case MONTH_NUM_FIRST:
5595 				mid = g_strdup (C_("recur-description", "on the first Wednesday"));
5596 				break;
5597 			case MONTH_NUM_SECOND:
5598 				mid = g_strdup (C_("recur-description", "on the second Wednesday"));
5599 				break;
5600 			case MONTH_NUM_THIRD:
5601 				mid = g_strdup (C_("recur-description", "on the third Wednesday"));
5602 				break;
5603 			case MONTH_NUM_FOURTH:
5604 				mid = g_strdup (C_("recur-description", "on the fourth Wednesday"));
5605 				break;
5606 			case MONTH_NUM_FIFTH:
5607 				mid = g_strdup (C_("recur-description", "on the fifth Wednesday"));
5608 				break;
5609 			case MONTH_NUM_LAST:
5610 				mid = g_strdup (C_("recur-description", "on the last Wednesday"));
5611 				break;
5612 			default:
5613 				g_warning ("%s: What is month_num:%d for month_day:%d?", G_STRFUNC, month_num, month_day);
5614 				break;
5615 			}
5616 			break;
5617 		case MONTH_DAY_THU:
5618 			switch (month_num) {
5619 			case MONTH_NUM_FIRST:
5620 				mid = g_strdup (C_("recur-description", "on the first Thursday"));
5621 				break;
5622 			case MONTH_NUM_SECOND:
5623 				mid = g_strdup (C_("recur-description", "on the second Thursday"));
5624 				break;
5625 			case MONTH_NUM_THIRD:
5626 				mid = g_strdup (C_("recur-description", "on the third Thursday"));
5627 				break;
5628 			case MONTH_NUM_FOURTH:
5629 				mid = g_strdup (C_("recur-description", "on the fourth Thursday"));
5630 				break;
5631 			case MONTH_NUM_FIFTH:
5632 				mid = g_strdup (C_("recur-description", "on the fifth Thursday"));
5633 				break;
5634 			case MONTH_NUM_LAST:
5635 				mid = g_strdup (C_("recur-description", "on the last Thursday"));
5636 				break;
5637 			default:
5638 				g_warning ("%s: What is month_num:%d for month_day:%d?", G_STRFUNC, month_num, month_day);
5639 				break;
5640 			}
5641 			break;
5642 		case MONTH_DAY_FRI:
5643 			switch (month_num) {
5644 			case MONTH_NUM_FIRST:
5645 				mid = g_strdup (C_("recur-description", "on the first Friday"));
5646 				break;
5647 			case MONTH_NUM_SECOND:
5648 				mid = g_strdup (C_("recur-description", "on the second Friday"));
5649 				break;
5650 			case MONTH_NUM_THIRD:
5651 				mid = g_strdup (C_("recur-description", "on the third Friday"));
5652 				break;
5653 			case MONTH_NUM_FOURTH:
5654 				mid = g_strdup (C_("recur-description", "on the fourth Friday"));
5655 				break;
5656 			case MONTH_NUM_FIFTH:
5657 				mid = g_strdup (C_("recur-description", "on the fifth Friday"));
5658 				break;
5659 			case MONTH_NUM_LAST:
5660 				mid = g_strdup (C_("recur-description", "on the last Friday"));
5661 				break;
5662 			default:
5663 				g_warning ("%s: What is month_num:%d for month_day:%d?", G_STRFUNC, month_num, month_day);
5664 				break;
5665 			}
5666 			break;
5667 		case MONTH_DAY_SAT:
5668 			switch (month_num) {
5669 			case MONTH_NUM_FIRST:
5670 				mid = g_strdup (C_("recur-description", "on the first Saturday"));
5671 				break;
5672 			case MONTH_NUM_SECOND:
5673 				mid = g_strdup (C_("recur-description", "on the second Saturday"));
5674 				break;
5675 			case MONTH_NUM_THIRD:
5676 				mid = g_strdup (C_("recur-description", "on the third Saturday"));
5677 				break;
5678 			case MONTH_NUM_FOURTH:
5679 				mid = g_strdup (C_("recur-description", "on the fourth Saturday"));
5680 				break;
5681 			case MONTH_NUM_FIFTH:
5682 				mid = g_strdup (C_("recur-description", "on the fifth Saturday"));
5683 				break;
5684 			case MONTH_NUM_LAST:
5685 				mid = g_strdup (C_("recur-description", "on the last Saturday"));
5686 				break;
5687 			default:
5688 				g_warning ("%s: What is month_num:%d for month_day:%d?", G_STRFUNC, month_num, month_day);
5689 				break;
5690 			}
5691 			break;
5692 		case MONTH_DAY_SUN:
5693 			switch (month_num) {
5694 			case MONTH_NUM_FIRST:
5695 				mid = g_strdup (C_("recur-description", "on the first Sunday"));
5696 				break;
5697 			case MONTH_NUM_SECOND:
5698 				mid = g_strdup (C_("recur-description", "on the second Sunday"));
5699 				break;
5700 			case MONTH_NUM_THIRD:
5701 				mid = g_strdup (C_("recur-description", "on the third Sunday"));
5702 				break;
5703 			case MONTH_NUM_FOURTH:
5704 				mid = g_strdup (C_("recur-description", "on the fourth Sunday"));
5705 				break;
5706 			case MONTH_NUM_FIFTH:
5707 				mid = g_strdup (C_("recur-description", "on the fifth Sunday"));
5708 				break;
5709 			case MONTH_NUM_LAST:
5710 				mid = g_strdup (C_("recur-description", "on the last Sunday"));
5711 				break;
5712 			default:
5713 				g_warning ("%s: What is month_num:%d for month_day:%d?", G_STRFUNC, month_num, month_day);
5714 				break;
5715 			}
5716 			break;
5717 		}
5718 
5719 		break;
5720 	}
5721 
5722 	case I_CAL_YEARLY_RECURRENCE:
5723 		if (n_by_day != 0 ||
5724 		    n_by_month_day != 0 ||
5725 		    n_by_year_day != 0 ||
5726 		    n_by_week_no != 0 ||
5727 		    n_by_month != 0 ||
5728 		    n_by_set_pos != 0)
5729 			goto custom;
5730 
5731 		if (i_cal_recurrence_get_interval (rrule) > 0) {
5732 			if (!i_cal_recurrence_get_count (rrule) && (!until || !i_cal_time_get_year (until))) {
5733 				if (prefixed) {
5734 					result = g_strdup_printf (
5735 						g_dngettext (GETTEXT_PACKAGE,
5736 							"every year forever",
5737 							"every %d years forever",
5738 							i_cal_recurrence_get_interval (rrule)), i_cal_recurrence_get_interval (rrule));
5739 				} else {
5740 					result = g_strdup_printf (
5741 						g_dngettext (GETTEXT_PACKAGE,
5742 							"Every year forever",
5743 							"Every %d years forever",
5744 							i_cal_recurrence_get_interval (rrule)), i_cal_recurrence_get_interval (rrule));
5745 				}
5746 			} else {
5747 				if (prefixed) {
5748 					prefix = g_strdup_printf (
5749 						g_dngettext (GETTEXT_PACKAGE,
5750 							"every year",
5751 							"every %d years",
5752 							i_cal_recurrence_get_interval (rrule)), i_cal_recurrence_get_interval (rrule));
5753 				} else {
5754 					prefix = g_strdup_printf (
5755 						g_dngettext (GETTEXT_PACKAGE,
5756 							"Every year",
5757 							"Every %d years",
5758 							i_cal_recurrence_get_interval (rrule)), i_cal_recurrence_get_interval (rrule));
5759 				}
5760 			}
5761 		}
5762 		break;
5763 
5764 	default:
5765 		break;
5766 	}
5767 
5768 	if (prefix) {
5769 		if (i_cal_recurrence_get_count (rrule)) {
5770 			suffix = g_strdup_printf (
5771 				g_dngettext (GETTEXT_PACKAGE,
5772 					/* Translators: This is one of the last possible parts of a recurrence description.
5773 					   The text is appended at the end of the complete recurrence description, making it
5774 					   for example: "Every 3 days for 10 occurrences" */
5775 					"for one occurrence",
5776 					"for %d occurrences",
5777 					i_cal_recurrence_get_count (rrule)), i_cal_recurrence_get_count (rrule));
5778 		} else if (until && i_cal_time_get_year (until)) {
5779 			gchar dt_str[256];
5780 
5781 			dt_str[0] = 0;
5782 
5783 			if (!i_cal_time_is_date (until)) {
5784 				ICalTimezone *from_zone, *to_zone;
5785 
5786 				from_zone = i_cal_timezone_get_utc_timezone ();
5787 				to_zone = i_cal_time_get_timezone (dtstart);
5788 
5789 				if (to_zone)
5790 					i_cal_time_convert_timezone (until, from_zone, to_zone);
5791 
5792 				i_cal_time_set_time (until, 0, 0, 0);
5793 				i_cal_time_set_is_date (until, TRUE);
5794 			}
5795 
5796 			if (datetime_fmt_func) {
5797 				datetime_fmt_func (until, dt_str, 255);
5798 			} else {
5799 				struct tm tm;
5800 
5801 				tm = e_cal_util_icaltime_to_tm (until);
5802 
5803 				e_time_format_date_and_time (&tm, FALSE, FALSE, FALSE, dt_str, 255);
5804 			}
5805 
5806 			if (*dt_str) {
5807 				/* Translators: This is one of the last possible parts of a recurrence description.
5808 				   The '%s' is replaced with actual date, thus it can create something like
5809 				   "until Mon 15.1.2018". The text is appended at the end of the complete
5810 				   recurrence description, making it for example: "Every 3 days until Mon 15.1.2018" */
5811 				suffix = g_strdup_printf (C_("recur-description", "until %s"), dt_str);
5812 			}
5813 		} else {
5814 			/* Translators: This is one of the last possible parts of a recurrence description.
5815 			   The text is appended at the end of the complete recurrence description, making it
5816 			   for example: "Every 2 months on Tuesday, Thursday and Friday forever" */
5817 			suffix = g_strdup (C_("recur-description", "forever"));
5818 		}
5819 	}
5820 
5821  custom:
5822 	if (!result && prefix && suffix) {
5823 		if (mid) {
5824 			/* Translators: This constructs a complete recurrence description; the '%1$s' is like "Every 2 weeks",
5825 			   the '%2$s' is like "on Tuesday and Friday" and the '%3$s' is like "for 10 occurrences", constructing
5826 			   together one sentence: "Every 2 weeks on Tuesday and Friday for 10 occurrences". */
5827 			result = g_strdup_printf (C_("recur-description", "%1$s %2$s %3$s"), prefix, mid, suffix);
5828 		} else {
5829 			/* Translators: This constructs a complete recurrence description; the '%1$s' is like "Every 2 days",
5830 			   the '%2$s' is like "for 10 occurrences", constructing together one sentence:
5831 			   "Every 2 days for 10 occurrences". */
5832 			result = g_strdup_printf (C_("recur-description", "%1$s %2$s"), prefix, suffix);
5833 		}
5834 	}
5835 
5836 	if (result) {
5837 		gint n_exdates;
5838 		gchar *tmp;
5839 
5840 		n_exdates = i_cal_component_count_properties (icalcomp, I_CAL_EXDATE_PROPERTY);
5841 		if (n_exdates > 0) {
5842 			gchar *exdates_str;
5843 
5844 			exdates_str = g_strdup_printf (
5845 				g_dngettext (GETTEXT_PACKAGE,
5846 				/* Translators: This text is appended at the end of complete recur description using "%s%s" in
5847 				   context "recur-description" */
5848 					", with one exception",
5849 					", with %d exceptions",
5850 					n_exdates), n_exdates);
5851 
5852 			/* Translators: This appends text like ", with 3 exceptions" at the end of complete recurrence description.
5853 			   The "%1$s" is replaced with the recurrence description, the "%2$s" with the text about exceptions.
5854 			   It will form something like: "Every 2 weeks on Tuesday and Friday for 10 occurrences, with 3 exceptions" */
5855 			tmp = g_strdup_printf (C_("recur-description", "%1$s%2$s"), result, exdates_str);
5856 
5857 			g_free (exdates_str);
5858 			g_free (result);
5859 			result = tmp;
5860 		}
5861 
5862 		if (prefixed) {
5863 			const gchar *comp_prefix;
5864 
5865 			if (i_cal_component_isa (icalcomp) == I_CAL_VEVENT_COMPONENT) {
5866 				if (i_cal_component_count_properties (icalcomp, I_CAL_ORGANIZER_PROPERTY) > 0 &&
5867 				    i_cal_component_count_properties (icalcomp, I_CAL_ATTENDEE_PROPERTY) > 0) {
5868 					comp_prefix = C_("recur-description", "The meeting recurs");
5869 				} else {
5870 					comp_prefix = C_("recur-description", "The appointment recurs");
5871 				}
5872 			} else if (i_cal_component_isa (icalcomp) == I_CAL_VTODO_COMPONENT) {
5873 				comp_prefix = C_("recur-description", "The task recurs");
5874 			} else /* if (i_cal_component_isa (comp) == I_CAL_VJOURNAL_COMPONENT) */ {
5875 				comp_prefix = C_("recur-description", "The memo recurs");
5876 			}
5877 
5878 			/* Translators: This adds a prefix in front of the complete recurrence description.
5879 			   The '%1$s' is replaced with something like "The meeting recurs" and
5880 			   the '%2$s' with something like "every 2 days forever", thus forming
5881 			   sentence like "This meeting recurs every 2 days forever" */
5882 			tmp = g_strdup_printf (C_("recur-description-prefix", "%1$s %2$s"), comp_prefix, result);
5883 
5884 			g_free (result);
5885 			result = tmp;
5886 		}
5887 	} else if (fallback) {
5888 		if (i_cal_component_isa (icalcomp) == I_CAL_VEVENT_COMPONENT) {
5889 			if (i_cal_component_count_properties (icalcomp, I_CAL_ORGANIZER_PROPERTY) > 0 &&
5890 			    i_cal_component_count_properties (icalcomp, I_CAL_ATTENDEE_PROPERTY) > 0) {
5891 				result = g_strdup (C_("recur-description", "The meeting recurs"));
5892 			} else {
5893 				result = g_strdup (C_("recur-description", "The appointment recurs"));
5894 			}
5895 		} else if (i_cal_component_isa (icalcomp) == I_CAL_VTODO_COMPONENT) {
5896 			result = g_strdup (C_("recur-description", "The task recurs"));
5897 		} else if (i_cal_component_isa (icalcomp) == I_CAL_VJOURNAL_COMPONENT) {
5898 			result = g_strdup (C_("recur-description", "The memo recurs"));
5899 		}
5900 	}
5901 
5902 	g_clear_object (&dtstart);
5903 	g_clear_object (&until);
5904 	g_clear_object (&rrule);
5905 	g_clear_object (&prop);
5906 	g_free (prefix);
5907 	g_free (mid);
5908 	g_free (suffix);
5909 
5910 	return result;
5911 }
5912 
5913 /**
5914  * e_cal_recur_describe_recurrence:
5915  * @icalcomp: an #ICalComponent
5916  * @week_start_day: a day when the week starts
5917  * @flags: bit-or of #ECalRecurDescribeRecurrenceFlags
5918  *
5919  * Describes some simple types of recurrences in a human-readable and localized way.
5920  * The @flags influence the output format and what to do when the @icalcomp
5921  * contains more complicated recurrence, some which the function cannot describe.
5922  *
5923  * The @week_start_day is used for weekly recurrences, to start the list of selected
5924  * days at that day.
5925  *
5926  * Uses e_time_format_date_and_time() to format the date/time value in the string.
5927  * Call e_cal_recur_describe_recurrence_ex() with a custom formatting function.
5928  *
5929  * Free the returned string with g_free(), when no longer needed.
5930  *
5931  * Returns: (nullable) (transfer full): a newly allocated string, which
5932  *    describes the recurrence of the @icalcomp, or #NULL, when the @icalcomp
5933  *    doesn't recur or the recurrence is too complicated to describe, also
5934  *    according to given @flags.
5935  *
5936  * Since: 3.30
5937  **/
5938 gchar *
e_cal_recur_describe_recurrence(ICalComponent * icalcomp,GDateWeekday week_start_day,guint32 flags)5939 e_cal_recur_describe_recurrence (ICalComponent *icalcomp,
5940 				 GDateWeekday week_start_day,
5941 				 guint32 flags)
5942 {
5943 	return e_cal_recur_describe_recurrence_ex (icalcomp, week_start_day, flags, NULL);
5944 }
5945