diff options
Diffstat (limited to 'libical/src/libical/icalrecur.c')
-rw-r--r-- | libical/src/libical/icalrecur.c | 910 |
1 files changed, 600 insertions, 310 deletions
diff --git a/libical/src/libical/icalrecur.c b/libical/src/libical/icalrecur.c index d5c73e80dd..51fdf63a79 100644 --- a/libical/src/libical/icalrecur.c +++ b/libical/src/libical/icalrecur.c @@ -99,26 +99,29 @@ larger time span than the interval. For instance, if INTERVAL=DAILY, BYMONTH is a contracting rule part. - Check_contracting_rules() uses check_restriction() to do its - work. Check_restriction() uses expand_map[] to determine if a rule + Check_contracting_rules() uses icalrecur_check_rulepart() to do its + work. icalrecur_check_rulepart() uses expand_map[] to determine if a rule is contracting, and if it is, and if the BY rule part has some data, then the routine checks if the value of a component of the time is part of the byrule part. For instance, for "INTERVAL=DAILY; - BYMONTH=6,10", check_restriction() would check that the time value + BYMONTH=6,10", icalrecur_check_rulepart() would check that the time value given to it has a month of either 6 or 10. - icalrecurrencetype_test() Finally, icalrecur_iterator_next() does a few other checks on the time value, and if it passes, it returns the time. - A note about the end_of_data flag. This flag is usually set early in - a next_* routine and returned in the end. The way it is used allows - the next_* routine to set the last time back to the first element in - a BYxx rule, and then signal to the higer level routine to increment - the next higher level. For instance. WITH FREQ=MONTHLY;BYDAY=TU,FR, - After next_weekday_by_month runs though both TU and FR, it sets the - week day back to TU and sets end_of_data. This signals next_month to - increment the month. + A note about the end_of_data flag. The flag indicates that the + routine is at the end of its data -- the last BY rule if the routine + is using by rules, or the last day of the week/month/year/etc if + not. + + This flag is usually set early in a next_* routine and returned in + the end. The way it is used allows the next_* routine to set the + last time back to the first element in a BYxx rule, and then signal + to the higer level routine to increment the next higher level. For + instance. WITH FREQ=MONTHLY;BYDAY=TU,FR, After next_weekday_by_month + runs though both TU and FR, it sets the week day back to TU and sets + end_of_data to 1x. This signals next_month to increment the month. ======================================================================*/ @@ -143,9 +146,31 @@ #include <assert.h> #include <stddef.h> /* For offsetof() macro */ +#include "pvl.h" + #define TEMP_MAX 1024 +#define BYDAYIDX impl->by_indices[BY_DAY] +#define BYDAYPTR impl->by_ptrs[BY_DAY] + +#define BYMONIDX impl->by_indices[BY_MONTH] +#define BYMONPTR impl->by_ptrs[BY_MONTH] + +#define BYMDIDX impl->by_indices[BY_MONTH_DAY] +#define BYMDPTR impl->by_ptrs[BY_MONTH_DAY] + +#define BYWEEKIDX impl->by_indices[BY_WEEK_NO] +#define BYWEEKPTR impl->by_ptrs[BY_WEEK_NO] + +const char* icalrecur_freq_to_string(icalrecurrencetype_frequency kind); +icalrecurrencetype_frequency icalrecur_string_to_freq(const char* str); + +const char* icalrecur_weekday_to_string(icalrecurrencetype_weekday kind); +icalrecurrencetype_weekday icalrecur_string_to_weekday(const char* str); + + + /*********************** Rule parsing routines ************************/ struct icalrecur_parser { @@ -250,10 +275,10 @@ void icalrecur_add_byrules(struct icalrecur_parser *parser, short *array, /* Get optional sign. HACK. sign is not allowed for all BYxxx rule parts */ if( *t == '-'){ - sign = 1; + sign = -1; t++; } else if (*t == '+'){ - sign = -1; + sign = 1; t++; } @@ -279,7 +304,7 @@ void icalrecur_add_bydayrules(struct icalrecur_parser *parser, const char* vals) char* end; char* vals_copy; - vals_copy = strdup(vals); + vals_copy = icalmemory_strdup(vals); end = (char*)vals_copy+strlen(vals_copy); n = vals_copy; @@ -343,7 +368,7 @@ struct icalrecurrencetype icalrecurrencetype_from_string(const char* str) /* Set up the parser struct */ parser.rule = str; - parser.copy = strdup(parser.rule); + parser.copy = icalmemory_strdup(parser.rule); parser.this_clause = parser.copy; if(parser.copy == 0){ @@ -366,7 +391,7 @@ struct icalrecurrencetype icalrecurrencetype_from_string(const char* str) } if (strcmp(name,"FREQ") == 0){ - parser.rt.freq = icalrecur_string_to_recurrence(value); + parser.rt.freq = icalrecur_string_to_freq(value); } else if (strcmp(name,"COUNT") == 0){ parser.rt.count = atoi(value); } else if (strcmp(name,"UNTIL") == 0){ @@ -451,7 +476,7 @@ char* icalrecurrencetype_as_string(struct icalrecurrencetype *recur) icalmemory_append_string(&str,&str_p,&buf_sz,"FREQ="); icalmemory_append_string(&str,&str_p,&buf_sz, - icalrecur_recurrence_to_string(recur->freq)); + icalrecur_freq_to_string(recur->freq)); if(recur->until.year != 0){ @@ -493,7 +518,7 @@ char* icalrecurrencetype_as_string(struct icalrecurrencetype *recur) pos = icalrecurrencetype_day_position(array[i]); - if (pos == 1) + if (pos == 0) icalmemory_append_string(&str,&str_p,&buf_sz,daystr); else { sprintf(temp,"%d%s",pos,daystr); @@ -538,20 +563,21 @@ enum byrule { struct icalrecur_iterator_impl { - struct icaltimetype dtstart; /* Hack. Make into time_t */ - struct icaltimetype last; /* last time return from _iterator_next*/ - int occurrence_no; /* number of step made on t iterator */ - struct icalrecurrencetype rule; - - short days[366]; - short days_index; - - enum byrule byrule; - short by_indices[9]; - short orig_data[9]; /* 1 if there was data in the byrule */ - - - short *by_ptrs[9]; /* Pointers into the by_* array elements of the rule */ + struct icaltimetype dtstart; /* Hack. Make into time_t */ + struct icaltimetype last; /* last time return from _iterator_next*/ + int occurrence_no; /* number of step made on t iterator */ + struct icalrecurrencetype rule; + + short days[366]; + short days_index; + + enum byrule byrule; + short by_indices[9]; + short orig_data[9]; /* 1 if there was data in the byrule */ + + + short *by_ptrs[9]; /* Pointers into the by_* array elements of the rule */ + }; int icalrecur_iterator_sizeof_byarray(short* byarray) @@ -689,20 +715,23 @@ void setup_defaults(struct icalrecur_iterator_impl* impl, } -int expand_year_days(struct icalrecur_iterator_impl* impl,short year); - int has_by_data(struct icalrecur_iterator_impl* impl, enum byrule byrule){ return (impl->orig_data[byrule] == 1); } +int expand_year_days(struct icalrecur_iterator_impl* impl,short year); + + icalrecur_iterator* icalrecur_iterator_new(struct icalrecurrencetype rule, struct icaltimetype dtstart) { struct icalrecur_iterator_impl* impl; icalrecurrencetype_frequency freq; + short days_in_month; + if ( ( impl = (struct icalrecur_iterator_impl *) malloc(sizeof(struct icalrecur_iterator_impl))) == 0) { icalerror_set_errno(ICAL_NEWFAILED_ERROR); @@ -733,6 +762,11 @@ icalrecur_iterator* icalrecur_iterator_new(struct icalrecurrencetype rule, memset(impl->orig_data,0,9); + /* Note which by rules had data in them when the iterator was + created. We can't use the actuall by_x arrays, because the + empty ones will be given default values later in this + routine. The orig_data array will be used later in has_by_data */ + impl->orig_data[BY_MONTH] = (impl->rule.by_month[0]!=ICAL_RECURRENCE_ARRAY_MAX); impl->orig_data[BY_WEEK_NO] @@ -762,7 +796,7 @@ icalrecur_iterator* icalrecur_iterator_new(struct icalrecurrencetype rule, icalrecur_two_byrule(impl,BY_YEAR_DAY,BY_MONTH_DAY) || icalrecur_two_byrule(impl,BY_YEAR_DAY,BY_DAY) ){ - icalerror_set_errno(ICAL_USAGE_ERROR); + icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR); return 0; } @@ -770,18 +804,18 @@ icalrecur_iterator* icalrecur_iterator_new(struct icalrecurrencetype rule, /* BYWEEKNO and BYMONTH rule parts may not both appear.*/ if(icalrecur_two_byrule(impl,BY_WEEK_NO,BY_MONTH)){ - icalerror_set_errno(ICAL_USAGE_ERROR); + icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR); - icalerror_set_errno(ICAL_USAGE_ERROR); + icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR); return 0; } /* BYWEEKNO and BYMONTHDAY rule parts may not both appear.*/ if(icalrecur_two_byrule(impl,BY_WEEK_NO,BY_MONTH_DAY)){ - icalerror_set_errno(ICAL_USAGE_ERROR); + icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR); - icalerror_set_errno(ICAL_USAGE_ERROR); + icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR); return 0; } @@ -790,10 +824,8 @@ icalrecur_iterator* icalrecur_iterator_new(struct icalrecurrencetype rule, BYWEEKNO may appear. */ if(freq == ICAL_MONTHLY_RECURRENCE && - ( icalrecur_one_byrule(impl,BY_WEEK_NO) || - icalrecur_one_byrule(impl,BY_YEAR_DAY)) ) { - - icalerror_set_errno(ICAL_USAGE_ERROR); + icalrecur_one_byrule(impl,BY_WEEK_NO)){ + icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR); return 0; } @@ -802,13 +834,17 @@ icalrecur_iterator* icalrecur_iterator_new(struct icalrecurrencetype rule, BYYEARDAY may appear. */ if(freq == ICAL_WEEKLY_RECURRENCE && - ( icalrecur_one_byrule(impl,BY_MONTH_DAY) || - icalrecur_one_byrule(impl,BY_YEAR_DAY)) ) { - - icalerror_set_errno(ICAL_USAGE_ERROR); + icalrecur_one_byrule(impl,BY_MONTH_DAY )) { + icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR); return 0; } + /* BYYEARDAY may only appear in YEARLY rules */ + if(freq != ICAL_YEARLY_RECURRENCE && + icalrecur_one_byrule(impl,BY_YEAR_DAY )) { + icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR); + return 0; + } /* Rewrite some of the rules and set up defaults to make later processing easier. Primarily, t involves copying an element @@ -831,6 +867,7 @@ icalrecur_iterator* icalrecur_iterator_new(struct icalrecurrencetype rule, setup_defaults(impl,BY_MONTH,ICAL_MONTHLY_RECURRENCE,impl->dtstart.month, &(impl->last.month)); + if(impl->rule.freq == ICAL_WEEKLY_RECURRENCE ){ if(impl->by_ptrs[BY_DAY][0] == ICAL_RECURRENCE_ARRAY_MAX){ @@ -841,7 +878,7 @@ icalrecur_iterator* icalrecur_iterator_new(struct icalrecurrencetype rule, } else { /* If there is BY_DAY data, then we need to move the initial - time to the start of the BY_DAY data. That if if the + time to the start of the BY_DAY data. That is if the start time is on a Wednesday, and the rule has BYDAY=MO,WE,FR, move the initial time back to monday. Otherwise, jumping to the next week ( jumping 7 @@ -864,10 +901,11 @@ icalrecur_iterator* icalrecur_iterator_new(struct icalrecurrencetype rule, } + /* For YEARLY rule, begin by setting up the year days array */ if(impl->rule.freq == ICAL_YEARLY_RECURRENCE){ - expand_year_days(impl,impl->dtstart.year); - } + expand_year_days(impl,impl->last.year); + } /* If this is a monthly interval with by day data, then we need to @@ -882,28 +920,49 @@ icalrecur_iterator* icalrecur_iterator_new(struct icalrecurrencetype rule, impl->by_ptrs[BY_DAY][impl->by_indices[BY_DAY]]); short poscount = 0; - short days_in_month = - icaltime_days_in_month(impl->last.month, impl->last.year) ; + days_in_month = + icaltime_days_in_month(impl->last.month, impl->last.year); - for(impl->last.day = 1; - impl->last.day <= days_in_month; - impl->last.day++){ - - if(icaltime_day_of_week(impl->last) == dow){ - if(++poscount == pos){ - break; - } - } - } + if(pos >= 0){ + /* Count up from the first day pf the month to find the + pos'th weekday of dow ( like the second monday. ) */ + + for(impl->last.day = 1; + impl->last.day <= days_in_month; + impl->last.day++){ + + if(icaltime_day_of_week(impl->last) == dow){ + if(++poscount == pos || pos == 0){ + break; + } + } + } + } else { + /* Count down from the last day pf the month to find the + pos'th weekday of dow ( like the second to last monday. ) */ + pos = -pos; + for(impl->last.day = days_in_month; + impl->last.day != 0; + impl->last.day--){ + + if(icaltime_day_of_week(impl->last) == dow){ + if(++poscount == pos ){ + break; + } + } + } + } + - if(impl->last.day > days_in_month){ - icalerror_set_errno(ICAL_USAGE_ERROR); + if(impl->last.day > days_in_month || impl->last.day == 0){ + icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR); return 0; } } + return impl; } @@ -921,34 +980,59 @@ void icalrecur_iterator_free(icalrecur_iterator* i) } - - void increment_year(struct icalrecur_iterator_impl* impl, int inc) { impl->last.year+=inc; } - - - -void increment_month(struct icalrecur_iterator_impl* impl, int inc) +/* Increment month is different that the other incement_* routines -- + it figures out the interval for itself, and uses BYMONTH data if + available. */ +void increment_month(struct icalrecur_iterator_impl* impl) { int years; - impl->last.month+=inc; - - /* Months are offset by one */ - impl->last.month--; - - years = impl->last.month / 12; - - impl->last.month = impl->last.month % 12; - - impl->last.month++; - - if (years != 0){ - increment_year(impl,years); - } + if(has_by_data(impl,BY_MONTH) ){ + /* Ignore the frequency and use the byrule data */ + + impl->by_indices[BY_MONTH]++; + + if (impl->by_ptrs[BY_MONTH][impl->by_indices[BY_MONTH]] + ==ICAL_RECURRENCE_ARRAY_MAX){ + impl->by_indices[BY_MONTH] = 0; + + increment_year(impl,1); + + } + + impl->last.month = + impl->by_ptrs[BY_MONTH][impl->by_indices[BY_MONTH]]; + + } else { + + int inc; + + if(impl->rule.freq == ICAL_MONTHLY_RECURRENCE){ + inc = impl->rule.interval; + } else { + inc = 1; + } + + impl->last.month+=inc; + + /* Months are offset by one */ + impl->last.month--; + + years = impl->last.month / 12; + + impl->last.month = impl->last.month % 12; + + impl->last.month++; + + if (years != 0){ + increment_year(impl,years); + } + } } void increment_monthday(struct icalrecur_iterator_impl* impl, int inc) @@ -964,7 +1048,7 @@ void increment_monthday(struct icalrecur_iterator_impl* impl, int inc) if (impl->last.day > days_in_month){ impl->last.day = impl->last.day-days_in_month; - increment_month(impl,1); + increment_month(impl); } } } @@ -1211,46 +1295,6 @@ int next_day(struct icalrecur_iterator_impl* impl) } -/* This routine is only called by next_month and next_year, so it does - not have a clause for this_frequency */ -int next_monthday(struct icalrecur_iterator_impl* impl) -{ - - short has_by_data = (impl->by_ptrs[BY_MONTH_DAY][0]!=ICAL_RECURRENCE_ARRAY_MAX); - short mday; - short end_of_data = 0; - - assert(has_by_data ); - - if (next_hour(impl) == 0){ - return 0; - } - - impl->by_indices[BY_MONTH_DAY]++; - - mday = impl->by_ptrs[BY_MONTH_DAY][impl->by_indices[BY_MONTH_DAY]]; - - if ( mday ==ICAL_RECURRENCE_ARRAY_MAX){ - impl->by_indices[BY_MONTH_DAY] = 0; - - end_of_data = 1; - } - - if (mday > 0){ - impl->last.day = mday; - } else { - short days_in_month = icaltime_days_in_month(impl->last.month, - impl->last.year); - impl->last.day = days_in_month-mday+1; - } - - if(has_by_data && end_of_data ){ - increment_month(impl,1); - } - - return end_of_data; - -} int next_yearday(struct icalrecur_iterator_impl* impl) { @@ -1336,143 +1380,185 @@ int next_weekday_by_week(struct icalrecur_iterator_impl* impl) } -int next_weekday_by_month(struct icalrecur_iterator_impl* impl) -{ +int nth_weekday(short dow, short pos, struct icaltimetype t){ - short end_of_data = 0; - struct icaltimetype start_of_month; /* Start of month */ - short pos, poscount, dow, days_in_month; + short days_in_month = icaltime_days_in_month(t.month,t.year); + short end_dow, start_dow; + short wd; - if (next_hour(impl) == 0){ - return 0; - } + if(pos >= 0){ + t.day = 1; + start_dow = icaltime_day_of_week(t); + + if (pos != 0) { + pos--; + } + + /* find month day of first occurrence of dow -- such as the + month day of the first monday */ - assert( impl->by_ptrs[BY_DAY][0]!=ICAL_RECURRENCE_ARRAY_MAX); + wd = dow-start_dow+1; - while(1) { - impl->by_indices[BY_DAY]++; /* Look at next elem in BYDAY array */ - - /* Are we at the end of the BYDAY array? */ - if (impl->by_ptrs[BY_DAY][impl->by_indices[BY_DAY]] - ==ICAL_RECURRENCE_ARRAY_MAX){ - - impl->by_indices[BY_DAY] = 0; /* Reset to 0 */ - end_of_data = 1; /* Signal that we're at the end */ - } - - dow = icalrecurrencetype_day_day_of_week( - impl->by_ptrs[BY_DAY][impl->by_indices[BY_DAY]]); - pos = icalrecurrencetype_day_position( - impl->by_ptrs[BY_DAY][impl->by_indices[BY_DAY]]); + if (wd <= 0){ + wd = wd + 7; + } - start_of_month = impl->last; + wd = wd + pos * 7; + + } else { + t.day = days_in_month; + end_dow = icaltime_day_of_week(t); - /* Find right day in month. HACK. Find an arithmetic way to do - this */ + pos++; - poscount = 0; - days_in_month = - icaltime_days_in_month(impl->last.month, impl->last.year) ; + /* find month day of last occurrence of dow -- such as the + month day of the last monday */ - for(start_of_month.day = 1; - start_of_month.day <= days_in_month; - start_of_month.day++){ + wd = (end_dow - dow); - if(icaltime_day_of_week(start_of_month) == dow){ - if(++poscount == pos){ - break; - } - } - } + if (wd < 0){ + wd = wd+ 7; + } - if (!end_of_data == 1 && - ( - start_of_month.day > days_in_month || - icaltime_compare(start_of_month,impl->last) <= 0 - ) - ){ - continue; - } + wd = days_in_month - wd; - impl->last.day = start_of_month.day; - impl->last.month = start_of_month.month; - impl->last.year = start_of_month.year; - - return end_of_data; - } + wd = wd + pos * 7; + } + + return wd; } + int next_month(struct icalrecur_iterator_impl* impl) { + int data_valid = 1; + + short this_frequency = (impl->rule.freq == ICAL_MONTHLY_RECURRENCE); + + assert( has_by_data(impl,BY_MONTH) || this_frequency); + + /* Iterate through the occurrences within a day. If we don't get to + the end of the intra-day data, don't bother going to the next + month */ + + if (next_hour(impl) == 0){ + return data_valid; /* Signal that the data is valid */ + } + + + /* Now iterate through the occurrences within a month -- by days, + weeks or weekdays. */ + + if(has_by_data(impl,BY_DAY) && has_by_data(impl,BY_MONTH_DAY)){ + /* Cases like: FREQ=MONTHLY;INTERVAL=1;BYDAY=FR;BYMONTHDAY=13 */ + short day, idx,j; + short days_in_month = icaltime_days_in_month(impl->last.month, + impl->last.year); + /* Iterate through the remaining days in the month and check if + each day is listed in the BY_DAY array and in the BY_MONTHDAY + array. This seems very inneficient, but I think it is the + simplest way to account for both BYDAY=1FR (First friday in + month) and BYDAY=FR ( every friday in month ) */ + + for(day = impl->last.day+1; day <= days_in_month; day++){ + for(idx = 0; BYDAYPTR[idx] != ICAL_RECURRENCE_ARRAY_MAX; idx++){ + for(j = 0; BYMDPTR[j]!=ICAL_RECURRENCE_ARRAY_MAX; j++){ + short dow = + icalrecurrencetype_day_day_of_week(BYDAYPTR[idx]); + short pos = icalrecurrencetype_day_position(BYDAYPTR[idx]); + short mday = BYMDPTR[j]; + short this_dow; + + impl->last.day = day; + this_dow = icaltime_day_of_week(impl->last); + + if( (pos == 0 && dow == this_dow && mday == day) || + (nth_weekday(dow,pos,impl->last) == day && mday==day)){ + goto MDEND; + } + } + } + } - short this_frequency = (impl->rule.freq == ICAL_MONTHLY_RECURRENCE); - - short end_of_data = 0; + MDEND: - assert( has_by_data(impl,BY_MONTH) || this_frequency); + if ( day > days_in_month){ + impl->last.day = 1; + increment_month(impl); + data_valid = 0; /* signal that impl->last is invalid */ + } - /* Week day data overrides monthday data */ - if(has_by_data(impl,BY_DAY)){ + + } else if(has_by_data(impl,BY_DAY)){ + /* Cases like: FREQ=MONTHLY;INTERVAL=1;BYDAY=FR */ /* For this case, the weekdays are relative to the month. BYDAY=FR -> First Friday in month, etc. */ - if (next_weekday_by_month(impl) == 0){ - return 0; - } - } else { - if (next_monthday(impl) == 0){ - return 0; - } - } - - if(has_by_data(impl,BY_MONTH) ){ - /* Ignore the frequency and use the byrule data */ - - impl->by_indices[BY_MONTH]++; - - if (impl->by_ptrs[BY_MONTH][impl->by_indices[BY_MONTH]] - ==ICAL_RECURRENCE_ARRAY_MAX){ - impl->by_indices[BY_MONTH] = 0; - - end_of_data = 1; + short day, idx; + short days_in_month = icaltime_days_in_month(impl->last.month, + impl->last.year); + + assert( BYDAYPTR[0]!=ICAL_RECURRENCE_ARRAY_MAX); + + /* Iterate through the remaining days in the month and check if + each day is listed in the BY_DAY array. This seems very + inneficient, but I think it is the simplest way to account + for both BYDAY=1FR (First friday in month) and BYDAY=FR ( + every friday in month ) */ + + for(day = impl->last.day+1; day <= days_in_month; day++){ + for(idx = 0; BYDAYPTR[idx] != ICAL_RECURRENCE_ARRAY_MAX; idx++){ + short dow = icalrecurrencetype_day_day_of_week(BYDAYPTR[idx]); + short pos = icalrecurrencetype_day_position(BYDAYPTR[idx]); + short this_dow; + + impl->last.day = day; + this_dow = icaltime_day_of_week(impl->last); + + if( (pos == 0 && dow == this_dow ) || + (nth_weekday(dow,pos,impl->last) == day)){ + goto DEND; + } + } } - impl->last.month = - impl->by_ptrs[BY_MONTH][impl->by_indices[BY_MONTH]]; + DEND: - } else if( !has_by_data(impl,BY_MONTH) && this_frequency ){ - - if(has_by_data(impl,BY_DAY)){ - - short dayinc = 28; - - /* BY_DAY data specified a day of week, but incrementing the - month changes the day of the week -- Nov 2 is not the - same DOW as Oct 2. So, we need to fix the day of week by - incrementing in even weeks into the next month. . */ - - - if ( impl->last.day + dayinc - <= icaltime_days_in_month(impl->last.month, impl->last.year)){ - dayinc += 7; - } + if ( day > days_in_month){ + impl->last.day = 1; + increment_month(impl); + data_valid = 0; /* signal that impl->last is invalid */ + } - increment_monthday(impl,dayinc); + } else if (has_by_data(impl,BY_MONTH_DAY)) { + /* Cases like: FREQ=MONTHLY;COUNT=10;BYMONTHDAY=-3 */ + short day; - } else { + assert( BYMDPTR[0]!=ICAL_RECURRENCE_ARRAY_MAX); - /* Compute the next value from the last time and the - frequency interval*/ - increment_month(impl,impl->rule.interval); + BYMDIDX++; + + /* Are we at the end of the BYDAY array? */ + if (BYMDPTR[BYMDIDX] ==ICAL_RECURRENCE_ARRAY_MAX){ + + BYMDIDX = 0; /* Reset to 0 */ + increment_month(impl); } - - } - - - if(has_by_data(impl,BY_MONTH) && end_of_data && this_frequency ){ - increment_year(impl,1); + + day = BYMDPTR[BYMDIDX]; + + if (day < 0) { + day = icaltime_days_in_month(impl->last.month,impl->last.year)+ + day + 1; + } + + impl->last.day = day; + + } else { + increment_month(impl); } - return end_of_data; + + return data_valid; /* Signal that the data is valid */ } @@ -1484,11 +1570,16 @@ int next_week(struct icalrecur_iterator_impl* impl) short end_of_data = 0; + /* Increment to the next week day */ if (next_weekday_by_week(impl) == 0){ - return 0; + return 0; /* Have not reached end of week yet */ } - if( impl->by_ptrs[BY_WEEK_NO][0]!=ICAL_RECURRENCE_ARRAY_MAX){ + /* If we get here, we have incremented through the entire week, and + can increment to the next week */ + + + if( has_by_data){ /* Use the Week Number byrule data */ int week_no; struct icaltimetype t; @@ -1513,11 +1604,11 @@ int next_week(struct icalrecur_iterator_impl* impl) impl->last = icaltime_normalize(impl->last); } else if( !has_by_data && this_frequency ){ - - + /* If there is no BY_WEEK_NO data, just jump forward 7 days. */ increment_monthday(impl,7*impl->rule.interval); } + if(has_by_data && end_of_data && this_frequency ){ increment_year(impl,1); } @@ -1527,6 +1618,69 @@ int next_week(struct icalrecur_iterator_impl* impl) } +pvl_list expand_by_day(struct icalrecur_iterator_impl* impl,short year) +{ + /* Try to calculate each of the occurrences. */ + int i; + pvl_list days_list = pvl_newlist(); + + short start_dow, end_dow, end_year_day, start_doy; + struct icaltimetype tmp = impl->last; + + tmp.year= year; + tmp.month = 1; + tmp.day = 1; + tmp.is_date = 1; + + start_dow = icaltime_day_of_week(tmp); + start_doy = icaltime_start_doy_of_week(tmp); + + /* Get the last day of the year*/ + tmp.year++; + tmp = icaltime_normalize(tmp); + tmp.day--; + tmp = icaltime_normalize(tmp); + + end_dow = icaltime_day_of_week(tmp); + end_year_day = icaltime_day_of_year(tmp); + + for(i = 0; BYDAYPTR[i] != ICAL_RECURRENCE_ARRAY_MAX; i++){ + short dow = + icalrecurrencetype_day_day_of_week(BYDAYPTR[i]); + short pos = icalrecurrencetype_day_position(BYDAYPTR[i]); + + if(pos == 0){ + /* add all of the days of the year with this day-of-week*/ + int week; + for(week = 0; week < 52 ; week ++){ + short doy = start_doy + (week * 7) + dow-1; + + if(doy > end_year_day){ + break; + } else { + pvl_push(days_list,(void*)(int)doy); + } + } + + } else if ( pos > 0) { + int first; + /* First occurrence of dow in year */ + if( dow >= start_dow) { + first = dow - start_dow + 1; + } else { + first = dow - start_dow + 8; + } + + pvl_push(days_list,(void*)(first+ (pos-1) * 7)); + + } else { /* pos < 0 */ + assert(0); + } + } + + return days_list; +} + /* For INTERVAL=YEARLY, set up the days[] array in the iterator to list all of the days of the current year that are specified in this @@ -1537,15 +1691,31 @@ int expand_year_days(struct icalrecur_iterator_impl* impl,short year) int j,k; int days_index=0; struct icaltimetype t; + int flags; +#define HBD(x) has_by_data(impl,x) t.is_date = 1; /* Needed to make day_of_year routines work property */ memset(&t,0,sizeof(t)); memset(impl->days,ICAL_RECURRENCE_ARRAY_MAX_BYTE,sizeof(impl->days)); - if(has_by_data(impl,BY_MONTH) && !has_by_data(impl,BY_MONTH_DAY) - && !has_by_data(impl,BY_DAY)){ + flags = (HBD(BY_DAY) ? 1<<BY_DAY : 0) + + (HBD(BY_WEEK_NO) ? 1<<BY_WEEK_NO : 0) + + (HBD(BY_MONTH_DAY) ? 1<<BY_MONTH_DAY : 0) + + (HBD(BY_MONTH) ? 1<<BY_MONTH : 0) + + (HBD(BY_YEAR_DAY) ? 1<<BY_YEAR_DAY : 0); + + + switch(flags) { + + case 0: { + /* FREQ=YEARLY; */ + + break; + } + case 1<<BY_MONTH: { + /* FREQ=YEARLY; BYMONTH=3,11*/ for(j=0;impl->by_ptrs[BY_MONTH][j]!=ICAL_RECURRENCE_ARRAY_MAX;j++){ struct icaltimetype t; @@ -1562,10 +1732,81 @@ int expand_year_days(struct icalrecur_iterator_impl* impl,short year) impl->days[days_index++] = doy; } + break; + } + case 1<<BY_MONTH_DAY: { + /* FREQ=YEARLY; BYMONTHDAY=1,15*/ + assert(0); + break; + } + + case (1<<BY_MONTH_DAY) + (1<<BY_MONTH): { + /* FREQ=YEARLY; BYMONTHDAY=1,15; BYMONTH=10 */ + + for(j=0;impl->by_ptrs[BY_MONTH][j]!=ICAL_RECURRENCE_ARRAY_MAX;j++){ + for(k=0;impl->by_ptrs[BY_MONTH_DAY][k]!=ICAL_RECURRENCE_ARRAY_MAX;k++) + { + short month = impl->by_ptrs[BY_MONTH][j]; + short month_day = impl->by_ptrs[BY_MONTH_DAY][k]; + short doy; + + t.day = month_day; + t.month = month; + t.year = year; + t.is_date = 1; + + doy = icaltime_day_of_year(t); + + impl->days[days_index++] = doy; + + } + } + + break; + } + + case 1<<BY_WEEK_NO: { + /* FREQ=YEARLY; BYWEEKNO=20,50 */ + + struct icaltimetype t; + short dow; + + t.day = impl->dtstart.day; + t.month = impl->dtstart.month; + t.year = year; + t.is_date = 1; + + dow = icaltime_day_of_week(t); + /* HACK Not finished */ + + break; + } + case (1<<BY_WEEK_NO) + (1<<BY_MONTH_DAY): { + /*FREQ=YEARLY; WEEKNO=20,50; BYMONTH= 6,11 */ + assert(0); + break; } - else if ( has_by_data(impl,BY_MONTH) && has_by_data(impl,BY_DAY)){ + + case 1<<BY_DAY: { + /*FREQ=YEARLY; BYDAY=TH,20MO,-10FR*/ + int days_index = 0; + pvl_elem i; + pvl_list days = expand_by_day(impl,year); + + + for(i=pvl_head(days);i!=0;i=pvl_next(i)){ + short day = (short)(int)pvl_data(i); + impl->days[days_index++] = day; + } + + break; + } + + case (1<<BY_DAY)+(1<<BY_MONTH): { + /*FREQ=YEARLY; BYDAY=TH,20MO,-10FR; BYMONTH = 12*/ + for(j=0;impl->by_ptrs[BY_MONTH][j]!=ICAL_RECURRENCE_ARRAY_MAX;j++){ short month = impl->by_ptrs[BY_MONTH][j]; @@ -1596,58 +1837,90 @@ int expand_year_days(struct icalrecur_iterator_impl* impl,short year) } } } - } else if (has_by_data(impl,BY_MONTH) && has_by_data(impl,BY_MONTH_DAY)){ + break; + } - for(j=0;impl->by_ptrs[BY_MONTH][j]!=ICAL_RECURRENCE_ARRAY_MAX;j++){ - for(k=0;impl->by_ptrs[BY_MONTH_DAY][k]!=ICAL_RECURRENCE_ARRAY_MAX;k++) - { - short month = impl->by_ptrs[BY_MONTH][j]; - short month_day = impl->by_ptrs[BY_MONTH_DAY][k]; - short doy; + case (1<<BY_DAY) + (1<<BY_MONTH_DAY) : { + /*FREQ=YEARLY; BYDAY=TH,20MO,-10FR; BYMONTHDAY=1,15*/ + assert(0); + break; + } - t.day = month_day; - t.month = month; - t.year = year; - t.is_date = 1; + case (1<<BY_DAY) + (1<<BY_MONTH_DAY) + (1<<BY_MONTH): { + /*FREQ=YEARLY; BYDAY=TH,20MO,-10FR; BYMONTHDAY=10; MYMONTH=6,11*/ + + int days_index = 0; + pvl_elem itr; + pvl_list days = expand_by_day(impl,year); + + for(itr=pvl_head(days);itr!=0;itr=pvl_next(itr)){ + short day = (short)(int)pvl_data(itr); + struct icaltimetype tt; + short i,j; + + tt = icaltime_from_day_of_year(day,year); + + for(i = 0; BYMONPTR[i] != ICAL_RECURRENCE_ARRAY_MAX; i++){ + for(j = 0; BYMDPTR[j]!=ICAL_RECURRENCE_ARRAY_MAX; j++){ + short mday = BYMDPTR[j]; + short month = BYMONPTR[i]; + + if(tt.month == month && tt.day == mday){ + impl->days[days_index++] = day; + } + } + } - doy = icaltime_day_of_year(t); + } - impl->days[days_index++] = doy; + break; + } + + case (1<<BY_DAY) + (1<<BY_WEEK_NO) : { + /*FREQ=YEARLY; BYDAY=TH,20MO,-10FR; WEEKNO=20,50*/ + + int days_index = 0; + pvl_elem itr; + pvl_list days = expand_by_day(impl,year); + + for(itr=pvl_head(days);itr!=0;itr=pvl_next(itr)){ + short day = (short)(int)pvl_data(itr); + struct icaltimetype tt; + short i; + + tt = icaltime_from_day_of_year(day,year); + + for(i = 0; BYWEEKPTR[i] != ICAL_RECURRENCE_ARRAY_MAX; i++){ + short weekno = BYWEEKPTR[i]; + + if(weekno== icaltime_week_number(tt)){ + impl->days[days_index++] = day; + } } + } - } else if (has_by_data(impl,BY_WEEK_NO) && !has_by_data(impl,BY_DAY)){ - - struct icaltimetype t; - short dow; - - t.day = impl->dtstart.day; - t.month = impl->dtstart.month; - t.year = year; - t.is_date = 1; + break; + } - dow = icaltime_day_of_week(t); - /* HACK Not finished */ - + case (1<<BY_DAY) + (1<<BY_WEEK_NO) + (1<<BY_MONTH_DAY): { + /*FREQ=YEARLY; BYDAY=TH,20MO,-10FR; WEEKNO=20,50; BYMONTHDAY=1,15*/ + assert(0); + break; + } - } else if (has_by_data(impl,BY_WEEK_NO) && has_by_data(impl,BY_DAY)){ - /* HACK Not finished */ - } else if (has_by_data(impl,BY_YEAR_DAY)){ - + case 1<<BY_YEAR_DAY: { for(j=0;impl->by_ptrs[BY_YEAR_DAY][j]!=ICAL_RECURRENCE_ARRAY_MAX;j++){ short doy = impl->by_ptrs[BY_YEAR_DAY][j]; impl->days[days_index++] = doy; - } - - } else if (has_by_data(impl,BY_MONTH_DAY) ){ - /* HACK Not finished */ - - } else if (has_by_data(impl,BY_DAY)){ - /* HACK Not finished */ + } + break; + } - } else { - assert(0); - /* HACK Not finished */ + default: { + assert(0); + break; + } } @@ -1658,17 +1931,15 @@ int expand_year_days(struct icalrecur_iterator_impl* impl,short year) int next_year(struct icalrecur_iterator_impl* impl) { struct icaltimetype next; - short end_of_data=0; if (next_hour(impl) == 0){ return 0; } - impl->days_index++; - - if (impl->days[impl->days_index] == ICAL_RECURRENCE_ARRAY_MAX){ + if (impl->days[++impl->days_index] == ICAL_RECURRENCE_ARRAY_MAX){ impl->days_index = 0; - end_of_data = 1; + increment_year(impl,impl->rule.interval); + expand_year_days(impl,impl->last.year); } next = icaltime_from_day_of_year(impl->days[impl->days_index],impl->last.year); @@ -1676,16 +1947,26 @@ int next_year(struct icalrecur_iterator_impl* impl) impl->last.day = next.day; impl->last.month = next.month; - - if(end_of_data){ - increment_year(impl,impl->rule.interval); - expand_year_days(impl,impl->last.year); - } - return 1; } -int check_restriction(struct icalrecur_iterator_impl* impl, +int icalrecur_check_rulepart(struct icalrecur_iterator_impl* impl, + short v, enum byrule byrule) +{ + int itr; + + if(impl->by_ptrs[byrule][0]!=ICAL_RECURRENCE_ARRAY_MAX){ + for(itr=0; impl->by_ptrs[byrule][itr]!=ICAL_RECURRENCE_ARRAY_MAX;itr++){ + if(impl->by_ptrs[byrule][itr] == v){ + return 1; + } + } + } + + return 0; +} + +int check_contract_restriction(struct icalrecur_iterator_impl* impl, enum byrule byrule, short v) { int pass = 0; @@ -1709,6 +1990,7 @@ int check_restriction(struct icalrecur_iterator_impl* impl, } } + int check_contracting_rules(struct icalrecur_iterator_impl* impl) { @@ -1717,14 +1999,14 @@ int check_contracting_rules(struct icalrecur_iterator_impl* impl) int year_day=0; if ( - check_restriction(impl,BY_SECOND,impl->last.second) && - check_restriction(impl,BY_MINUTE,impl->last.minute) && - check_restriction(impl,BY_HOUR,impl->last.hour) && - check_restriction(impl,BY_DAY,day_of_week) && - check_restriction(impl,BY_WEEK_NO,week_no) && - check_restriction(impl,BY_MONTH_DAY,impl->last.day) && - check_restriction(impl,BY_MONTH,impl->last.month) && - check_restriction(impl,BY_YEAR_DAY,year_day) ) + check_contract_restriction(impl,BY_SECOND,impl->last.second) && + check_contract_restriction(impl,BY_MINUTE,impl->last.minute) && + check_contract_restriction(impl,BY_HOUR,impl->last.hour) && + check_contract_restriction(impl,BY_DAY,day_of_week) && + check_contract_restriction(impl,BY_WEEK_NO,week_no) && + check_contract_restriction(impl,BY_MONTH_DAY,impl->last.day) && + check_contract_restriction(impl,BY_MONTH,impl->last.month) && + check_contract_restriction(impl,BY_YEAR_DAY,year_day) ) { return 1; @@ -1735,6 +2017,7 @@ int check_contracting_rules(struct icalrecur_iterator_impl* impl) struct icaltimetype icalrecur_iterator_next(icalrecur_iterator *itr) { + int valid = 1; struct icalrecur_iterator_impl* impl = (struct icalrecur_iterator_impl*)itr; @@ -1752,6 +2035,7 @@ struct icaltimetype icalrecur_iterator_next(icalrecur_iterator *itr) } do { + valid = 1; switch(impl->rule.freq){ case ICAL_SECONDLY_RECURRENCE: { @@ -1775,7 +2059,7 @@ struct icaltimetype icalrecur_iterator_next(icalrecur_iterator *itr) break; } case ICAL_MONTHLY_RECURRENCE: { - next_month(impl); + valid = next_month(impl); break; } case ICAL_YEARLY_RECURRENCE:{ @@ -1783,17 +2067,19 @@ struct icaltimetype icalrecur_iterator_next(icalrecur_iterator *itr) break; } default:{ - assert(0); /* HACK, need a better error */ + icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR); + return icaltime_null_time(); } } - if(impl->last.year >= 2038){ + if(impl->last.year >= 2038 ){ /* HACK */ return icaltime_null_time(); } } while(!check_contracting_rules(impl) - || icaltime_compare(impl->last,impl->dtstart) < 0); + || icaltime_compare(impl->last,impl->dtstart) < 0 + || valid == 0); /* Ignore null times and times that are after the until time */ @@ -1830,6 +2116,9 @@ thursday of the year) These routines decode the day values. The day's position in the period ( Nth-ness) and the numerical value of the day are encoded together as: pos*7 + dow + +A position of 0 means 'any' or 'every' + */ enum icalrecurrencetype_weekday icalrecurrencetype_day_day_of_week(short day) @@ -1845,9 +2134,6 @@ short icalrecurrencetype_day_position(short day) pos = (abs(day)-wd)/8 * ((day<0)?-1:1); - if(pos == 0){ - pos = 1; - } return pos; } @@ -1909,7 +2195,7 @@ struct { {ICAL_NO_RECURRENCE,0} }; -const char* icalrecur_recurrence_to_string(icalrecurrencetype_frequency kind) +const char* icalrecur_freq_to_string(icalrecurrencetype_frequency kind) { int i; @@ -1921,7 +2207,7 @@ const char* icalrecur_recurrence_to_string(icalrecurrencetype_frequency kind) return 0; } -icalrecurrencetype_frequency icalrecur_string_to_recurrence(const char* str) +icalrecurrencetype_frequency icalrecur_string_to_freq(const char* str) { int i; @@ -1933,6 +2219,10 @@ icalrecurrencetype_frequency icalrecur_string_to_recurrence(const char* str) return ICAL_NO_RECURRENCE; } +/* Fill an array with the 'count' number of occurrences generated by + the rrule. Note that the times are returned in UTC, but the times + are calculated in local time. YOu will have to convert the results + back into local time before using them. */ int icalrecur_expand_recurrence(char* rule, time_t start, int count, time_t* array) @@ -1953,7 +2243,7 @@ int icalrecur_expand_recurrence(char* rule, time_t start, next = icalrecur_iterator_next(ritr); !icaltime_is_null_time(next) && i < count; next = icalrecur_iterator_next(ritr)){ - + tt = icaltime_as_timet(next); if (tt >= start ){ |