2000-07-31 Federico Mena Quintero <federico@helixcode.com> * cal-util/cal-recur.c (*): Use CalComponent and the new property types instead of the old iCalObject stuff. (cal_recur_generate_instances): Renamed from cal_object_generate_events(). Ensure that the component has the DTSTART property. (generate_instances_for_year): Renamed from cal_object_generate_events_for_year(). (cal_obj_expand_recurrence): Made static. (cal_recur_from_icalrecurrencetype): New function. We should really convert this whole file to use struct icalrecurrencetype instead. (cal_recur_free): New function. * cal-util/cal-recur.h (CalRecurType): Renamed from CalObjRecurType. (CalRecurrence): Renamed from CalObjRecurrence. * cal-util/timeutil.c (time_from_icaltimetype): New function. * cal-util/Makefile.am: Commented out the test-recur program. svn path=/trunk/; revision=4464
3055 lines
84 KiB
C
3055 lines
84 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
|
|
/*
|
|
* Evolution calendar recurrence rule functions
|
|
*
|
|
* Copyright (C) 2000 Helix Code, Inc.
|
|
*
|
|
* Author: Damon Chaplin <damon@helixcode.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <cal-util/cal-recur.h>
|
|
#include <cal-util/timeutil.h>
|
|
|
|
|
|
/*
|
|
* Introduction to The Recurrence Generation Functions:
|
|
*
|
|
* Note: This is pretty complicated. See the iCalendar spec (RFC 2445) for
|
|
* the specification of the recurrence rules and lots of examples
|
|
* (sections 4.3.10 & 4.8.5). We also want to support the older
|
|
* vCalendar spec, though this should be easy since it is basically a
|
|
* subset of iCalendar.
|
|
*
|
|
* o An iCalendar event can have any number of recurrence rules specifying
|
|
* occurrences of the event, as well as dates & times of specific
|
|
* occurrences. It can also have any number of recurrence rules and
|
|
* specific dates & times specifying exceptions to the occurrences.
|
|
* So we first merge all the occurrences generated, eliminating any
|
|
* duplicates, then we generate all the exceptions and remove these to
|
|
* form the final set of occurrences.
|
|
*
|
|
* o There are 7 frequencies of occurrences: YEARLY, MONTHLY, WEEKLY, DAILY,
|
|
* HOURLY, MINUTELY & SECONDLY. The 'interval' property specifies the
|
|
* multiples of the frequency which we step by. We generate a 'set' of
|
|
* occurrences for each period defined by the frequency & interval.
|
|
* So for a YEARLY frequency with an interval of 3, we generate a set of
|
|
* occurrences for every 3rd year. We use complete years here - any
|
|
* generated occurrences that occur before the event's start (or after its
|
|
* end) are just discarded.
|
|
*
|
|
* o There are 8 frequency modifiers: BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY,
|
|
* BYDAY, BYHOUR, BYMINUTE & BYSECOND. These can either add extra occurrences
|
|
* or filter out occurrences. For example 'FREQ=YEARLY;BYMONTH=1,2' produces
|
|
* 2 occurrences for each year rather than the default 1. And
|
|
* 'FREQ=DAILY;BYMONTH=1' filters out all occurrences except those in Jan.
|
|
* If the modifier works on periods which are less than the recurrence
|
|
* frequency, then extra occurrences are added, otherwise occurrences are
|
|
* filtered. So we have 2 functions for each modifier - one to expand events
|
|
* and the other to filter. We use a table of functions for each frequency
|
|
* which points to the appropriate function to use for each modifier.
|
|
*
|
|
* o Any number of frequency modifiers can be used in a recurrence rule.
|
|
* (Though the iCalendar spec says that BYWEEKNO can only be used in a YEARLY
|
|
* rule, and some modifiers aren't appropriate for some frequencies - e.g.
|
|
* BYMONTHDAY is not really useful in a WEEKLY frequency, and BYYEARDAY is
|
|
* not useful in a MONTHLY or WEEKLY frequency).
|
|
* The frequency modifiers are applied in the order given above. The first 5
|
|
* modifier rules (BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY & BYDAY) all
|
|
* produce the days on which the occurrences take place, and so we have to
|
|
* compute some of these in parallel rather than sequentially, or we may end
|
|
* up with too many days.
|
|
*
|
|
* o Note that some expansion functions may produce days which are invalid,
|
|
* e.g. 31st September, 30th Feb. These invalid days are removed before the
|
|
* BYHOUR, BYMINUTE & BYSECOND modifier functions are applied.
|
|
*
|
|
* o After the set of occurrences for the frequency interval are generated,
|
|
* the BYSETPOS property is used to select which of the occurrences are
|
|
* finally output. If BYSETPOS is not specified then all the occurrences are
|
|
* output.
|
|
*/
|
|
|
|
|
|
/* This is what we use to pass to all the filter functions. */
|
|
typedef struct _RecurData RecurData;
|
|
struct _RecurData {
|
|
CalRecurrence *recur;
|
|
|
|
/* This is used for the WEEKLY frequency. */
|
|
gint weekday;
|
|
|
|
/* This is used for fast lookup in BYMONTH filtering. */
|
|
guint8 months[12];
|
|
|
|
/* This is used for fast lookup in BYYEARDAY filtering. */
|
|
guint8 yeardays[367], neg_yeardays[367]; /* Days are 1 - 366. */
|
|
|
|
/* This is used for fast lookup in BYMONTHDAY filtering. */
|
|
guint8 monthdays[32], neg_monthdays[32]; /* Days are 1 to 31. */
|
|
|
|
/* This is used for fast lookup in BYDAY filtering. */
|
|
guint8 weekdays[7];
|
|
|
|
/* This is used for fast lookup in BYHOUR filtering. */
|
|
guint8 hours[24];
|
|
|
|
/* This is used for fast lookup in BYMINUTE filtering. */
|
|
guint8 minutes[60];
|
|
|
|
/* This is used for fast lookup in BYSECOND filtering. */
|
|
guint8 seconds[61];
|
|
};
|
|
|
|
|
|
|
|
typedef gboolean (*CalObjFindStartFn) (CalObjTime *event_start,
|
|
CalObjTime *event_end,
|
|
RecurData *recur_data,
|
|
CalObjTime *interval_start,
|
|
CalObjTime *interval_end,
|
|
CalObjTime *cotime);
|
|
typedef gboolean (*CalObjFindNextFn) (CalObjTime *cotime,
|
|
CalObjTime *event_end,
|
|
RecurData *recur_data,
|
|
CalObjTime *interval_end);
|
|
typedef GArray* (*CalObjFilterFn) (RecurData *recur_data,
|
|
GArray *occs);
|
|
|
|
typedef struct _CalRecurVTable CalRecurVTable;
|
|
struct _CalRecurVTable {
|
|
CalObjFindStartFn find_start_position;
|
|
CalObjFindNextFn find_next_position;
|
|
|
|
CalObjFilterFn bymonth_filter;
|
|
CalObjFilterFn byweekno_filter;
|
|
CalObjFilterFn byyearday_filter;
|
|
CalObjFilterFn bymonthday_filter;
|
|
CalObjFilterFn byday_filter;
|
|
CalObjFilterFn byhour_filter;
|
|
CalObjFilterFn byminute_filter;
|
|
CalObjFilterFn bysecond_filter;
|
|
};
|
|
|
|
|
|
/* This is used to specify which parts of the CalObjTime to compare in
|
|
cal_obj_time_compare(). */
|
|
typedef enum {
|
|
CALOBJ_YEAR,
|
|
CALOBJ_MONTH,
|
|
CALOBJ_DAY,
|
|
CALOBJ_HOUR,
|
|
CALOBJ_MINUTE,
|
|
CALOBJ_SECOND
|
|
} CalObjTimeComparison;
|
|
|
|
static void cal_object_compute_duration (CalObjTime *start,
|
|
CalObjTime *end,
|
|
gint *days,
|
|
gint *seconds);
|
|
|
|
static gboolean generate_instances_for_year (CalComponent *comp,
|
|
time_t comp_dtstart,
|
|
GSList *rrules,
|
|
GSList *rdates,
|
|
GSList *exrules,
|
|
GSList *exdates,
|
|
CalObjTime *event_start,
|
|
CalObjTime *interval_start,
|
|
CalObjTime *interval_end,
|
|
time_t interval_start_time,
|
|
time_t interval_end_time,
|
|
gint duration_days,
|
|
gint duration_seconds,
|
|
CalRecurInstanceFn cb,
|
|
gpointer cb_data);
|
|
|
|
static GArray* cal_obj_expand_recurrence (CalObjTime *event_start,
|
|
CalRecurrence *recur,
|
|
CalObjTime *interval_start,
|
|
CalObjTime *interval_end);
|
|
|
|
static GArray* cal_obj_generate_set_yearly (RecurData *recur_data,
|
|
CalRecurVTable *vtable,
|
|
CalObjTime *occ);
|
|
static GArray* cal_obj_generate_set_monthly (RecurData *recur_data,
|
|
CalRecurVTable *vtable,
|
|
CalObjTime *occ);
|
|
static GArray* cal_obj_generate_set_default (RecurData *recur_data,
|
|
CalRecurVTable *vtable,
|
|
CalObjTime *occ);
|
|
|
|
|
|
static CalRecurVTable* cal_obj_get_vtable (CalRecurType recur_type);
|
|
static void cal_obj_initialize_recur_data (RecurData *recur_data,
|
|
CalRecurrence *recur,
|
|
CalObjTime *event_start);
|
|
static void cal_obj_sort_occurrences (GArray *occs);
|
|
static gint cal_obj_time_compare_func (const void *arg1,
|
|
const void *arg2);
|
|
static void cal_obj_remove_duplicates_and_invalid_dates (GArray *occs);
|
|
static void cal_obj_remove_exceptions (GArray *occs,
|
|
GArray *ex_occs);
|
|
static GArray* cal_obj_bysetpos_filter (CalRecurrence *recur,
|
|
GArray *occs);
|
|
|
|
|
|
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);
|
|
static gboolean cal_obj_yearly_find_next_position (CalObjTime *cotime,
|
|
CalObjTime *event_end,
|
|
RecurData *recur_data,
|
|
CalObjTime *interval_end);
|
|
|
|
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);
|
|
static gboolean cal_obj_monthly_find_next_position (CalObjTime *cotime,
|
|
CalObjTime *event_end,
|
|
RecurData *recur_data,
|
|
CalObjTime *interval_end);
|
|
|
|
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);
|
|
static gboolean cal_obj_weekly_find_next_position (CalObjTime *cotime,
|
|
CalObjTime *event_end,
|
|
RecurData *recur_data,
|
|
CalObjTime *interval_end);
|
|
|
|
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);
|
|
static gboolean cal_obj_daily_find_next_position (CalObjTime *cotime,
|
|
CalObjTime *event_end,
|
|
RecurData *recur_data,
|
|
CalObjTime *interval_end);
|
|
|
|
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);
|
|
static gboolean cal_obj_hourly_find_next_position (CalObjTime *cotime,
|
|
CalObjTime *event_end,
|
|
RecurData *recur_data,
|
|
CalObjTime *interval_end);
|
|
|
|
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);
|
|
static gboolean cal_obj_minutely_find_next_position (CalObjTime *cotime,
|
|
CalObjTime *event_end,
|
|
RecurData *recur_data,
|
|
CalObjTime *interval_end);
|
|
|
|
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);
|
|
static gboolean cal_obj_secondly_find_next_position (CalObjTime *cotime,
|
|
CalObjTime *event_end,
|
|
RecurData *recur_data,
|
|
CalObjTime *interval_end);
|
|
|
|
static GArray* cal_obj_bymonth_expand (RecurData *recur_data,
|
|
GArray *occs);
|
|
static GArray* cal_obj_bymonth_filter (RecurData *recur_data,
|
|
GArray *occs);
|
|
static GArray* cal_obj_byweekno_expand (RecurData *recur_data,
|
|
GArray *occs);
|
|
#if 0
|
|
/* This isn't used at present. */
|
|
static GArray* cal_obj_byweekno_filter (RecurData *recur_data,
|
|
GArray *occs);
|
|
#endif
|
|
static GArray* cal_obj_byyearday_expand (RecurData *recur_data,
|
|
GArray *occs);
|
|
static GArray* cal_obj_byyearday_filter (RecurData *recur_data,
|
|
GArray *occs);
|
|
static GArray* cal_obj_bymonthday_expand (RecurData *recur_data,
|
|
GArray *occs);
|
|
static GArray* cal_obj_bymonthday_filter (RecurData *recur_data,
|
|
GArray *occs);
|
|
static GArray* cal_obj_byday_expand_yearly (RecurData *recur_data,
|
|
GArray *occs);
|
|
static GArray* cal_obj_byday_expand_monthly (RecurData *recur_data,
|
|
GArray *occs);
|
|
static GArray* cal_obj_byday_expand_weekly (RecurData *recur_data,
|
|
GArray *occs);
|
|
static GArray* cal_obj_byday_filter (RecurData *recur_data,
|
|
GArray *occs);
|
|
static GArray* cal_obj_byhour_expand (RecurData *recur_data,
|
|
GArray *occs);
|
|
static GArray* cal_obj_byhour_filter (RecurData *recur_data,
|
|
GArray *occs);
|
|
static GArray* cal_obj_byminute_expand (RecurData *recur_data,
|
|
GArray *occs);
|
|
static GArray* cal_obj_byminute_filter (RecurData *recur_data,
|
|
GArray *occs);
|
|
static GArray* cal_obj_bysecond_expand (RecurData *recur_data,
|
|
GArray *occs);
|
|
static GArray* cal_obj_bysecond_filter (RecurData *recur_data,
|
|
GArray *occs);
|
|
|
|
static void cal_obj_time_add_months (CalObjTime *cotime,
|
|
gint months);
|
|
static void cal_obj_time_add_days (CalObjTime *cotime,
|
|
gint days);
|
|
static void cal_obj_time_subtract_days (CalObjTime *cotime,
|
|
gint days);
|
|
static void cal_obj_time_add_hours (CalObjTime *cotime,
|
|
gint hours);
|
|
static void cal_obj_time_add_minutes (CalObjTime *cotime,
|
|
gint minutes);
|
|
static void cal_obj_time_add_seconds (CalObjTime *cotime,
|
|
gint seconds);
|
|
static gint cal_obj_time_compare (CalObjTime *cotime1,
|
|
CalObjTime *cotime2,
|
|
CalObjTimeComparison type);
|
|
static gint cal_obj_time_weekday (CalObjTime *cotime,
|
|
CalRecurrence *recur);
|
|
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);
|
|
|
|
|
|
CalRecurVTable cal_obj_yearly_vtable = {
|
|
cal_obj_yearly_find_start_position,
|
|
cal_obj_yearly_find_next_position,
|
|
|
|
cal_obj_bymonth_expand,
|
|
cal_obj_byweekno_expand,
|
|
cal_obj_byyearday_expand,
|
|
cal_obj_bymonthday_expand,
|
|
cal_obj_byday_expand_yearly,
|
|
cal_obj_byhour_expand,
|
|
cal_obj_byminute_expand,
|
|
cal_obj_bysecond_expand
|
|
};
|
|
|
|
CalRecurVTable cal_obj_monthly_vtable = {
|
|
cal_obj_monthly_find_start_position,
|
|
cal_obj_monthly_find_next_position,
|
|
|
|
cal_obj_bymonth_filter,
|
|
NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
|
|
NULL, /* BYYEARDAY is not useful in a MONTHLY frequency. */
|
|
cal_obj_bymonthday_expand,
|
|
cal_obj_byday_expand_monthly,
|
|
cal_obj_byhour_expand,
|
|
cal_obj_byminute_expand,
|
|
cal_obj_bysecond_expand
|
|
};
|
|
|
|
CalRecurVTable cal_obj_weekly_vtable = {
|
|
cal_obj_weekly_find_start_position,
|
|
cal_obj_weekly_find_next_position,
|
|
|
|
cal_obj_bymonth_filter,
|
|
NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
|
|
NULL, /* BYYEARDAY is not useful in a WEEKLY frequency. */
|
|
NULL, /* BYMONTHDAY is not useful in a WEEKLY frequency. */
|
|
cal_obj_byday_expand_weekly,
|
|
cal_obj_byhour_expand,
|
|
cal_obj_byminute_expand,
|
|
cal_obj_bysecond_expand
|
|
};
|
|
|
|
CalRecurVTable cal_obj_daily_vtable = {
|
|
cal_obj_daily_find_start_position,
|
|
cal_obj_daily_find_next_position,
|
|
|
|
cal_obj_bymonth_filter,
|
|
NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
|
|
cal_obj_byyearday_filter,
|
|
cal_obj_bymonthday_filter,
|
|
cal_obj_byday_filter,
|
|
cal_obj_byhour_expand,
|
|
cal_obj_byminute_expand,
|
|
cal_obj_bysecond_expand
|
|
};
|
|
|
|
CalRecurVTable cal_obj_hourly_vtable = {
|
|
cal_obj_hourly_find_start_position,
|
|
cal_obj_hourly_find_next_position,
|
|
|
|
cal_obj_bymonth_filter,
|
|
NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
|
|
cal_obj_byyearday_filter,
|
|
cal_obj_bymonthday_filter,
|
|
cal_obj_byday_filter,
|
|
cal_obj_byhour_filter,
|
|
cal_obj_byminute_expand,
|
|
cal_obj_bysecond_expand
|
|
};
|
|
|
|
CalRecurVTable cal_obj_minutely_vtable = {
|
|
cal_obj_minutely_find_start_position,
|
|
cal_obj_minutely_find_next_position,
|
|
|
|
cal_obj_bymonth_filter,
|
|
NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
|
|
cal_obj_byyearday_filter,
|
|
cal_obj_bymonthday_filter,
|
|
cal_obj_byday_filter,
|
|
cal_obj_byhour_filter,
|
|
cal_obj_byminute_filter,
|
|
cal_obj_bysecond_expand
|
|
};
|
|
|
|
CalRecurVTable cal_obj_secondly_vtable = {
|
|
cal_obj_secondly_find_start_position,
|
|
cal_obj_secondly_find_next_position,
|
|
|
|
cal_obj_bymonth_filter,
|
|
NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
|
|
cal_obj_byyearday_filter,
|
|
cal_obj_bymonthday_filter,
|
|
cal_obj_byday_filter,
|
|
cal_obj_byhour_filter,
|
|
cal_obj_byminute_filter,
|
|
cal_obj_bysecond_filter
|
|
};
|
|
|
|
/*
|
|
* Calls the given callback function for each occurrence of the event between
|
|
* the given start and end times. If end is 0 it continues until the event
|
|
* ends or forever if the event has an infinite recurrence rule.
|
|
* If the callback routine return 0 the occurrence generation stops.
|
|
*/
|
|
void
|
|
cal_recur_generate_instances (CalComponent *comp,
|
|
time_t start,
|
|
time_t end,
|
|
CalRecurInstanceFn cb,
|
|
gpointer cb_data)
|
|
{
|
|
CalComponentDateTime dtstart, dtend;
|
|
time_t dtstart_time;
|
|
GSList *rrules, *rdates, *exrules, *exdates;
|
|
CalObjTime interval_start, interval_end, event_start;
|
|
CalObjTime chunk_start, chunk_end;
|
|
gint days, seconds, year;
|
|
|
|
g_return_if_fail (comp != NULL);
|
|
g_return_if_fail (cb != NULL);
|
|
|
|
/* Get dtstart, dtend, recurrences, and exceptions */
|
|
|
|
cal_component_get_dtstart (comp, &dtstart);
|
|
cal_component_get_dtend (comp, &dtend);
|
|
|
|
if (!dtstart.value) {
|
|
g_message ("cal_recur_generate_instances(): bogus component, does not have "
|
|
"DTSTART. Skipping...");
|
|
goto out;
|
|
}
|
|
|
|
dtstart_time = time_from_icaltimetype (*dtstart.value);
|
|
|
|
cal_component_get_rrule_list (comp, &rrules);
|
|
cal_component_get_rdate_list (comp, &rdates);
|
|
cal_component_get_exrule_list (comp, &exrules);
|
|
cal_component_get_exdate_list (comp, &exdates);
|
|
|
|
/* If there is no recurrence, just call the callback if the event
|
|
intersects the given interval. */
|
|
|
|
if (!(rrules || rdates || exrules || exdates)) {
|
|
time_t dtend_time;
|
|
|
|
if (dtend.value)
|
|
dtend_time = time_from_icaltimetype (*dtend.value);
|
|
else
|
|
dtend_time = time_day_end (dtstart_time);
|
|
|
|
if ((end && dtstart_time < end && dtend_time > start)
|
|
|| (end == 0 && dtend_time > start)) {
|
|
(* cb) (comp, dtstart_time, dtend_time, cb_data);
|
|
}
|
|
|
|
goto out;
|
|
}
|
|
|
|
/* Convert the interval start & end to CalObjTime. */
|
|
cal_object_time_from_time (&interval_start, start);
|
|
cal_object_time_from_time (&interval_end, end);
|
|
|
|
cal_object_time_from_time (&event_start, dtstart_time);
|
|
|
|
/* Calculate the duration of the event, which we use for all
|
|
occurrences. We can't just subtract start from end since that may
|
|
be affected by daylight-saving time. We also don't want to just
|
|
use the number of seconds, since leap seconds will then cause a
|
|
problem. So we want a value of days + seconds. */
|
|
cal_object_compute_duration (&interval_start, &interval_end,
|
|
&days, &seconds);
|
|
|
|
/* Expand the recurrence for each year between start & end, or until
|
|
the callback returns 0 if end is 0. */
|
|
for (year = interval_start.year; year <= interval_end.year; year++) {
|
|
chunk_start = interval_start;
|
|
chunk_start.year = year;
|
|
chunk_end = interval_end;
|
|
chunk_end.year = year;
|
|
|
|
if (year != interval_start.year) {
|
|
chunk_start.month = 0;
|
|
chunk_start.day = 0;
|
|
chunk_start.hour = 0;
|
|
chunk_start.minute = 0;
|
|
chunk_start.second = 0;
|
|
}
|
|
if (year != interval_end.year) {
|
|
chunk_end.year++;
|
|
chunk_end.month = 0;
|
|
chunk_end.day = 0;
|
|
chunk_end.hour = 0;
|
|
chunk_end.minute = 0;
|
|
chunk_end.second = 0;
|
|
}
|
|
|
|
if (!generate_instances_for_year (comp, dtstart_time,
|
|
rrules, rdates,
|
|
exrules, exdates,
|
|
&event_start,
|
|
&interval_start,
|
|
&interval_end,
|
|
start, end,
|
|
days, seconds,
|
|
cb, cb_data))
|
|
break;
|
|
}
|
|
|
|
out:
|
|
cal_component_free_datetime (&dtstart);
|
|
cal_component_free_datetime (&dtend);
|
|
}
|
|
|
|
/* Builds a list of GINT_TO_POINTER() elements out of a short array from a
|
|
* struct icalrecurrencetype.
|
|
*/
|
|
static GList *
|
|
array_to_list (short *array, int max_elements)
|
|
{
|
|
GList *l;
|
|
int i;
|
|
|
|
l = NULL;
|
|
|
|
for (i = 0; i < max_elements && array[i] != SHRT_MAX; i++)
|
|
l = g_list_prepend (l, GINT_TO_POINTER ((int) (array[i])));
|
|
|
|
return g_list_reverse (l);
|
|
}
|
|
|
|
/**
|
|
* cal_recur_from_icalrecurrencetype:
|
|
* @ir: A struct #icalrecurrencetype.
|
|
*
|
|
* Converts a struct #icalrecurrencetype to a #CalRecurrence. This should be
|
|
* freed using the cal_recur_free() function.
|
|
*
|
|
* Return value: #CalRecurrence structure.
|
|
**/
|
|
CalRecurrence *
|
|
cal_recur_from_icalrecurrencetype (struct icalrecurrencetype *ir)
|
|
{
|
|
CalRecurrence *r;
|
|
|
|
g_return_val_if_fail (ir != NULL, NULL);
|
|
|
|
r = g_new (CalRecurrence, 1);
|
|
|
|
switch (ir->freq) {
|
|
case ICAL_SECONDLY_RECURRENCE:
|
|
r->type = CAL_RECUR_SECONDLY;
|
|
break;
|
|
|
|
case ICAL_MINUTELY_RECURRENCE:
|
|
r->type = CAL_RECUR_MINUTELY;
|
|
break;
|
|
|
|
case ICAL_HOURLY_RECURRENCE:
|
|
r->type = CAL_RECUR_HOURLY;
|
|
break;
|
|
|
|
case ICAL_DAILY_RECURRENCE:
|
|
r->type = CAL_RECUR_DAILY;
|
|
break;
|
|
|
|
case ICAL_WEEKLY_RECURRENCE:
|
|
r->type = CAL_RECUR_WEEKLY;
|
|
break;
|
|
|
|
case ICAL_MONTHLY_RECURRENCE:
|
|
r->type = CAL_RECUR_MONTHLY;
|
|
break;
|
|
|
|
case ICAL_YEARLY_RECURRENCE:
|
|
r->type = CAL_RECUR_YEARLY;
|
|
break;
|
|
|
|
default:
|
|
g_message ("cal_recur_from_icalrecurrencetype(): Unknown recurrence frequency %d",
|
|
(int) ir->freq);
|
|
g_free (r);
|
|
return NULL;
|
|
}
|
|
|
|
r->interval = ir->interval;
|
|
|
|
/* FIXME: we don't deal with ir->count. Also, how does libical
|
|
* distinguish between n-occurrences and until-some-date rules?
|
|
*/
|
|
r->enddate = time_from_icaltimetype (ir->until);
|
|
|
|
switch (ir->week_start) {
|
|
case ICAL_MONDAY_WEEKDAY:
|
|
r->week_start_day = 0;
|
|
break;
|
|
|
|
case ICAL_TUESDAY_WEEKDAY:
|
|
r->week_start_day = 1;
|
|
break;
|
|
|
|
case ICAL_WEDNESDAY_WEEKDAY:
|
|
r->week_start_day = 2;
|
|
break;
|
|
|
|
case ICAL_THURSDAY_WEEKDAY:
|
|
r->week_start_day = 3;
|
|
break;
|
|
|
|
case ICAL_FRIDAY_WEEKDAY:
|
|
r->week_start_day = 4;
|
|
break;
|
|
|
|
case ICAL_SATURDAY_WEEKDAY:
|
|
r->week_start_day = 5;
|
|
break;
|
|
|
|
case ICAL_SUNDAY_WEEKDAY:
|
|
r->week_start_day = 6;
|
|
break;
|
|
|
|
default:
|
|
g_message ("cal_recur_from_icalrecurrencetype(): Unknown week day %d",
|
|
ir->week_start);
|
|
g_free (r);
|
|
return NULL;
|
|
}
|
|
|
|
r->bymonth = array_to_list (ir->by_month,
|
|
sizeof (ir->by_month) / sizeof (ir->by_month[0]));
|
|
|
|
r->byweekno = array_to_list (ir->by_week_no,
|
|
sizeof (ir->by_week_no) / sizeof (ir->by_week_no[0]));
|
|
|
|
r->byyearday = array_to_list (ir->by_year_day,
|
|
sizeof (ir->by_year_day) / sizeof (ir->by_year_day[0]));
|
|
|
|
r->bymonthday = array_to_list (ir->by_month_day,
|
|
sizeof (ir->by_month_day) / sizeof (ir->by_month_day[0]));
|
|
|
|
r->byday = NULL; /* FIXME: libical sucks in this respect */
|
|
|
|
r->byhour = array_to_list (ir->by_hour,
|
|
sizeof (ir->by_hour) / sizeof (ir->by_hour[0]));
|
|
|
|
r->byminute = array_to_list (ir->by_minute,
|
|
sizeof (ir->by_minute) / sizeof (ir->by_minute[0]));
|
|
|
|
r->bysecond = array_to_list (ir->by_second,
|
|
sizeof (ir->by_second) / sizeof (ir->by_second[0]));
|
|
|
|
r->bysetpos = array_to_list (ir->by_set_pos,
|
|
sizeof (ir->by_set_pos) / sizeof (ir->by_set_pos[0]));
|
|
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* cal_recur_free:
|
|
* @r: A #CalRecurrence structure.
|
|
*
|
|
* Frees a #CalRecurrence structure.
|
|
**/
|
|
void
|
|
cal_recur_free (CalRecurrence *r)
|
|
{
|
|
g_return_if_fail (r != NULL);
|
|
|
|
g_list_free (r->bymonth);
|
|
g_list_free (r->byweekno);
|
|
g_list_free (r->byyearday);
|
|
g_list_free (r->bymonthday);
|
|
g_list_free (r->byday);
|
|
g_list_free (r->byhour);
|
|
g_list_free (r->byminute);
|
|
g_list_free (r->bysecond);
|
|
g_list_free (r->bysetpos);
|
|
|
|
g_free (r);
|
|
}
|
|
|
|
/* Generates one year's worth of recurrence instances. Returns TRUE if all the
|
|
* callback invocations returned TRUE, or FALSE when any one of them returns
|
|
* FALSE, i.e. meaning that the instance generation should be stopped.
|
|
*/
|
|
static gboolean
|
|
generate_instances_for_year (CalComponent *comp,
|
|
time_t comp_dtstart,
|
|
GSList *rrules,
|
|
GSList *rdates,
|
|
GSList *exrules,
|
|
GSList *exdates,
|
|
CalObjTime *event_start,
|
|
CalObjTime *interval_start,
|
|
CalObjTime *interval_end,
|
|
time_t interval_start_time,
|
|
time_t interval_end_time,
|
|
gint duration_days,
|
|
gint duration_seconds,
|
|
CalRecurInstanceFn cb,
|
|
gpointer cb_data)
|
|
{
|
|
GArray *occs, *ex_occs, *tmp_occs;
|
|
CalObjTime cotime, *occ;
|
|
GSList *elem;
|
|
gint i, status;
|
|
time_t start_time, end_time;
|
|
struct tm start_tm, end_tm;
|
|
|
|
occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
ex_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
/* Expand each of the recurrence rules. */
|
|
for (elem = rrules; elem; elem = elem->next) {
|
|
struct icalrecurrencetype *ir;
|
|
CalRecurrence *r;
|
|
|
|
ir = elem->data;
|
|
r = cal_recur_from_icalrecurrencetype (ir);
|
|
|
|
tmp_occs = cal_obj_expand_recurrence (event_start, r,
|
|
interval_start,
|
|
interval_end);
|
|
cal_recur_free (r);
|
|
|
|
g_array_append_vals (occs, tmp_occs->data, tmp_occs->len);
|
|
g_array_free (tmp_occs, TRUE);
|
|
}
|
|
|
|
/* Add on specific occurrence dates. */
|
|
for (elem = rdates; elem; elem = elem->next) {
|
|
CalComponentPeriod *period;
|
|
time_t t;
|
|
|
|
/* FIXME: this only deals with the start time */
|
|
|
|
period = elem->data;
|
|
t = time_from_icaltimetype (period->start);
|
|
|
|
cal_object_time_from_time (&cotime, t);
|
|
g_array_append_val (occs, cotime);
|
|
}
|
|
|
|
/* Expand each of the exception rules. */
|
|
for (elem = exrules; elem; elem = elem->next) {
|
|
struct icalrecurrencetype *ir;
|
|
CalRecurrence *r;
|
|
|
|
ir = elem->data;
|
|
r = cal_recur_from_icalrecurrencetype (ir);
|
|
|
|
tmp_occs = cal_obj_expand_recurrence (event_start, r,
|
|
interval_start,
|
|
interval_end);
|
|
cal_recur_free (r);
|
|
|
|
g_array_append_vals (ex_occs, tmp_occs->data, tmp_occs->len);
|
|
g_array_free (tmp_occs, TRUE);
|
|
}
|
|
|
|
/* Add on specific exception dates. */
|
|
for (elem = exdates; elem; elem = elem->next) {
|
|
CalComponentPeriod *period;
|
|
time_t t;
|
|
|
|
/* FIXME: this only deals with the start time */
|
|
|
|
period = elem->data;
|
|
t = time_from_icaltimetype (period->start);
|
|
|
|
cal_object_time_from_time (&cotime, t);
|
|
g_array_append_val (ex_occs, cotime);
|
|
}
|
|
|
|
|
|
/* Sort both arrays. */
|
|
cal_obj_sort_occurrences (occs);
|
|
cal_obj_sort_occurrences (ex_occs);
|
|
|
|
/* Create the final array, by removing the exceptions from the
|
|
occurrences, and removing any duplicates. */
|
|
cal_obj_remove_exceptions (occs, ex_occs);
|
|
|
|
|
|
/* Call the callback for each occurrence. If it returns 0 we break
|
|
out of the loop. */
|
|
for (i = 0; i < occs->len; i++) {
|
|
/* Convert each CalObjTime into a start & end time_t, and
|
|
check it is within the bounds of the event & interval. */
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
|
|
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_time = mktime (&start_tm);
|
|
|
|
if (start_time < comp_dtstart
|
|
|| start_time >= interval_end_time)
|
|
continue;
|
|
|
|
cal_obj_time_add_days (occ, duration_days);
|
|
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_time = mktime (&end_tm);
|
|
|
|
if (end_time < interval_start_time)
|
|
continue;
|
|
|
|
status = (*cb) (comp, start_time, end_time, cb_data);
|
|
if (!status)
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static void
|
|
cal_object_compute_duration (CalObjTime *start,
|
|
CalObjTime *end,
|
|
gint *days,
|
|
gint *seconds)
|
|
{
|
|
GDate start_date, end_date;
|
|
gint start_seconds, end_seconds;
|
|
|
|
g_date_clear (&start_date, 1);
|
|
g_date_clear (&end_date, 1);
|
|
g_date_set_dmy (&start_date, start->day, start->month + 1,
|
|
start->year);
|
|
g_date_set_dmy (&end_date, end->day, end->month + 1,
|
|
end->year);
|
|
|
|
*days = g_date_julian (&end_date) - g_date_julian (&start_date);
|
|
start_seconds = start->hour * 3600 + start->minute * 60
|
|
+ start->second;
|
|
end_seconds = end->hour * 3600 + end->minute * 60 + end->second;
|
|
|
|
*seconds = end_seconds - start_seconds;
|
|
if (*seconds < 0) {
|
|
*days = *days - 1;
|
|
*seconds += 24 * 60 * 60;
|
|
}
|
|
}
|
|
|
|
|
|
/* Returns an unsorted GArray of CalObjTime's resulting from expanding the
|
|
given recurrence rule within the given interval. Note that it doesn't
|
|
clip the generated occurrences to the interval, i.e. if the interval
|
|
starts part way through the year this function still returns all the
|
|
occurrences for the year. Clipping is done later. */
|
|
static GArray*
|
|
cal_obj_expand_recurrence (CalObjTime *event_start,
|
|
CalRecurrence *recur,
|
|
CalObjTime *interval_start,
|
|
CalObjTime *interval_end)
|
|
{
|
|
CalRecurVTable *vtable;
|
|
CalObjTime *event_end = NULL, event_end_cotime;
|
|
RecurData recur_data;
|
|
CalObjTime occ, *cotime;
|
|
GArray *all_occs, *occs;
|
|
gint len;
|
|
|
|
vtable = cal_obj_get_vtable (recur->type);
|
|
|
|
/* This is the resulting array of CalObjTime elements. */
|
|
all_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
/* Calculate some useful data such as some fast lookup tables. */
|
|
cal_obj_initialize_recur_data (&recur_data, recur, event_start);
|
|
|
|
/* Compute the event_end, if the recur's enddate is set. */
|
|
if (recur->enddate) {
|
|
cal_object_time_from_time (&event_end_cotime,
|
|
recur->enddate);
|
|
event_end = &event_end_cotime;
|
|
}
|
|
|
|
/* Get the first period based on the frequency and the interval that
|
|
intersects the interval between start and end. */
|
|
if ((*vtable->find_start_position) (event_start, event_end,
|
|
&recur_data,
|
|
interval_start, interval_end,
|
|
&occ))
|
|
return all_occs;
|
|
|
|
/* Loop until the event ends or we go past the end of the required
|
|
interval. */
|
|
for (;;) {
|
|
/* Generate the set of occurrences for this period. */
|
|
switch (recur->type) {
|
|
case CAL_RECUR_YEARLY:
|
|
occs = cal_obj_generate_set_yearly (&recur_data,
|
|
vtable, &occ);
|
|
break;
|
|
case CAL_RECUR_MONTHLY:
|
|
occs = cal_obj_generate_set_monthly (&recur_data,
|
|
vtable, &occ);
|
|
break;
|
|
default:
|
|
occs = cal_obj_generate_set_default (&recur_data,
|
|
vtable, &occ);
|
|
break;
|
|
}
|
|
|
|
/* Sort the occurrences and remove duplicates. */
|
|
cal_obj_sort_occurrences (occs);
|
|
cal_obj_remove_duplicates_and_invalid_dates (occs);
|
|
|
|
/* Apply the BYSETPOS property. */
|
|
occs = cal_obj_bysetpos_filter (recur, occs);
|
|
|
|
/* Remove any occs after event_end. */
|
|
len = occs->len - 1;
|
|
if (event_end) {
|
|
while (len >= 0) {
|
|
cotime = &g_array_index (occs, CalObjTime,
|
|
len);
|
|
if (cal_obj_time_compare_func (cotime,
|
|
event_end) <= 0)
|
|
break;
|
|
len--;
|
|
}
|
|
}
|
|
|
|
/* Add the occurrences onto the main array. */
|
|
if (len >= 0)
|
|
g_array_append_vals (all_occs, occs->data, len + 1);
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
/* Skip to the next period, or exit the loop if finished. */
|
|
if ((*vtable->find_next_position) (&occ, event_end,
|
|
&recur_data, interval_end))
|
|
break;
|
|
}
|
|
|
|
return all_occs;
|
|
}
|
|
|
|
|
|
static GArray*
|
|
cal_obj_generate_set_yearly (RecurData *recur_data,
|
|
CalRecurVTable *vtable,
|
|
CalObjTime *occ)
|
|
{
|
|
CalRecurrence *recur = recur_data->recur;
|
|
GArray *occs_arrays[4], *occs, *occs2;
|
|
gint num_occs_arrays = 0, i;
|
|
|
|
/* This is a bit complicated, since the iCalendar spec says that
|
|
several BYxxx modifiers can be used simultaneously. So we have to
|
|
be quite careful when determining the days of the occurrences.
|
|
The BYHOUR, BYMINUTE & BYSECOND modifiers are no problem at all.
|
|
|
|
The modifiers we have to worry about are: BYMONTH, BYWEEKNO,
|
|
BYYEARDAY, BYMONTHDAY & BYDAY. We can't do these sequentially
|
|
since each filter will mess up the results of the previous one.
|
|
But they aren't all completely independant, e.g. BYMONTHDAY and
|
|
BYDAY are related to BYMONTH, and BYDAY is related to BYWEEKNO.
|
|
|
|
BYDAY & BYMONTHDAY can also be applied independently, which makes
|
|
it worse. So we assume that if BYMONTH or BYWEEKNO is used, then
|
|
the BYDAY modifier applies to those, else it is applied
|
|
independantly.
|
|
|
|
We expand the occurrences in parallel into the occs_arrays[] array,
|
|
and then merge them all into one GArray before expanding BYHOUR,
|
|
BYMINUTE & BYSECOND. */
|
|
|
|
if (recur->bymonth) {
|
|
occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
g_array_append_vals (occs, occ, 1);
|
|
|
|
occs = (*vtable->bymonth_filter) (recur_data, occs);
|
|
|
|
/* If BYMONTHDAY & BYDAY are both set we need to expand them
|
|
in parallel and add the results. */
|
|
if (recur->bymonthday && recur->byday) {
|
|
/* Copy the occs array. */
|
|
occs2 = g_array_new (FALSE, FALSE,
|
|
sizeof (CalObjTime));
|
|
g_array_append_vals (occs2, occs->data, occs->len);
|
|
|
|
occs = (*vtable->bymonthday_filter) (recur_data, occs);
|
|
/* Note that we explicitly call the monthly version
|
|
of the BYDAY expansion filter. */
|
|
occs2 = cal_obj_byday_expand_monthly (recur_data,
|
|
occs2);
|
|
|
|
/* Add the 2 resulting arrays together. */
|
|
g_array_append_vals (occs, occs2->data, occs2->len);
|
|
g_array_free (occs2, TRUE);
|
|
} else {
|
|
occs = (*vtable->bymonthday_filter) (recur_data, occs);
|
|
/* Note that we explicitly call the monthly version
|
|
of the BYDAY expansion filter. */
|
|
occs = cal_obj_byday_expand_monthly (recur_data, occs);
|
|
}
|
|
|
|
occs_arrays[num_occs_arrays++] = occs;
|
|
}
|
|
|
|
if (recur->byweekno) {
|
|
occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
g_array_append_vals (occs, occ, 1);
|
|
|
|
occs = (*vtable->byweekno_filter) (recur_data, occs);
|
|
/* Note that we explicitly call the weekly version of the
|
|
BYDAY expansion filter. */
|
|
occs = cal_obj_byday_expand_weekly (recur_data, occs);
|
|
|
|
occs_arrays[num_occs_arrays++] = occs;
|
|
}
|
|
|
|
if (recur->byyearday) {
|
|
occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
g_array_append_vals (occs, occ, 1);
|
|
|
|
occs = (*vtable->byyearday_filter) (recur_data, occs);
|
|
|
|
occs_arrays[num_occs_arrays++] = occs;
|
|
}
|
|
|
|
/* If BYMONTHDAY is set, and BYMONTH is not set, we need to
|
|
expand BYMONTHDAY independantly. */
|
|
if (recur->bymonthday && !recur->bymonth) {
|
|
occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
g_array_append_vals (occs, occ, 1);
|
|
|
|
occs = (*vtable->bymonthday_filter) (recur_data, occs);
|
|
|
|
occs_arrays[num_occs_arrays++] = occs;
|
|
}
|
|
|
|
/* If BYDAY is set, and BYMONTH and BYWEEKNO are not set, we need to
|
|
expand BYDAY independantly. */
|
|
if (recur->byday && !recur->bymonth && !recur->byweekno) {
|
|
occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
g_array_append_vals (occs, occ, 1);
|
|
|
|
occs = (*vtable->byday_filter) (recur_data, occs);
|
|
|
|
occs_arrays[num_occs_arrays++] = occs;
|
|
}
|
|
|
|
/* Add all the arrays together. */
|
|
occs = occs_arrays[0];
|
|
for (i = 1; i < num_occs_arrays; i++) {
|
|
occs2 = occs_arrays[i];
|
|
g_array_append_vals (occs, occs2->data, occs2->len);
|
|
g_array_free (occs2, TRUE);
|
|
}
|
|
|
|
/* Now expand BYHOUR, BYMINUTE & BYSECOND. */
|
|
occs = (*vtable->byhour_filter) (recur_data, occs);
|
|
occs = (*vtable->byminute_filter) (recur_data, occs);
|
|
occs = (*vtable->bysecond_filter) (recur_data, occs);
|
|
|
|
return occs;
|
|
}
|
|
|
|
|
|
static GArray*
|
|
cal_obj_generate_set_monthly (RecurData *recur_data,
|
|
CalRecurVTable *vtable,
|
|
CalObjTime *occ)
|
|
{
|
|
GArray *occs, *occs2;
|
|
|
|
/* We start with just the one time in each set. */
|
|
occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
g_array_append_vals (occs, occ, 1);
|
|
|
|
occs = (*vtable->bymonth_filter) (recur_data, occs);
|
|
|
|
/* We need to combine the output of BYMONTHDAY & BYDAY, by doing them
|
|
in parallel rather than sequentially. If we did them sequentially
|
|
then we would lose the occurrences generated by BYMONTHDAY, and
|
|
instead have repetitions of the occurrences from BYDAY. */
|
|
if (recur_data->recur->bymonthday && recur_data->recur->byday) {
|
|
occs2 = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
g_array_append_vals (occs2, occs->data, occs->len);
|
|
|
|
occs = (*vtable->bymonthday_filter) (recur_data, occs);
|
|
occs2 = (*vtable->byday_filter) (recur_data, occs2);
|
|
|
|
g_array_append_vals (occs, occs2->data, occs2->len);
|
|
g_array_free (occs2, TRUE);
|
|
} else {
|
|
occs = (*vtable->bymonthday_filter) (recur_data, occs);
|
|
occs = (*vtable->byday_filter) (recur_data, occs);
|
|
}
|
|
|
|
occs = (*vtable->byhour_filter) (recur_data, occs);
|
|
occs = (*vtable->byminute_filter) (recur_data, occs);
|
|
occs = (*vtable->bysecond_filter) (recur_data, occs);
|
|
|
|
return occs;
|
|
}
|
|
|
|
|
|
static GArray*
|
|
cal_obj_generate_set_default (RecurData *recur_data,
|
|
CalRecurVTable *vtable,
|
|
CalObjTime *occ)
|
|
{
|
|
GArray *occs;
|
|
#if 0
|
|
g_print ("Generating set for %i/%i/%i %02i:%02i:%02i\n",
|
|
occ->day, occ->month, occ->year, occ->hour, occ->minute,
|
|
occ->second);
|
|
#endif
|
|
/* We start with just the one time in the set. */
|
|
occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
g_array_append_vals (occs, occ, 1);
|
|
|
|
occs = (*vtable->bymonth_filter) (recur_data, occs);
|
|
if (vtable->byweekno_filter)
|
|
occs = (*vtable->byweekno_filter) (recur_data, occs);
|
|
if (vtable->byyearday_filter)
|
|
occs = (*vtable->byyearday_filter) (recur_data, occs);
|
|
if (vtable->bymonthday_filter)
|
|
occs = (*vtable->bymonthday_filter) (recur_data, occs);
|
|
occs = (*vtable->byday_filter) (recur_data, occs);
|
|
|
|
occs = (*vtable->byhour_filter) (recur_data, occs);
|
|
occs = (*vtable->byminute_filter) (recur_data, occs);
|
|
occs = (*vtable->bysecond_filter) (recur_data, occs);
|
|
|
|
return occs;
|
|
}
|
|
|
|
|
|
|
|
/* Returns the function table corresponding to the recurrence frequency. */
|
|
static CalRecurVTable*
|
|
cal_obj_get_vtable (CalRecurType recur_type)
|
|
{
|
|
switch (recur_type) {
|
|
case CAL_RECUR_YEARLY:
|
|
return &cal_obj_yearly_vtable;
|
|
case CAL_RECUR_MONTHLY:
|
|
return &cal_obj_monthly_vtable;
|
|
case CAL_RECUR_WEEKLY:
|
|
return &cal_obj_weekly_vtable;
|
|
case CAL_RECUR_DAILY:
|
|
return &cal_obj_daily_vtable;
|
|
case CAL_RECUR_HOURLY:
|
|
return &cal_obj_hourly_vtable;
|
|
case CAL_RECUR_MINUTELY:
|
|
return &cal_obj_minutely_vtable;
|
|
case CAL_RECUR_SECONDLY:
|
|
return &cal_obj_secondly_vtable;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* This creates a number of fast lookup tables used when filtering with the
|
|
modifier properties BYMONTH, BYYEARDAY etc. */
|
|
static void
|
|
cal_obj_initialize_recur_data (RecurData *recur_data,
|
|
CalRecurrence *recur,
|
|
CalObjTime *event_start)
|
|
{
|
|
GList *elem;
|
|
gint month, yearday, monthday, weekday, week_num, hour, minute, second;
|
|
|
|
/* Clear the entire RecurData. */
|
|
memset (recur_data, 0, sizeof (RecurData));
|
|
|
|
recur_data->recur = recur;
|
|
|
|
/* Set the weekday, used for the WEEKLY frequency and the BYWEEKNO
|
|
modifier. */
|
|
recur_data->weekday = cal_obj_time_weekday (event_start, recur);
|
|
|
|
/* Create an array of months from bymonths for fast lookup. */
|
|
elem = recur->bymonth;
|
|
while (elem) {
|
|
month = GPOINTER_TO_INT (elem->data);
|
|
recur_data->months[month] = 1;
|
|
elem = elem->next;
|
|
}
|
|
|
|
/* Create an array of yeardays from byyearday for fast lookup.
|
|
We create a second array to handle the negative values. The first
|
|
element there corresponds to the last day of the year. */
|
|
elem = recur->byyearday;
|
|
while (elem) {
|
|
yearday = GPOINTER_TO_INT (elem->data);
|
|
if (yearday >= 0)
|
|
recur_data->yeardays[yearday] = 1;
|
|
else
|
|
recur_data->neg_yeardays[-yearday] = 1;
|
|
elem = elem->next;
|
|
}
|
|
|
|
/* Create an array of monthdays from bymonthday for fast lookup.
|
|
We create a second array to handle the negative values. The first
|
|
element there corresponds to the last day of the month. */
|
|
elem = recur->bymonthday;
|
|
while (elem) {
|
|
monthday = GPOINTER_TO_INT (elem->data);
|
|
if (monthday >= 0)
|
|
recur_data->monthdays[monthday] = 1;
|
|
else
|
|
recur_data->neg_monthdays[-monthday] = 1;
|
|
elem = elem->next;
|
|
}
|
|
|
|
/* Create an array of weekdays from byday for fast lookup. */
|
|
elem = recur->byday;
|
|
while (elem) {
|
|
weekday = GPOINTER_TO_INT (elem->data);
|
|
elem = elem->next;
|
|
/* The week number is not used when filtering. */
|
|
week_num = GPOINTER_TO_INT (elem->data);
|
|
elem = elem->next;
|
|
|
|
recur_data->weekdays[weekday] = 1;
|
|
}
|
|
|
|
/* Create an array of hours from byhour for fast lookup. */
|
|
elem = recur->byhour;
|
|
while (elem) {
|
|
hour = GPOINTER_TO_INT (elem->data);
|
|
recur_data->hours[hour] = 1;
|
|
elem = elem->next;
|
|
}
|
|
|
|
/* Create an array of minutes from byminutes for fast lookup. */
|
|
elem = recur->byminute;
|
|
while (elem) {
|
|
minute = GPOINTER_TO_INT (elem->data);
|
|
recur_data->minutes[minute] = 1;
|
|
elem = elem->next;
|
|
}
|
|
|
|
/* Create an array of seconds from byseconds for fast lookup. */
|
|
elem = recur->bysecond;
|
|
while (elem) {
|
|
second = GPOINTER_TO_INT (elem->data);
|
|
recur_data->seconds[second] = 1;
|
|
elem = elem->next;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
cal_obj_sort_occurrences (GArray *occs)
|
|
{
|
|
qsort (occs->data, occs->len, sizeof (CalObjTime),
|
|
cal_obj_time_compare_func);
|
|
}
|
|
|
|
|
|
static void
|
|
cal_obj_remove_duplicates_and_invalid_dates (GArray *occs)
|
|
{
|
|
static const int days_in_month[12] = {
|
|
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
|
|
};
|
|
|
|
CalObjTime *occ, *prev_occ = NULL;
|
|
gint len, i, j = 0, year, month, days;
|
|
gboolean keep_occ;
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
keep_occ = TRUE;
|
|
|
|
if (prev_occ && cal_obj_time_compare_func (occ,
|
|
prev_occ) == 0)
|
|
keep_occ = FALSE;
|
|
|
|
year = occ->year;
|
|
month = occ->month;
|
|
days = days_in_month[occ->month];
|
|
/* If it is february and a leap year, add a day. */
|
|
if (month == 1 && (year % 4 == 0
|
|
&& (year % 100 != 0
|
|
|| year % 400 == 0)))
|
|
days++;
|
|
if (occ->day > days)
|
|
keep_occ = FALSE;
|
|
|
|
if (keep_occ) {
|
|
if (i != j)
|
|
g_array_index (occs, CalObjTime, j)
|
|
= g_array_index (occs, CalObjTime, i);
|
|
j++;
|
|
}
|
|
|
|
prev_occ = occ;
|
|
}
|
|
|
|
g_array_set_size (occs, j);
|
|
}
|
|
|
|
|
|
/* Removes the exceptions from the ex_occs array from the occurrences in the
|
|
occs array, and removes any duplicates. Both arrays are sorted. */
|
|
static void
|
|
cal_obj_remove_exceptions (GArray *occs,
|
|
GArray *ex_occs)
|
|
{
|
|
CalObjTime *occ, *prev_occ = NULL, *ex_occ;
|
|
gint i, j = 0, cmp, ex_index, occs_len, ex_occs_len;
|
|
gboolean keep_occ;
|
|
|
|
if (occs->len == 0 || ex_occs->len == 0)
|
|
return;
|
|
|
|
ex_index = 0;
|
|
occs_len = occs->len;
|
|
ex_occs_len = ex_occs->len;
|
|
|
|
ex_occ = &g_array_index (ex_occs, CalObjTime, ex_index);
|
|
for (i = 0; i < occs_len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
keep_occ = TRUE;
|
|
|
|
/* If the occurrence is a duplicate of the previous one, skip
|
|
it. */
|
|
if (prev_occ
|
|
&& cal_obj_time_compare_func (occ, prev_occ) == 0) {
|
|
keep_occ = FALSE;
|
|
} else if (ex_occ) {
|
|
/* Step through the exceptions until we come to one
|
|
that matches or follows this occurrence. */
|
|
while (ex_occ) {
|
|
cmp = cal_obj_time_compare_func (ex_occ, occ);
|
|
if (cmp > 0)
|
|
break;
|
|
|
|
/* Move to the next exception, or set ex_occ
|
|
to NULL when we reach the end of array. */
|
|
ex_index++;
|
|
if (ex_index < ex_occs_len)
|
|
ex_occ = &g_array_index (ex_occs,
|
|
CalObjTime,
|
|
ex_index);
|
|
else
|
|
ex_occ = NULL;
|
|
|
|
/* If the current exception matches this
|
|
occurrence we remove it. */
|
|
if (cmp == 0) {
|
|
keep_occ = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (keep_occ) {
|
|
if (i != j)
|
|
g_array_index (occs, CalObjTime, j)
|
|
= g_array_index (occs, CalObjTime, i);
|
|
j++;
|
|
}
|
|
|
|
prev_occ = occ;
|
|
}
|
|
|
|
g_array_set_size (occs, j);
|
|
}
|
|
|
|
|
|
|
|
static GArray*
|
|
cal_obj_bysetpos_filter (CalRecurrence *recur,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ;
|
|
GList *elem;
|
|
gint len, pos;
|
|
|
|
/* If BYSETPOS has not been specified, or the array is empty, just
|
|
return the array. */
|
|
elem = recur->bysetpos;
|
|
if (!elem || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
/* Iterate over the indices given in bysetpos, adding the corresponding
|
|
element from occs to new_occs. */
|
|
len = occs->len;
|
|
while (elem) {
|
|
pos = GPOINTER_TO_INT (elem->data);
|
|
|
|
/* Negative values count back from the end of the array. */
|
|
if (pos < 0)
|
|
pos += len;
|
|
|
|
if (pos >= 0 && pos < len) {
|
|
occ = &g_array_index (occs, CalObjTime, pos);
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
}
|
|
elem = elem->next;
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Finds the first year from the event_start, counting in multiples of the
|
|
recurrence interval, that intersects the given interval. It returns TRUE
|
|
if there is no intersection. */
|
|
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)
|
|
{
|
|
*cotime = *event_start;
|
|
|
|
/* Move on to the next interval, if the event starts before the
|
|
given interval. */
|
|
if (cotime->year < interval_start->year) {
|
|
gint years = interval_start->year - cotime->year
|
|
+ recur_data->recur->interval - 1;
|
|
years -= years % recur_data->recur->interval;
|
|
/* NOTE: The day may now be invalid, e.g. 29th Feb. */
|
|
cotime->year += years;
|
|
}
|
|
|
|
if ((event_end && cotime->year > event_end->year)
|
|
|| (interval_end && cotime->year > interval_end->year))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
cal_obj_yearly_find_next_position (CalObjTime *cotime,
|
|
CalObjTime *event_end,
|
|
RecurData *recur_data,
|
|
CalObjTime *interval_end)
|
|
{
|
|
/* NOTE: The day may now be invalid, e.g. 29th Feb. */
|
|
cotime->year += recur_data->recur->interval;
|
|
|
|
if ((event_end && cotime->year > event_end->year)
|
|
|| (interval_end && cotime->year > interval_end->year))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
{
|
|
*cotime = *event_start;
|
|
|
|
/* Move on to the next interval, if the event starts before the
|
|
given interval. */
|
|
if (cal_obj_time_compare (cotime, interval_start, CALOBJ_MONTH) < 0) {
|
|
gint months = (interval_start->year - cotime->year) * 12
|
|
+ interval_start->month - cotime->month
|
|
+ recur_data->recur->interval - 1;
|
|
months -= months % recur_data->recur->interval;
|
|
/* NOTE: The day may now be invalid, e.g. 31st Sep. */
|
|
cal_obj_time_add_months (cotime, months);
|
|
}
|
|
|
|
if (event_end && cal_obj_time_compare (cotime, event_end,
|
|
CALOBJ_MONTH) > 0)
|
|
return TRUE;
|
|
if (interval_end && cal_obj_time_compare (cotime, interval_end,
|
|
CALOBJ_MONTH) > 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
cal_obj_monthly_find_next_position (CalObjTime *cotime,
|
|
CalObjTime *event_end,
|
|
RecurData *recur_data,
|
|
CalObjTime *interval_end)
|
|
{
|
|
/* NOTE: The day may now be invalid, e.g. 31st Sep. */
|
|
cal_obj_time_add_months (cotime, recur_data->recur->interval);
|
|
|
|
if (event_end && cal_obj_time_compare (cotime, event_end,
|
|
CALOBJ_MONTH) > 0)
|
|
return TRUE;
|
|
if (interval_end && cal_obj_time_compare (cotime, interval_end,
|
|
CALOBJ_MONTH) > 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
{
|
|
GDate event_start_date, interval_start_date;
|
|
guint32 event_start_julian, interval_start_julian;
|
|
gint interval_start_weekday;
|
|
CalObjTime week_start;
|
|
|
|
if (event_end && cal_obj_time_compare (event_end, interval_start,
|
|
CALOBJ_DAY) < 0)
|
|
return TRUE;
|
|
if (interval_end && cal_obj_time_compare (event_start, interval_end,
|
|
CALOBJ_DAY) > 0)
|
|
return TRUE;
|
|
|
|
*cotime = *event_start;
|
|
|
|
/* Convert the event start and interval start to GDates, so we can
|
|
easily find the number of days between them. */
|
|
g_date_clear (&event_start_date, 1);
|
|
g_date_set_dmy (&event_start_date, event_start->day,
|
|
event_start->month + 1, event_start->year);
|
|
g_date_clear (&interval_start_date, 1);
|
|
g_date_set_dmy (&interval_start_date, interval_start->day,
|
|
interval_start->month + 1, interval_start->year);
|
|
|
|
/* Calculate the start of the weeks corresponding to the event start
|
|
and interval start. */
|
|
event_start_julian = g_date_julian (&event_start_date);
|
|
event_start_julian -= recur_data->weekday;
|
|
|
|
interval_start_julian = g_date_julian (&interval_start_date);
|
|
interval_start_weekday = cal_obj_time_weekday (interval_start,
|
|
recur_data->recur);
|
|
interval_start_julian -= interval_start_weekday;
|
|
|
|
/* We want to find the first full week using the recurrence interval
|
|
that intersects the given interval dates. */
|
|
if (event_start_julian < interval_start_julian) {
|
|
gint weeks = (interval_start_julian - event_start_julian) / 7;
|
|
weeks += recur_data->recur->interval - 1;
|
|
weeks -= weeks % recur_data->recur->interval;
|
|
cal_obj_time_add_days (cotime, weeks * 7);
|
|
}
|
|
|
|
week_start = *cotime;
|
|
cal_obj_time_subtract_days (&week_start, recur_data->weekday);
|
|
|
|
if (event_end && cal_obj_time_compare (&week_start, event_end,
|
|
CALOBJ_DAY) > 0)
|
|
return TRUE;
|
|
if (interval_end && cal_obj_time_compare (&week_start, interval_end,
|
|
CALOBJ_DAY) > 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
cal_obj_weekly_find_next_position (CalObjTime *cotime,
|
|
CalObjTime *event_end,
|
|
RecurData *recur_data,
|
|
CalObjTime *interval_end)
|
|
{
|
|
CalObjTime week_start;
|
|
|
|
cal_obj_time_add_days (cotime, recur_data->recur->interval * 7);
|
|
|
|
/* Return TRUE if the start of this week is after the event finishes
|
|
or is after the end of the required interval. */
|
|
week_start = *cotime;
|
|
cal_obj_time_subtract_days (&week_start, recur_data->weekday);
|
|
|
|
if (event_end && cal_obj_time_compare (&week_start, event_end,
|
|
CALOBJ_DAY) > 0)
|
|
return TRUE;
|
|
if (interval_end && cal_obj_time_compare (&week_start, interval_end,
|
|
CALOBJ_DAY) > 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
GDate event_start_date, interval_start_date;
|
|
guint32 event_start_julian, interval_start_julian, days;
|
|
|
|
if (interval_end && cal_obj_time_compare (event_start, interval_end,
|
|
CALOBJ_DAY) > 0)
|
|
return TRUE;
|
|
if (event_end && cal_obj_time_compare (event_end, interval_start,
|
|
CALOBJ_DAY) < 0)
|
|
return TRUE;
|
|
|
|
*cotime = *event_start;
|
|
|
|
/* Convert the event start and interval start to GDates, so we can
|
|
easily find the number of days between them. */
|
|
g_date_clear (&event_start_date, 1);
|
|
g_date_set_dmy (&event_start_date, event_start->day,
|
|
event_start->month + 1, event_start->year);
|
|
g_date_clear (&interval_start_date, 1);
|
|
g_date_set_dmy (&interval_start_date, interval_start->day,
|
|
interval_start->month + 1, interval_start->year);
|
|
|
|
event_start_julian = g_date_julian (&event_start_date);
|
|
interval_start_julian = g_date_julian (&interval_start_date);
|
|
|
|
if (event_start_julian < interval_start_julian) {
|
|
days = interval_start_julian - event_start_julian
|
|
+ recur_data->recur->interval - 1;
|
|
days -= days % recur_data->recur->interval;
|
|
cal_obj_time_add_days (cotime, days);
|
|
}
|
|
|
|
if (event_end && cal_obj_time_compare (cotime, event_end,
|
|
CALOBJ_DAY) > 0)
|
|
return TRUE;
|
|
if (interval_end && cal_obj_time_compare (cotime, interval_end,
|
|
CALOBJ_DAY) > 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
cal_obj_daily_find_next_position (CalObjTime *cotime,
|
|
CalObjTime *event_end,
|
|
RecurData *recur_data,
|
|
CalObjTime *interval_end)
|
|
{
|
|
cal_obj_time_add_days (cotime, recur_data->recur->interval);
|
|
|
|
if (event_end && cal_obj_time_compare (cotime, event_end,
|
|
CALOBJ_DAY) > 0)
|
|
return TRUE;
|
|
if (interval_end && cal_obj_time_compare (cotime, interval_end,
|
|
CALOBJ_DAY) > 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
GDate event_start_date, interval_start_date;
|
|
guint32 event_start_julian, interval_start_julian, hours;
|
|
|
|
if (interval_end && cal_obj_time_compare (event_start, interval_end,
|
|
CALOBJ_HOUR) > 0)
|
|
return TRUE;
|
|
if (event_end && cal_obj_time_compare (event_end, interval_start,
|
|
CALOBJ_HOUR) < 0)
|
|
return TRUE;
|
|
|
|
*cotime = *event_start;
|
|
|
|
if (cal_obj_time_compare (event_start, interval_start,
|
|
CALOBJ_HOUR) < 0) {
|
|
/* Convert the event start and interval start to GDates, so we
|
|
can easily find the number of days between them. */
|
|
g_date_clear (&event_start_date, 1);
|
|
g_date_set_dmy (&event_start_date, event_start->day,
|
|
event_start->month + 1, event_start->year);
|
|
g_date_clear (&interval_start_date, 1);
|
|
g_date_set_dmy (&interval_start_date, interval_start->day,
|
|
interval_start->month + 1,
|
|
interval_start->year);
|
|
|
|
event_start_julian = g_date_julian (&event_start_date);
|
|
interval_start_julian = g_date_julian (&interval_start_date);
|
|
|
|
hours = (interval_start_julian - event_start_julian) * 24;
|
|
hours += interval_start->hour - event_start->hour;
|
|
hours += recur_data->recur->interval - 1;
|
|
hours -= hours % recur_data->recur->interval;
|
|
cal_obj_time_add_hours (cotime, hours);
|
|
}
|
|
|
|
if (event_end && cal_obj_time_compare (cotime, event_end,
|
|
CALOBJ_HOUR) > 0)
|
|
return TRUE;
|
|
if (interval_end && cal_obj_time_compare (cotime, interval_end,
|
|
CALOBJ_HOUR) > 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
cal_obj_hourly_find_next_position (CalObjTime *cotime,
|
|
CalObjTime *event_end,
|
|
RecurData *recur_data,
|
|
CalObjTime *interval_end)
|
|
{
|
|
cal_obj_time_add_hours (cotime, recur_data->recur->interval);
|
|
|
|
if (event_end && cal_obj_time_compare (cotime, event_end,
|
|
CALOBJ_HOUR) > 0)
|
|
return TRUE;
|
|
if (interval_end && cal_obj_time_compare (cotime, interval_end,
|
|
CALOBJ_HOUR) > 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
GDate event_start_date, interval_start_date;
|
|
guint32 event_start_julian, interval_start_julian, minutes;
|
|
|
|
if (interval_end && cal_obj_time_compare (event_start, interval_end,
|
|
CALOBJ_MINUTE) > 0)
|
|
return TRUE;
|
|
if (event_end && cal_obj_time_compare (event_end, interval_start,
|
|
CALOBJ_MINUTE) < 0)
|
|
return TRUE;
|
|
|
|
*cotime = *event_start;
|
|
|
|
if (cal_obj_time_compare (event_start, interval_start,
|
|
CALOBJ_MINUTE) < 0) {
|
|
/* Convert the event start and interval start to GDates, so we
|
|
can easily find the number of days between them. */
|
|
g_date_clear (&event_start_date, 1);
|
|
g_date_set_dmy (&event_start_date, event_start->day,
|
|
event_start->month + 1, event_start->year);
|
|
g_date_clear (&interval_start_date, 1);
|
|
g_date_set_dmy (&interval_start_date, interval_start->day,
|
|
interval_start->month + 1,
|
|
interval_start->year);
|
|
|
|
event_start_julian = g_date_julian (&event_start_date);
|
|
interval_start_julian = g_date_julian (&interval_start_date);
|
|
|
|
minutes = (interval_start_julian - event_start_julian)
|
|
* 24 * 60;
|
|
minutes += (interval_start->hour - event_start->hour) * 24;
|
|
minutes += interval_start->minute - event_start->minute;
|
|
minutes += recur_data->recur->interval - 1;
|
|
minutes -= minutes % recur_data->recur->interval;
|
|
cal_obj_time_add_minutes (cotime, minutes);
|
|
}
|
|
|
|
if (event_end && cal_obj_time_compare (cotime, event_end,
|
|
CALOBJ_MINUTE) > 0)
|
|
return TRUE;
|
|
if (interval_end && cal_obj_time_compare (cotime, interval_end,
|
|
CALOBJ_MINUTE) > 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
cal_obj_minutely_find_next_position (CalObjTime *cotime,
|
|
CalObjTime *event_end,
|
|
RecurData *recur_data,
|
|
CalObjTime *interval_end)
|
|
{
|
|
cal_obj_time_add_minutes (cotime, recur_data->recur->interval);
|
|
|
|
if (event_end && cal_obj_time_compare (cotime, event_end,
|
|
CALOBJ_MINUTE) > 0)
|
|
return TRUE;
|
|
if (interval_end && cal_obj_time_compare (cotime, interval_end,
|
|
CALOBJ_MINUTE) > 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
GDate event_start_date, interval_start_date;
|
|
guint32 event_start_julian, interval_start_julian, seconds;
|
|
|
|
if (interval_end && cal_obj_time_compare (event_start, interval_end,
|
|
CALOBJ_SECOND) > 0)
|
|
return TRUE;
|
|
if (event_end && cal_obj_time_compare (event_end, interval_start,
|
|
CALOBJ_SECOND) < 0)
|
|
return TRUE;
|
|
|
|
*cotime = *event_start;
|
|
|
|
if (cal_obj_time_compare (event_start, interval_start,
|
|
CALOBJ_SECOND) < 0) {
|
|
/* Convert the event start and interval start to GDates, so we
|
|
can easily find the number of days between them. */
|
|
g_date_clear (&event_start_date, 1);
|
|
g_date_set_dmy (&event_start_date, event_start->day,
|
|
event_start->month + 1, event_start->year);
|
|
g_date_clear (&interval_start_date, 1);
|
|
g_date_set_dmy (&interval_start_date, interval_start->day,
|
|
interval_start->month + 1,
|
|
interval_start->year);
|
|
|
|
event_start_julian = g_date_julian (&event_start_date);
|
|
interval_start_julian = g_date_julian (&interval_start_date);
|
|
|
|
seconds = (interval_start_julian - event_start_julian)
|
|
* 24 * 60 * 60;
|
|
seconds += (interval_start->hour - event_start->hour)
|
|
* 24 * 60;
|
|
seconds += (interval_start->minute - event_start->minute) * 60;
|
|
seconds += interval_start->second - event_start->second;
|
|
seconds += recur_data->recur->interval - 1;
|
|
seconds -= seconds % recur_data->recur->interval;
|
|
cal_obj_time_add_seconds (cotime, seconds);
|
|
}
|
|
|
|
if (event_end && cal_obj_time_compare (cotime, event_end,
|
|
CALOBJ_SECOND) >= 0)
|
|
return TRUE;
|
|
if (interval_end && cal_obj_time_compare (cotime, interval_end,
|
|
CALOBJ_SECOND) >= 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
cal_obj_secondly_find_next_position (CalObjTime *cotime,
|
|
CalObjTime *event_end,
|
|
RecurData *recur_data,
|
|
CalObjTime *interval_end)
|
|
{
|
|
cal_obj_time_add_seconds (cotime, recur_data->recur->interval);
|
|
|
|
if (event_end && cal_obj_time_compare (cotime, event_end,
|
|
CALOBJ_SECOND) >= 0)
|
|
return TRUE;
|
|
if (interval_end && cal_obj_time_compare (cotime, interval_end,
|
|
CALOBJ_SECOND) >= 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* If the BYMONTH rule is specified it expands each occurrence in occs, by
|
|
using each of the months in the bymonth list. */
|
|
static GArray*
|
|
cal_obj_bymonth_expand (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ;
|
|
GList *elem;
|
|
gint len, i;
|
|
|
|
/* If BYMONTH has not been specified, or the array is empty, just
|
|
return the array. */
|
|
if (!recur_data->recur->bymonth || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
|
|
elem = recur_data->recur->bymonth;
|
|
while (elem) {
|
|
/* NOTE: The day may now be invalid, e.g. 31st Feb. */
|
|
occ->month = GPOINTER_TO_INT (elem->data);
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
elem = elem->next;
|
|
}
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
/* If the BYMONTH rule is specified it filters out all occurrences in occs
|
|
which do not match one of the months in the bymonth list. */
|
|
static GArray*
|
|
cal_obj_bymonth_filter (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ;
|
|
gint len, i;
|
|
|
|
/* If BYMONTH has not been specified, or the array is empty, just
|
|
return the array. */
|
|
if (!recur_data->recur->bymonth || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
if (recur_data->months[occ->month])
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
|
|
static GArray*
|
|
cal_obj_byweekno_expand (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ, year_start_cotime, year_end_cotime, cotime;
|
|
GList *elem;
|
|
gint len, i, weekno;
|
|
|
|
/* If BYWEEKNO has not been specified, or the array is empty, just
|
|
return the array. */
|
|
if (!recur_data->recur->byweekno || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
|
|
/* Find the day that would correspond to week 1 (note that
|
|
week 1 is the first week starting from the specified week
|
|
start day that has 4 days in the new year). */
|
|
year_start_cotime = *occ;
|
|
cal_obj_time_find_first_week (&year_start_cotime,
|
|
recur_data);
|
|
|
|
/* Find the day that would correspond to week 1 of the next
|
|
year, which we use for -ve week numbers. */
|
|
year_end_cotime = *occ;
|
|
year_end_cotime.year++;
|
|
cal_obj_time_find_first_week (&year_end_cotime,
|
|
recur_data);
|
|
|
|
/* Now iterate over the week numbers in byweekno, generating a
|
|
new occurrence for each one. */
|
|
elem = recur_data->recur->byweekno;
|
|
while (elem) {
|
|
weekno = GPOINTER_TO_INT (elem->data);
|
|
if (weekno > 0) {
|
|
cotime = year_start_cotime;
|
|
cal_obj_time_add_days (&cotime,
|
|
(weekno - 1) * 7);
|
|
} else {
|
|
cotime = year_end_cotime;
|
|
cal_obj_time_subtract_days (&cotime,
|
|
-weekno * 7);
|
|
}
|
|
|
|
/* Skip occurrences if they fall outside the year. */
|
|
if (cotime.year == occ->year)
|
|
g_array_append_val (new_occs, cotime);
|
|
elem = elem->next;
|
|
}
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
#if 0
|
|
/* This isn't used at present. */
|
|
static GArray*
|
|
cal_obj_byweekno_filter (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
|
|
return occs;
|
|
}
|
|
#endif
|
|
|
|
|
|
static GArray*
|
|
cal_obj_byyearday_expand (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ, year_start_cotime, year_end_cotime, cotime;
|
|
GList *elem;
|
|
gint len, i, dayno;
|
|
|
|
/* If BYYEARDAY has not been specified, or the array is empty, just
|
|
return the array. */
|
|
if (!recur_data->recur->byyearday || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
|
|
/* Find the day that would correspond to day 1. */
|
|
year_start_cotime = *occ;
|
|
year_start_cotime.month = 0;
|
|
year_start_cotime.day = 1;
|
|
|
|
/* Find the day that would correspond to day 1 of the next
|
|
year, which we use for -ve day numbers. */
|
|
year_end_cotime = *occ;
|
|
year_end_cotime.year++;
|
|
year_end_cotime.month = 0;
|
|
year_end_cotime.day = 1;
|
|
|
|
/* Now iterate over the day numbers in byyearday, generating a
|
|
new occurrence for each one. */
|
|
elem = recur_data->recur->byyearday;
|
|
while (elem) {
|
|
dayno = GPOINTER_TO_INT (elem->data);
|
|
if (dayno > 0) {
|
|
cotime = year_start_cotime;
|
|
cal_obj_time_add_days (&cotime, dayno - 1);
|
|
} else {
|
|
cotime = year_end_cotime;
|
|
cal_obj_time_subtract_days (&cotime, -dayno);
|
|
}
|
|
|
|
/* Skip occurrences if they fall outside the year. */
|
|
if (cotime.year == occ->year)
|
|
g_array_append_val (new_occs, cotime);
|
|
elem = elem->next;
|
|
}
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
/* Note: occs must not contain invalid dates, e.g. 31st September. */
|
|
static GArray*
|
|
cal_obj_byyearday_filter (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ;
|
|
gint yearday, len, i, days_in_year;
|
|
|
|
/* If BYYEARDAY has not been specified, or the array is empty, just
|
|
return the array. */
|
|
if (!recur_data->recur->byyearday || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
yearday = cal_obj_time_day_of_year (occ);
|
|
if (recur_data->yeardays[yearday]) {
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
} else {
|
|
days_in_year = g_date_is_leap_year (occ->year)
|
|
? 366 : 365;
|
|
if (recur_data->neg_yeardays[days_in_year + 1
|
|
- yearday])
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
}
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
|
|
static GArray*
|
|
cal_obj_bymonthday_expand (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ, month_start_cotime, month_end_cotime, cotime;
|
|
GList *elem;
|
|
gint len, i, dayno;
|
|
|
|
/* If BYMONTHDAY has not been specified, or the array is empty, just
|
|
return the array. */
|
|
if (!recur_data->recur->bymonthday || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
|
|
/* Find the day that would correspond to day 1. */
|
|
month_start_cotime = *occ;
|
|
month_start_cotime.day = 1;
|
|
|
|
/* Find the day that would correspond to day 1 of the next
|
|
month, which we use for -ve day numbers. */
|
|
month_end_cotime = *occ;
|
|
month_end_cotime.month++;
|
|
month_end_cotime.day = 1;
|
|
|
|
/* Now iterate over the day numbers in bymonthday, generating a
|
|
new occurrence for each one. */
|
|
elem = recur_data->recur->bymonthday;
|
|
while (elem) {
|
|
dayno = GPOINTER_TO_INT (elem->data);
|
|
if (dayno > 0) {
|
|
cotime = month_start_cotime;
|
|
cal_obj_time_add_days (&cotime, dayno - 1);
|
|
} else {
|
|
cotime = month_end_cotime;
|
|
cal_obj_time_subtract_days (&cotime, -dayno);
|
|
}
|
|
|
|
/* Skip occurrences if they fall outside the month. */
|
|
if (cotime.month == occ->month)
|
|
g_array_append_val (new_occs, cotime);
|
|
elem = elem->next;
|
|
}
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
static GArray*
|
|
cal_obj_bymonthday_filter (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ;
|
|
gint len, i, days_in_month;
|
|
|
|
/* If BYMONTHDAY has not been specified, or the array is empty, just
|
|
return the array. */
|
|
if (!recur_data->recur->bymonthday || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
if (recur_data->monthdays[occ->day]) {
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
} else {
|
|
days_in_month = time_days_in_month (occ->year,
|
|
occ->month);
|
|
if (recur_data->neg_monthdays[days_in_month + 1
|
|
- occ->day])
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
}
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
|
|
static GArray*
|
|
cal_obj_byday_expand_yearly (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ;
|
|
GList *elem;
|
|
gint len, i, weekday, week_num;
|
|
gint first_weekday, last_weekday, offset;
|
|
guint16 year;
|
|
|
|
/* If BYDAY has not been specified, or the array is empty, just
|
|
return the array. */
|
|
if (!recur_data->recur->byday || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
|
|
elem = recur_data->recur->byday;
|
|
while (elem) {
|
|
weekday = GPOINTER_TO_INT (elem->data);
|
|
elem = elem->next;
|
|
week_num = GPOINTER_TO_INT (elem->data);
|
|
elem = elem->next;
|
|
|
|
year = occ->year;
|
|
if (week_num == 0) {
|
|
occ->month = 0;
|
|
occ->day = 1;
|
|
first_weekday = cal_obj_time_weekday (occ, recur_data->recur);
|
|
offset = (weekday + 7 - first_weekday) % 7;
|
|
cal_obj_time_add_days (occ, offset);
|
|
|
|
while (occ->year == year) {
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
cal_obj_time_add_days (occ, 7);
|
|
}
|
|
|
|
} else if (week_num > 0) {
|
|
occ->month = 0;
|
|
occ->day = 1;
|
|
first_weekday = cal_obj_time_weekday (occ, recur_data->recur);
|
|
offset = (weekday + 7 - first_weekday) % 7;
|
|
offset += (week_num - 1) * 7;
|
|
cal_obj_time_add_days (occ, offset);
|
|
if (occ->year == year)
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
|
|
} else {
|
|
occ->month = 11;
|
|
occ->day = 31;
|
|
last_weekday = cal_obj_time_weekday (occ, recur_data->recur);
|
|
offset = (last_weekday + 7 - weekday) % 7;
|
|
offset += (week_num - 1) * 7;
|
|
cal_obj_time_subtract_days (occ, offset);
|
|
if (occ->year == year)
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
}
|
|
|
|
/* Reset the year, as we may have gone past the end. */
|
|
occ->year = year;
|
|
}
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
static GArray*
|
|
cal_obj_byday_expand_monthly (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ;
|
|
GList *elem;
|
|
gint len, i, weekday, week_num;
|
|
gint first_weekday, last_weekday, offset;
|
|
guint16 year;
|
|
guint8 month;
|
|
|
|
/* If BYDAY has not been specified, or the array is empty, just
|
|
return the array. */
|
|
if (!recur_data->recur->byday || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
|
|
elem = recur_data->recur->byday;
|
|
while (elem) {
|
|
weekday = GPOINTER_TO_INT (elem->data);
|
|
elem = elem->next;
|
|
week_num = GPOINTER_TO_INT (elem->data);
|
|
elem = elem->next;
|
|
|
|
year = occ->year;
|
|
month = occ->month;
|
|
if (week_num == 0) {
|
|
occ->day = 1;
|
|
first_weekday = cal_obj_time_weekday (occ, recur_data->recur);
|
|
offset = (weekday + 7 - first_weekday) % 7;
|
|
cal_obj_time_add_days (occ, offset);
|
|
|
|
while (occ->year == year
|
|
&& occ->month == month) {
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
cal_obj_time_add_days (occ, 7);
|
|
}
|
|
|
|
} else if (week_num > 0) {
|
|
occ->day = 1;
|
|
first_weekday = cal_obj_time_weekday (occ, recur_data->recur);
|
|
offset = (weekday + 7 - first_weekday) % 7;
|
|
offset += (week_num - 1) * 7;
|
|
cal_obj_time_add_days (occ, offset);
|
|
if (occ->year == year && occ->month == month)
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
|
|
} else {
|
|
occ->day = time_days_in_month (occ->year,
|
|
occ->month);
|
|
last_weekday = cal_obj_time_weekday (occ, recur_data->recur);
|
|
offset = (last_weekday + 7 - weekday) % 7;
|
|
offset += (week_num - 1) * 7;
|
|
cal_obj_time_subtract_days (occ, offset);
|
|
if (occ->year == year && occ->month == month)
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
}
|
|
|
|
/* Reset the year & month, as we may have gone past
|
|
the end. */
|
|
occ->year = year;
|
|
occ->month = month;
|
|
}
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
/* Note: occs must not contain invalid dates, e.g. 31st September. */
|
|
static GArray*
|
|
cal_obj_byday_expand_weekly (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ;
|
|
GList *elem;
|
|
gint len, i, weekday, week_num;
|
|
gint current_weekday;
|
|
gint day_of_week, new_day_of_week, days_to_add;
|
|
|
|
/* If BYDAY has not been specified, or the array is empty, just
|
|
return the array. */
|
|
if (!recur_data->recur->byday || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
|
|
elem = recur_data->recur->byday;
|
|
while (elem) {
|
|
weekday = GPOINTER_TO_INT (elem->data);
|
|
elem = elem->next;
|
|
week_num = GPOINTER_TO_INT (elem->data);
|
|
elem = elem->next;
|
|
|
|
current_weekday = cal_obj_time_weekday (occ, recur_data->recur);
|
|
day_of_week = (current_weekday + 7
|
|
- recur_data->recur->week_start_day) % 7;
|
|
new_day_of_week = (weekday + 7
|
|
- recur_data->recur->week_start_day) % 7;
|
|
days_to_add = new_day_of_week - day_of_week;
|
|
if (days_to_add > 0)
|
|
cal_obj_time_add_days (occ, days_to_add);
|
|
else if (days_to_add < 0)
|
|
cal_obj_time_subtract_days (occ, -days_to_add);
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
}
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
/* Note: occs must not contain invalid dates, e.g. 31st September. */
|
|
static GArray*
|
|
cal_obj_byday_filter (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ;
|
|
gint len, i, weekday;
|
|
|
|
/* If BYDAY has not been specified, or the array is empty, just
|
|
return the array. */
|
|
if (!recur_data->recur->byday || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
weekday = cal_obj_time_weekday (occ, recur_data->recur);
|
|
|
|
/* See if the weekday on its own is set. */
|
|
if (recur_data->weekdays[weekday])
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
|
|
/* If the BYHOUR rule is specified it expands each occurrence in occs, by
|
|
using each of the hours in the byhour list. */
|
|
static GArray*
|
|
cal_obj_byhour_expand (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ;
|
|
GList *elem;
|
|
gint len, i;
|
|
|
|
/* If BYHOUR has not been specified, or the array is empty, just
|
|
return the array. */
|
|
if (!recur_data->recur->byhour || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
|
|
elem = recur_data->recur->byhour;
|
|
while (elem) {
|
|
occ->hour = GPOINTER_TO_INT (elem->data);
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
elem = elem->next;
|
|
}
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
/* If the BYHOUR rule is specified it filters out all occurrences in occs
|
|
which do not match one of the hours in the byhour list. */
|
|
static GArray*
|
|
cal_obj_byhour_filter (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ;
|
|
gint len, i;
|
|
|
|
/* If BYHOUR has not been specified, or the array is empty, just
|
|
return the array. */
|
|
if (!recur_data->recur->byhour || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
if (recur_data->hours[occ->hour])
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
|
|
/* If the BYMINUTE rule is specified it expands each occurrence in occs, by
|
|
using each of the minutes in the byminute list. */
|
|
static GArray*
|
|
cal_obj_byminute_expand (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ;
|
|
GList *elem;
|
|
gint len, i;
|
|
|
|
/* If BYMINUTE has not been specified, or the array is empty, just
|
|
return the array. */
|
|
if (!recur_data->recur->byminute || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
|
|
elem = recur_data->recur->byminute;
|
|
while (elem) {
|
|
occ->minute = GPOINTER_TO_INT (elem->data);
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
elem = elem->next;
|
|
}
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
/* If the BYMINUTE rule is specified it filters out all occurrences in occs
|
|
which do not match one of the minutes in the byminute list. */
|
|
static GArray*
|
|
cal_obj_byminute_filter (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ;
|
|
gint len, i;
|
|
|
|
/* If BYMINUTE has not been specified, or the array is empty, just
|
|
return the array. */
|
|
if (!recur_data->recur->byminute || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
if (recur_data->minutes[occ->minute])
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
|
|
/* If the BYSECOND rule is specified it expands each occurrence in occs, by
|
|
using each of the seconds in the bysecond list. */
|
|
static GArray*
|
|
cal_obj_bysecond_expand (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ;
|
|
GList *elem;
|
|
gint len, i;
|
|
|
|
/* If BYSECOND has not been specified, or the array is empty, just
|
|
return the array. */
|
|
if (!recur_data->recur->bysecond || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
|
|
elem = recur_data->recur->bysecond;
|
|
while (elem) {
|
|
occ->second = GPOINTER_TO_INT (elem->data);
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
elem = elem->next;
|
|
}
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
/* If the BYSECOND rule is specified it filters out all occurrences in occs
|
|
which do not match one of the seconds in the bysecond list. */
|
|
static GArray*
|
|
cal_obj_bysecond_filter (RecurData *recur_data,
|
|
GArray *occs)
|
|
{
|
|
GArray *new_occs;
|
|
CalObjTime *occ;
|
|
gint len, i;
|
|
|
|
/* If BYSECOND has not been specified, or the array is empty, just
|
|
return the array. */
|
|
if (!recur_data->recur->bysecond || occs->len == 0)
|
|
return occs;
|
|
|
|
new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
|
|
|
|
len = occs->len;
|
|
for (i = 0; i < len; i++) {
|
|
occ = &g_array_index (occs, CalObjTime, i);
|
|
if (recur_data->seconds[occ->second])
|
|
g_array_append_vals (new_occs, occ, 1);
|
|
}
|
|
|
|
g_array_free (occs, TRUE);
|
|
|
|
return new_occs;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Adds a positive number of months to the given CalObjTime, updating the year
|
|
appropriately so we end up with a valid month. Note that the day may be
|
|
invalid. */
|
|
static void
|
|
cal_obj_time_add_months (CalObjTime *cotime,
|
|
gint months)
|
|
{
|
|
guint month;
|
|
|
|
/* We use a guint to avoid overflow on the guint8. */
|
|
month = cotime->month + months;
|
|
cotime->year += month / 12;
|
|
cotime->month = month % 12;
|
|
}
|
|
|
|
|
|
/* Adds a positive number of days to the given CalObjTime, updating the month
|
|
and year appropriately so we end up with a valid day. */
|
|
static void
|
|
cal_obj_time_add_days (CalObjTime *cotime,
|
|
gint days)
|
|
{
|
|
guint day, days_in_month;
|
|
|
|
/* We use a guint to avoid overflow on the guint8. */
|
|
day = (guint) cotime->day;
|
|
day += days;
|
|
|
|
for (;;) {
|
|
days_in_month = time_days_in_month (cotime->year,
|
|
cotime->month);
|
|
if (day <= days_in_month)
|
|
break;
|
|
|
|
cotime->month++;
|
|
if (cotime->month >= 12) {
|
|
cotime->year++;
|
|
cotime->month = 0;
|
|
}
|
|
|
|
day -= days_in_month;
|
|
}
|
|
|
|
cotime->day = (guint8) day;
|
|
}
|
|
|
|
|
|
/* Subtracts a positive number of days from the given CalObjTime, updating the
|
|
month and year appropriately so we end up with a valid day. */
|
|
static void
|
|
cal_obj_time_subtract_days (CalObjTime *cotime,
|
|
gint days)
|
|
{
|
|
gint day, days_in_month;
|
|
|
|
/* We use a gint to avoid overflow on the guint8. */
|
|
day = (gint) cotime->day;
|
|
day -= days;
|
|
|
|
while (day <= 0) {
|
|
if (cotime->month == 0) {
|
|
cotime->year--;
|
|
cotime->month = 11;
|
|
} else {
|
|
cotime->month--;
|
|
}
|
|
|
|
days_in_month = time_days_in_month (cotime->year,
|
|
cotime->month);
|
|
|
|
day += days_in_month;
|
|
}
|
|
|
|
cotime->day = (guint8) day;
|
|
}
|
|
|
|
|
|
/* Adds a positive number of hours to the given CalObjTime, updating the day,
|
|
month & year appropriately so we end up with a valid time. */
|
|
static void
|
|
cal_obj_time_add_hours (CalObjTime *cotime,
|
|
gint hours)
|
|
{
|
|
guint hour;
|
|
|
|
/* We use a guint to avoid overflow on the guint8. */
|
|
hour = cotime->hour + hours;
|
|
cotime->hour = hour % 24;
|
|
if (hour >= 24)
|
|
cal_obj_time_add_days (cotime, hour / 24);
|
|
}
|
|
|
|
|
|
/* Adds a positive number of minutes to the given CalObjTime, updating the
|
|
rest of the CalObjTime appropriately. */
|
|
static void
|
|
cal_obj_time_add_minutes (CalObjTime *cotime,
|
|
gint minutes)
|
|
{
|
|
guint minute;
|
|
|
|
/* We use a guint to avoid overflow on the guint8. */
|
|
minute = cotime->minute + minutes;
|
|
cotime->minute = minute % 60;
|
|
if (minute >= 60)
|
|
cal_obj_time_add_hours (cotime, minute / 60);
|
|
}
|
|
|
|
|
|
/* Adds a positive number of seconds to the given CalObjTime, updating the
|
|
rest of the CalObjTime appropriately. */
|
|
static void
|
|
cal_obj_time_add_seconds (CalObjTime *cotime,
|
|
gint seconds)
|
|
{
|
|
guint second;
|
|
|
|
/* We use a guint to avoid overflow on the guint8. */
|
|
second = cotime->second + seconds;
|
|
cotime->second = second % 60;
|
|
if (second >= 60)
|
|
cal_obj_time_add_minutes (cotime, second / 60);
|
|
}
|
|
|
|
|
|
/* Compares 2 CalObjTimes. Returns -1 if the cotime1 is before cotime2, 0 if
|
|
they are the same, or 1 if cotime1 is after cotime2. The comparison type
|
|
specifies which parts of the times we are interested in, e.g. if CALOBJ_DAY
|
|
is used we only want to know if the days are different. */
|
|
static gint
|
|
cal_obj_time_compare (CalObjTime *cotime1,
|
|
CalObjTime *cotime2,
|
|
CalObjTimeComparison type)
|
|
{
|
|
if (cotime1->year < cotime2->year)
|
|
return -1;
|
|
if (cotime1->year > cotime2->year)
|
|
return 1;
|
|
|
|
if (type == CALOBJ_YEAR)
|
|
return 0;
|
|
|
|
if (cotime1->month < cotime2->month)
|
|
return -1;
|
|
if (cotime1->month > cotime2->month)
|
|
return 1;
|
|
|
|
if (type == CALOBJ_MONTH)
|
|
return 0;
|
|
|
|
if (cotime1->day < cotime2->day)
|
|
return -1;
|
|
if (cotime1->day > cotime2->day)
|
|
return 1;
|
|
|
|
if (type == CALOBJ_DAY)
|
|
return 0;
|
|
|
|
if (cotime1->hour < cotime2->hour)
|
|
return -1;
|
|
if (cotime1->hour > cotime2->hour)
|
|
return 1;
|
|
|
|
if (type == CALOBJ_HOUR)
|
|
return 0;
|
|
|
|
if (cotime1->minute < cotime2->minute)
|
|
return -1;
|
|
if (cotime1->minute > cotime2->minute)
|
|
return 1;
|
|
|
|
if (type == CALOBJ_MINUTE)
|
|
return 0;
|
|
|
|
if (cotime1->second < cotime2->second)
|
|
return -1;
|
|
if (cotime1->second > cotime2->second)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* This is the same as the above function, but without the comparison type.
|
|
It is used for qsort(). */
|
|
static gint
|
|
cal_obj_time_compare_func (const void *arg1,
|
|
const void *arg2)
|
|
{
|
|
CalObjTime *cotime1, *cotime2;
|
|
|
|
cotime1 = (CalObjTime*) arg1;
|
|
cotime2 = (CalObjTime*) arg2;
|
|
|
|
if (cotime1->year < cotime2->year)
|
|
return -1;
|
|
if (cotime1->year > cotime2->year)
|
|
return 1;
|
|
|
|
if (cotime1->month < cotime2->month)
|
|
return -1;
|
|
if (cotime1->month > cotime2->month)
|
|
return 1;
|
|
|
|
if (cotime1->day < cotime2->day)
|
|
return -1;
|
|
if (cotime1->day > cotime2->day)
|
|
return 1;
|
|
|
|
if (cotime1->hour < cotime2->hour)
|
|
return -1;
|
|
if (cotime1->hour > cotime2->hour)
|
|
return 1;
|
|
|
|
if (cotime1->minute < cotime2->minute)
|
|
return -1;
|
|
if (cotime1->minute > cotime2->minute)
|
|
return 1;
|
|
|
|
if (cotime1->second < cotime2->second)
|
|
return -1;
|
|
if (cotime1->second > cotime2->second)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Returns the weekday of the given CalObjTime, from 0 - 6. The week start
|
|
day is Monday by default, but can be set in the recurrence rule. */
|
|
static gint
|
|
cal_obj_time_weekday (CalObjTime *cotime,
|
|
CalRecurrence *recur)
|
|
{
|
|
GDate date;
|
|
gint weekday;
|
|
|
|
g_date_clear (&date, 1);
|
|
g_date_set_dmy (&date, cotime->day, cotime->month + 1, cotime->year);
|
|
|
|
/* This results in a value of 0 (Monday) - 6 (Sunday). */
|
|
weekday = g_date_weekday (&date) - 1;
|
|
|
|
/* This calculates the offset of our day from the start of the week.
|
|
We just add on a week (to avoid any possible negative values) and
|
|
then subtract the specified week start day, then convert it into a
|
|
value from 0-6. */
|
|
weekday = (weekday + 7 - recur->week_start_day) % 7;
|
|
|
|
return weekday;
|
|
}
|
|
|
|
|
|
/* Returns the day of the year of the given CalObjTime, from 1 - 366. */
|
|
static gint
|
|
cal_obj_time_day_of_year (CalObjTime *cotime)
|
|
{
|
|
GDate date;
|
|
|
|
g_date_clear (&date, 1);
|
|
g_date_set_dmy (&date, cotime->day, cotime->month + 1, cotime->year);
|
|
|
|
return g_date_day_of_year (&date);
|
|
}
|
|
|
|
|
|
/* Finds the first week in the given CalObjTime's year, using the same weekday
|
|
as the event start day (i.e. from the RecurData).
|
|
The first week of the year is the first week starting from the specified
|
|
week start day that has 4 days in the new year. It may be in the previous
|
|
year. */
|
|
static void
|
|
cal_obj_time_find_first_week (CalObjTime *cotime,
|
|
RecurData *recur_data)
|
|
{
|
|
GDate date;
|
|
gint weekday, week_start_day, offset;
|
|
|
|
/* Find out the weekday of the 1st of the year. */
|
|
g_date_clear (&date, 1);
|
|
g_date_set_dmy (&date, 1, 1, cotime->year);
|
|
|
|
/* This results in a value of 0 (Monday) - 6 (Sunday). */
|
|
weekday = g_date_weekday (&date) - 1;
|
|
|
|
/* Calculate the first day of the year that starts a new week. */
|
|
week_start_day = recur_data->recur->week_start_day;
|
|
offset = (week_start_day + 7 - weekday) % 7;
|
|
|
|
/* Now see if we have to move backwards 1 week, i.e. if the week
|
|
starts on or after Jan 5th (since the previous week has 4 days in
|
|
this year and so will be the first week of the year). */
|
|
if (offset >= 4)
|
|
offset -= 7;
|
|
|
|
/* Now move to the required day. */
|
|
offset += (recur_data->weekday + 7 - week_start_day) % 7;
|
|
|
|
/* Now move the cotime to the appropriate day. */
|
|
cotime->month = 0;
|
|
cotime->day = 1;
|
|
if (offset > 0)
|
|
cal_obj_time_add_days (cotime, offset);
|
|
else
|
|
cal_obj_time_subtract_days (cotime, offset);
|
|
}
|
|
|
|
|
|
static void
|
|
cal_object_time_from_time (CalObjTime *cotime,
|
|
time_t t)
|
|
{
|
|
struct tm *tmp_tm;
|
|
time_t tmp_time_t;
|
|
|
|
tmp_time_t = t;
|
|
tmp_tm = localtime (&tmp_time_t);
|
|
|
|
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;
|
|
}
|