aboutsummaryrefslogtreecommitdiffstats
path: root/calendar/cal-util/cal-recur.c
diff options
context:
space:
mode:
Diffstat (limited to 'calendar/cal-util/cal-recur.c')
-rw-r--r--calendar/cal-util/cal-recur.c267
1 files changed, 172 insertions, 95 deletions
diff --git a/calendar/cal-util/cal-recur.c b/calendar/cal-util/cal-recur.c
index 1e21f3109c..8df567fa5c 100644
--- a/calendar/cal-util/cal-recur.c
+++ b/calendar/cal-util/cal-recur.c
@@ -115,8 +115,8 @@ typedef struct {
int interval;
- /* Specifies the end of the recurrence. No occurrences are generated
- after this date. If it is 0, the event recurs forever. */
+ /* Specifies the end of the recurrence, inclusive. No occurrences are
+ generated after this date. If it is 0, the event recurs forever. */
time_t enddate;
/* WKST property - the week start day: 0 = Monday to 6 = Sunday. */
@@ -197,8 +197,13 @@ struct _CalObjTime {
guint8 hour; /* 0 - 23 */
guint8 minute; /* 0 - 59 */
guint8 second; /* 0 - 59 (maybe up to 61 for leap seconds) */
- guint8 is_rdate; /* TRUE if this is an RDATE, which may have an
- end or duration set. */
+ guint8 flags; /* The meaning of this depends on where the
+ CalObjTime is used. In most cases this is
+ set to TRUE to indicate that this is an
+ RDATE with an end or a duration set.
+ In the exceptions code, this is set to TRUE
+ to indicate that this is an EXDATE with a
+ DATE value. */
};
/* This is what we use to represent specific recurrence dates.
@@ -262,7 +267,8 @@ static void cal_recur_generate_instances_of_rule (CalComponent *comp,
gpointer tz_cb_data);
static CalRecurrence * cal_recur_from_icalproperty (icalproperty *prop,
- gboolean exception);
+ gboolean exception,
+ icaltimezone *zone);
static gint cal_recur_ical_weekday_to_weekday (enum icalrecurrencetype_weekday day);
static void cal_recur_free (CalRecurrence *r);
@@ -276,6 +282,7 @@ static void cal_object_compute_duration (CalObjTime *start,
static gboolean generate_instances_for_chunk (CalComponent *comp,
time_t comp_dtstart,
+ icaltimezone *zone,
GSList *rrules,
GSList *rdates,
GSList *exrules,
@@ -291,6 +298,7 @@ static gboolean generate_instances_for_chunk (CalComponent *comp,
gpointer cb_data);
static GArray* cal_obj_expand_recurrence (CalObjTime *event_start,
+ icaltimezone *zone,
CalRecurrence *recur,
CalObjTime *interval_start,
CalObjTime *interval_end,
@@ -458,11 +466,10 @@ static gint cal_obj_time_day_of_year (CalObjTime *cotime);
static void cal_obj_time_find_first_week (CalObjTime *cotime,
RecurData *recur_data);
static void cal_object_time_from_time (CalObjTime *cotime,
- time_t t);
-#if 0
-static gint cal_obj_date_only_compare_func (const void *arg1,
- const void *arg2);
-#endif
+ time_t t,
+ icaltimezone *zone);
+static gint cal_obj_date_only_compare_func (const void *arg1,
+ const void *arg2);
@@ -722,13 +729,14 @@ cal_recur_generate_instances_of_rule (CalComponent *comp,
/* Convert the interval start & end to CalObjTime. Note that if end
is -1 interval_end won't be set, so don't use it!
Also note that we use end - 1 since we want the interval to be
- inclusive as it makes the code simpler. */
- cal_object_time_from_time (&interval_start, start);
+ inclusive as it makes the code simpler. We do all calculation
+ in the timezone of the DTSTART. */
+ cal_object_time_from_time (&interval_start, start, start_zone);
if (end != -1)
- cal_object_time_from_time (&interval_end, end - 1);
+ cal_object_time_from_time (&interval_end, end - 1, start_zone);
- cal_object_time_from_time (&event_start, dtstart_time);
- cal_object_time_from_time (&event_end, dtend_time);
+ cal_object_time_from_time (&event_start, dtstart_time, start_zone);
+ cal_object_time_from_time (&event_end, dtend_time, start_zone);
/* Calculate the duration of the event, which we use for all
occurrences. We can't just subtract start from end since that may
@@ -776,10 +784,11 @@ cal_recur_generate_instances_of_rule (CalComponent *comp,
chunk_end.hour = 23;
chunk_end.minute = 59;
chunk_end.second = 61;
- chunk_end.is_rdate = FALSE;
+ chunk_end.flags = FALSE;
}
if (!generate_instances_for_chunk (comp, dtstart_time,
+ start_zone,
rrules, rdates,
exrules, exdates,
single_rule,
@@ -820,6 +829,8 @@ array_to_list (short *array, int max_elements)
/**
* cal_recur_from_icalproperty:
* @ir: An RRULE or EXRULE #icalproperty.
+ * @zone: The DTSTART timezone, used for converting the UNTIL property if it
+ * is given as a DATE value.
*
* Converts an #icalproperty to a #CalRecurrence. This should be
* freed using the cal_recur_free() function.
@@ -827,7 +838,8 @@ array_to_list (short *array, int max_elements)
* Return value: #CalRecurrence structure.
**/
static CalRecurrence *
-cal_recur_from_icalproperty (icalproperty *prop, gboolean exception)
+cal_recur_from_icalproperty (icalproperty *prop, gboolean exception,
+ icaltimezone *zone)
{
struct icalrecurrencetype ir;
CalRecurrence *r;
@@ -846,16 +858,31 @@ cal_recur_from_icalproperty (icalproperty *prop, gboolean exception)
r->interval = ir.interval;
if (ir.count != 0) {
+ /* If COUNT is set, we use the pre-calculated enddate. */
r->enddate = cal_recur_get_rule_end_date (prop);
} else {
- /* FIXME: icaltime_as_timet() seems to return -1 if UNTIL isn't
- set, but a simpler test would be better. */
- r->enddate = icaltime_as_timet (ir.until);
- if (r->enddate == -1)
+ if (icaltime_is_null_time (ir.until)) {
+ /* If neither COUNT or UNTIL is set, the event
+ recurs forever. */
r->enddate = 0;
- else if (ir.until.is_date)
- /* FIXME: Decide what to do here. */
- r->enddate = time_add_day (r->enddate, 1) - 1;
+ } else if (ir.until.is_date) {
+ /* If UNTIL is a DATE, we stop at the end of
+ the day, in local time (with the DTSTART timezone).
+ Note that UNTIL is inclusive so we stop before
+ midnight. */
+ ir.until.hour = 23;
+ ir.until.minute = 59;
+ ir.until.second = 59;
+
+ r->enddate = icaltime_as_timet_with_zone (ir.until,
+ zone);
+ } else {
+ /* If UNTIL is a DATE-TIME, it must be in UTC. */
+ icaltimezone *utc_zone;
+ utc_zone = icaltimezone_get_utc_timezone ();
+ r->enddate = icaltime_as_timet_with_zone (ir.until,
+ utc_zone);
+ }
}
r->week_start_day = cal_recur_ical_weekday_to_weekday (ir.week_start);
@@ -984,6 +1011,7 @@ cal_recur_free (CalRecurrence *r)
static gboolean
generate_instances_for_chunk (CalComponent *comp,
time_t comp_dtstart,
+ icaltimezone *zone,
GSList *rrules,
GSList *rdates,
GSList *exrules,
@@ -1003,7 +1031,7 @@ generate_instances_for_chunk (CalComponent *comp,
GSList *elem;
gint i;
time_t start_time, end_time;
- struct tm start_tm, end_tm;
+ struct icaltimetype start_tt, end_tt;
gboolean cb_status = TRUE, rule_finished, finished = TRUE;
#if 0
@@ -1041,9 +1069,9 @@ generate_instances_for_chunk (CalComponent *comp,
CalRecurrence *r;
prop = elem->data;
- r = cal_recur_from_icalproperty (prop, FALSE);
+ r = cal_recur_from_icalproperty (prop, FALSE, zone);
- tmp_occs = cal_obj_expand_recurrence (event_start, r,
+ tmp_occs = cal_obj_expand_recurrence (event_start, zone, r,
chunk_start,
chunk_end,
&rule_finished);
@@ -1058,16 +1086,26 @@ generate_instances_for_chunk (CalComponent *comp,
g_array_free (tmp_occs, TRUE);
}
- /* Add on specific occurrence dates, flag them as RDATEs, and store
- a pointer to the period in the rdate_periods array. */
+ /* Add on specific RDATE occurrence dates. If they have an end time
+ or duration set, flag them as RDATEs, and store a pointer to the
+ period in the rdate_periods array. Otherwise we can just treat them
+ as normal occurrences. */
for (elem = rdates; elem; elem = elem->next) {
CalComponentPeriod *p;
CalObjRecurrenceDate rdate;
- time_t t;
p = elem->data;
- t = icaltime_as_timet (p->start);
- cal_object_time_from_time (&cotime, t);
+
+ /* FIXME: We currently assume RDATEs are in the same timezone
+ as DTSTART. We should get the RDATE timezone and convert
+ to the DTSTART timezone first. */
+ cotime.year = p->start.year;
+ cotime.month = p->start.month - 1;
+ cotime.day = p->start.day;
+ cotime.hour = p->start.hour;
+ cotime.minute = p->start.minute;
+ cotime.second = p->start.second;
+ cotime.flags = FALSE;
/* If the rdate is after the current chunk we set finished
to FALSE, and we skip it. */
@@ -1078,10 +1116,11 @@ generate_instances_for_chunk (CalComponent *comp,
/* Check if the end date or duration is set. If it is we need
to store it so we can get it later. (libical seems to set
- second to -1 to denote an unset time. See icalvalue.c) */
+ second to -1 to denote an unset time. See icalvalue.c)
+ FIXME. */
if (p->type != CAL_COMPONENT_PERIOD_DATETIME
|| p->u.end.second != -1) {
- cotime.is_rdate = TRUE;
+ cotime.flags = TRUE;
rdate.start = cotime;
rdate.period = p;
@@ -1097,9 +1136,9 @@ generate_instances_for_chunk (CalComponent *comp,
CalRecurrence *r;
prop = elem->data;
- r = cal_recur_from_icalproperty (prop, FALSE);
+ r = cal_recur_from_icalproperty (prop, FALSE, zone);
- tmp_occs = cal_obj_expand_recurrence (event_start, r,
+ tmp_occs = cal_obj_expand_recurrence (event_start, zone, r,
chunk_start,
chunk_end,
&rule_finished);
@@ -1112,16 +1151,30 @@ generate_instances_for_chunk (CalComponent *comp,
/* Add on specific exception dates. */
for (elem = exdates; elem; elem = elem->next) {
CalComponentDateTime *cdt;
- time_t t;
-
- /* FIXME we should only be dealing with dates, not times too.
- No, I think it is supposed to be dates & times - Damon.
- I'm not sure what the semantics of just a date would be,
- since the event could recur several times each day. */
cdt = elem->data;
- t = icaltime_as_timet (*cdt->value);
- cal_object_time_from_time (&cotime, t);
+
+ /* FIXME: We currently assume EXDATEs are in the same timezone
+ as DTSTART. We should get the EXDATE timezone and convert
+ to the DTSTART timezone first. */
+ cotime.year = cdt->value->year;
+ cotime.month = cdt->value->month - 1;
+ cotime.day = cdt->value->day;
+
+ /* If the EXDATE has a DATE value, set the time to the start
+ of the day and set flags to TRUE so we know to skip all
+ occurrences on that date. */
+ if (cdt->value->is_date) {
+ cotime.hour = 0;
+ cotime.minute = 0;
+ cotime.second = 0;
+ cotime.flags = TRUE;
+ } else {
+ cotime.hour = cdt->value->hour;
+ cotime.minute = cdt->value->minute;
+ cotime.second = cdt->value->second;
+ cotime.flags = FALSE;
+ }
g_array_append_val (ex_occs, cotime);
}
@@ -1148,17 +1201,17 @@ generate_instances_for_chunk (CalComponent *comp,
g_print ("Checking occurrence: %s\n",
cal_obj_time_to_string (occ));
#endif
- start_tm.tm_year = occ->year - 1900;
- start_tm.tm_mon = occ->month;
- start_tm.tm_mday = occ->day;
- start_tm.tm_hour = occ->hour;
- start_tm.tm_min = occ->minute;
- start_tm.tm_sec = occ->second;
- start_tm.tm_isdst = -1;
- start_time = mktime (&start_tm);
+ start_tt.year = occ->year;
+ start_tt.month = occ->month + 1;
+ start_tt.day = occ->day;
+ start_tt.hour = occ->hour;
+ start_tt.minute = occ->minute;
+ start_tt.second = occ->second;
+ start_tt.is_daylight = -1;
+ start_time = icaltime_as_timet_with_zone (start_tt, zone);
if (start_time == -1) {
- g_warning ("mktime failed - time_t out of range?");
+ g_warning ("time_t out of range");
finished = TRUE;
break;
}
@@ -1177,7 +1230,10 @@ generate_instances_for_chunk (CalComponent *comp,
continue;
}
- if (occ->is_rdate) {
+ if (occ->flags) {
+ /* If it is an RDATE, we see if the end date or
+ duration was set. If not, we use the same duration
+ as the original occurrence. */
if (!cal_object_get_rdate_end (occ, rdate_periods)) {
cal_obj_time_add_days (occ, duration_days);
cal_obj_time_add_seconds (occ,
@@ -1188,17 +1244,17 @@ generate_instances_for_chunk (CalComponent *comp,
cal_obj_time_add_seconds (occ, duration_seconds);
}
- end_tm.tm_year = occ->year - 1900;
- end_tm.tm_mon = occ->month;
- end_tm.tm_mday = occ->day;
- end_tm.tm_hour = occ->hour;
- end_tm.tm_min = occ->minute;
- end_tm.tm_sec = occ->second;
- end_tm.tm_isdst = -1;
- end_time = mktime (&end_tm);
+ end_tt.year = occ->year;
+ end_tt.month = occ->month + 1;
+ end_tt.day = occ->day;
+ end_tt.hour = occ->hour;
+ end_tt.minute = occ->minute;
+ end_tt.second = occ->second;
+ end_tt.is_daylight = -1;
+ end_time = icaltime_as_timet_with_zone (end_tt, zone);
if (end_time == -1) {
- g_warning ("mktime failed - time_t out of range?");
+ g_warning ("time_t out of range");
finished = TRUE;
break;
}
@@ -1238,7 +1294,6 @@ cal_object_get_rdate_end (CalObjTime *occ,
CalObjRecurrenceDate *rdate = NULL;
CalComponentPeriod *p;
gint lower, upper, middle, cmp = 0;
- time_t t;
lower = 0;
upper = rdate_periods->len;
@@ -1267,8 +1322,16 @@ cal_object_get_rdate_end (CalObjTime *occ,
p = rdate->period;
if (p->type == CAL_COMPONENT_PERIOD_DATETIME) {
- t = icaltime_as_timet (p->u.end);
- cal_object_time_from_time (occ, t);
+ /* FIXME: We currently assume RDATEs are in the same timezone
+ as DTSTART. We should get the RDATE timezone and convert
+ to the DTSTART timezone first. */
+ occ->year = p->u.end.year;
+ occ->month = p->u.end.month - 1;
+ occ->day = p->u.end.day;
+ occ->hour = p->u.end.hour;
+ occ->minute = p->u.end.minute;
+ occ->second = p->u.end.second;
+ occ->flags = FALSE;
} else {
cal_obj_time_add_days (occ, p->u.duration.weeks * 7
+ p->u.duration.days);
@@ -1319,6 +1382,7 @@ cal_object_compute_duration (CalObjTime *start,
after the given interval.*/
static GArray*
cal_obj_expand_recurrence (CalObjTime *event_start,
+ icaltimezone *zone,
CalRecurrence *recur,
CalObjTime *interval_start,
CalObjTime *interval_end,
@@ -1346,7 +1410,7 @@ cal_obj_expand_recurrence (CalObjTime *event_start,
/* Compute the event_end, if the recur's enddate is set. */
if (recur->enddate > 0) {
cal_object_time_from_time (&event_end_cotime,
- recur->enddate);
+ recur->enddate, zone);
event_end = &event_end_cotime;
/* If the enddate is before the requested interval return. */
@@ -1653,7 +1717,7 @@ static CalRecurVTable* cal_obj_get_vtable (icalrecurrencetype_frequency recur_ty
vtable = &cal_obj_secondly_vtable;
break;
default:
- g_warning ("Unknown recurrence frequenct");
+ g_warning ("Unknown recurrence frequency");
vtable = NULL;
}
@@ -1836,15 +1900,15 @@ cal_obj_remove_exceptions (GArray *occs,
&& cal_obj_time_compare_func (occ, prev_occ) == 0) {
keep_occ = FALSE;
- /* If this occurrence is an RDATE, and the previous
- occurrence in the array was kept, set the RDATE flag
- of the last one, so we still use the end date
- or duration. */
- if (occ->is_rdate && !current_time_is_exception) {
+ /* If this occurrence is an RDATE with an end or
+ duration set, and the previous occurrence in the
+ array was kept, set the RDATE flag of the last one,
+ so we still use the end date or duration. */
+ if (occ->flags && !current_time_is_exception) {
last_occ_kept = &g_array_index (occs,
CalObjTime,
j - 1);
- last_occ_kept->is_rdate = TRUE;
+ last_occ_kept->flags = TRUE;
}
} else {
/* We've found a new occurrence time. Reset the flag
@@ -1857,9 +1921,14 @@ cal_obj_remove_exceptions (GArray *occs,
to one that matches or follows this
occurrence. */
while (ex_occ) {
- cmp = cal_obj_time_compare_func (ex_occ, occ);
- /* I'm pretty sure this is wrong. */
- /*cmp = cal_obj_date_only_compare_func (ex_occ, occ);*/
+ /* If the exception is an EXDATE with
+ a DATE value, we only have to
+ compare the date. */
+ if (ex_occ->flags)
+ cmp = cal_obj_date_only_compare_func (ex_occ, occ);
+ else
+ cmp = cal_obj_time_compare_func (ex_occ, occ);
+
if (cmp > 0)
break;
@@ -1886,6 +1955,10 @@ cal_obj_remove_exceptions (GArray *occs,
}
if (keep_occ) {
+ /* We are keeping this occurrence, so we move it to
+ the next free space, unless its position hasn't
+ changed (i.e. all previous occurrences were also
+ kept). */
if (i != j)
g_array_index (occs, CalObjTime, j)
= g_array_index (occs, CalObjTime, i);
@@ -3465,7 +3538,7 @@ cal_obj_time_compare_func (const void *arg1,
return retval;
}
-#if 0
+
static gint
cal_obj_date_only_compare_func (const void *arg1,
const void *arg2)
@@ -3492,7 +3565,7 @@ cal_obj_date_only_compare_func (const void *arg1,
return 0;
}
-#endif
+
/* Returns the weekday of the given CalObjTime, from 0 (Mon) - 6 (Sun). */
static gint
@@ -3590,23 +3663,21 @@ cal_obj_time_find_first_week (CalObjTime *cotime,
static void
-cal_object_time_from_time (CalObjTime *cotime,
- time_t t)
+cal_object_time_from_time (CalObjTime *cotime,
+ time_t t,
+ icaltimezone *zone)
{
- struct tm *tmp_tm;
- time_t tmp_time_t;
+ struct icaltimetype tt;
- tmp_time_t = t;
- /* FIXME */
- tmp_tm = localtime (&tmp_time_t);
+ tt = icaltime_from_timet_with_zone (t, FALSE, zone);
- cotime->year = tmp_tm->tm_year + 1900;
- cotime->month = tmp_tm->tm_mon;
- cotime->day = tmp_tm->tm_mday;
- cotime->hour = tmp_tm->tm_hour;
- cotime->minute = tmp_tm->tm_min;
- cotime->second = tmp_tm->tm_sec;
- cotime->is_rdate = FALSE;
+ cotime->year = tt.year;
+ cotime->month = tt.month - 1;
+ cotime->day = tt.day;
+ cotime->hour = tt.hour;
+ cotime->minute = tt.minute;
+ cotime->second = tt.second;
+ cotime->flags = FALSE;
}
@@ -3746,6 +3817,7 @@ cal_recur_get_rule_end_date (icalproperty *prop)
const char *xname, *xvalue;
icalvalue *value;
struct icaltimetype icaltime;
+ icaltimezone *utc_zone;
param = icalproperty_get_first_parameter (prop, ICAL_X_PARAMETER);
while (param) {
@@ -3758,7 +3830,9 @@ cal_recur_get_rule_end_date (icalproperty *prop)
icaltime = icalvalue_get_datetime (value);
icalvalue_free (value);
- return icaltime_as_timet (icaltime);
+ utc_zone = icaltimezone_get_utc_timezone ();
+ return icaltime_as_timet_with_zone (icaltime,
+ utc_zone);
}
}
@@ -3776,10 +3850,13 @@ cal_recur_set_rule_end_date (icalproperty *prop,
{
icalparameter *param;
icalvalue *value;
+ icaltimezone *utc_zone;
struct icaltimetype icaltime;
const char *end_date_string, *xname;
- icaltime = icaltime_from_timet (end_date, FALSE);
+ /* We save the value as a UTC DATE-TIME. */
+ utc_zone = icaltimezone_get_utc_timezone ();
+ icaltime = icaltime_from_timet_with_zone (end_date, FALSE, utc_zone);
value = icalvalue_new_datetime (icaltime);
end_date_string = icalvalue_as_ical_string (value);
icalvalue_free (value);