diff options
Diffstat (limited to 'libical/src/libical/icalrecur.c')
-rw-r--r-- | libical/src/libical/icalrecur.c | 1174 |
1 files changed, 1174 insertions, 0 deletions
diff --git a/libical/src/libical/icalrecur.c b/libical/src/libical/icalrecur.c new file mode 100644 index 0000000000..a4b51e60d5 --- /dev/null +++ b/libical/src/libical/icalrecur.c @@ -0,0 +1,1174 @@ +/* -*- Mode: C -*- + ====================================================================== + FILE: icalrecur.c + CREATOR: eric 16 May 2000 + + $Id$ + $Locker$ + + + (C) COPYRIGHT 2000, Eric Busboom, http://www.softwarestudio.org + + This program is free software; you can redistribute it and/or modify + it under the terms of either: + + The LGPL as published by the Free Software Foundation, version + 2.1, available at: http://www.fsf.org/copyleft/lesser.html + + Or: + + The Mozilla Public License Version 1.0. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + + How this code works: + + Processing starts when the caller generates a new recurrence + iterator via icalrecur_iterator_new(). This routine copies the + recurrence rule into the iterator, extracts things like start and + end dates. Then, it checks if the rule is legal, using some logic + from RFC2445 and some logic that probably should be in RFC2445. + + Then, icalrecur_iterator_new() re-writes some of the BY* + arrays. This involves ( via a call to setup_defaults() ) : + + 1) For BY rule parts with no data ( ie BYSECOND was not specified ) + copy the corresponding time part from DTSTART into the BY array. ( + So impl->by_ptrs[BY_SECOND] will then have one element if is + originally had none ) This only happens if the BY* rule part data + would expand the number of occurrences in the occurrence set. This + lets the code ignore DTSTART later on and still use it to get the + time parts that were not specified in any other way. + + 2) For the by rule part that are not the same interval as the + frequency -- for HOURLY anything but BYHOUR, for instance -- copy the + first data element from the rule part into the first occurrence. For + example, for "INTERVAL=MONTHLY and BYHOUR=10,30", initialize the + first time to be returned to have an hour of 10. + + Finally, for INTERVAL=YEARLY, the routine expands the rule to get + all of the days specified in the rule. The code will do this for + each new year, and this is the first expansion. This is a special + case for the yearly interval; no other frequency gets expanded this + way. The yearly interval is the most complex, so some special + processing is required. + + After creating a new iterator, the caller will make successive calls + to icalrecur_iterator_next() to get the next time specified by the + rule. The main part of this routine is a switch on the frequency of + the rule. Each different frequency is handled by a different + routine. + + For example, next_hour handles the case of INTERVAL=HOURLY, and it + is called by other routines to get the next hour. First, the routine + tries to get the next minute part of a time with a call to + next_minute(). If next_minute() returns 1, it has reached the end of + its data, usually the last element of the BYMINUTE array. Then, if + there is data in the BYHOUR array, the routine changes the hour to + the next one in the array. If INTERVAL=HOURLY, the routine advances + the hour by the interval. + + If the routine used the last hour in the BYHOUR array, and the + INTERVAL=HOURLY, then the routine calls increment_monthday() to set + the next month day. The increment_* routines may call higher routine + to increment the month or year also. + + The code for INTERVAL=DAILY is handled by next_day(). First, the + routine tries to get the next hour part of a time with a call to + next_hour. If next_hour() returns 1, it has reached the end of its + data, usually the last element of the BYHOUR array. This means that + next_day() should increment the time to the next day. If FREQUENCY==DAILY, + the routine increments the day by the interval; otherwise, it + increments the day by 1. + + Next_day() differs from next_hour because it does not use the BYDAY + array to select an appropriate day. Instead, it returns every day ( + incrementing by 1 if the frequency is not DAILY with INTERVAL!=1) + Any days that are not specified in an non-empty BYDAY array are + filtered out later. + + Generally, the flow of these routine is for a next_* call a next_* + routine of a lower interval ( next_day calls next_hour) and then to + possibly call an increment_* routine of an equal or higher + interval. ( next_day calls increment_monthday() ) + + When the call to the original next_* routine returns, + icalrecur_iterator_next() will check the returned data against other + BYrule parts to determine if is should be excluded by calling + check_contracting_rules. Generally, a contracting rule is any with a + 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 + 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 + 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. + + ======================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "icalrecur.h" +#include "icalerror.h" +#include "icalmemory.h" +#include <stdlib.h> /* for malloc */ +#include <errno.h> /* for errno */ +#include <string.h> /* for icalmemory_strdup */ +#include <assert.h> +#include <limits.h> /* for SHRT_MAX */ + +#define TEMP_MAX 1024 + + + +enum byrule { + NO_CONTRACTION = -1, + BY_SECOND = 0, + BY_MINUTE = 1, + BY_HOUR = 2, + BY_DAY = 3, + BY_MONTH_DAY = 4, + BY_YEAR_DAY = 5, + BY_WEEK_NO = 6, + BY_MONTH = 7, + BY_SET_POS +}; + + + +struct icalrecur_iterator_impl { + + struct icaltimetype dtstart; + struct icaltimetype last; /* last time return from _iterator_next*/ + int occurrence_no; /* number of step made on this iterator */ + struct icalrecurrencetype rule; + + short days[366]; + short days_index; + + enum byrule byrule; + short by_indices[9]; + + + short *by_ptrs[9]; /* Pointers into the by_* array elements of the rule */ +}; + +int icalrecur_iterator_sizeof_byarray(short* byarray) +{ + int array_itr; + + for(array_itr = 0; + byarray[array_itr] != ICAL_RECURRENCE_ARRAY_MAX; + array_itr++){ + } + + return array_itr; +} + +enum expand_table { + UNKNOWN = 0, + CONTRACT = 1, + EXPAND =2, + ILLEGAL=3 +}; + +struct expand_split_map_struct +{ + icalrecurrencetype_frequency frequency; + + /* Elements of the 'map' array correspond to the BYxxx rules: + Second,Minute,Hour,Day,Month Day,Year Day,Week No,Month*/ + + short map[8]; +}; + +struct expand_split_map_struct expand_map[] = +{ + {ICAL_SECONDLY_RECURRENCE,1,1,1,1,1,1,1,1}, + {ICAL_MINUTELY_RECURRENCE,2,1,1,1,1,1,1,1}, + {ICAL_HOURLY_RECURRENCE, 2,2,1,1,1,1,1,1}, + {ICAL_DAILY_RECURRENCE, 2,2,2,1,1,1,1,1}, + {ICAL_WEEKLY_RECURRENCE, 2,2,2,2,3,3,1,1}, + {ICAL_MONTHLY_RECURRENCE, 2,2,2,2,2,3,3,1}, + {ICAL_YEARLY_RECURRENCE, 2,2,2,2,2,2,2,2}, + {ICAL_NO_RECURRENCE, 0,0,0,0,0,0,0,0} + +}; + + + +/* Check that the rule has only the two given interday byrule parts. */ +int icalrecur_two_byrule(struct icalrecur_iterator_impl* impl, + enum byrule one,enum byrule two) +{ + short test_array[9]; + enum byrule itr; + int passes = 0; + + memset(test_array,0,9); + + test_array[one] = 1; + test_array[two] = 1; + + for(itr = BY_DAY; itr != BY_SET_POS; itr++){ + + if( (test_array[itr] == 0 && + impl->by_ptrs[itr][0] != ICAL_RECURRENCE_ARRAY_MAX + ) || + (test_array[itr] == 1 && + impl->by_ptrs[itr][0] == ICAL_RECURRENCE_ARRAY_MAX + ) + ) { + /* test failed */ + passes = 0; + } + } + + return passes; + +} + +/* Check that the rule has only the one given interdat byrule parts. */ +int icalrecur_one_byrule(struct icalrecur_iterator_impl* impl,enum byrule one) +{ + int passes = 1; + enum byrule itr; + + for(itr = BY_DAY; itr != BY_SET_POS; itr++){ + + if ((itr==one && impl->by_ptrs[itr][0] == ICAL_RECURRENCE_ARRAY_MAX) || + (itr!=one && impl->by_ptrs[itr][0] != ICAL_RECURRENCE_ARRAY_MAX)) { + passes = 0; + } + } + + return passes; +} + +int count_byrules(struct icalrecur_iterator_impl* impl) +{ + int count = 0; + enum byrule itr; + + for(itr = BY_DAY; itr <= BY_SET_POS; itr++){ + if(impl->by_ptrs[itr][0] != ICAL_RECURRENCE_ARRAY_MAX){ + count++; + } + } + + return count; +} + + +void setup_defaults(struct icalrecur_iterator_impl* impl, + enum byrule byrule, icalrecurrencetype_frequency req, + short deftime, int *timepart) +{ + + icalrecurrencetype_frequency freq; + freq = impl->rule.freq; + + /* Re-write the BY rule arrays with data from the DTSTART time so + we don't hav to explicitly deal with DTSTART */ + + if(impl->by_ptrs[byrule][0] == ICAL_RECURRENCE_ARRAY_MAX && + expand_map[freq].map[byrule] != CONTRACT){ + impl->by_ptrs[byrule][0] = deftime; + } + + /* Initialize the first occurence */ + if( freq != req && expand_map[freq].map[byrule] != CONTRACT){ + *timepart = impl->by_ptrs[byrule][0]; + } + + +} + +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; + + if ( ( impl = (struct icalrecur_iterator_impl *) + malloc(sizeof(struct icalrecur_iterator_impl))) == 0) { + icalerror_set_errno(ICAL_NEWFAILED_ERROR); + return 0; + } + + memset(impl,0,sizeof(struct icalrecur_iterator_impl)); + + impl->rule = rule; + impl->last = dtstart; + impl->dtstart = dtstart; + impl->days_index =0; + impl->occurrence_no = 0; + freq = impl->rule.freq; + + /* Set up convienience pointers to make the code simpler. Allows + us to iterate through all of the BY* arrays in the rule. */ + + impl->by_ptrs[BY_MONTH]=impl->rule.by_month; + impl->by_ptrs[BY_WEEK_NO]=impl->rule.by_week_no; + impl->by_ptrs[BY_YEAR_DAY]=impl->rule.by_year_day; + impl->by_ptrs[BY_MONTH_DAY]=impl->rule.by_month_day; + impl->by_ptrs[BY_DAY]=impl->rule.by_day; + impl->by_ptrs[BY_HOUR]=impl->rule.by_hour; + impl->by_ptrs[BY_MINUTE]=impl->rule.by_minute; + impl->by_ptrs[BY_SECOND]=impl->rule.by_second; + impl->by_ptrs[BY_SET_POS]=impl->rule.by_set_pos; + + + /* Check if the recurrence rule is legal */ + + /* If the BYYEARDAY appears, no other date rule part may appear. */ + + if(icalrecur_two_byrule(impl,BY_YEAR_DAY,BY_MONTH) || + icalrecur_two_byrule(impl,BY_YEAR_DAY,BY_WEEK_NO) || + 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); + + return 0; + } + + /* 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_USAGE_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_USAGE_ERROR); + return 0; + } + + + /*For MONTHLY recurrences (FREQ=MONTHLY) neither BYYEARDAY nor + 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); + return 0; + } + + + /*For WEEKLY recurrences (FREQ=WEEKLY) neither BYMONTHDAY nor + 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); + return 0; + } + + + /* Rewrite some of the rules and set up defaults to make later + processing easier */ + + + setup_defaults(impl,BY_SECOND,ICAL_SECONDLY_RECURRENCE,impl->dtstart.second, + &(impl->last.second)); + + setup_defaults(impl,BY_MINUTE,ICAL_MINUTELY_RECURRENCE,impl->dtstart.minute, + &(impl->last.minute)); + + setup_defaults(impl,BY_HOUR,ICAL_HOURLY_RECURRENCE,impl->dtstart.hour, + &(impl->last.hour)); + + setup_defaults(impl,BY_MONTH_DAY,ICAL_DAILY_RECURRENCE,impl->dtstart.day, + &(impl->last.day)); + + setup_defaults(impl,BY_MONTH,ICAL_MONTHLY_RECURRENCE,impl->dtstart.month, + &(impl->last.month)); + + if(impl->rule.freq == ICAL_WEEKLY_RECURRENCE && + impl->by_ptrs[BY_DAY][0] == ICAL_RECURRENCE_ARRAY_MAX){ + impl->by_ptrs[BY_DAY][0] = icaltime_day_of_week(impl->dtstart); + } + + + if(impl->rule.freq == ICAL_YEARLY_RECURRENCE){ + expand_year_days(impl,impl->dtstart.year); + } + + return impl; +} + + +void icalrecur_iterator_free(icalrecur_iterator* i) +{ + + struct icalrecur_iterator_impl* impl = + (struct icalrecur_iterator_impl*)i; + + icalerror_check_arg_rv((impl!=0),"impl"); + + free(impl); + +} + + + + +void increment_year(struct icalrecur_iterator_impl* impl, int inc) +{ + impl->last.year+=inc; +} + + + + +void increment_month(struct icalrecur_iterator_impl* impl, int inc) +{ + impl->last.month+=inc; + + if (impl->last.month > 12){ + impl->last.month = 1; + increment_year(impl,1); + } +} + +void increment_monthday(struct icalrecur_iterator_impl* impl, int inc) +{ + + short days_in_month = icaltime_days_in_month(impl->last.month,impl->last.year); + + impl->last.day+=inc; + + if (impl->last.day > days_in_month){ + int md = impl->last.day -days_in_month; + impl->last.day = md; + increment_month(impl,1); + } + +} + + +void increment_hour(struct icalrecur_iterator_impl* impl, int inc) +{ + impl->last.hour+=inc; + + if (impl->last.hour > 24){ + impl->last.hour = 0; + increment_monthday(impl,1); + } + +} + +void increment_minute(struct icalrecur_iterator_impl* impl, int inc) +{ + impl->last.minute+=inc; + + if (impl->last.minute > 59){ + impl->last.minute = 0; + increment_hour(impl,1); + } + +} + +void increment_second(struct icalrecur_iterator_impl* impl, int inc) +{ + impl->last.second+=inc; + + if (impl->last.second > 59){ + impl->last.second = 0; + increment_minute(impl,1); + } + +} + + +short next_second(struct icalrecur_iterator_impl* impl) +{ + + short has_by_data = (impl->by_ptrs[BY_SECOND][0]!=ICAL_RECURRENCE_ARRAY_MAX); + short this_frequency = (impl->rule.freq == ICAL_SECONDLY_RECURRENCE); + + short end_of_data = 0; + + assert(has_by_data || this_frequency); + + if( has_by_data ){ + /* Ignore the frequency and use the byrule data */ + + impl->by_indices[BY_SECOND]++; + + if (impl->by_ptrs[BY_SECOND][impl->by_indices[BY_SECOND]] + ==ICAL_RECURRENCE_ARRAY_MAX){ + impl->by_indices[BY_SECOND] = 0; + + end_of_data = 1; + } + + + impl->last.second = + impl->by_ptrs[BY_SECOND][impl->by_indices[BY_SECOND]]; + + + } else if( !has_by_data && this_frequency ){ + /* Compute the next value from the last time and the frequency interval*/ + increment_second(impl, impl->rule.interval); + + } + + /* If we have gone through all of the seconds on the BY list, then we + need to move to the next minute */ + + if(has_by_data && end_of_data && this_frequency ){ + increment_minute(impl,1); + } + + return end_of_data; + +} + +int next_minute(struct icalrecur_iterator_impl* impl) +{ + + short has_by_data = (impl->by_ptrs[BY_MINUTE][0]!=ICAL_RECURRENCE_ARRAY_MAX); + short this_frequency = (impl->rule.freq == ICAL_MINUTELY_RECURRENCE); + + short end_of_data = 0; + + assert(has_by_data || this_frequency); + + + if (next_second(impl) == 0){ + return 0; + } + + if( has_by_data ){ + /* Ignore the frequency and use the byrule data */ + + impl->by_indices[BY_MINUTE]++; + + if (impl->by_ptrs[BY_MINUTE][impl->by_indices[BY_MINUTE]] + ==ICAL_RECURRENCE_ARRAY_MAX){ + + impl->by_indices[BY_MINUTE] = 0; + + end_of_data = 1; + } + + impl->last.minute = + impl->by_ptrs[BY_MINUTE][impl->by_indices[BY_MINUTE]]; + + } else if( !has_by_data && this_frequency ){ + /* Compute the next value from the last time and the frequency interval*/ + increment_minute(impl,impl->rule.interval); + } + +/* If we have gone through all of the minutes on the BY list, then we + need to move to the next hour */ + + if(has_by_data && end_of_data && this_frequency ){ + increment_hour(impl,1); + } + + return end_of_data; +} + +int next_hour(struct icalrecur_iterator_impl* impl) +{ + + short has_by_data = (impl->by_ptrs[BY_HOUR][0]!=ICAL_RECURRENCE_ARRAY_MAX); + short this_frequency = (impl->rule.freq == ICAL_HOURLY_RECURRENCE); + + short end_of_data = 0; + + assert(has_by_data || this_frequency); + + if (next_minute(impl) == 0){ + return 0; + } + + if( has_by_data ){ + /* Ignore the frequency and use the byrule data */ + + impl->by_indices[BY_HOUR]++; + + if (impl->by_ptrs[BY_HOUR][impl->by_indices[BY_HOUR]] + ==ICAL_RECURRENCE_ARRAY_MAX){ + impl->by_indices[BY_HOUR] = 0; + + end_of_data = 1; + } + + impl->last.hour = + impl->by_ptrs[BY_HOUR][impl->by_indices[BY_HOUR]]; + + } else if( !has_by_data && this_frequency ){ + /* Compute the next value from the last time and the frequency interval*/ + increment_hour(impl,impl->rule.interval); + + } + + /* If we have gone through all of the hours on the BY list, then we + need to move to the next day */ + + if(has_by_data && end_of_data && this_frequency ){ + increment_monthday(impl,1); + } + + return end_of_data; + +} + +int next_day(struct icalrecur_iterator_impl* impl) +{ + + short has_by_data = (impl->by_ptrs[BY_DAY][0]!=ICAL_RECURRENCE_ARRAY_MAX); + short this_frequency = (impl->rule.freq == ICAL_DAILY_RECURRENCE); + + assert(has_by_data || this_frequency); + + if (next_hour(impl) == 0){ + return 0; + } + + /* Always increment through the interval, since this routine is not + called by any other next_* routine, and the days that are + excluded will be taken care of by restriction filtering */ + + if(this_frequency){ + increment_monthday(impl,impl->rule.interval); + } else { + increment_monthday(impl,1); + } + + + return 0; + +} + +/* 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) +{ + + short has_by_data = (impl->by_ptrs[BY_YEAR_DAY][0]!=ICAL_RECURRENCE_ARRAY_MAX); + + short end_of_data = 0; + + assert(has_by_data ); + + if (next_hour(impl) == 0){ + return 0; + } + + impl->by_indices[BY_YEAR_DAY]++; + + if (impl->by_ptrs[BY_YEAR_DAY][impl->by_indices[BY_YEAR_DAY]] + ==ICAL_RECURRENCE_ARRAY_MAX){ + impl->by_indices[BY_YEAR_DAY] = 0; + + end_of_data = 1; + } + + impl->last.day = + impl->by_ptrs[BY_YEAR_DAY][impl->by_indices[BY_YEAR_DAY]]; + + if(has_by_data && end_of_data){ + increment_year(impl,1); + } + + return end_of_data; + +} + +/* This routine is only called by next_week or next_month, so it does +not have a clause for this_frequency. In both cases, it is certain +that BY_DAY has data */ + +int next_weekday(struct icalrecur_iterator_impl* impl) +{ + + short end_of_data = 0; + short start_of_week, dow; + struct icaltimetype next; + + if (next_hour(impl) == 0){ + return 0; + } + + assert( impl->by_ptrs[BY_DAY][0]!=ICAL_RECURRENCE_ARRAY_MAX); + + impl->by_indices[BY_DAY]++; + + if (impl->by_ptrs[BY_DAY][impl->by_indices[BY_DAY]] + ==ICAL_RECURRENCE_ARRAY_MAX){ + impl->by_indices[BY_DAY] = 0; + + end_of_data = 1; + } + + dow = impl->by_ptrs[BY_DAY][impl->by_indices[BY_DAY]]; + + start_of_week = icaltime_start_doy_of_week(impl->last); + next = icaltime_from_day_of_year(start_of_week + dow - 1,impl->last.year); + + impl->last.day = next.day; + impl->last.month = next.month; + + return end_of_data; + +} + +int next_month(struct icalrecur_iterator_impl* impl) +{ + + short has_by_data = (impl->by_ptrs[BY_MONTH][0]!=ICAL_RECURRENCE_ARRAY_MAX); + short this_frequency = (impl->rule.freq == ICAL_MONTHLY_RECURRENCE); + + short end_of_data = 0; + + assert(has_by_data || this_frequency); + + /* Week day data overrides monthday data */ + if(impl->by_ptrs[BY_DAY][0]!=ICAL_RECURRENCE_ARRAY_MAX){ + if (next_weekday(impl) == 0){ + return 0; + } + } else { + if (next_monthday(impl) == 0){ + return 0; + } + } + + if( has_by_data ){ + /* 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; + } + + impl->last.month = + impl->by_ptrs[BY_MONTH][impl->by_indices[BY_MONTH]]; + + } else if( !has_by_data && this_frequency ){ + /* Compute the next value from the last time and the frequency interval*/ + increment_month(impl,impl->rule.interval); + + } + + if(has_by_data && end_of_data && this_frequency ){ + increment_year(impl,1); + } + return end_of_data; + +} + + +int next_week(struct icalrecur_iterator_impl* impl) +{ + short has_by_data = (impl->by_ptrs[BY_WEEK_NO][0]!=ICAL_RECURRENCE_ARRAY_MAX); + short this_frequency = (impl->rule.freq == ICAL_WEEKLY_RECURRENCE); + short end_of_data = 0; + + int sec_in_week = 60*60*24*7; + + if (next_weekday(impl) == 0){ + return 0; + } + + if( impl->by_ptrs[BY_WEEK_NO][0]!=ICAL_RECURRENCE_ARRAY_MAX){ + /* Use the Week Number byrule data */ + int week_no; + time_t tt; + struct icaltimetype t; + + impl->by_indices[BY_WEEK_NO]++; + + if (impl->by_ptrs[BY_WEEK_NO][impl->by_indices[BY_WEEK_NO]] + ==ICAL_RECURRENCE_ARRAY_MAX){ + impl->by_indices[BY_WEEK_NO] = 0; + + end_of_data = 1; + } + + t = impl->last; + t.month=1; /* HACK, should be setting to the date of the first week of year*/ + t.day=1; + + week_no = impl->by_ptrs[BY_WEEK_NO][impl->by_indices[BY_WEEK_NO]]; + + tt = icaltime_as_timet(impl->last); + + tt+=sec_in_week*week_no; + + impl->last = icaltime_from_timet(tt,impl->last.is_date,impl->last.is_utc); + + } else if( !has_by_data && this_frequency ){ + increment_monthday(impl,7*impl->rule.interval); + } + + if(has_by_data && end_of_data && this_frequency ){ + increment_year(impl,1); + } + + return end_of_data; + +} + +int has_by_data(struct icalrecur_iterator_impl* impl, enum byrule byrule){ + + return (impl->by_ptrs[byrule][0] != ICAL_RECURRENCE_ARRAY_MAX); +} + + +/* 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 + rule. */ + +int expand_year_days(struct icalrecur_iterator_impl* impl,short year) +{ + int j,k; + int days_index=0; + struct icaltimetype t; + + + 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)){ + + for(j=0;impl->by_ptrs[BY_MONTH][j]!=ICAL_RECURRENCE_ARRAY_MAX;j++){ + struct icaltimetype t; + short month = impl->by_ptrs[BY_MONTH][j]; + short doy; + + t = impl->dtstart; + t.year = year; + t.month = month; + + doy = icaltime_day_of_year(t); + + impl->days[days_index++] = doy; + + } + + + } + else if ( has_by_data(impl,BY_MONTH) && has_by_data(impl,BY_DAY)){ + + for(j=0;impl->by_ptrs[BY_MONTH][j]!=ICAL_RECURRENCE_ARRAY_MAX;j++){ + short month = impl->by_ptrs[BY_MONTH][j]; + short days_in_month = icaltime_days_in_month(month,year); + + struct icaltimetype t; + memset(&t,0,sizeof(struct icaltimetype)); + t.day = 1; + t.year = year; + t.month = month; + + for(t.day = 1; t.day <=days_in_month; t.day++){ + + short current_dow = icaltime_day_of_week(t); + + for(k=0;impl->by_ptrs[BY_DAY][k]!=ICAL_RECURRENCE_ARRAY_MAX;k++){ + + enum icalrecurrencetype_weekday dow = + icalrecurrencetype_day_day_of_week(impl->by_ptrs[BY_DAY][k]); + + if(current_dow == dow){ + short doy = icaltime_day_of_year(t); + /* HACK, incomplete Nth day of week handling */ + impl->days[days_index++] = doy; + + } + } + } + } + } else if (has_by_data(impl,BY_MONTH) && has_by_data(impl,BY_MONTH_DAY)){ + + 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; + + doy = icaltime_day_of_year(t); + + impl->days[days_index++] = doy; + + } + } + } 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; + + dow = icaltime_day_of_week(t); + + } else if (has_by_data(impl,BY_WEEK_NO) && has_by_data(impl,BY_DAY)){ + + } else if (has_by_data(impl,BY_YEAR_DAY)){ + + } else if (has_by_data(impl,BY_MONTH_DAY) ){ + + } else if (has_by_data(impl,BY_DAY)){ + + } else { + + } + + return 0; +} + + +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){ + impl->days_index = 0; + end_of_data = 1; + } + + next = icaltime_from_day_of_year(impl->days[impl->days_index],impl->last.year); + + 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, + enum byrule byrule, short v) +{ + int pass = 0; + int itr; + icalrecurrencetype_frequency freq = impl->rule.freq; + + if(impl->by_ptrs[byrule][0]!=ICAL_RECURRENCE_ARRAY_MAX && + expand_map[freq].map[byrule] == CONTRACT){ + for(itr=0; impl->by_ptrs[byrule][itr]!=ICAL_RECURRENCE_ARRAY_MAX;itr++){ + if(impl->by_ptrs[byrule][itr] == v){ + pass=1; + break; + } + } + + return pass; + } else { + /* This is not a contracting byrule, or it has no data, so the + test passes*/ + return 1; + } +} + +int check_contracting_rules(struct icalrecur_iterator_impl* impl) +{ + enum byrule; + + int day_of_week=0; + int week_no=0; + 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) ) + { + + return 1; + } else { + return 0; + } +} + +struct icaltimetype icalrecur_iterator_next(icalrecur_iterator *itr) +{ + struct icalrecur_iterator_impl* impl = + (struct icalrecur_iterator_impl*)itr; + + if( (impl->rule.count!=0 &&impl->occurrence_no >= impl->rule.count) || + (!icaltime_is_null_time(impl->rule.until) && + icaltime_compare(impl->last,impl->rule.until) > 0)) { + return icaltime_null_time(); + } + + if(impl->occurrence_no == 0){ + impl->occurrence_no++; + return impl->last; + } + + + do { + switch(impl->rule.freq){ + + case ICAL_SECONDLY_RECURRENCE: { + next_second(impl); + break; + } + case ICAL_MINUTELY_RECURRENCE: { + next_minute(impl); + break; + } + case ICAL_HOURLY_RECURRENCE: { + next_hour(impl); + break; + } + case ICAL_DAILY_RECURRENCE: { + next_day(impl); + break; + } + case ICAL_WEEKLY_RECURRENCE: { + next_week(impl); + break; + } + case ICAL_MONTHLY_RECURRENCE: { + next_month(impl); + break; + } + case ICAL_YEARLY_RECURRENCE:{ + next_year(impl); + break; + } + default:{ + assert(0); /* HACK, need a better error */ + } + } + + if(impl->last.year >= 2038){ + /* HACK */ + return icaltime_null_time(); + } + + + } while(!check_contracting_rules(impl) + || icaltime_compare(impl->last,impl->dtstart) < 0); + + + if( !icaltime_is_null_time(impl->rule.until) && + icaltime_compare(impl->last,impl->rule.until) > 0) { + return icaltime_null_time(); + } + + impl->occurrence_no++; + + return impl->last; +} + +#include "ical.h" +void icalrecurrencetype_test() +{ + icalvalue *v = icalvalue_new_from_string( + ICAL_RECUR_VALUE, + "FREQ=YEARLY;UNTIL=20060101T000000;INTERVAL=2;BYDAY=SU,WE;BYSECOND=15,30; BYMONTH=1,6,11"); + + struct icalrecurrencetype r = icalvalue_get_recur(v); + struct icaltimetype t = icaltime_from_timet( time(0), 0, 0); + struct icaltimetype next; + time_t tt; + + struct icalrecur_iterator_impl* itr + = (struct icalrecur_iterator_impl*) icalrecur_iterator_new(r,t); + + do { + + next = icalrecur_iterator_next(itr); + tt = icaltime_as_timet(next); + + printf("%s",ctime(&tt )); + + } while( ! icaltime_is_null_time(next)); + +} + |