2 ======================================================================
4 CREATOR: eric 16 May 2000
6 $Id: icalrecur.c,v 1.71 2008-02-03 16:10:46 dothebart Exp $
10 (C) COPYRIGHT 2000, Eric Busboom <eric@softwarestudio.org>
11 http://www.softwarestudio.org
13 This program is free software; you can redistribute it and/or modify
14 it under the terms of either:
16 The LGPL as published by the Free Software Foundation, version
17 2.1, available at: http://www.fsf.org/copyleft/lesser.html
21 The Mozilla Public License Version 1.0. You may obtain a copy of
22 the License at http://www.mozilla.org/MPL/
27 @brief Implementation of routines for dealing with recurring time
31 Processing starts when the caller generates a new recurrence
32 iterator via icalrecur_iterator_new(). This routine copies the
33 recurrence rule into the iterator and extracts things like start and
34 end dates. Then, it checks if the rule is legal, using some logic
35 from RFC2445 and some logic that probably should be in RFC2445.
37 Then, icalrecur_iterator_new() re-writes some of the BY*
38 arrays. This involves ( via a call to setup_defaults() ) :
40 1) For BY rule parts with no data ( ie BYSECOND was not specified )
41 copy the corresponding time part from DTSTART into the BY array. (
42 So impl->by_ptrs[BY_SECOND] will then have one element if is
43 originally had none ) This only happens if the BY* rule part data
44 would expand the number of occurrences in the occurrence set. This
45 lets the code ignore DTSTART later on and still use it to get the
46 time parts that were not specified in any other way.
48 2) For the by rule part that are not the same interval as the
49 frequency -- for HOURLY anything but BYHOUR, for instance -- copy the
50 first data element from the rule part into the first occurrence. For
51 example, for "INTERVAL=MONTHLY and BYHOUR=10,30", initialize the
52 first time to be returned to have an hour of 10.
54 Finally, for INTERVAL=YEARLY, the routine expands the rule to get
55 all of the days specified in the rule. The code will do this for
56 each new year, and this is the first expansion. This is a special
57 case for the yearly interval; no other frequency gets expanded this
58 way. The yearly interval is the most complex, so some special
59 processing is required.
61 After creating a new iterator, the caller will make successive calls
62 to icalrecur_iterator_next() to get the next time specified by the
63 rule. The main part of this routine is a switch on the frequency of
64 the rule. Each different frequency is handled by a different
67 For example, next_hour handles the case of INTERVAL=HOURLY, and it
68 is called by other routines to get the next hour. First, the routine
69 tries to get the next minute part of a time with a call to
70 next_minute(). If next_minute() returns 1, it has reached the end of
71 its data, usually the last element of the BYMINUTE array. Then, if
72 there is data in the BYHOUR array, the routine changes the hour to
73 the next one in the array. If INTERVAL=HOURLY, the routine advances
74 the hour by the interval.
76 If the routine used the last hour in the BYHOUR array, and the
77 INTERVAL=HOURLY, then the routine calls increment_monthday() to set
78 the next month day. The increment_* routines may call higher routine
79 to increment the month or year also.
81 The code for INTERVAL=DAILY is handled by next_day(). First, the
82 routine tries to get the next hour part of a time with a call to
83 next_hour. If next_hour() returns 1, it has reached the end of its
84 data, usually the last element of the BYHOUR array. This means that
85 next_day() should increment the time to the next day. If FREQUENCY==DAILY,
86 the routine increments the day by the interval; otherwise, it
87 increments the day by 1.
89 Next_day() differs from next_hour because it does not use the BYDAY
90 array to select an appropriate day. Instead, it returns every day (
91 incrementing by 1 if the frequency is not DAILY with INTERVAL!=1)
92 Any days that are not specified in an non-empty BYDAY array are
95 Generally, the flow of these routine is for a next_* call a next_*
96 routine of a lower interval ( next_day calls next_hour) and then to
97 possibly call an increment_* routine of an equal or higher
98 interval. ( next_day calls increment_monthday() )
100 When the call to the original next_* routine returns,
101 icalrecur_iterator_next() will check the returned data against other
102 BYrule parts to determine if is should be excluded by calling
103 check_contracting_rules. Generally, a contracting rule is any with a
104 larger time span than the interval. For instance, if
105 INTERVAL=DAILY, BYMONTH is a contracting rule part.
107 Check_contracting_rules() uses icalrecur_check_rulepart() to do its
108 work. icalrecur_check_rulepart() uses expand_map[] to determine if a rule
109 is contracting, and if it is, and if the BY rule part has some data,
110 then the routine checks if the value of a component of the time is
111 part of the byrule part. For instance, for "INTERVAL=DAILY;
112 BYMONTH=6,10", icalrecur_check_rulepart() would check that the time value
113 given to it has a month of either 6 or 10.
115 Finally, icalrecur_iterator_next() does a few other checks on the
116 time value, and if it passes, it returns the time.
118 A note about the end_of_data flag. The flag indicates that the
119 routine is at the end of its data -- the last BY rule if the routine
120 is using by rules, or the last day of the week/month/year/etc if
123 This flag is usually set early in a next_* routine and returned in
124 the end. The way it is used allows the next_* routine to set the
125 last time back to the first element in a BYxx rule, and then signal
126 to the higer level routine to increment the next higher level. For
127 instance. WITH FREQ=MONTHLY;BYDAY=TU,FR, After next_weekday_by_month
128 runs though both TU and FR, it sets the week day back to TU and sets
129 end_of_data to 1x. This signals next_month to increment the month.
132 ======================================================================*/
145 #if defined(_MSC_VER)
146 #define snprintf _snprintf
151 #ifndef HAVE_INTPTR_T
152 #if (defined (WIN32) && !defined (__MINGW32__)) || defined (XP_BEOS)
153 typedef long intptr_t;
158 #define strcasecmp stricmp
161 #include "icalrecur.h"
163 #include "icalerror.h"
164 #include "icalmemory.h"
166 #include <stdlib.h> /* for malloc, strtol */
167 #include <errno.h> /* for errno */
168 #include <string.h> /* for strdup and strchr*/
170 #include <stddef.h> /* For offsetof() macro */
171 #ifdef HAVE_INTTYPES_H
172 #include <inttypes.h>
177 /** This is the last year we will go up to, since 32-bit time_t values
178 only go up to the start of 2038. */
179 #define MAX_TIME_T_YEAR 2037
181 #define TEMP_MAX 1024
184 #define BYDAYIDX impl->by_indices[BY_DAY]
185 #define BYDAYPTR impl->by_ptrs[BY_DAY]
187 #define BYMONIDX impl->by_indices[BY_MONTH]
188 #define BYMONPTR impl->by_ptrs[BY_MONTH]
190 #define BYMDIDX impl->by_indices[BY_MONTH_DAY]
191 #define BYMDPTR impl->by_ptrs[BY_MONTH_DAY]
193 #define BYWEEKIDX impl->by_indices[BY_WEEK_NO]
194 #define BYWEEKPTR impl->by_ptrs[BY_WEEK_NO]
196 const char* icalrecur_freq_to_string(icalrecurrencetype_frequency kind);
197 icalrecurrencetype_frequency icalrecur_string_to_freq(const char* str);
199 const char* icalrecur_weekday_to_string(icalrecurrencetype_weekday kind);
200 icalrecurrencetype_weekday icalrecur_string_to_weekday(const char* str);
203 /*********************** Rule parsing routines ************************/
205 struct icalrecur_parser {
211 struct icalrecurrencetype rt;
214 const char* icalrecur_first_clause(struct icalrecur_parser *parser)
217 parser->this_clause = parser->copy;
219 idx = strchr(parser->this_clause,';');
222 parser->next_clause = 0;
228 parser->next_clause = idx;
230 return parser->this_clause;
234 const char* icalrecur_next_clause(struct icalrecur_parser *parser)
238 parser->this_clause = parser->next_clause;
240 if(parser->this_clause == 0){
244 idx = strchr(parser->this_clause,';');
247 parser->next_clause = 0;
252 parser->next_clause = idx;
255 return parser->this_clause;
259 void icalrecur_clause_name_and_value(struct icalrecur_parser *parser,
260 char** name, char** value)
264 *name = parser->this_clause;
266 idx = strchr(parser->this_clause,'=');
279 void icalrecur_add_byrules(struct icalrecur_parser *parser, short *array,
280 int size, char* vals)
304 /* Get optional sign. HACK. sign is not allowed for all BYxxx
309 } else if (*t == '+'){
319 array[i++] = (short)v;
320 array[i] = ICAL_RECURRENCE_ARRAY_MAX;
327 * Days in the BYDAY rule are expected by the code to be sorted, and while
328 * this may be the common case, the RFC doesn't actually mandate it. This
329 * function sorts the days taking into account the first day of week.
332 sort_bydayrules(struct icalrecur_parser *parser)
335 int week_start, one, two, i, j;
337 array = parser->rt.by_day;
338 week_start = parser->rt.week_start;
341 i<ICAL_BY_DAY_SIZE && array[i] != ICAL_RECURRENCE_ARRAY_MAX;
343 for (j=0; j<i; j++) {
344 one = icalrecurrencetype_day_day_of_week(array[j]) - week_start;
345 if (one < 0) one += 7;
346 two = icalrecurrencetype_day_day_of_week(array[i]) - week_start;
347 if (two < 0) two += 7;
350 short tmp = array[j];
358 void icalrecur_add_bydayrules(struct icalrecur_parser *parser, const char* vals)
364 char weekno = 0; /* note: Novell/Groupwise sends BYDAY=255SU, so we fit in a signed char to get -1 SU for last sunday. */
365 icalrecurrencetype_weekday wd;
366 short *array = parser->rt.by_day;
370 vals_copy = icalmemory_strdup(vals);
372 end = (char*)vals_copy+strlen(vals_copy);
387 /* Get optional sign. */
391 } else if (*t == '+'){
398 /* Get Optional weekno */
399 weekno = (char)strtol(t,&t,10);
401 /* Outlook/Exchange generate "BYDAY=MO, FR" and "BYDAY=2 TH".
407 wd = icalrecur_string_to_weekday(t);
409 array[i++] = (short)(sign* (wd + 8*weekno));
410 array[i] = ICAL_RECURRENCE_ARRAY_MAX;
416 sort_bydayrules(parser);
420 struct icalrecurrencetype icalrecurrencetype_from_string(const char* str)
422 struct icalrecur_parser parser;
424 memset(&parser,0,sizeof(parser));
425 icalrecurrencetype_clear(&parser.rt);
427 icalerror_check_arg_re(str!=0,"str",parser.rt);
430 /* Set up the parser struct */
432 parser.copy = icalmemory_strdup(parser.rule);
433 parser.this_clause = parser.copy;
435 if(parser.copy == 0){
436 icalerror_set_errno(ICAL_NEWFAILED_ERROR);
440 /* Loop through all of the clauses */
441 for(icalrecur_first_clause(&parser);
442 parser.this_clause != 0;
443 icalrecur_next_clause(&parser))
446 icalrecur_clause_name_and_value(&parser,&name,&value);
449 icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
450 icalrecurrencetype_clear(&parser.rt);
455 if (strcasecmp(name,"FREQ") == 0){
456 parser.rt.freq = icalrecur_string_to_freq(value);
457 } else if (strcasecmp(name,"COUNT") == 0){
458 parser.rt.count = atoi(value);
459 } else if (strcasecmp(name,"UNTIL") == 0){
460 parser.rt.until = icaltime_from_string(value);
461 } else if (strcasecmp(name,"INTERVAL") == 0){
462 parser.rt.interval = (short)atoi(value);
463 /* don't allow an interval to be less than 1
464 (RFC specifies an interval must be a positive integer) */
465 if (parser.rt.interval < 1){
466 parser.rt.interval = 1;
468 } else if (strcasecmp(name,"WKST") == 0){
469 parser.rt.week_start = icalrecur_string_to_weekday(value);
470 sort_bydayrules(&parser);
471 } else if (strcasecmp(name,"BYSECOND") == 0){
472 icalrecur_add_byrules(&parser,parser.rt.by_second,
473 ICAL_BY_SECOND_SIZE,value);
474 } else if (strcasecmp(name,"BYMINUTE") == 0){
475 icalrecur_add_byrules(&parser,parser.rt.by_minute,
476 ICAL_BY_MINUTE_SIZE,value);
477 } else if (strcasecmp(name,"BYHOUR") == 0){
478 icalrecur_add_byrules(&parser,parser.rt.by_hour,
479 ICAL_BY_HOUR_SIZE,value);
480 } else if (strcasecmp(name,"BYDAY") == 0){
481 icalrecur_add_bydayrules(&parser,value);
482 } else if (strcasecmp(name,"BYMONTHDAY") == 0){
483 icalrecur_add_byrules(&parser,parser.rt.by_month_day,
484 ICAL_BY_MONTHDAY_SIZE,value);
485 } else if (strcasecmp(name,"BYYEARDAY") == 0){
486 icalrecur_add_byrules(&parser,parser.rt.by_year_day,
487 ICAL_BY_YEARDAY_SIZE,value);
488 } else if (strcasecmp(name,"BYWEEKNO") == 0){
489 icalrecur_add_byrules(&parser,parser.rt.by_week_no,
490 ICAL_BY_WEEKNO_SIZE,value);
491 } else if (strcasecmp(name,"BYMONTH") == 0){
492 icalrecur_add_byrules(&parser,parser.rt.by_month,
493 ICAL_BY_MONTH_SIZE,value);
494 } else if (strcasecmp(name,"BYSETPOS") == 0){
495 icalrecur_add_byrules(&parser,parser.rt.by_set_pos,
496 ICAL_BY_SETPOS_SIZE,value);
498 icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
499 icalrecurrencetype_clear(&parser.rt);
512 static struct {const char* str;size_t offset; int limit; } recurmap[] =
514 {";BYSECOND=",offsetof(struct icalrecurrencetype,by_second),ICAL_BY_SECOND_SIZE - 1},
515 {";BYMINUTE=",offsetof(struct icalrecurrencetype,by_minute),ICAL_BY_MINUTE_SIZE - 1},
516 {";BYHOUR=",offsetof(struct icalrecurrencetype,by_hour),ICAL_BY_HOUR_SIZE - 1},
517 {";BYDAY=",offsetof(struct icalrecurrencetype,by_day),ICAL_BY_DAY_SIZE - 1},
518 {";BYMONTHDAY=",offsetof(struct icalrecurrencetype,by_month_day),ICAL_BY_MONTHDAY_SIZE - 1},
519 {";BYYEARDAY=",offsetof(struct icalrecurrencetype,by_year_day),ICAL_BY_YEARDAY_SIZE - 1},
520 {";BYWEEKNO=",offsetof(struct icalrecurrencetype,by_week_no),ICAL_BY_WEEKNO_SIZE - 1},
521 {";BYMONTH=",offsetof(struct icalrecurrencetype,by_month),ICAL_BY_MONTH_SIZE - 1},
522 {";BYSETPOS=",offsetof(struct icalrecurrencetype,by_set_pos),ICAL_BY_SETPOS_SIZE - 1},
526 /* A private routine in icalvalue.c */
527 void print_date_to_string(char* str, struct icaltimetype *data);
528 void print_datetime_to_string(char* str, struct icaltimetype *data);
530 char* icalrecurrencetype_as_string(struct icalrecurrencetype *recur)
533 buf = icalrecurrencetype_as_string_r(recur);
534 icalmemory_add_tmp_buffer(buf);
539 char* icalrecurrencetype_as_string_r(struct icalrecurrencetype *recur)
547 if(recur->freq == ICAL_NO_RECURRENCE){
551 str = (char*)icalmemory_new_buffer(buf_sz);
554 icalmemory_append_string(&str,&str_p,&buf_sz,"FREQ=");
555 icalmemory_append_string(&str,&str_p,&buf_sz,
556 icalrecur_freq_to_string(recur->freq));
558 if(recur->until.year != 0){
561 if (recur->until.is_date)
562 print_date_to_string(temp,&(recur->until));
564 print_datetime_to_string(temp,&(recur->until));
566 icalmemory_append_string(&str,&str_p,&buf_sz,";UNTIL=");
567 icalmemory_append_string(&str,&str_p,&buf_sz, temp);
570 if(recur->count != 0){
571 snprintf(temp,sizeof(temp),"%d",recur->count);
572 icalmemory_append_string(&str,&str_p,&buf_sz,";COUNT=");
573 icalmemory_append_string(&str,&str_p,&buf_sz, temp);
576 if(recur->interval != 1){
577 snprintf(temp,sizeof(temp),"%d",recur->interval);
578 icalmemory_append_string(&str,&str_p,&buf_sz,";INTERVAL=");
579 icalmemory_append_string(&str,&str_p,&buf_sz, temp);
582 for(j =0; recurmap[j].str != 0; j++){
583 short* array = (short*)(recurmap[j].offset+ (size_t)recur);
584 int limit = recurmap[j].limit;
586 /* Skip unused arrays */
587 if( array[0] != ICAL_RECURRENCE_ARRAY_MAX ) {
589 icalmemory_append_string(&str,&str_p,&buf_sz,recurmap[j].str);
592 i< limit && array[i] != ICAL_RECURRENCE_ARRAY_MAX;
594 if (j == 3) { /* BYDAY */
595 const char *daystr = icalrecur_weekday_to_string(
596 icalrecurrencetype_day_day_of_week(array[i]));
599 pos = icalrecurrencetype_day_position(array[i]);
602 icalmemory_append_string(&str,&str_p,&buf_sz,daystr);
604 snprintf(temp,sizeof(temp),"%d%s",pos,daystr);
605 icalmemory_append_string(&str,&str_p,&buf_sz,temp);
609 snprintf(temp,sizeof(temp),"%d",array[i]);
610 icalmemory_append_string(&str,&str_p,&buf_sz, temp);
613 if( (i+1)<limit &&array[i+1]
614 != ICAL_RECURRENCE_ARRAY_MAX){
615 icalmemory_append_char(&str,&str_p,&buf_sz,',');
621 /* Monday is the default, so no need to write that out */
622 if ( recur->week_start != ICAL_MONDAY_WEEKDAY && recur->week_start != ICAL_NO_WEEKDAY ) {
623 const char *daystr = icalrecur_weekday_to_string(
624 icalrecurrencetype_day_day_of_week( recur->week_start ));
625 icalmemory_append_string(&str,&str_p,&buf_sz,";WKST=");
626 icalmemory_append_string(&str,&str_p,&buf_sz,daystr);
633 /************************* occurrence iteration routiens ******************/
650 struct icalrecur_iterator_impl {
652 struct icaltimetype dtstart; /* Hack. Make into time_t */
653 struct icaltimetype last; /* last time return from _iterator_next*/
654 int occurrence_no; /* number of step made on t iterator */
655 struct icalrecurrencetype rule;
662 short orig_data[9]; /**< 1 if there was data in the byrule */
665 short *by_ptrs[9]; /**< Pointers into the by_* array elements of the rule */
669 static void increment_year(icalrecur_iterator* impl, int inc);
671 int icalrecur_iterator_sizeof_byarray(short* byarray)
676 byarray[array_itr] != ICAL_RECURRENCE_ARRAY_MAX;
691 * The split map indicates, for a particular interval, wether a BY_*
692 * rule part expands the number of instances in the occcurrence set or
693 * contracts it. 1=> contract, 2=>expand, and 3 means the pairing is
697 struct expand_split_map_struct
699 icalrecurrencetype_frequency frequency;
701 /* Elements of the 'map' array correspond to the BYxxx rules:
702 Second,Minute,Hour,Day,Month Day,Year Day,Week No,Month*/
707 static const struct expand_split_map_struct expand_map[] =
709 {ICAL_SECONDLY_RECURRENCE,{1,1,1,1,1,1,1,1}},
710 {ICAL_MINUTELY_RECURRENCE,{2,1,1,1,1,1,1,1}},
711 {ICAL_HOURLY_RECURRENCE, {2,2,1,1,1,1,1,1}},
712 {ICAL_DAILY_RECURRENCE, {2,2,2,1,1,1,1,1}},
713 {ICAL_WEEKLY_RECURRENCE, {2,2,2,2,3,3,1,1}},
714 {ICAL_MONTHLY_RECURRENCE, {2,2,2,2,2,3,3,1}},
715 {ICAL_YEARLY_RECURRENCE, {2,2,2,2,2,2,2,2}},
716 {ICAL_NO_RECURRENCE, {0,0,0,0,0,0,0,0}}
722 /** Check that the rule has only the two given interday byrule parts. */
724 int icalrecur_two_byrule(icalrecur_iterator* impl,
725 enum byrule one,enum byrule two)
731 memset(test_array,0,sizeof(test_array));
736 for(itr = BY_DAY; itr != BY_SET_POS; itr++){
738 if( (test_array[itr] == 0 &&
739 impl->by_ptrs[itr][0] != ICAL_RECURRENCE_ARRAY_MAX
741 (test_array[itr] == 1 &&
742 impl->by_ptrs[itr][0] == ICAL_RECURRENCE_ARRAY_MAX
754 /** Check that the rule has only the one given interdat byrule parts. */
755 static int icalrecur_one_byrule(icalrecur_iterator* impl,enum byrule one)
760 for(itr = BY_DAY; itr != BY_SET_POS; itr++){
762 if ((itr==one && impl->by_ptrs[itr][0] == ICAL_RECURRENCE_ARRAY_MAX) ||
763 (itr!=one && impl->by_ptrs[itr][0] != ICAL_RECURRENCE_ARRAY_MAX)) {
771 static int count_byrules(icalrecur_iterator* impl)
776 for(itr = BY_DAY; itr <= BY_SET_POS; itr++){
777 if(impl->by_ptrs[itr][0] != ICAL_RECURRENCE_ARRAY_MAX){
786 static void setup_defaults(icalrecur_iterator* impl,
787 enum byrule byrule, icalrecurrencetype_frequency req,
788 int deftime, int *timepart)
791 icalrecurrencetype_frequency freq;
792 freq = impl->rule.freq;
794 /* Re-write the BY rule arrays with data from the DTSTART time so
795 we don't have to explicitly deal with DTSTART */
797 if(impl->by_ptrs[byrule][0] == ICAL_RECURRENCE_ARRAY_MAX &&
798 expand_map[freq].map[byrule] != CONTRACT){
799 impl->by_ptrs[byrule][0] = (short)deftime;
802 /* Initialize the first occurrence */
803 if( freq != req && expand_map[freq].map[byrule] != CONTRACT){
804 *timepart = impl->by_ptrs[byrule][0];
810 static int has_by_data(icalrecur_iterator* impl, enum byrule byrule){
812 return (impl->orig_data[byrule] == 1);
816 static int expand_year_days(icalrecur_iterator* impl, int year);
819 icalrecur_iterator* icalrecur_iterator_new(struct icalrecurrencetype rule,
820 struct icaltimetype dtstart)
822 icalrecur_iterator* impl;
823 icalrecurrencetype_frequency freq;
825 icalerror_clear_errno();
827 if ( ( impl = (icalrecur_iterator*)
828 malloc(sizeof(icalrecur_iterator))) == 0) {
829 icalerror_set_errno(ICAL_NEWFAILED_ERROR);
833 memset(impl,0,sizeof(icalrecur_iterator));
836 impl->last = dtstart;
837 impl->dtstart = dtstart;
839 impl->occurrence_no = 0;
840 freq = impl->rule.freq;
842 /* Set up convienience pointers to make the code simpler. Allows
843 us to iterate through all of the BY* arrays in the rule. */
845 impl->by_ptrs[BY_MONTH]=impl->rule.by_month;
846 impl->by_ptrs[BY_WEEK_NO]=impl->rule.by_week_no;
847 impl->by_ptrs[BY_YEAR_DAY]=impl->rule.by_year_day;
848 impl->by_ptrs[BY_MONTH_DAY]=impl->rule.by_month_day;
849 impl->by_ptrs[BY_DAY]=impl->rule.by_day;
850 impl->by_ptrs[BY_HOUR]=impl->rule.by_hour;
851 impl->by_ptrs[BY_MINUTE]=impl->rule.by_minute;
852 impl->by_ptrs[BY_SECOND]=impl->rule.by_second;
853 impl->by_ptrs[BY_SET_POS]=impl->rule.by_set_pos;
855 memset(impl->orig_data,0,9*sizeof(short));
857 /* Note which by rules had data in them when the iterator was
858 created. We can't use the actuall by_x arrays, because the
859 empty ones will be given default values later in this
860 routine. The orig_data array will be used later in has_by_data */
862 impl->orig_data[BY_MONTH]
863 = (short)(impl->rule.by_month[0]!=ICAL_RECURRENCE_ARRAY_MAX);
864 impl->orig_data[BY_WEEK_NO]
865 =(short)(impl->rule.by_week_no[0]!=ICAL_RECURRENCE_ARRAY_MAX);
866 impl->orig_data[BY_YEAR_DAY]
867 =(short)(impl->rule.by_year_day[0]!=ICAL_RECURRENCE_ARRAY_MAX);
868 impl->orig_data[BY_MONTH_DAY]
869 =(short)(impl->rule.by_month_day[0]!=ICAL_RECURRENCE_ARRAY_MAX);
870 impl->orig_data[BY_DAY]
871 = (short)(impl->rule.by_day[0]!=ICAL_RECURRENCE_ARRAY_MAX);
872 impl->orig_data[BY_HOUR]
873 = (short)(impl->rule.by_hour[0]!=ICAL_RECURRENCE_ARRAY_MAX);
874 impl->orig_data[BY_MINUTE]
875 = (short)(impl->rule.by_minute[0]!=ICAL_RECURRENCE_ARRAY_MAX);
876 impl->orig_data[BY_SECOND]
877 = (short)(impl->rule.by_second[0]!=ICAL_RECURRENCE_ARRAY_MAX);
878 impl->orig_data[BY_SET_POS]
879 = (short)(impl->rule.by_set_pos[0]!=ICAL_RECURRENCE_ARRAY_MAX);
882 /* Check if the recurrence rule is legal */
884 /* If the BYYEARDAY appears, no other date rule part may appear. */
886 if(icalrecur_two_byrule(impl,BY_YEAR_DAY,BY_MONTH) ||
887 icalrecur_two_byrule(impl,BY_YEAR_DAY,BY_WEEK_NO) ||
888 icalrecur_two_byrule(impl,BY_YEAR_DAY,BY_MONTH_DAY) ||
889 icalrecur_two_byrule(impl,BY_YEAR_DAY,BY_DAY) ){
891 icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
898 /* BYWEEKNO and BYMONTHDAY rule parts may not both appear.*/
900 if(icalrecur_two_byrule(impl,BY_WEEK_NO,BY_MONTH_DAY)){
901 icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
907 /*For MONTHLY recurrences (FREQ=MONTHLY) neither BYYEARDAY nor
908 BYWEEKNO may appear. */
910 if(freq == ICAL_MONTHLY_RECURRENCE &&
911 icalrecur_one_byrule(impl,BY_WEEK_NO)){
912 icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
918 /*For WEEKLY recurrences (FREQ=WEEKLY) neither BYMONTHDAY nor
919 BYYEARDAY may appear. */
921 if(freq == ICAL_WEEKLY_RECURRENCE &&
922 icalrecur_one_byrule(impl,BY_MONTH_DAY )) {
923 icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
928 /* BYYEARDAY may only appear in YEARLY rules */
929 if(freq != ICAL_YEARLY_RECURRENCE &&
930 icalrecur_one_byrule(impl,BY_YEAR_DAY )) {
931 icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
936 /* Rewrite some of the rules and set up defaults to make later
937 processing easier. Primarily, t involves copying an element
938 from the start time into the corresponding BY_* array when the
939 BY_* array is empty */
942 setup_defaults(impl,BY_SECOND,ICAL_SECONDLY_RECURRENCE,
943 impl->dtstart.second,
944 &(impl->last.second));
946 setup_defaults(impl,BY_MINUTE,ICAL_MINUTELY_RECURRENCE,
947 impl->dtstart.minute,
948 &(impl->last.minute));
950 setup_defaults(impl,BY_HOUR,ICAL_HOURLY_RECURRENCE,
954 setup_defaults(impl,BY_MONTH_DAY,ICAL_DAILY_RECURRENCE,
958 setup_defaults(impl,BY_MONTH,ICAL_MONTHLY_RECURRENCE,
960 &(impl->last.month));
963 if(impl->rule.freq == ICAL_WEEKLY_RECURRENCE ){
965 if(impl->by_ptrs[BY_DAY][0] == ICAL_RECURRENCE_ARRAY_MAX){
967 /* Weekly recurrences with no BY_DAY data should occur on the
968 same day of the week as the start time . */
969 impl->by_ptrs[BY_DAY][0] = (short)icaltime_day_of_week(impl->dtstart);
972 /* If there is BY_DAY data, then we need to move the initial
973 time to the start of the BY_DAY data. That is if the
974 start time is on a Wednesday, and the rule has
975 BYDAY=MO,WE,FR, move the initial time back to
976 monday. Otherwise, jumping to the next week ( jumping 7
977 days ahead ) will skip over some occurrences in the
980 /* This depends on impl->by_ptrs[BY_DAY] being correctly sorted by
981 * day. This should probably be abstracted to make such assumption
983 short dow = (short)(impl->by_ptrs[BY_DAY][0]-icaltime_day_of_week(impl->last));
985 if((icaltime_day_of_week(impl->last) < impl->by_ptrs[BY_DAY][0] && dow >= 0) || dow < 0)
987 /* initial time is after first day of BY_DAY data */
988 impl->last.day += dow;
989 impl->last = icaltime_normalize(impl->last);
997 /* For YEARLY rule, begin by setting up the year days array . The
998 YEARLY rules work by expanding one year at a time. */
1000 if(impl->rule.freq == ICAL_YEARLY_RECURRENCE){
1001 struct icaltimetype next;
1002 icalerror_clear_errno();
1004 /* Fail after hitting the year 20000 if no expanded days match */
1005 while (impl->last.year < 20000) {
1006 expand_year_days(impl, impl->last.year);
1007 if( icalerrno != ICAL_NO_ERROR) {
1008 icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
1012 if (impl->days[0] != ICAL_RECURRENCE_ARRAY_MAX)
1013 break; /* break when no days are expanded */
1014 increment_year(impl,impl->rule.interval);
1017 /* Copy the first day into last. */
1018 next = icaltime_from_day_of_year(impl->days[0], impl->last.year);
1020 impl->last.day = next.day;
1021 impl->last.month = next.month;
1025 /* If this is a monthly interval with by day data, then we need to
1026 set the last value to the appropriate day of the month */
1028 if(impl->rule.freq == ICAL_MONTHLY_RECURRENCE &&
1029 has_by_data(impl,BY_DAY)) {
1031 int dow = icalrecurrencetype_day_day_of_week(
1032 impl->by_ptrs[BY_DAY][impl->by_indices[BY_DAY]]);
1033 int pos = icalrecurrencetype_day_position(
1034 impl->by_ptrs[BY_DAY][impl->by_indices[BY_DAY]]);
1038 icaltime_days_in_month(impl->last.month, impl->last.year);
1041 /* Count up from the first day pf the month to find the
1042 pos'th weekday of dow ( like the second monday. ) */
1044 for(impl->last.day = 1;
1045 impl->last.day <= days_in_month;
1048 if(icaltime_day_of_week(impl->last) == dow){
1049 if(++poscount == pos || pos == 0){
1055 /* Count down from the last day pf the month to find the
1056 pos'th weekday of dow ( like the second to last monday. ) */
1058 for(impl->last.day = days_in_month;
1059 impl->last.day != 0;
1062 if(icaltime_day_of_week(impl->last) == dow){
1063 if(++poscount == pos ){
1071 if(impl->last.day > days_in_month || impl->last.day == 0){
1072 icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
1077 } else if (has_by_data(impl,BY_MONTH_DAY)) {
1078 impl->last = icaltime_normalize(impl->last);
1087 void icalrecur_iterator_free(icalrecur_iterator* i)
1089 icalerror_check_arg_rv((i!=0),"impl");
1095 static void increment_year(icalrecur_iterator* impl, int inc)
1097 impl->last.year+=inc;
1100 /** Increment month is different that the other incement_* routines --
1101 it figures out the interval for itself, and uses BYMONTH data if
1103 static void increment_month(icalrecur_iterator* impl)
1107 if(has_by_data(impl,BY_MONTH) ){
1108 /* Ignore the frequency and use the byrule data */
1110 impl->by_indices[BY_MONTH]++;
1112 if (impl->by_ptrs[BY_MONTH][impl->by_indices[BY_MONTH]]
1113 ==ICAL_RECURRENCE_ARRAY_MAX){
1114 impl->by_indices[BY_MONTH] = 0;
1116 increment_year(impl,1);
1121 impl->by_ptrs[BY_MONTH][impl->by_indices[BY_MONTH]];
1127 if(impl->rule.freq == ICAL_MONTHLY_RECURRENCE){
1128 inc = impl->rule.interval;
1133 impl->last.month+=inc;
1135 /* Months are offset by one */
1138 years = impl->last.month / 12;
1140 impl->last.month = impl->last.month % 12;
1145 increment_year(impl,years);
1150 static void increment_monthday(icalrecur_iterator* impl, int inc)
1154 for(i=0; i<inc; i++){
1157 icaltime_days_in_month(impl->last.month, impl->last.year);
1161 if (impl->last.day > days_in_month){
1162 impl->last.day = impl->last.day-days_in_month;
1163 increment_month(impl);
1169 static void increment_hour(icalrecur_iterator* impl, int inc)
1173 impl->last.hour+=inc;
1175 days = impl->last.hour / 24;
1176 impl->last.hour = impl->last.hour % 24;
1179 increment_monthday(impl,days);
1183 static void increment_minute(icalrecur_iterator* impl, int inc)
1187 impl->last.minute+=inc;
1189 hours = impl->last.minute / 60;
1190 impl->last.minute = impl->last.minute % 60;
1193 increment_hour(impl,hours);
1198 static void increment_second(icalrecur_iterator* impl, int inc)
1202 impl->last.second+=inc;
1204 minutes = impl->last.second / 60;
1205 impl->last.second = impl->last.second % 60;
1209 increment_minute(impl, minutes);
1215 void test_increment()
1217 icalrecur_iterator impl;
1219 impl.last = icaltime_from_string("20000101T000000Z");
1221 printf("Orig: %s\n",icaltime_as_ctime(impl.last));
1223 increment_second(&impl,5);
1224 printf("+ 5 sec : %s\n",icaltime_as_ctime(impl.last));
1226 increment_second(&impl,355);
1227 printf("+ 355 sec : %s\n",icaltime_as_ctime(impl.last));
1229 increment_minute(&impl,5);
1230 printf("+ 5 min : %s\n",icaltime_as_ctime(impl.last));
1232 increment_minute(&impl,360);
1233 printf("+ 360 min : %s\n",icaltime_as_ctime(impl.last));
1234 increment_hour(&impl,5);
1235 printf("+ 5 hours : %s\n",icaltime_as_ctime(impl.last));
1236 increment_hour(&impl,43);
1237 printf("+ 43 hours : %s\n",icaltime_as_ctime(impl.last));
1238 increment_monthday(&impl,3);
1239 printf("+ 3 days : %s\n",icaltime_as_ctime(impl.last));
1240 increment_monthday(&impl,600);
1241 printf("+ 600 days : %s\n",icaltime_as_ctime(impl.last));
1247 static int next_second(icalrecur_iterator* impl)
1250 int has_by_second = (impl->by_ptrs[BY_SECOND][0]!=ICAL_RECURRENCE_ARRAY_MAX);
1251 int this_frequency = (impl->rule.freq == ICAL_SECONDLY_RECURRENCE);
1253 int end_of_data = 0;
1255 assert(has_by_second || this_frequency);
1257 if( has_by_second ){
1258 /* Ignore the frequency and use the byrule data */
1260 impl->by_indices[BY_SECOND]++;
1262 if (impl->by_ptrs[BY_SECOND][impl->by_indices[BY_SECOND]]
1263 ==ICAL_RECURRENCE_ARRAY_MAX){
1264 impl->by_indices[BY_SECOND] = 0;
1271 impl->by_ptrs[BY_SECOND][impl->by_indices[BY_SECOND]];
1274 } else if( !has_by_second && this_frequency ){
1275 /* Compute the next value from the last time and the frequency interval*/
1276 increment_second(impl, impl->rule.interval);
1280 /* If we have gone through all of the seconds on the BY list, then we
1281 need to move to the next minute */
1283 if(has_by_second && end_of_data && this_frequency ){
1284 increment_minute(impl,1);
1291 static int next_minute(icalrecur_iterator* impl)
1294 int has_by_minute = (impl->by_ptrs[BY_MINUTE][0]!=ICAL_RECURRENCE_ARRAY_MAX);
1295 int this_frequency = (impl->rule.freq == ICAL_MINUTELY_RECURRENCE);
1297 int end_of_data = 0;
1299 assert(has_by_minute || this_frequency);
1302 if (next_second(impl) == 0){
1306 if( has_by_minute ){
1307 /* Ignore the frequency and use the byrule data */
1309 impl->by_indices[BY_MINUTE]++;
1311 if (impl->by_ptrs[BY_MINUTE][impl->by_indices[BY_MINUTE]]
1312 ==ICAL_RECURRENCE_ARRAY_MAX){
1314 impl->by_indices[BY_MINUTE] = 0;
1320 impl->by_ptrs[BY_MINUTE][impl->by_indices[BY_MINUTE]];
1322 } else if( !has_by_minute && this_frequency ){
1323 /* Compute the next value from the last time and the frequency interval*/
1324 increment_minute(impl,impl->rule.interval);
1327 /* If we have gone through all of the minutes on the BY list, then we
1328 need to move to the next hour */
1330 if(has_by_minute && end_of_data && this_frequency ){
1331 increment_hour(impl,1);
1337 static int next_hour(icalrecur_iterator* impl)
1340 int has_by_hour = (impl->by_ptrs[BY_HOUR][0]!=ICAL_RECURRENCE_ARRAY_MAX);
1341 int this_frequency = (impl->rule.freq == ICAL_HOURLY_RECURRENCE);
1343 int end_of_data = 0;
1345 assert(has_by_hour || this_frequency);
1347 if (next_minute(impl) == 0){
1352 /* Ignore the frequency and use the byrule data */
1354 impl->by_indices[BY_HOUR]++;
1356 if (impl->by_ptrs[BY_HOUR][impl->by_indices[BY_HOUR]]
1357 ==ICAL_RECURRENCE_ARRAY_MAX){
1358 impl->by_indices[BY_HOUR] = 0;
1364 impl->by_ptrs[BY_HOUR][impl->by_indices[BY_HOUR]];
1366 } else if( !has_by_hour && this_frequency ){
1367 /* Compute the next value from the last time and the frequency interval*/
1368 increment_hour(impl,impl->rule.interval);
1372 /* If we have gone through all of the hours on the BY list, then we
1373 need to move to the next day */
1375 if(has_by_hour && end_of_data && this_frequency ){
1376 increment_monthday(impl,1);
1383 static int next_day(icalrecur_iterator* impl)
1386 int has_by_day = (impl->by_ptrs[BY_DAY][0]!=ICAL_RECURRENCE_ARRAY_MAX);
1387 int this_frequency = (impl->rule.freq == ICAL_DAILY_RECURRENCE);
1389 assert(has_by_day || this_frequency);
1391 if (next_hour(impl) == 0){
1395 /* Always increment through the interval, since this routine is not
1396 called by any other next_* routine, and the days that are
1397 excluded will be taken care of by restriction filtering */
1400 increment_monthday(impl,impl->rule.interval);
1402 increment_monthday(impl,1);
1411 static int next_yearday(icalrecur_iterator* impl)
1414 int has_by_yearday = (impl->by_ptrs[BY_YEAR_DAY][0]!=ICAL_RECURRENCE_ARRAY_MAX);
1416 int end_of_data = 0;
1418 assert(has_by_yearday );
1420 if (next_hour(impl) == 0){
1424 impl->by_indices[BY_YEAR_DAY]++;
1426 if (impl->by_ptrs[BY_YEAR_DAY][impl->by_indices[BY_YEAR_DAY]]
1427 ==ICAL_RECURRENCE_ARRAY_MAX){
1428 impl->by_indices[BY_YEAR_DAY] = 0;
1434 impl->by_ptrs[BY_YEAR_DAY][impl->by_indices[BY_YEAR_DAY]];
1436 if(has_by_yearday && end_of_data){
1437 increment_year(impl,1);
1445 /* Returns the day of the month for the current month of t that is the
1446 pos'th instance of the day-of-week dow */
1448 static int nth_weekday(int dow, int pos, struct icaltimetype t){
1450 int days_in_month = icaltime_days_in_month(t.month, t.year);
1451 int end_dow, start_dow;
1456 start_dow = icaltime_day_of_week(t);
1462 /* find month day of first occurrence of dow -- such as the
1463 month day of the first monday */
1465 wd = dow-start_dow+1;
1474 t.day = days_in_month;
1475 end_dow = icaltime_day_of_week(t);
1479 /* find month day of last occurrence of dow -- such as the
1480 month day of the last monday */
1482 wd = (end_dow - dow);
1488 wd = days_in_month - wd;
1496 static int is_day_in_byday(icalrecur_iterator* impl,struct icaltimetype tt){
1500 for(idx = 0; BYDAYPTR[idx] != ICAL_RECURRENCE_ARRAY_MAX; idx++){
1501 int dow = icalrecurrencetype_day_day_of_week(BYDAYPTR[idx]);
1502 int pos = icalrecurrencetype_day_position(BYDAYPTR[idx]);
1503 int this_dow = icaltime_day_of_week(tt);
1505 if( (pos == 0 && dow == this_dow ) || /* Just a dow, like "TU" or "FR" */
1506 (nth_weekday(dow,pos,tt) == tt.day)){ /*pos+wod: "3FR" or -1TU" */
1514 int check_set_position(icalrecur_iterator* impl, int set_pos)
1518 for (i = 0; impl->rule.by_set_pos[i] != ICAL_RECURRENCE_ARRAY_MAX &&
1519 i != ICAL_BY_SETPOS_SIZE; i++){
1520 if (impl->rule.by_set_pos[i] == set_pos) {
1528 static int next_month(icalrecur_iterator* impl)
1532 int this_frequency = (impl->rule.freq == ICAL_MONTHLY_RECURRENCE);
1534 assert( has_by_data(impl,BY_MONTH) || this_frequency);
1536 /* Iterate through the occurrences within a day. If we don't get to
1537 the end of the intra-day data, don't bother going to the next
1540 if (next_hour(impl) == 0){
1541 return data_valid; /* Signal that the data is valid */
1544 /* Now iterate through the occurrences within a month -- by days,
1545 weeks or weekdays. */
1549 * Rules Like: FREQ=MONTHLY;INTERVAL=1;BYDAY=FR;BYMONTHDAY=13
1552 if(has_by_data(impl,BY_DAY) && has_by_data(impl,BY_MONTH_DAY)){
1554 int days_in_month = icaltime_days_in_month(impl->last.month,
1556 /* Iterate through the remaining days in the month and check if
1557 each day is listed in the BY_DAY array and in the BY_MONTHDAY
1558 array. This seems very inneficient, but I think it is the
1559 simplest way to account for both BYDAY=1FR (First friday in
1560 month) and BYDAY=FR ( every friday in month ) */
1562 for(day = impl->last.day+1; day <= days_in_month; day++){
1563 for(idx = 0; BYDAYPTR[idx] != ICAL_RECURRENCE_ARRAY_MAX; idx++){
1564 for(j = 0; BYMDPTR[j]!=ICAL_RECURRENCE_ARRAY_MAX; j++){
1566 icalrecurrencetype_day_day_of_week(BYDAYPTR[idx]);
1567 int pos = icalrecurrencetype_day_position(BYDAYPTR[idx]);
1568 int mday = BYMDPTR[j];
1571 impl->last.day = day;
1572 this_dow = icaltime_day_of_week(impl->last);
1574 if( (pos == 0 && dow == this_dow && mday == day) ||
1575 (nth_weekday(dow,pos,impl->last) == day && mday==day)){
1584 if ( day > days_in_month){
1586 increment_month(impl);
1587 data_valid = 0; /* signal that impl->last is invalid */
1593 * Rules Like: FREQ=MONTHLY;INTERVAL=1;BYDAY=FR
1596 } else if(has_by_data(impl,BY_DAY)){
1597 /* For this case, the weekdays are relative to the
1598 month. BYDAY=FR -> First Friday in month, etc. */
1600 /* This code iterates through the remaining days in the month
1601 and checks if each day is listed in the BY_DAY array. This
1602 seems very inneficient, but I think it is the simplest way to
1603 account for both BYDAY=1FR (First friday in month) and
1604 BYDAY=FR ( every friday in month ) */
1607 int days_in_month = icaltime_days_in_month(impl->last.month,
1609 int set_pos_counter = 0;
1610 int set_pos_total = 0;
1614 assert( BYDAYPTR[0]!=ICAL_RECURRENCE_ARRAY_MAX);
1616 /* Count the past positions for the BYSETPOS calculation */
1617 if(has_by_data(impl,BY_SET_POS)){
1618 int last_day = impl->last.day;
1619 for(day = 1; day <= days_in_month; day++){
1620 impl->last.day = day;
1622 if(is_day_in_byday(impl,impl->last)){
1628 impl->last.day = last_day;
1631 for(day = impl->last.day+1; day <= days_in_month; day++){
1632 impl->last.day = day;
1634 if(is_day_in_byday(impl,impl->last)){
1635 /* If there is no BYSETPOS rule, calculate only by BYDAY
1636 If there is BYSETPOS rule, take into account the occurence
1637 matches with BYDAY */
1638 if(!has_by_data(impl,BY_SET_POS) || check_set_position(impl, ++set_pos_counter)
1639 || check_set_position(impl, set_pos_counter-set_pos_total-1)) {
1648 if ( day > days_in_month){
1650 increment_month(impl);
1652 /* Did moving to the next month put us on a valid date? if
1653 so, note that the new data is valid, if, not, mark it
1656 if(is_day_in_byday(impl,impl->last)){
1657 /* If there is no BYSETPOS rule or BYSETPOS=1, new data is valid */
1658 if(!has_by_data(impl,BY_SET_POS) || check_set_position(impl,1))
1661 data_valid = 0; /* signal that impl->last is invalid */
1667 * Rules Like: FREQ=MONTHLY;COUNT=10;BYMONTHDAY=-3
1670 } else if (has_by_data(impl,BY_MONTH_DAY)) {
1671 int day, days_in_month;
1673 assert( BYMDPTR[0]!=ICAL_RECURRENCE_ARRAY_MAX);
1677 /* Are we at the end of the BYDAY array? */
1678 if (BYMDPTR[BYMDIDX] ==ICAL_RECURRENCE_ARRAY_MAX){
1680 BYMDIDX = 0; /* Reset to 0 */
1681 increment_month(impl);
1684 days_in_month = icaltime_days_in_month(impl->last.month,
1687 day = BYMDPTR[BYMDIDX];
1690 day = icaltime_days_in_month(impl->last.month, impl->last.year) + day + 1;
1693 if ( day > days_in_month){
1696 /* Did moving to the next month put us on a valid date? if
1697 so, note that the new data is valid, if, not, mark it
1700 if(is_day_in_byday(impl,impl->last)){
1703 data_valid = 0; /* signal that impl->last is invalid */
1707 impl->last.day = day;
1712 assert( BYMDPTR[0]!=ICAL_RECURRENCE_ARRAY_MAX);
1713 impl->last.day = BYMDPTR[0];
1715 increment_month(impl);
1717 days_in_month = icaltime_days_in_month(impl->last.month,
1719 if (impl->last.day > days_in_month){
1720 impl->last.day = days_in_month;
1728 static int next_weekday_by_week(icalrecur_iterator* impl)
1731 int end_of_data = 0;
1732 int start_of_week, dow;
1733 struct icaltimetype next;
1735 if (next_hour(impl) == 0){
1739 if(!has_by_data(impl,BY_DAY)){
1743 /* If we get here, we need to step to tne next day */
1746 struct icaltimetype tt = icaltime_null_time();
1747 BYDAYIDX++; /* Look at next elem in BYDAY array */
1749 /* Are we at the end of the BYDAY array? */
1750 if (BYDAYPTR[BYDAYIDX]==ICAL_RECURRENCE_ARRAY_MAX){
1751 BYDAYIDX = 0; /* Reset to 0 */
1752 end_of_data = 1; /* Signal that we're at the end */
1755 /* Add the day of week offset to to the start of this week, and use
1756 that to get the next day */
1757 /* ignore position of dow ("4FR"), only use dow ("FR")*/
1758 dow = icalrecurrencetype_day_day_of_week(BYDAYPTR[BYDAYIDX]);
1759 dow -= impl->rule.week_start; /* Set Sunday to be 0 */
1764 tt.year = impl->last.year;
1765 tt.day = impl->last.day;
1766 tt.month = impl->last.month;
1768 start_of_week = icaltime_start_doy_week(tt, impl->rule.week_start);
1770 if(dow+start_of_week <1){
1771 /* The selected date is in the previous year. */
1777 next = icaltime_from_day_of_year(start_of_week + dow,impl->last.year);
1779 impl->last.day = next.day;
1780 impl->last.month = next.month;
1781 impl->last.year = next.year;
1788 static int next_week(icalrecur_iterator* impl)
1790 int end_of_data = 0;
1792 /* Increment to the next week day, if there is data at a level less than a week */
1793 if (next_weekday_by_week(impl) == 0){
1794 return 0; /* Have not reached end of week yet */
1797 /* If we get here, we have incremented through the entire week, and
1798 can increment to the next week */
1800 if( has_by_data(impl,BY_WEEK_NO)){
1801 /*FREQ=WEEKLY;BYWEEK=20*/
1802 /* Use the Week Number byrule data */
1804 struct icaltimetype t;
1806 impl->by_indices[BY_WEEK_NO]++;
1808 if (impl->by_ptrs[BY_WEEK_NO][impl->by_indices[BY_WEEK_NO]]
1809 ==ICAL_RECURRENCE_ARRAY_MAX){
1810 impl->by_indices[BY_WEEK_NO] = 0;
1816 t.month=1; /* HACK, should be setting to the date of the first week of year*/
1819 week_no = impl->by_ptrs[BY_WEEK_NO][impl->by_indices[BY_WEEK_NO]];
1821 impl->last.day += week_no*7;
1823 impl->last = icaltime_normalize(impl->last);
1826 /* Jump to the next week */
1827 increment_monthday(impl,7*impl->rule.interval);
1830 if( has_by_data(impl,BY_WEEK_NO) && end_of_data){
1831 increment_year(impl,1);
1838 /** Expand the BYDAY rule part and return a pointer to a newly allocated list of days. */
1839 static pvl_list expand_by_day(icalrecur_iterator* impl, int year)
1841 /* Try to calculate each of the occurrences. */
1843 pvl_list days_list = pvl_newlist();
1845 int start_dow, end_dow, end_year_day;
1846 struct icaltimetype tmp = impl->last;
1853 /* Find the day that 1st Jan falls on, 1 (Sun) to 7 (Sat). */
1854 start_dow = icaltime_day_of_week(tmp);
1856 /* Get the last day of the year*/
1862 end_dow = icaltime_day_of_week(tmp);
1863 end_year_day = icaltime_day_of_year(tmp);
1865 for(i = 0; BYDAYPTR[i] != ICAL_RECURRENCE_ARRAY_MAX; i++){
1866 /* This is 1 (Sun) to 7 (Sat). */
1868 icalrecurrencetype_day_day_of_week(BYDAYPTR[i]);
1869 int pos = icalrecurrencetype_day_position(BYDAYPTR[i]);
1872 /* The day was specified without a position -- it is just
1873 a bare day of the week ( BYDAY=SU) so add all of the
1874 days of the year with this day-of-week*/
1875 int doy, tmp_start_doy;
1877 tmp_start_doy = ((dow + 7 - start_dow) % 7) + 1;
1879 for (doy = tmp_start_doy; doy <= end_year_day; doy += 7)
1880 pvl_push(days_list,(void*)(ptrdiff_t)doy);
1882 } else if ( pos > 0) {
1884 /* First occurrence of dow in year */
1885 if( dow >= start_dow) {
1886 first = dow - start_dow + 1;
1888 first = dow - start_dow + 8;
1891 /* Then just multiple the position times 7 to get the pos'th day in the year */
1892 pvl_push(days_list,(void*)(first+ (pos-1) * 7));
1894 } else { /* pos < 0 */
1898 /* last occurrence of dow in year */
1899 if( dow <= end_dow) {
1900 last = end_year_day - end_dow + dow;
1902 last = end_year_day - end_dow + dow - 7;
1905 pvl_push(days_list,(void*)(last - (pos-1) * 7));
1912 /* For INTERVAL=YEARLY, set up the days[] array in the iterator to
1913 list all of the days of the current year that are specified in this
1916 static int expand_year_days(icalrecur_iterator* impl, int year)
1920 struct icaltimetype t;
1923 t = icaltime_null_date();
1925 #define HBD(x) has_by_data(impl,x)
1927 memset(impl->days,ICAL_RECURRENCE_ARRAY_MAX_BYTE,sizeof(impl->days));
1929 /* The flags and the following switch statement select which code
1930 to use to expand the yers days, based on which BY-rules are
1933 flags = (HBD(BY_DAY) ? 1<<BY_DAY : 0) +
1934 (HBD(BY_WEEK_NO) ? 1<<BY_WEEK_NO : 0) +
1935 (HBD(BY_MONTH_DAY) ? 1<<BY_MONTH_DAY : 0) +
1936 (HBD(BY_MONTH) ? 1<<BY_MONTH : 0) +
1937 (HBD(BY_YEAR_DAY) ? 1<<BY_YEAR_DAY : 0);
1940 /* BY_WEEK_NO together with BY_MONTH - may conflict, in this case BY_MONTH wins */
1941 if( (flags & 1<<BY_MONTH) && (flags & 1<<BY_WEEK_NO) ){
1942 int valid_weeks[ICAL_BY_WEEKNO_SIZE];
1944 memset(valid_weeks, 0, sizeof(valid_weeks));
1948 /* calculate valid week numbers */
1949 for(j=0; impl->by_ptrs[BY_MONTH][j]!=ICAL_RECURRENCE_ARRAY_MAX; j++){
1950 int month = impl->by_ptrs[BY_MONTH][j];
1951 int first_week, last_week;
1954 first_week = icaltime_week_number(t);
1955 t.day = icaltime_days_in_month(month,year);
1956 last_week = icaltime_week_number(t);
1957 for(j=first_week; j<last_week; j++) {
1962 /* check valid weeks */
1963 for(i = 0; BYWEEKPTR[i] != ICAL_RECURRENCE_ARRAY_MAX && valid; i++){
1964 int weekno = BYWEEKPTR[i];
1965 if(weekno < ICAL_BY_WEEKNO_SIZE)
1966 valid &= valid_weeks[i]; /* check if the week number is valid */
1968 valid = 0; /* invalid week number */
1971 /* let us make the decision which rule to keep */
1972 if(valid) { /* BYWEEKNO wins */
1973 flags -= 1<<BY_MONTH;
1975 else { /* BYMONTH vins */
1976 flags -= 1<<BY_WEEK_NO;
1985 t.year = impl->last.year;
1987 impl->days[days_index++] = (short)icaltime_day_of_year(t);
1992 /* FREQ=YEARLY; BYMONTH=3,11*/
1994 for(j=0;impl->by_ptrs[BY_MONTH][j]!=ICAL_RECURRENCE_ARRAY_MAX;j++){
1995 int month = impl->by_ptrs[BY_MONTH][j];
2003 doy = icaltime_day_of_year(t);
2005 impl->days[days_index++] = (short)doy;
2011 case 1<<BY_MONTH_DAY: {
2012 /* FREQ=YEARLY; BYMONTHDAY=1,15*/
2013 for(k=0;impl->by_ptrs[BY_MONTH_DAY][k]!=ICAL_RECURRENCE_ARRAY_MAX;k++)
2015 int month_day = impl->by_ptrs[BY_MONTH_DAY][k];
2023 doy = icaltime_day_of_year(t);
2025 impl->days[days_index++] = (short)doy;
2031 case (1<<BY_MONTH_DAY) + (1<<BY_MONTH): {
2032 /* FREQ=YEARLY; BYMONTHDAY=1,15; BYMONTH=10 */
2034 for(j=0;impl->by_ptrs[BY_MONTH][j]!=ICAL_RECURRENCE_ARRAY_MAX;j++){
2035 for(k=0;impl->by_ptrs[BY_MONTH_DAY][k]!=ICAL_RECURRENCE_ARRAY_MAX;k++)
2037 int month = impl->by_ptrs[BY_MONTH][j];
2038 int month_day = impl->by_ptrs[BY_MONTH_DAY][k];
2046 doy = icaltime_day_of_year(t);
2048 impl->days[days_index++] = (short)doy;
2056 case 1<<BY_WEEK_NO: {
2057 /* FREQ=YEARLY; BYWEEKNO=20,50 */
2061 t.day = impl->dtstart.day;
2062 t.month = impl->dtstart.month;
2066 dow = icaltime_day_of_week(t);
2067 /* HACK Not finished */
2069 icalerror_set_errno(ICAL_UNIMPLEMENTED_ERROR);
2074 case (1<<BY_WEEK_NO) + (1<<BY_MONTH_DAY): {
2075 /*FREQ=YEARLY; WEEKNO=20,50; BYMONTH= 6,11 */
2076 icalerror_set_errno(ICAL_UNIMPLEMENTED_ERROR);
2081 /*FREQ=YEARLY; BYDAY=TH,20MO,-10FR*/
2083 pvl_list days = expand_by_day(impl,year);
2086 for(i=pvl_head(days);i!=0;i=pvl_next(i)){
2087 short day = (short)(intptr_t)pvl_data(i);
2088 impl->days[days_index++] = day;
2096 case (1<<BY_DAY)+(1<<BY_MONTH): {
2097 /*FREQ=YEARLY; BYDAY=TH,20MO,-10FR; BYMONTH = 12*/
2100 for(j=0;impl->by_ptrs[BY_MONTH][j]!=ICAL_RECURRENCE_ARRAY_MAX;j++){
2101 int month = impl->by_ptrs[BY_MONTH][j];
2102 int days_in_month = icaltime_days_in_month(month,year);
2103 int first_dow, last_dow, doy_offset;
2110 first_dow = icaltime_day_of_week(t);
2112 /* This holds the day offset used to calculate the day of the year
2113 from the month day. Just add the month day to this. */
2114 doy_offset = icaltime_day_of_year(t) - 1;
2116 t.day = days_in_month;
2117 last_dow = icaltime_day_of_week(t);
2119 if(has_by_data(impl,BY_SET_POS)) {
2120 /*FREQ=YEARLY; BYDAY=TH,20MO,-10FR; BYMONTH = 12; BYSETPOS=1*/
2122 int set_pos_counter = 0;
2123 int set_pos_total = 0;
2124 int by_month_day[ICAL_BY_MONTHDAY_SIZE];
2125 for(day = 1; day <= days_in_month; day++){
2127 if(is_day_in_byday(impl,t))
2128 by_month_day[set_pos_total++] = day;
2130 for(set_pos_counter = 0; set_pos_counter < set_pos_total; set_pos_counter++){
2131 if(check_set_position(impl, set_pos_counter+1) ||
2132 check_set_position(impl, set_pos_counter-set_pos_total))
2133 impl->days[days_index++] = doy_offset + by_month_day[set_pos_counter];
2136 else for(k=0;impl->by_ptrs[BY_DAY][k]!=ICAL_RECURRENCE_ARRAY_MAX;k++){
2137 short day_coded = impl->by_ptrs[BY_DAY][k];
2138 enum icalrecurrencetype_weekday dow =
2139 icalrecurrencetype_day_day_of_week(day_coded);
2140 int pos = icalrecurrencetype_day_position(day_coded);
2141 int first_matching_day, last_matching_day, day, month_day;
2143 /* Calculate the first day in the month with the given weekday,
2144 and the last day. */
2145 first_matching_day = ((dow + 7 - first_dow) % 7) + 1;
2146 last_matching_day = days_in_month - ((last_dow + 7 - dow) % 7);
2149 /* Add all of instances of the weekday within the month. */
2150 for (day = first_matching_day; day <= days_in_month; day += 7)
2151 impl->days[days_index++] = (short)(doy_offset + day);
2153 } else if (pos > 0) {
2154 /* Add the nth instance of the weekday within the month. */
2155 month_day = first_matching_day + (pos - 1) * 7;
2157 if (month_day <= days_in_month)
2158 impl->days[days_index++] = (short)(doy_offset + month_day);
2161 /* Add the -nth instance of the weekday within the month.*/
2162 month_day = last_matching_day + (pos + 1) * 7;
2165 impl->days[days_index++] = (short)(doy_offset + month_day);
2172 case (1<<BY_DAY) + (1<<BY_MONTH_DAY) : {
2173 /*FREQ=YEARLY; BYDAY=TH,20MO,-10FR; BYMONTHDAY=1,15*/
2176 pvl_list days = expand_by_day(impl,year);
2178 for(itr=pvl_head(days);itr!=0;itr=pvl_next(itr)){
2179 short day = (short)(intptr_t)pvl_data(itr);
2180 struct icaltimetype tt;
2182 tt = icaltime_from_day_of_year(day,year);
2184 for(j = 0; BYMDPTR[j]!=ICAL_RECURRENCE_ARRAY_MAX; j++){
2185 int mday = BYMDPTR[j];
2188 impl->days[days_index++] = day;
2199 case (1<<BY_DAY) + (1<<BY_MONTH_DAY) + (1<<BY_MONTH): {
2200 /*FREQ=YEARLY; BYDAY=TH,20MO,-10FR; BYMONTHDAY=10; MYMONTH=6,11*/
2203 pvl_list days = expand_by_day(impl,year);
2205 for(itr=pvl_head(days);itr!=0;itr=pvl_next(itr)){
2206 short day = (short)(intptr_t)pvl_data(itr);
2207 struct icaltimetype tt;
2210 tt = icaltime_from_day_of_year(day,year);
2212 for(i = 0; BYMONPTR[i] != ICAL_RECURRENCE_ARRAY_MAX; i++){
2213 for(j = 0; BYMDPTR[j]!=ICAL_RECURRENCE_ARRAY_MAX; j++){
2214 int mday = BYMDPTR[j];
2215 int month = BYMONPTR[i];
2217 if(tt.month == month && tt.day == mday){
2218 impl->days[days_index++] = day;
2231 case (1<<BY_DAY) + (1<<BY_WEEK_NO) : {
2232 /*FREQ=YEARLY; BYDAY=TH,20MO,-10FR; WEEKNO=20,50*/
2235 pvl_list days = expand_by_day(impl,year);
2237 for(itr=pvl_head(days);itr!=0;itr=pvl_next(itr)){
2238 short day = (short)(intptr_t)pvl_data(itr);
2239 struct icaltimetype tt;
2242 tt = icaltime_from_day_of_year(day,year);
2244 for(i = 0; BYWEEKPTR[i] != ICAL_RECURRENCE_ARRAY_MAX; i++){
2245 int weekno = BYWEEKPTR[i];
2246 int this_weekno = icaltime_week_number(tt);
2247 if(weekno== this_weekno){
2248 impl->days[days_index++] = day;
2258 case (1<<BY_DAY) + (1<<BY_WEEK_NO) + (1<<BY_MONTH_DAY): {
2259 /*FREQ=YEARLY; BYDAY=TH,20MO,-10FR; WEEKNO=20,50; BYMONTHDAY=1,15*/
2260 icalerror_set_errno(ICAL_UNIMPLEMENTED_ERROR);
2264 case 1<<BY_YEAR_DAY: {
2265 for(j=0;impl->by_ptrs[BY_YEAR_DAY][j]!=ICAL_RECURRENCE_ARRAY_MAX;j++){
2266 impl->days[days_index++] = impl->by_ptrs[BY_YEAR_DAY][j];
2272 icalerror_set_errno(ICAL_UNIMPLEMENTED_ERROR);
2282 static int next_year(icalrecur_iterator* impl)
2284 struct icaltimetype next;
2286 if (next_hour(impl) == 0){
2290 if (impl->days[++impl->days_index] == ICAL_RECURRENCE_ARRAY_MAX){
2291 impl->days_index = 0;
2294 increment_year(impl,impl->rule.interval);
2295 expand_year_days(impl,impl->last.year);
2296 if (impl->days[0] != ICAL_RECURRENCE_ARRAY_MAX)
2301 next = icaltime_from_day_of_year(impl->days[impl->days_index], impl->last.year);
2303 impl->last.day = next.day;
2304 impl->last.month = next.month;
2309 int icalrecur_check_rulepart(icalrecur_iterator* impl,
2310 int v, enum byrule byrule)
2314 if(impl->by_ptrs[byrule][0]!=ICAL_RECURRENCE_ARRAY_MAX){
2315 for(itr=0; impl->by_ptrs[byrule][itr]!=ICAL_RECURRENCE_ARRAY_MAX;itr++){
2316 if(impl->by_ptrs[byrule][itr] == v){
2325 static int check_contract_restriction(icalrecur_iterator* impl,
2326 enum byrule byrule, int v)
2330 icalrecurrencetype_frequency freq = impl->rule.freq;
2332 if(impl->by_ptrs[byrule][0]!=ICAL_RECURRENCE_ARRAY_MAX &&
2333 expand_map[freq].map[byrule] == CONTRACT){
2334 for(itr=0; impl->by_ptrs[byrule][itr]!=ICAL_RECURRENCE_ARRAY_MAX;itr++){
2335 if(impl->by_ptrs[byrule][itr] == v){
2343 /* This is not a contracting byrule, or it has no data, so the
2350 static int check_contracting_rules(icalrecur_iterator* impl)
2353 int day_of_week = icaltime_day_of_week(impl->last);
2354 int week_no = icaltime_week_number(impl->last);
2355 int year_day = icaltime_day_of_year(impl->last);
2358 check_contract_restriction(impl,BY_SECOND, impl->last.second) &&
2359 check_contract_restriction(impl,BY_MINUTE, impl->last.minute) &&
2360 check_contract_restriction(impl,BY_HOUR, impl->last.hour) &&
2361 check_contract_restriction(impl,BY_DAY, day_of_week) &&
2362 check_contract_restriction(impl,BY_WEEK_NO, week_no) &&
2363 check_contract_restriction(impl,BY_MONTH_DAY, impl->last.day) &&
2364 check_contract_restriction(impl,BY_MONTH, impl->last.month) &&
2365 check_contract_restriction(impl,BY_YEAR_DAY, year_day) )
2374 struct icaltimetype icalrecur_iterator_next(icalrecur_iterator *impl)
2378 if( !impl || (impl->rule.count!=0 &&impl->occurrence_no >= impl->rule.count) ||
2379 (!icaltime_is_null_time(impl->rule.until) &&
2380 icaltime_compare(impl->last,impl->rule.until) > 0)) {
2381 return icaltime_null_time();
2384 if(impl->occurrence_no == 0
2385 && icaltime_compare(impl->last,impl->dtstart) >= 0){
2387 impl->occurrence_no++;
2393 switch(impl->rule.freq){
2395 case ICAL_SECONDLY_RECURRENCE: {
2399 case ICAL_MINUTELY_RECURRENCE: {
2403 case ICAL_HOURLY_RECURRENCE: {
2407 case ICAL_DAILY_RECURRENCE: {
2411 case ICAL_WEEKLY_RECURRENCE: {
2415 case ICAL_MONTHLY_RECURRENCE: {
2416 valid = next_month(impl);
2419 case ICAL_YEARLY_RECURRENCE:{
2424 icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
2425 return icaltime_null_time();
2429 if(impl->last.year >= 2038 ){
2431 return icaltime_null_time();
2434 } while(!check_contracting_rules(impl)
2435 || icaltime_compare(impl->last,impl->dtstart) < 0
2439 /* Ignore null times and times that are after the until time */
2440 if( !icaltime_is_null_time(impl->rule.until) &&
2441 icaltime_compare(impl->last,impl->rule.until) > 0 ) {
2442 return icaltime_null_time();
2445 impl->occurrence_no++;
2451 /************************** Type Routines **********************/
2454 void icalrecurrencetype_clear(struct icalrecurrencetype *recur)
2456 memset(recur,ICAL_RECURRENCE_ARRAY_MAX_BYTE,
2457 sizeof(struct icalrecurrencetype));
2459 recur->week_start = ICAL_MONDAY_WEEKDAY;
2460 recur->freq = ICAL_NO_RECURRENCE;
2461 recur->interval = 1;
2462 memset(&(recur->until),0,sizeof(struct icaltimetype));
2466 /** The 'day' element of icalrecurrencetype_weekday is encoded to
2467 * allow representation of both the day of the week ( Monday, Tueday),
2468 * but also the Nth day of the week ( First tuesday of the month, last
2469 * thursday of the year) These routines decode the day values.
2471 * The day's position in the period ( Nth-ness) and the numerical
2472 * value of the day are encoded together as: pos*7 + dow
2474 * A position of 0 means 'any' or 'every'
2477 enum icalrecurrencetype_weekday icalrecurrencetype_day_day_of_week(short day)
2482 int icalrecurrencetype_day_position(short day)
2486 wd = icalrecurrencetype_day_day_of_week(day);
2488 pos = (abs(day)-wd)/8 * ((day<0)?-1:1);
2495 /****************** Enumeration Routines ******************/
2497 static struct {icalrecurrencetype_weekday wd; const char * str; }
2499 {ICAL_SUNDAY_WEEKDAY,"SU"},
2500 {ICAL_MONDAY_WEEKDAY,"MO"},
2501 {ICAL_TUESDAY_WEEKDAY,"TU"},
2502 {ICAL_WEDNESDAY_WEEKDAY,"WE"},
2503 {ICAL_THURSDAY_WEEKDAY,"TH"},
2504 {ICAL_FRIDAY_WEEKDAY,"FR"},
2505 {ICAL_SATURDAY_WEEKDAY,"SA"},
2509 const char* icalrecur_weekday_to_string(icalrecurrencetype_weekday kind)
2513 for (i=0; wd_map[i].wd != ICAL_NO_WEEKDAY; i++) {
2514 if ( wd_map[i].wd == kind) {
2515 return wd_map[i].str;
2522 icalrecurrencetype_weekday icalrecur_string_to_weekday(const char* str)
2526 for (i=0; wd_map[i].wd != ICAL_NO_WEEKDAY; i++) {
2527 if ( strcasecmp(str,wd_map[i].str) == 0){
2528 return wd_map[i].wd;
2532 return ICAL_NO_WEEKDAY;
2538 icalrecurrencetype_frequency kind;
2541 {ICAL_SECONDLY_RECURRENCE,"SECONDLY"},
2542 {ICAL_MINUTELY_RECURRENCE,"MINUTELY"},
2543 {ICAL_HOURLY_RECURRENCE,"HOURLY"},
2544 {ICAL_DAILY_RECURRENCE,"DAILY"},
2545 {ICAL_WEEKLY_RECURRENCE,"WEEKLY"},
2546 {ICAL_MONTHLY_RECURRENCE,"MONTHLY"},
2547 {ICAL_YEARLY_RECURRENCE,"YEARLY"},
2548 {ICAL_NO_RECURRENCE,0}
2551 const char* icalrecur_freq_to_string(icalrecurrencetype_frequency kind)
2555 for (i=0; freq_map[i].kind != ICAL_NO_RECURRENCE ; i++) {
2556 if ( freq_map[i].kind == kind ) {
2557 return freq_map[i].str;
2563 icalrecurrencetype_frequency icalrecur_string_to_freq(const char* str)
2567 for (i=0; freq_map[i].kind != ICAL_NO_RECURRENCE ; i++) {
2568 if ( strcasecmp(str,freq_map[i].str) == 0){
2569 return freq_map[i].kind;
2572 return ICAL_NO_RECURRENCE;
2575 /** Fill an array with the 'count' number of occurrences generated by
2576 * the rrule. Note that the times are returned in UTC, but the times
2577 * are calculated in local time. YOu will have to convert the results
2578 * back into local time before using them.
2581 int icalrecur_expand_recurrence(char* rule, time_t start,
2582 int count, time_t* array)
2584 struct icalrecurrencetype recur;
2585 icalrecur_iterator* ritr;
2587 struct icaltimetype icstart, next;
2590 memset(array, 0, count*sizeof(time_t));
2592 icstart = icaltime_from_timet_with_zone(start,0,0);
2594 recur = icalrecurrencetype_from_string(rule);
2595 ritr = icalrecur_iterator_new(recur,icstart);
2597 for(next = icalrecur_iterator_next(ritr);
2598 !icaltime_is_null_time(next) && i < count;
2599 next = icalrecur_iterator_next(ritr)){
2601 tt = icaltime_as_timet(next);
2607 icalrecur_iterator_free(ritr);