1 /*======================================================================
2  FILE: icalrecur.c
3  CREATOR: eric 16 May 2000
4 
5  (C) COPYRIGHT 2000, Eric Busboom <eric@civicknowledge.com>
6 
7  This library is free software; you can redistribute it and/or modify
8  it under the terms of either:
9 
10     The LGPL as published by the Free Software Foundation, version
11     2.1, available at: https://www.gnu.org/licenses/lgpl-2.1.html
12 
13  Or:
14 
15     The Mozilla Public License Version 2.0. You may obtain a copy of
16     the License at https://www.mozilla.org/MPL/
17 ========================================================================*/
18 
19 /**
20   @file icalrecur.c
21   @brief Implementation of routines for dealing with recurring time
22 
23   How this code works:
24 
25   Processing starts when the caller generates a new recurrence
26   iterator via icalrecur_iterator_new(). This routine copies the
27   recurrence rule into the iterator and extracts things like start and
28   end dates. Then, it checks if the rule is legal, using some logic
29   from RFC5545 and some logic that probably should be in RFC5545.
30 
31   If compiled with support for Non-Gregorian Recurrence Rules (RFC7529),
32   icalrecur_iterator_new() verifies that the given RSCALE is supported
33   and configures ICU4C to convert occurrences to/from non-Gregorian dates.
34 
35   Then, icalrecur_iterator_new() re-writes some of the BY*
36   arrays. This involves ( via a call to setup_defaults() ) :
37 
38   1) For BY rule parts with no data ( ie BYSECOND was not specified )
39   copy the corresponding time part from DTSTART into the BY array. (
40   So impl->by_ptrs[BY_SECOND] will then have one element if is
41   originally had none ) This only happens if the BY* rule part data
42   would expand the number of occurrences in the occurrence set. This
43   lets the code ignore DTSTART later on and still use it to get the
44   time parts that were not specified in any other way.
45 
46   2) For the by rule part that are not the same interval as the
47   frequency -- for HOURLY anything but BYHOUR, for instance -- copy the
48   first data element from the rule part into the first occurrence. For
49   example, for "INTERVAL=MONTHLY and BYHOUR=10,30", initialize the
50   first time to be returned to have an hour of 10.
51 
52   Finally, for INTERVAL=YEARLY, the routine expands the rule to get
53   all of the days specified in the rule. The code will do this for
54   each new year, and this is the first expansion. This is a special
55   case for the yearly interval; no other frequency gets expanded this
56   way. The yearly interval is the most complex, so some special
57   processing is required.
58 
59   After creating a new iterator, the caller will make successive calls
60   to icalrecur_iterator_next() to get the next time specified by the
61   rule. The main part of this routine is a switch on the frequency of
62   the rule. Each different frequency is handled by a different
63   routine.
64 
65   For example, next_hour handles the case of INTERVAL=HOURLY, and it
66   is called by other routines to get the next hour. First, the routine
67   tries to get the next minute part of a time with a call to
68   next_minute(). If next_minute() returns 1, it has reached the end of
69   its data, usually the last element of the BYMINUTE array. Then, if
70   there is data in the BYHOUR array, the routine changes the hour to
71   the next one in the array. If INTERVAL=HOURLY, the routine advances
72   the hour by the interval.
73 
74   If the routine used the last hour in the BYHOUR array, and the
75   INTERVAL=HOURLY, then the routine calls increment_monthday() to set
76   the next month day. The increment_* routines may call higher routine
77   to increment the month or year also.
78 
79   The code for INTERVAL=DAILY is handled by next_day(). First, the
80   routine tries to get the next hour part of a time with a call to
81   next_hour. If next_hour() returns 1, it has reached the end of its
82   data, usually the last element of the BYHOUR array. This means that
83   next_day() should increment the time to the next day. If FREQUENCY==DAILY,
84   the routine increments the day by the interval; otherwise, it
85   increments the day by 1.
86 
87   Next_day() differs from next_hour because it does not use the BYDAY
88   array to select an appropriate day. Instead, it returns every day (
89   incrementing by 1 if the frequency is not DAILY with INTERVAL!=1)
90   Any days that are not specified in an non-empty BYDAY array are
91   filtered out later.
92 
93   Generally, the flow of these routine is for a next_* call a next_*
94   routine of a lower interval ( next_day calls next_hour) and then to
95   possibly call an increment_* routine of an equal or higher
96   interval. ( next_day calls increment_monthday() )
97 
98   When the call to the original next_* routine returns,
99   icalrecur_iterator_next() will check the returned data against other
100   BYrule parts to determine if is should be excluded by calling
101   check_contracting_rules. Generally, a contracting rule is any with a
102   larger time span than the interval. For instance, if
103   INTERVAL=DAILY, BYMONTH is a contracting rule part.
104 
105   Check_contracting_rules() uses icalrecur_check_rulepart() to do its
106   work. icalrecur_check_rulepart() uses expand_map[] to determine if a rule
107   is contracting, and if it is, and if the BY rule part has some data,
108   then the routine checks if the value of a component of the time is
109   part of the byrule part. For instance, for "INTERVAL=DAILY;
110   BYMONTH=6,10", icalrecur_check_rulepart() would check that the time value
111   given to it has a month of either 6 or 10.
112 
113   Finally, icalrecur_iterator_next() does a few other checks on the
114   time value, and if it passes, it returns the time.
115 
116   A note about the end_of_data flag. The flag indicates that the
117   routine is at the end of its data -- the last BY rule if the routine
118   is using by rules, or the last day of the week/month/year/etc if
119   not.
120 
121   This flag is usually set early in a next_* routine and returned in
122   the end. The way it is used allows the next_* routine to set the
123   last time back to the first element in a BYxx rule, and then signal
124   to the higher level routine to increment the next higher level. For
125   instance. WITH FREQ=MONTHLY;BYDAY=TU,FR, After next_weekday_by_month
126   runs though both TU and FR, it sets the week day back to TU and sets
127   end_of_data to 1x. This signals next_month to increment the month.
128 
129  ======================================================================*/
130 
131 #ifdef HAVE_CONFIG_H
132 #include <config.h>
133 #endif
134 
135 #include "icalrecur.h"
136 #include "icalerror.h"
137 #include "icalmemory.h"
138 #include "icaltimezone.h"
139 #include "icalvalue.h"  /* for print_date[time]_to_string() */
140 
141 #include <ctype.h>
142 #include <stddef.h>     /* For offsetof() macro */
143 #include <stdlib.h>
144 
145 #if defined(HAVE_LIBICU)
146 #include <unicode/ucal.h>
147 #include <unicode/ustring.h>
148 #define RSCALE_IS_SUPPORTED 1
149 #else
150 #define RSCALE_IS_SUPPORTED 0
151 
152 /* The maximums below are based on Gregorian leap years */
153 #undef ICAL_BY_MONTH_SIZE
154 #undef ICAL_BY_WEEKNO_SIZE
155 #undef ICAL_BY_YEARDAY_SIZE
156 #define ICAL_BY_MONTH_SIZE      13      /* 1 to 12 */
157 #define ICAL_BY_WEEKNO_SIZE     54      /* 1 to 53 */
158 #define ICAL_BY_YEARDAY_SIZE    367     /* 1 to 366 */
159 #endif
160 
161 #if (SIZEOF_TIME_T > 4)
162 /** Arbitrarily go up to 1000th anniversary of Gregorian calendar, since
163     64-bit time_t values get us up to the tm_year limit of 2+ billion years. */
164 #define MAX_TIME_T_YEAR 2582
165 #else
166 /** This is the last year we will go up to, since 32-bit time_t values
167    only go up to the start of 2038. */
168 #define MAX_TIME_T_YEAR 2037
169 #endif
170 
171 #define BYDAYIDX impl->by_indices[BY_DAY]
172 #define BYDAYPTR impl->by_ptrs[BY_DAY]
173 
174 #define BYMONIDX impl->by_indices[BY_MONTH]
175 #define BYMONPTR impl->by_ptrs[BY_MONTH]
176 
177 #define BYMDIDX impl->by_indices[BY_MONTH_DAY]
178 #define BYMDPTR impl->by_ptrs[BY_MONTH_DAY]
179 
180 #define BYYDIDX impl->by_indices[BY_YEAR_DAY]
181 #define BYYDPTR impl->by_ptrs[BY_YEAR_DAY]
182 
183 #define BYWEEKIDX impl->by_indices[BY_WEEK_NO]
184 #define BYWEEKPTR impl->by_ptrs[BY_WEEK_NO]
185 
186 #define LEAP_MONTH 0x1000
187 
icalrecurrencetype_rscale_is_supported(void)188 int icalrecurrencetype_rscale_is_supported(void)
189 {
190     return RSCALE_IS_SUPPORTED;
191 }
192 
193 /****************** Enumeration Routines ******************/
194 
195 static struct freq_map
196 {
197     icalrecurrencetype_frequency kind;
198     const char *str;
199 } freq_map[] = {
200     {ICAL_SECONDLY_RECURRENCE, "SECONDLY"},
201     {ICAL_MINUTELY_RECURRENCE, "MINUTELY"},
202     {ICAL_HOURLY_RECURRENCE, "HOURLY"},
203     {ICAL_DAILY_RECURRENCE, "DAILY"},
204     {ICAL_WEEKLY_RECURRENCE, "WEEKLY"},
205     {ICAL_MONTHLY_RECURRENCE, "MONTHLY"},
206     {ICAL_YEARLY_RECURRENCE, "YEARLY"},
207     {ICAL_NO_RECURRENCE, 0}
208 };
209 
icalrecur_string_to_freq(const char * str)210 icalrecurrencetype_frequency icalrecur_string_to_freq(const char *str)
211 {
212     int i;
213 
214     for (i = 0; freq_map[i].kind != ICAL_NO_RECURRENCE; i++) {
215         if (strcasecmp(str, freq_map[i].str) == 0) {
216             return freq_map[i].kind;
217         }
218     }
219     return ICAL_NO_RECURRENCE;
220 }
221 
icalrecur_freq_to_string(icalrecurrencetype_frequency kind)222 const char *icalrecur_freq_to_string(icalrecurrencetype_frequency kind)
223 {
224     int i;
225 
226     for (i = 0; freq_map[i].kind != ICAL_NO_RECURRENCE; i++) {
227         if (freq_map[i].kind == kind) {
228             return freq_map[i].str;
229         }
230     }
231     return 0;
232 }
233 
234 static struct skip_map
235 {
236     icalrecurrencetype_skip kind;
237     const char *str;
238 } skip_map[] = {
239     {ICAL_SKIP_BACKWARD, "BACKWARD"},
240     {ICAL_SKIP_FORWARD, "FORWARD"},
241     {ICAL_SKIP_OMIT, "OMIT"},
242     {ICAL_SKIP_UNDEFINED, 0}
243 };
244 
icalrecur_string_to_skip(const char * str)245 icalrecurrencetype_skip icalrecur_string_to_skip(const char *str)
246 {
247     int i;
248 
249     for (i = 0; skip_map[i].kind != ICAL_SKIP_UNDEFINED; i++) {
250         if (strcasecmp(str, skip_map[i].str) == 0) {
251             return skip_map[i].kind;
252         }
253     }
254     return ICAL_SKIP_UNDEFINED;
255 }
256 
icalrecur_skip_to_string(icalrecurrencetype_skip kind)257 const char *icalrecur_skip_to_string(icalrecurrencetype_skip kind)
258 {
259     int i;
260 
261     for (i = 0; skip_map[i].kind != ICAL_SKIP_UNDEFINED; i++) {
262         if (skip_map[i].kind == kind) {
263             return skip_map[i].str;
264         }
265     }
266     return 0;
267 }
268 
269 static struct wd_map
270 {
271     icalrecurrencetype_weekday wd;
272     const char *str;
273 } wd_map[] = {
274     {ICAL_SUNDAY_WEEKDAY, "SU"},
275     {ICAL_MONDAY_WEEKDAY, "MO"},
276     {ICAL_TUESDAY_WEEKDAY, "TU"},
277     {ICAL_WEDNESDAY_WEEKDAY, "WE"},
278     {ICAL_THURSDAY_WEEKDAY, "TH"},
279     {ICAL_FRIDAY_WEEKDAY, "FR"},
280     {ICAL_SATURDAY_WEEKDAY, "SA"},
281     {ICAL_NO_WEEKDAY, 0}
282 };
283 
icalrecur_weekday_to_string(icalrecurrencetype_weekday kind)284 const char *icalrecur_weekday_to_string(icalrecurrencetype_weekday kind)
285 {
286     int i;
287 
288     for (i = 0; wd_map[i].wd != ICAL_NO_WEEKDAY; i++) {
289         if (wd_map[i].wd == kind) {
290             return wd_map[i].str;
291         }
292     }
293 
294     return 0;
295 }
296 
icalrecur_string_to_weekday(const char * str)297 icalrecurrencetype_weekday icalrecur_string_to_weekday(const char *str)
298 {
299     int i;
300 
301     for (i = 0; wd_map[i].wd != ICAL_NO_WEEKDAY; i++) {
302         if (strcasecmp(str, wd_map[i].str) == 0) {
303             return wd_map[i].wd;
304         }
305     }
306 
307     return ICAL_NO_WEEKDAY;
308 }
309 
310 /*********************** Rule parsing routines ************************/
311 
312 struct icalrecur_parser
313 {
314     const char *rule;
315     char *copy;
316     char *this_clause;
317     char *next_clause;
318 
319     struct icalrecurrencetype rt;
320 };
321 
icalrecur_first_clause(struct icalrecur_parser * parser)322 static const char *icalrecur_first_clause(struct icalrecur_parser *parser)
323 {
324     char *idx;
325 
326     parser->this_clause = parser->copy;
327 
328     idx = strchr(parser->this_clause, ';');
329 
330     if (idx == 0) {
331         parser->next_clause = 0;
332         return 0;
333     }
334 
335     *idx = 0;
336     idx++;
337     parser->next_clause = idx;
338 
339     return parser->this_clause;
340 }
341 
icalrecur_next_clause(struct icalrecur_parser * parser)342 static const char *icalrecur_next_clause(struct icalrecur_parser *parser)
343 {
344     char *idx;
345 
346     parser->this_clause = parser->next_clause;
347 
348     if (parser->this_clause == 0) {
349         return 0;
350     }
351 
352     idx = strchr(parser->this_clause, ';');
353 
354     if (idx == 0) {
355         parser->next_clause = 0;
356     } else {
357 
358         *idx = 0;
359         idx++;
360         parser->next_clause = idx;
361     }
362 
363     return parser->this_clause;
364 }
365 
icalrecur_clause_name_and_value(struct icalrecur_parser * parser,char ** name,char ** value)366 static void icalrecur_clause_name_and_value(struct icalrecur_parser *parser,
367                                             char **name, char **value)
368 {
369     char *idx;
370 
371     *name = parser->this_clause;
372 
373     idx = strchr(parser->this_clause, '=');
374 
375     if (idx == 0) {
376         *name = 0;
377         *value = 0;
378         return;
379     }
380 
381     *idx = 0;
382     idx++;
383     *value = idx;
384 }
385 
386 /* returns < 0 if a parsing problem:
387    -2 if an RSCALE rule is encountered yet we don't RSCALE support enabled
388    -1 for all other parsing problems
389 */
icalrecur_add_byrules(struct icalrecur_parser * parser,short * array,int min,int size,char * vals)390 static int icalrecur_add_byrules(struct icalrecur_parser *parser, short *array,
391                                  int min, int size, char *vals)
392 {
393     char *t, *n;
394     int i = 0;
395     int v;
396     int max = size - (min == 0);
397 
398     n = vals;
399 
400     while (n != 0) {
401 
402         if (i == size) {
403             return -1;
404         }
405 
406         t = n;
407 
408         n = strchr(t, ',');
409 
410         if (n != 0) {
411             *n = 0;
412             n++;
413         }
414 
415         v = strtol(t, &t, 10);
416 
417         /* Sanity check value */
418         if (v < 0) {
419             if (min >= 0 || v <= -max) {
420                 return -1;
421             }
422         } else if (v > 0) {
423             if (v >= max) {
424                 return -1;
425             }
426         } else if (min) {
427             return -1;
428         }
429 
430         if (*t) {
431             /* Check for leap month suffix (RSCALE only) */
432             if (array == parser->rt.by_month && strcmp(t, "L") == 0) {
433                 if (icalrecurrencetype_rscale_is_supported()) {
434                     /* The "L" suffix in a BYMONTH recur-rule-part
435                        is encoded by setting a high-order bit */
436                     v |= LEAP_MONTH;
437                 } else {
438                     return -2;
439                 }
440             } else {
441                 return -1;
442             }
443         }
444 
445         array[i++] = (short)v;
446         array[i] = ICAL_RECURRENCE_ARRAY_MAX;
447     }
448 
449     return 0;
450 }
451 
452 /*
453  * Days in the BYDAY rule are expected by the code to be sorted, and while
454  * this may be the common case, the RFC doesn't actually mandate it. This
455  * function sorts the days taking into account the first day of week.
456  */
sort_bydayrules(struct icalrecur_parser * parser)457 static void sort_bydayrules(struct icalrecur_parser *parser)
458 {
459     short *array;
460     int week_start, one, two, i, j;
461 
462     array = parser->rt.by_day;
463     week_start = parser->rt.week_start;
464 
465     for (i = 0;
466          i < ICAL_BY_DAY_SIZE && array[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) {
467         for (j = 0; j < i; j++) {
468             one = icalrecurrencetype_day_day_of_week(array[j]) - week_start;
469             if (one < 0) {
470                 one += 7;
471             }
472             two = icalrecurrencetype_day_day_of_week(array[i]) - week_start;
473             if (two < 0) {
474                 two += 7;
475             }
476 
477             if (one > two) {
478                 short tmp = array[j];
479 
480                 array[j] = array[i];
481                 array[i] = tmp;
482             }
483         }
484     }
485 }
486 
icalrecur_add_bydayrules(struct icalrecur_parser * parser,const char * vals)487 static int icalrecur_add_bydayrules(struct icalrecur_parser *parser,
488                                     const char *vals)
489 {
490     char *t, *n;
491     int i = 0;
492     short *array = parser->rt.by_day;
493     char *vals_copy;
494 
495     vals_copy = icalmemory_strdup(vals);
496     n = vals_copy;
497 
498     while (n != 0) {
499         int sign = 1;
500         signed char weekno;  /* note: Novell/Groupwise sends BYDAY=255SU,
501                                 so we fit in a signed char to get -1 SU for last Sun */
502         icalrecurrencetype_weekday wd;
503 
504         if (i == ICAL_BY_DAY_SIZE) {
505             free(vals_copy);
506             return -1;
507         }
508 
509         t = n;
510 
511         n = strchr(t, ',');
512 
513         if (n != 0) {
514             *n = 0;
515             n++;
516         }
517 
518         /* Get Optional weekno */
519         weekno = (signed char)strtol(t, &t, 10);
520         if (weekno < 0) {
521             weekno = -weekno;
522             sign = -1;
523         }
524 
525         /* Outlook/Exchange generate "BYDAY=MO, FR" and "BYDAY=2 TH".
526          * Cope with that.
527          */
528         if (*t == ' ') {
529             t++;
530         }
531 
532         wd = icalrecur_string_to_weekday(t);
533 
534         /* Sanity check value */
535         if (wd == ICAL_NO_WEEKDAY || weekno >= ICAL_BY_WEEKNO_SIZE) {
536             free(vals_copy);
537             return -1;
538         }
539 
540         array[i++] = (short)(sign * (wd + 8 * weekno));
541         array[i] = ICAL_RECURRENCE_ARRAY_MAX;
542     }
543 
544     free(vals_copy);
545 
546     sort_bydayrules(parser);
547 
548     return 0;
549 }
550 
icalrecurrencetype_from_string(const char * str)551 struct icalrecurrencetype icalrecurrencetype_from_string(const char *str)
552 {
553     struct icalrecur_parser parser;
554 
555     memset(&parser, 0, sizeof(parser));
556     icalrecurrencetype_clear(&parser.rt);
557 
558     icalerror_check_arg_re(str != 0, "str", parser.rt);
559 
560     /* Set up the parser struct */
561     parser.rule = str;
562     parser.copy = icalmemory_strdup(parser.rule);
563     parser.this_clause = parser.copy;
564 
565     if (parser.copy == 0) {
566         icalerror_set_errno(ICAL_NEWFAILED_ERROR);
567         return parser.rt;
568     }
569 
570     /* Loop through all of the clauses */
571     for (icalrecur_first_clause(&parser);
572          parser.this_clause != 0; icalrecur_next_clause(&parser)) {
573         char *name, *value;
574         int r = 0;
575 
576         icalrecur_clause_name_and_value(&parser, &name, &value);
577 
578         if (name == 0) {
579             if (strlen(parser.this_clause) > 0) {
580                 r = -1;
581             } else {
582                 /* Hit an empty name/value pair,
583                    but we're also at the end of the string.
584                    This was probably a trailing semicolon with no data
585                    (e.g. "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO;")
586                 */
587                 break;
588             }
589         } else if (strcasecmp(name, "FREQ") == 0) {
590             parser.rt.freq = icalrecur_string_to_freq(value);
591             if (parser.rt.freq == ICAL_NO_RECURRENCE) {
592                 r = -1;
593             }
594         } else if (strcasecmp(name, "RSCALE") == 0) {
595             if (icalrecurrencetype_rscale_is_supported()) {
596                 parser.rt.rscale = icalmemory_strdup(value);
597             } else {
598                 r = -2; // RSCALE clause name without RSCALE support
599             }
600         } else if (strcasecmp(name, "SKIP") == 0) {
601             if (icalrecurrencetype_rscale_is_supported()) {
602                 parser.rt.skip = icalrecur_string_to_skip(value);
603                 if (parser.rt.skip == ICAL_SKIP_UNDEFINED) {
604                     r = -1;
605                 }
606             } else {
607                 r = -2; // RSCALE clause name without RSCALE support
608             }
609         } else if (strcasecmp(name, "COUNT") == 0) {
610             parser.rt.count = atoi(value);
611             if (parser.rt.count < 1) {
612                 /* don't allow count to be less than 1 */
613                 r = -1;
614             } else if (!icaltime_is_null_time(parser.rt.until)) {
615                 /* don't allow both count and until */
616                 r = -1;
617             }
618         } else if (strcasecmp(name, "UNTIL") == 0) {
619             parser.rt.until = icaltime_from_string(value);
620             if (icaltime_is_null_time(parser.rt.until)) {
621                 r = -1;
622             } else if (parser.rt.count > 0) {
623                /* don't allow both count and until */
624                r = -1;
625             }
626         } else if (strcasecmp(name, "INTERVAL") == 0) {
627             parser.rt.interval = (short)atoi(value);
628             /* don't allow an interval to be less than 1
629                (RFC specifies an interval must be a positive integer) */
630             if (parser.rt.interval < 1) r = -1;
631         } else if (strcasecmp(name, "WKST") == 0) {
632             parser.rt.week_start = icalrecur_string_to_weekday(value);
633             if (parser.rt.week_start == ICAL_NO_WEEKDAY) {
634                 r = -1;
635             } else {
636                 sort_bydayrules(&parser);
637             }
638         } else if (strcasecmp(name, "BYSECOND") == 0) {
639             r = icalrecur_add_byrules(&parser, parser.rt.by_second,
640                                       0, ICAL_BY_SECOND_SIZE, value);
641         } else if (strcasecmp(name, "BYMINUTE") == 0) {
642             r = icalrecur_add_byrules(&parser, parser.rt.by_minute,
643                                       0, ICAL_BY_MINUTE_SIZE, value);
644         } else if (strcasecmp(name, "BYHOUR") == 0) {
645             r = icalrecur_add_byrules(&parser, parser.rt.by_hour,
646                                       0, ICAL_BY_HOUR_SIZE, value);
647         } else if (strcasecmp(name, "BYDAY") == 0) {
648             r = icalrecur_add_bydayrules(&parser, value);
649         } else if (strcasecmp(name, "BYMONTHDAY") == 0) {
650             r = icalrecur_add_byrules(&parser, parser.rt.by_month_day,
651                                       -1, ICAL_BY_MONTHDAY_SIZE, value);
652         } else if (strcasecmp(name, "BYYEARDAY") == 0) {
653             r = icalrecur_add_byrules(&parser, parser.rt.by_year_day,
654                                       -1, ICAL_BY_YEARDAY_SIZE, value);
655         } else if (strcasecmp(name, "BYWEEKNO") == 0) {
656             r = icalrecur_add_byrules(&parser, parser.rt.by_week_no,
657                                       -1, ICAL_BY_WEEKNO_SIZE, value);
658         } else if (strcasecmp(name, "BYMONTH") == 0) {
659             r = icalrecur_add_byrules(&parser, parser.rt.by_month,
660                                       1, ICAL_BY_MONTH_SIZE, value);
661         } else if (strcasecmp(name, "BYSETPOS") == 0) {
662             r = icalrecur_add_byrules(&parser, parser.rt.by_set_pos,
663                                       -1, ICAL_BY_SETPOS_SIZE, value);
664         } else {
665             r = -1;
666         }
667 
668         if (r) {
669             /* Note: silently ignore when we have an RSCALE rule, yet don't have RSCALE support.
670                The magic value "-2" indicates when that happens.
671             */
672             if (r != -2) {
673                 icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
674             }
675             if (parser.rt.rscale) {
676                 free(parser.rt.rscale);
677             }
678             icalrecurrencetype_clear(&parser.rt);
679             break;
680         }
681     }
682 
683     free(parser.copy);
684 
685     return parser.rt;
686 }
687 
688 static struct recur_map
689 {
690     const char *str;
691     size_t offset;
692     int limit;
693 } recur_map[] = {
694     { ";BYSECOND=", offsetof(struct icalrecurrencetype, by_second),
695       ICAL_BY_SECOND_SIZE - 1 },
696     { ";BYMINUTE=", offsetof(struct icalrecurrencetype, by_minute),
697       ICAL_BY_MINUTE_SIZE - 1 },
698     { ";BYHOUR=", offsetof(struct icalrecurrencetype, by_hour),
699       ICAL_BY_HOUR_SIZE - 1 },
700     { ";BYDAY=", offsetof(struct icalrecurrencetype, by_day),
701       ICAL_BY_DAY_SIZE - 1 },
702     { ";BYMONTHDAY=", offsetof(struct icalrecurrencetype, by_month_day),
703       ICAL_BY_MONTHDAY_SIZE - 1 },
704     { ";BYYEARDAY=", offsetof(struct icalrecurrencetype, by_year_day),
705       ICAL_BY_YEARDAY_SIZE - 1 },
706     { ";BYWEEKNO=", offsetof(struct icalrecurrencetype, by_week_no),
707       ICAL_BY_WEEKNO_SIZE - 1 },
708     { ";BYMONTH=", offsetof(struct icalrecurrencetype, by_month),
709       ICAL_BY_MONTH_SIZE - 1 },
710     { ";BYSETPOS=", offsetof(struct icalrecurrencetype, by_set_pos),
711       ICAL_BY_SETPOS_SIZE - 1 },
712     { 0, 0, 0 }
713 };
714 
icalrecurrencetype_as_string(struct icalrecurrencetype * recur)715 char *icalrecurrencetype_as_string(struct icalrecurrencetype *recur)
716 {
717     char *buf;
718 
719     buf = icalrecurrencetype_as_string_r(recur);
720     icalmemory_add_tmp_buffer(buf);
721     return buf;
722 }
723 
icalrecurrencetype_as_string_r(struct icalrecurrencetype * recur)724 char *icalrecurrencetype_as_string_r(struct icalrecurrencetype *recur)
725 {
726     char *str;
727     char *str_p;
728     size_t buf_sz = 200;
729     char temp[20];
730     int i, j;
731 
732     if (recur == 0 || recur->freq == ICAL_NO_RECURRENCE) {
733         return 0;
734     }
735 
736     str = (char *)icalmemory_new_buffer(buf_sz);
737     str_p = str;
738 
739     if (recur->rscale != 0) {
740         icalmemory_append_string(&str, &str_p, &buf_sz, "RSCALE=");
741         icalmemory_append_string(&str, &str_p, &buf_sz, recur->rscale);
742         icalmemory_append_char(&str, &str_p, &buf_sz, ';');
743     }
744 
745     icalmemory_append_string(&str, &str_p, &buf_sz, "FREQ=");
746     icalmemory_append_string(&str, &str_p, &buf_sz,
747                              icalrecur_freq_to_string(recur->freq));
748 
749     if (recur->until.year != 0) {
750 
751         temp[0] = 0;
752         if (recur->until.is_date) {
753             print_date_to_string(temp, &(recur->until));
754         } else {
755             print_datetime_to_string(temp, &(recur->until));
756         }
757 
758         icalmemory_append_string(&str, &str_p, &buf_sz, ";UNTIL=");
759         icalmemory_append_string(&str, &str_p, &buf_sz, temp);
760     }
761 
762     else if (recur->count != 0) {
763         snprintf(temp, sizeof(temp), "%d", recur->count);
764         icalmemory_append_string(&str, &str_p, &buf_sz, ";COUNT=");
765         icalmemory_append_string(&str, &str_p, &buf_sz, temp);
766     }
767 
768     if (recur->interval != 1) {
769         snprintf(temp, sizeof(temp), "%d", recur->interval);
770         icalmemory_append_string(&str, &str_p, &buf_sz, ";INTERVAL=");
771         icalmemory_append_string(&str, &str_p, &buf_sz, temp);
772     }
773 
774     for (j = 0; recur_map[j].str != 0; j++) {
775         short *array = (short *)(recur_map[j].offset + (size_t) recur);
776         int limit = recur_map[j].limit;
777 
778         /* Skip unused arrays */
779         if (array[0] != ICAL_RECURRENCE_ARRAY_MAX) {
780 
781             icalmemory_append_string(&str, &str_p, &buf_sz, recur_map[j].str);
782 
783             for (i = 0;
784                  i < limit && array[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) {
785                 if (j == 3) { /* BYDAY */
786                     int pos = icalrecurrencetype_day_position(array[i]);
787                     int dow = icalrecurrencetype_day_day_of_week(array[i]);
788                     const char *daystr = icalrecur_weekday_to_string(dow);
789 
790                     if (pos == 0) {
791                         icalmemory_append_string(&str, &str_p, &buf_sz, daystr);
792                     } else {
793                         snprintf(temp, sizeof(temp), "%d%s", pos, daystr);
794                         icalmemory_append_string(&str, &str_p, &buf_sz, temp);
795                     }
796 
797                 } else if (j == 7 /* BYMONTH */ &&
798                            icalrecurrencetype_month_is_leap(array[i])) {
799                     snprintf(temp, sizeof(temp), "%dL",
800                              icalrecurrencetype_month_month(array[i]));
801                     icalmemory_append_string(&str, &str_p, &buf_sz, temp);
802                 } else {
803                     snprintf(temp, sizeof(temp), "%d", array[i]);
804                     icalmemory_append_string(&str, &str_p, &buf_sz, temp);
805                 }
806 
807                 if ((i + 1) < limit &&
808                     array[i + 1] != ICAL_RECURRENCE_ARRAY_MAX) {
809                     icalmemory_append_char(&str, &str_p, &buf_sz, ',');
810                 }
811             }
812         }
813     }
814 
815     /* Monday is the default, so no need to write that out */
816     if (recur->week_start != ICAL_MONDAY_WEEKDAY &&
817         recur->week_start != ICAL_NO_WEEKDAY) {
818         int dow = icalrecurrencetype_day_day_of_week(recur->week_start);
819         const char *daystr = icalrecur_weekday_to_string(dow);
820         icalmemory_append_string(&str, &str_p, &buf_sz, ";WKST=");
821         icalmemory_append_string(&str, &str_p, &buf_sz, daystr);
822     }
823 
824     if (recur->rscale != 0 && recur->skip != ICAL_SKIP_OMIT) {
825         const char *skipstr = icalrecur_skip_to_string(recur->skip);
826         icalmemory_append_string(&str, &str_p, &buf_sz, ";SKIP=");
827         icalmemory_append_string(&str, &str_p, &buf_sz, skipstr);
828     }
829 
830     return str;
831 }
832 
833 /************************* occurrence iteration routines ******************/
834 
835 enum byrule
836 {
837     NO_CONTRACTION = -1,
838     BY_SECOND = 0,
839     BY_MINUTE = 1,
840     BY_HOUR = 2,
841     BY_DAY = 3,
842     BY_MONTH_DAY = 4,
843     BY_YEAR_DAY = 5,
844     BY_WEEK_NO = 6,
845     BY_MONTH = 7,
846     BY_SET_POS
847 };
848 
849 /* Number of bits in an unsigned long */
850 #define BITS_PER_LONG      (8 * sizeof(unsigned long))
851 
852 /* Number of longs in mask of n bits */
853 #define LONGS_PER_BITS(n)  ((n + BITS_PER_LONG -1 ) / BITS_PER_LONG)
854 
855 #define ICAL_YEARDAYS_MASK_SIZE    (ICAL_BY_YEARDAY_SIZE + 7)
856 #define ICAL_YEARDAYS_MASK_OFFSET  4
857 
858 struct icalrecur_iterator_impl
859 {
860     struct icaltimetype dtstart;     /* copy of DTSTART: to fill in defaults */
861     struct icalrecurrencetype rule;  /* copy of RRULE */
862 
863     struct icaltimetype rstart;      /* DTSTART in RSCALE  */
864     struct icaltimetype istart;      /* Gregorian start time for iterator */
865     struct icaltimetype last;        /* last time returned from iterator */
866     int occurrence_no;               /* number of steps made on the iterator */
867 
868 #if defined(HAVE_LIBICU)
869     UCalendar *greg;                 /* Gregorian calendar */
870     UCalendar *rscale;               /* RSCALE calendar    */
871 #endif
872 
873     struct icaltimetype period_start;  /* Start date of monthly/yearly period */
874 
875     /* days[] is a bitmask of year days.  A bit value of 1 marks an occurrence.
876        The size of the bitmask is 7 + max days in year to accommodate full first
877        and last weeks of the year: up to 3 days in previous year and
878        up to 4 days in following year.  As a result, the days are offset by 4:
879        bit 0 is day -3 (3rd last day of previous year) and bit 4 is day 1
880        of the current year.  Days in the following year use higher day numbers,
881        e.g. day 367 is day 1 or 2 of following year depending on whether the
882        current year is a leap year.
883 
884        days_index is the day of year of the next occurrence,
885        with a range of -3 to 4 + days in year.
886     */
887     unsigned long days[LONGS_PER_BITS(ICAL_YEARDAYS_MASK_SIZE)];
888     short days_index;
889 
890     enum byrule byrule;
891     short by_indices[9];
892     short orig_data[9]; /**< 1 if there was data in the byrule */
893 
894     short *by_ptrs[9]; /**< Pointers into the by_* array elements of the rule */
895 
896 };
897 
daysmask_clearall(unsigned long mask[])898 static void daysmask_clearall(unsigned long mask[])
899 {
900     memset(mask, 0,
901            sizeof(unsigned long) * LONGS_PER_BITS(ICAL_YEARDAYS_MASK_SIZE));
902 }
903 
daysmask_setbit(unsigned long mask[],short n,int v)904 static void daysmask_setbit(unsigned long mask[], short n, int v)
905 {
906     n += ICAL_YEARDAYS_MASK_OFFSET;
907     if (v) {
908         mask[n / BITS_PER_LONG] |= (1UL << (n % BITS_PER_LONG));
909     } else {
910         mask[n / BITS_PER_LONG] &= ~(1UL << (n % BITS_PER_LONG));
911     }
912 }
913 
daysmask_getbit(unsigned long mask[],short n)914 static unsigned long daysmask_getbit(unsigned long mask[], short n)
915 {
916     n += ICAL_YEARDAYS_MASK_OFFSET;
917     return (mask[n / BITS_PER_LONG] >> (n % BITS_PER_LONG)) & 1;
918 }
919 
icalrecur_iterator_sizeof_byarray(short * byarray)920 int icalrecur_iterator_sizeof_byarray(short *byarray)
921 {
922     int array_itr;
923 
924     for (array_itr = 0;
925          byarray[array_itr] != ICAL_RECURRENCE_ARRAY_MAX; array_itr++) {
926     }
927 
928     return array_itr;
929 }
930 
931 enum expand_table
932 {
933     UNKNOWN = 0,
934     CONTRACT = 1,
935     EXPAND = 2,
936     ILLEGAL = 3
937 };
938 
939 /**
940  * The split map indicates, for a particular interval, whether a BY_*
941  * rule part expands the number of instances in the occurrence set or
942  * contracts it. 1=> contract, 2=>expand, and 3 means the pairing is
943  * not allowed.
944  */
945 
946 struct expand_split_map_struct
947 {
948     icalrecurrencetype_frequency frequency;
949 
950     /* Elements of the 'map' array correspond to the BYxxx rules:
951        Second,Minute,Hour,Day,Month Day,Year Day,Week No,Month,SetPos */
952 
953     short map[BY_SET_POS+1];
954 };
955 
956 static const struct expand_split_map_struct expand_map[] = {
957     /*                           s  m  h  D  MD YD W  M  P */
958     {ICAL_SECONDLY_RECURRENCE, { 1, 1, 1, 1, 1, 1, 3, 1, 1 }},
959     {ICAL_MINUTELY_RECURRENCE, { 2, 1, 1, 1, 1, 1, 3, 1, 1 }},
960     {ICAL_HOURLY_RECURRENCE,   { 2, 2, 1, 1, 1, 1, 3, 1, 1 }},
961     {ICAL_DAILY_RECURRENCE,    { 2, 2, 2, 1, 1, 3, 3, 1, 1 }},
962     {ICAL_WEEKLY_RECURRENCE,   { 2, 2, 2, 2, 3, 3, 3, 1, 1 }},
963     {ICAL_MONTHLY_RECURRENCE,  { 2, 2, 2, 2, 2, 3, 3, 1, 1 }},
964     {ICAL_YEARLY_RECURRENCE,   { 2, 2, 2, 2, 2, 2, 2, 2, 1 }},
965     {ICAL_NO_RECURRENCE,       { 0, 0, 0, 0, 0, 0, 0, 0, 0 }} //krazy:exclude=style
966 };
967 
has_by_data(icalrecur_iterator * impl,enum byrule byrule)968 static int has_by_data(icalrecur_iterator *impl, enum byrule byrule)
969 {
970     return (impl->orig_data[byrule] == 1);
971 }
972 
setup_defaults(icalrecur_iterator * impl,enum byrule byrule,int deftime)973 static void setup_defaults(icalrecur_iterator *impl,
974                            enum byrule byrule, int deftime)
975 {
976     icalrecurrencetype_frequency freq = impl->rule.freq;
977 
978     if (expand_map[freq].map[byrule] == EXPAND) {
979 
980         /* Re-write the BY rule arrays with data from the DTSTART time so
981            we don't have to explicitly deal with DTSTART */
982         if (impl->by_ptrs[byrule][0] == ICAL_RECURRENCE_ARRAY_MAX) {
983             impl->by_ptrs[byrule][0] = (short)deftime;
984         }
985     }
986 }
987 
988 /* Calculate the number of Gregorian months between 2 dates */
__greg_month_diff(icaltimetype a,icaltimetype b)989 static int __greg_month_diff(icaltimetype a, icaltimetype b)
990 {
991     return (12 * (b.year - a.year) + (b.month - a.month));
992 }
993 
994 static int __day_diff(icalrecur_iterator *impl, icaltimetype a, icaltimetype b);
995 
996 #if defined(HAVE_LIBICU)
997 /*
998  * Callbacks for recurrence rules with RSCALE support (using ICU)
999  *
1000  * References:
1001  *   - tools.ietf.org/html/rfc7529
1002  *   - en.wikipedia.org/wiki/Intercalation_%28timekeeping%29
1003  *   - icu-project.org/apiref/icu4c/ucal_8h.html
1004  *   - cldr.unicode.org/development/development-process/design-proposals/chinese-calendar-support
1005  *   - cldr.unicode.org/development/development-process/design-proposals/islamic-calendar-types
1006  *
1007  * ICU Notes:
1008  *   - Months are 0-based
1009  *   - Leap months in Chinese and Hebrew calendars are handled differently
1010  */
1011 
icalrecurrencetype_rscale_supported_calendars(void)1012 icalarray *icalrecurrencetype_rscale_supported_calendars(void)
1013 {
1014     UErrorCode status = U_ZERO_ERROR;
1015     UEnumeration *en;
1016     icalarray *calendars;
1017     const char *cal;
1018 
1019     calendars = icalarray_new(sizeof(const char **), 20);
1020 
1021     en = ucal_getKeywordValuesForLocale("calendar", NULL, false, &status);
1022     while ((cal = uenum_next(en, NULL, &status))) {
1023         cal = icalmemory_tmp_copy(cal);
1024         icalarray_append(calendars, &cal);
1025     }
1026     uenum_close(en);
1027 
1028     return calendars;
1029 }
1030 
set_second(icalrecur_iterator * impl,int second)1031 static void set_second(icalrecur_iterator *impl, int second)
1032 {
1033     ucal_set(impl->rscale, UCAL_SECOND, (int32_t) second);
1034 }
1035 
set_minute(icalrecur_iterator * impl,int minute)1036 static void set_minute(icalrecur_iterator *impl, int minute)
1037 {
1038     ucal_set(impl->rscale, UCAL_MINUTE, (int32_t) minute);
1039 }
1040 
set_hour(icalrecur_iterator * impl,int hour)1041 static void set_hour(icalrecur_iterator *impl, int hour)
1042 {
1043     ucal_set(impl->rscale, UCAL_HOUR_OF_DAY, (int32_t) hour);
1044 }
1045 
__set_month(icalrecur_iterator * impl,int month)1046 static void __set_month(icalrecur_iterator *impl, int month)
1047 {
1048     int is_leap_month = icalrecurrencetype_month_is_leap(month);
1049 
1050     month = icalrecurrencetype_month_month(month) - 1;  /* UCal is 0-based */
1051 
1052     ucal_set(impl->rscale, UCAL_MONTH, (int32_t) month);
1053     if (is_leap_month)
1054         ucal_set(impl->rscale, UCAL_IS_LEAP_MONTH, 1);
1055 }
1056 
set_month(icalrecur_iterator * impl,int month)1057 static int set_month(icalrecur_iterator *impl, int month)
1058 {
1059     UErrorCode status = U_ZERO_ERROR;
1060     int actual_month;
1061 
1062     __set_month(impl, month);
1063 
1064     ucal_set(impl->rscale, UCAL_DAY_OF_MONTH, (int32_t) 1);
1065 
1066     actual_month = 1 +  /* UCal is 0-based */
1067         (int)ucal_get(impl->rscale, UCAL_MONTH, &status);
1068 
1069     if (ucal_get(impl->rscale, UCAL_IS_LEAP_MONTH, &status)) {
1070         actual_month |= LEAP_MONTH;
1071     }
1072 
1073     if (actual_month != month) {
1074         switch (impl->rule.skip) {
1075         default:
1076             /* Should never get here! */
1077 
1078         case ICAL_SKIP_OMIT:
1079             /* Invalid month */
1080             return 0;
1081 
1082         case ICAL_SKIP_BACKWARD:
1083             /* Skip back to next valid month */
1084             ucal_add(impl->rscale, UCAL_MONTH, (int32_t) -1, &status);
1085             break;
1086 
1087         case ICAL_SKIP_FORWARD:
1088             /* UCal skips forward to valid month by default */
1089             break;
1090         }
1091     }
1092 
1093     return (1 +  /* UCal is 0-based */
1094             (int)ucal_get(impl->rscale, UCAL_MONTH, &status));
1095 }
1096 
get_months_in_year(icalrecur_iterator * impl,int year)1097 static int get_months_in_year(icalrecur_iterator *impl, int year)
1098 {
1099     UErrorCode status = U_ZERO_ERROR;
1100 
1101     if (year) {
1102         ucal_set(impl->rscale, UCAL_YEAR, (int32_t) year);
1103     }
1104 
1105     return (1 +  /* UCal is 0-based */
1106             (int)ucal_getLimit(impl->rscale, UCAL_MONTH,
1107                                UCAL_ACTUAL_MAXIMUM, &status));
1108 }
1109 
get_days_in_year(icalrecur_iterator * impl,int year)1110 static int get_days_in_year(icalrecur_iterator *impl, int year)
1111 {
1112     UErrorCode status = U_ZERO_ERROR;
1113 
1114     if (year) {
1115         ucal_set(impl->rscale, UCAL_YEAR, (int32_t) year);
1116     }
1117 
1118     return (int)ucal_getLimit(impl->rscale, UCAL_DAY_OF_YEAR,
1119                               UCAL_ACTUAL_MAXIMUM, &status);
1120 }
1121 
set_day_of_year(icalrecur_iterator * impl,int doy)1122 static void set_day_of_year(icalrecur_iterator *impl, int doy)
1123 {
1124     if (doy < 0) {
1125         doy += get_days_in_year(impl, 0) + 1;
1126     }
1127 
1128     ucal_set(impl->rscale, UCAL_DAY_OF_YEAR, (int32_t) doy);
1129 }
1130 
get_start_of_week(icalrecur_iterator * impl)1131 static int get_start_of_week(icalrecur_iterator *impl)
1132 {
1133     UErrorCode status = U_ZERO_ERROR;
1134     int doy, dow;
1135 
1136     doy = (int)ucal_get(impl->rscale, UCAL_DAY_OF_YEAR, &status);
1137     dow = (int)ucal_get(impl->rscale, UCAL_DAY_OF_WEEK, &status);
1138     dow -= impl->rule.week_start;
1139     if (dow < 0) {
1140         dow += 7;
1141     }
1142 
1143     return (doy - dow);
1144 }
1145 
get_day_of_week(icalrecur_iterator * impl)1146 static int get_day_of_week(icalrecur_iterator *impl)
1147 {
1148     UErrorCode status = U_ZERO_ERROR;
1149 
1150     return (int)ucal_get(impl->rscale, UCAL_DAY_OF_WEEK, &status);
1151 }
1152 
get_week_number(icalrecur_iterator * impl,struct icaltimetype tt)1153 static int get_week_number(icalrecur_iterator *impl, struct icaltimetype tt)
1154 {
1155     UErrorCode status = U_ZERO_ERROR;
1156     UDate last_millis;
1157     int month, weekno;
1158 
1159     /* Save existing rscale date */
1160     last_millis = ucal_getMillis(impl->rscale, &status);
1161 
1162     month = icalrecurrencetype_month_month(tt.month) - 1;  /* UCal is 0-based */
1163     ucal_setDate(impl->rscale,
1164                  (int32_t) tt.year, (int32_t) month, (int32_t) tt.day, &status);
1165     if (icalrecurrencetype_month_is_leap(tt.month)) {
1166         ucal_set(impl->rscale, UCAL_IS_LEAP_MONTH, 1);
1167     }
1168 
1169     weekno = (int)ucal_get(impl->rscale, UCAL_WEEK_OF_YEAR, &status);
1170 
1171     /* Restore saved rscale date */
1172     ucal_setMillis(impl->rscale, last_millis, &status);
1173 
1174     return weekno;
1175 }
1176 
get_days_in_month(icalrecur_iterator * impl,int month,int year)1177 static int get_days_in_month(icalrecur_iterator *impl, int month, int year)
1178 {
1179     UErrorCode status = U_ZERO_ERROR;
1180 
1181     ucal_set(impl->rscale, UCAL_YEAR, (int32_t) year);
1182 
1183     if (!month) {
1184         month = impl->rstart.month;
1185     }
1186     __set_month(impl, month);
1187 
1188     return (int)ucal_getLimit(impl->rscale,
1189                               UCAL_DAY_OF_MONTH, UCAL_ACTUAL_MAXIMUM, &status);
1190 }
1191 
get_day_of_year(icalrecur_iterator * impl,int year,int month,int day,int * dow)1192 static int get_day_of_year(icalrecur_iterator *impl,
1193                            int year, int month, int day, int *dow)
1194 {
1195     UErrorCode status = U_ZERO_ERROR;
1196 
1197     ucal_set(impl->rscale, UCAL_YEAR, (int32_t) year);
1198 
1199     if (!month) {
1200         month = impl->rstart.month;
1201     }
1202     __set_month(impl, month);
1203 
1204     if (!day) {
1205         day = impl->rstart.day;
1206     }
1207     else if (day < 0) {
1208         day += 1 + (int)ucal_getLimit(impl->rscale, UCAL_DAY_OF_MONTH,
1209                                       UCAL_ACTUAL_MAXIMUM, &status);
1210     }
1211     ucal_set(impl->rscale, UCAL_DAY_OF_MONTH, (int32_t) day);
1212 
1213     if (dow) {
1214         *dow = (int)ucal_get(impl->rscale, UCAL_DAY_OF_WEEK, &status);
1215     }
1216 
1217     return (int)ucal_get(impl->rscale, UCAL_DAY_OF_YEAR, &status);
1218 }
1219 
occurrence_as_icaltime(icalrecur_iterator * impl,int normalize)1220 static struct icaltimetype occurrence_as_icaltime(icalrecur_iterator *impl,
1221                                                   int normalize)
1222 {
1223     struct icaltimetype tt = impl->dtstart;
1224     UErrorCode status = U_ZERO_ERROR;
1225     UCalendar *cal = impl->rscale;
1226     int is_leap_month = 0;
1227 
1228     if (normalize && (impl->rscale != impl->greg)) {
1229         /* Convert to Gregorian date */
1230         UDate millis = ucal_getMillis(impl->rscale, &status);
1231 
1232         ucal_setMillis(impl->greg, millis, &status);
1233         cal = impl->greg;
1234     } else {
1235         is_leap_month =
1236             (int)ucal_get(impl->rscale, UCAL_IS_LEAP_MONTH, &status);
1237     }
1238 
1239     tt.year = (int)ucal_get(cal, UCAL_YEAR, &status);
1240     tt.day = (int)ucal_get(cal, UCAL_DATE, &status);
1241     tt.month = 1 +  /* UCal is 0-based */
1242         (int)ucal_get(cal, UCAL_MONTH, &status);
1243     if (is_leap_month)
1244         tt.month |= LEAP_MONTH;
1245 
1246     if (!tt.is_date) {
1247         tt.hour = (int)ucal_get(cal, UCAL_HOUR_OF_DAY, &status);
1248         tt.minute = (int)ucal_get(cal, UCAL_MINUTE, &status);
1249         tt.second = (int)ucal_get(cal, UCAL_SECOND, &status);
1250     }
1251 
1252     return tt;
1253 }
1254 
__icaltime_from_day_of_year(icalrecur_iterator * impl,int day,int year,int * weekno)1255 struct icaltimetype __icaltime_from_day_of_year(icalrecur_iterator *impl,
1256                                                 int day, int year, int *weekno)
1257 {
1258     ucal_set(impl->rscale, UCAL_YEAR, (int32_t) year);
1259     if (day < 0) {
1260         day += get_days_in_year(impl, 0) + 1;
1261     }
1262 
1263     ucal_set(impl->rscale, UCAL_DAY_OF_YEAR, (int32_t) day);
1264 
1265     if (weekno) {
1266         UErrorCode status = U_ZERO_ERROR;
1267 
1268         *weekno = (int)ucal_get(impl->rscale, UCAL_WEEK_OF_YEAR, &status);
1269     }
1270 
1271     return occurrence_as_icaltime(impl, 0);
1272 }
1273 
increment_year(icalrecur_iterator * impl,int inc)1274 static void increment_year(icalrecur_iterator *impl, int inc)
1275 {
1276     UErrorCode status = U_ZERO_ERROR;
1277 
1278     ucal_add(impl->rscale, UCAL_YEAR, (int32_t) inc, &status);
1279 }
1280 
__increment_month(icalrecur_iterator * impl,int inc)1281 static void __increment_month(icalrecur_iterator *impl, int inc)
1282 {
1283     UErrorCode status = U_ZERO_ERROR;
1284 
1285     ucal_add(impl->rscale, UCAL_MONTH, (int32_t) inc, &status);
1286 }
1287 
increment_monthday(icalrecur_iterator * impl,int inc)1288 static void increment_monthday(icalrecur_iterator *impl, int inc)
1289 {
1290     UErrorCode status = U_ZERO_ERROR;
1291 
1292     ucal_add(impl->rscale, UCAL_DAY_OF_MONTH, (int32_t) inc, &status);
1293 }
1294 
increment_hour(icalrecur_iterator * impl,int inc)1295 static void increment_hour(icalrecur_iterator *impl, int inc)
1296 {
1297     UErrorCode status = U_ZERO_ERROR;
1298 
1299     ucal_add(impl->rscale, UCAL_HOUR_OF_DAY, (int32_t) inc, &status);
1300 }
1301 
increment_minute(icalrecur_iterator * impl,int inc)1302 static void increment_minute(icalrecur_iterator *impl, int inc)
1303 {
1304     UErrorCode status = U_ZERO_ERROR;
1305 
1306     ucal_add(impl->rscale, UCAL_MINUTE, (int32_t) inc, &status);
1307 }
1308 
increment_second(icalrecur_iterator * impl,int inc)1309 static void increment_second(icalrecur_iterator *impl, int inc)
1310 {
1311     UErrorCode status = U_ZERO_ERROR;
1312 
1313     ucal_add(impl->rscale, UCAL_SECOND, (int32_t) inc, &status);
1314 }
1315 
validate_byrule(icalrecur_iterator * impl,enum byrule byrule,UCalendarDateFields field,short (* decode_val)(short *,unsigned),unsigned int decode_flags)1316 static int validate_byrule(icalrecur_iterator *impl,
1317                            enum byrule byrule, UCalendarDateFields field,
1318                            short (*decode_val)(short *, unsigned),
1319                            unsigned int decode_flags)
1320 {
1321     if (has_by_data(impl, byrule)) {
1322         UErrorCode status = U_ZERO_ERROR;
1323         short *by_ptr = impl->by_ptrs[byrule];
1324         short max =
1325             (short)ucal_getLimit(impl->rscale, field, UCAL_MAXIMUM, &status);
1326         short idx;
1327 
1328         for (idx = 0; by_ptr[idx] != ICAL_RECURRENCE_ARRAY_MAX; idx++) {
1329             short val = decode_val ?
1330                 decode_val(&by_ptr[idx], decode_flags) : by_ptr[idx];
1331 
1332             if (abs(val) > max) return 0;
1333         }
1334     }
1335 
1336     return 1;
1337 }
1338 
decode_month(short * month,unsigned int is_hebrew)1339 static short decode_month(short *month, unsigned int is_hebrew)
1340 {
1341     if (is_hebrew && *month > 5) {  /* 5L == 0x1005 */
1342         /* Hebrew calendar:
1343            Translate RSCALE months to ICU (numbered 1-13, where 6 is leap).
1344            Hence, 5L maps to 6 and 6-12 map to 7-13. */
1345         *month = icalrecurrencetype_month_month(*month) + 1;
1346     }
1347 
1348     return icalrecurrencetype_month_month(*month) - 1;  /* UCal is 0-based */
1349 }
1350 
decode_day(short * day,unsigned int flags)1351 static short decode_day(short *day, unsigned int flags)
1352 {
1353     _unused(flags);
1354 
1355     return icalrecurrencetype_day_position(*day);
1356 }
1357 
initialize_rscale(icalrecur_iterator * impl)1358 static int initialize_rscale(icalrecur_iterator *impl)
1359 {
1360     struct icalrecurrencetype rule = impl->rule;
1361     struct icaltimetype dtstart = impl->dtstart;
1362     char locale[ULOC_KEYWORD_AND_VALUES_CAPACITY] = "";
1363     UErrorCode status = U_ZERO_ERROR;
1364     UChar *tzid = (UChar *) UCAL_UNKNOWN_ZONE_ID;
1365     short is_hebrew = 0;
1366 
1367     if (dtstart.zone) {
1368         /* Convert the UTF8 timezoneid of dstart to ICU UChar. */
1369         const char *src = icaltimezone_get_tzid((icaltimezone *) dtstart.zone);
1370         size_t len = (strlen(src) + 1) * U_SIZEOF_UCHAR;
1371         tzid = icalmemory_tmp_buffer(len);
1372         tzid = u_strFromUTF8Lenient(tzid, (int32_t)len, NULL, src, -1, &status);
1373         if (U_FAILURE(status)) {
1374             icalerror_set_errno(ICAL_INTERNAL_ERROR);
1375             return 0;
1376         }
1377     }
1378 
1379     /* Create locale for Gregorian calendar */
1380     (void)uloc_setKeywordValue("calendar", "gregorian",
1381                                locale, sizeof(locale), &status);
1382 
1383     /* Create Gregorian calendar and set to DTSTART */
1384     impl->greg =
1385         ucal_open(tzid, -1, locale, UCAL_DEFAULT, &status);
1386     if (impl->greg) {
1387         ucal_setDateTime(impl->greg,
1388                          (int32_t) dtstart.year,
1389                          (int32_t) (dtstart.month - 1), /* UCal is 0-based */
1390                          (int32_t) dtstart.day,
1391                          (int32_t) dtstart.hour,
1392                          (int32_t) dtstart.minute,
1393                          (int32_t) dtstart.second, &status);
1394     }
1395     if (!impl->greg || U_FAILURE(status)) {
1396         icalerror_set_errno(ICAL_INTERNAL_ERROR);
1397         return 0;
1398     }
1399 
1400     if (!rule.rscale) {
1401         /* Use Gregorian as RSCALE */
1402         impl->rscale = impl->greg;
1403     } else {
1404         UEnumeration *en;
1405         const char *cal;
1406         char *r;
1407 
1408         /* Lowercase the specified calendar */
1409         for (r = rule.rscale; *r; r++) {
1410             *r = tolower((int)*r);
1411         }
1412 
1413         /* Check if specified calendar is supported */
1414         en = ucal_getKeywordValuesForLocale("calendar", NULL, false, &status);
1415         while ((cal = uenum_next(en, NULL, &status))) {
1416             if (!strcmp(cal, rule.rscale)) {
1417                 is_hebrew = !strcmp(rule.rscale, "hebrew");
1418                 break;
1419             }
1420         }
1421         uenum_close(en);
1422         if (!cal) {
1423             icalerror_set_errno(ICAL_UNIMPLEMENTED_ERROR);
1424             return 0;
1425         }
1426 
1427         /* Create locale for RSCALE calendar */
1428         (void)uloc_setKeywordValue("calendar", rule.rscale,
1429                                    locale, sizeof(locale), &status);
1430 
1431         /* Create RSCALE calendar and set to DTSTART */
1432         impl->rscale =
1433             ucal_open(tzid, -1, locale, UCAL_DEFAULT, &status);
1434         if (impl->rscale) {
1435             UDate millis = ucal_getMillis(impl->greg, &status);
1436 
1437             ucal_setMillis(impl->rscale, millis, &status);
1438         }
1439         if (!impl->rscale || U_FAILURE(status)) {
1440             icalerror_set_errno(ICAL_INTERNAL_ERROR);
1441             return 0;
1442         }
1443     }
1444 
1445     /* Validate BY_* array values whose legal maximums differ based on RSCALE */
1446     if (!validate_byrule(impl, BY_MONTH, UCAL_MONTH,
1447                          &decode_month, (unsigned int)is_hebrew) ||
1448         !validate_byrule(impl, BY_DAY, UCAL_WEEK_OF_YEAR, &decode_day, 0) ||
1449         !validate_byrule(impl, BY_MONTH_DAY, UCAL_DAY_OF_MONTH, NULL, 0) ||
1450         !validate_byrule(impl, BY_YEAR_DAY, UCAL_DAY_OF_YEAR, NULL, 0) ||
1451         !validate_byrule(impl, BY_WEEK_NO, UCAL_WEEK_OF_YEAR, NULL, 0) ||
1452         !validate_byrule(impl, BY_SET_POS, UCAL_DAY_OF_YEAR, NULL, 0)) {
1453         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
1454         return 0;
1455     }
1456 
1457     /* Set iCalendar defaults */
1458     ucal_setAttribute(impl->rscale, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK, 4);
1459     ucal_setAttribute(impl->rscale, UCAL_FIRST_DAY_OF_WEEK, rule.week_start);
1460 
1461     /* Get rstart (DTSTART in RSCALE) */
1462     impl->rstart = occurrence_as_icaltime(impl, 0);
1463 
1464     return 1;
1465 }
1466 
1467 /* Set Gregorian date and convert to RSCALE */
set_datetime(icalrecur_iterator * impl,icaltimetype date)1468 static void set_datetime(icalrecur_iterator *impl, icaltimetype date)
1469 {
1470     UErrorCode status = U_ZERO_ERROR;
1471 
1472     impl->last.is_date = impl->rstart.is_date;
1473     impl->last.zone = impl->rstart.zone;
1474 
1475     if (impl->rstart.is_date) {
1476         ucal_setDate(impl->greg,
1477                      (int32_t) date.year,
1478                      (int32_t) (date.month - 1), /* UCal is 0-based */
1479                      (int32_t) date.day, &status);
1480     } else {
1481         ucal_setDateTime(impl->greg,
1482                          (int32_t) date.year,
1483                          (int32_t) (date.month - 1), /* UCal is 0-based */
1484                          (int32_t) date.day,
1485                          (int32_t) (has_by_data(impl, BY_HOUR) ?
1486                                     impl->by_ptrs[BY_HOUR][0] : impl->rstart.hour),
1487                          (int32_t) (has_by_data(impl, BY_MINUTE) ?
1488                                     impl->by_ptrs[BY_MINUTE][0] : impl->rstart.minute),
1489                          (int32_t) (has_by_data(impl, BY_SECOND) ?
1490                                     impl->by_ptrs[BY_SECOND][0] : impl->rstart.second),
1491                          &status);
1492     }
1493 
1494     if (impl->rscale != impl->greg) {
1495         UDate millis = ucal_getMillis(impl->greg, &status);
1496         ucal_setMillis(impl->rscale, millis, &status);
1497     }
1498 }
1499 
1500 /* Calculate the number of RSCALE months between 2 dates */
month_diff(icalrecur_iterator * impl,icaltimetype a,icaltimetype b)1501 static int month_diff(icalrecur_iterator *impl, icaltimetype a, icaltimetype b)
1502 {
1503     int diff;
1504 
1505     if (impl->rscale == impl->greg) {
1506         /* Use simple Gregorian math */
1507         diff = __greg_month_diff(a, b);
1508     } else if (a.year == b.year) {
1509         diff = b.month - a.month;
1510     } else {
1511         /* Count months in each year to account for leap months */
1512         UErrorCode status = U_ZERO_ERROR;
1513         UDate millis;
1514         int year = a.year;
1515 
1516         /* Save current date */
1517         millis = ucal_getMillis(impl->rscale, &status);
1518 
1519         set_day_of_year(impl, 1);
1520         diff = get_months_in_year(impl, year) - a.month;
1521         while (++year < b.year) diff += get_months_in_year(impl, year);
1522         diff += b.month;
1523 
1524         /* Restore date */
1525         ucal_setMillis(impl->rscale, millis, &status);
1526     }
1527 
1528     return diff;
1529 }
1530 
1531 /* Calculate the number of RSCALE days between 2 dates */
day_diff(icalrecur_iterator * impl,icaltimetype a,icaltimetype b)1532 static int day_diff(icalrecur_iterator *impl, icaltimetype a, icaltimetype b)
1533 {
1534     UErrorCode status = U_ZERO_ERROR;
1535     UDate millis;
1536     int diff;
1537 
1538     /* Save current date */
1539     millis = ucal_getMillis(impl->rscale, &status);
1540 
1541     set_day_of_year(impl, 1);
1542 
1543     diff = __day_diff(impl, a, b);
1544 
1545     /* Restore date */
1546     ucal_setMillis(impl->rscale, millis, &status);
1547 
1548     return diff;
1549 }
1550 
1551 #else /* !HAVE_LIBICU */
1552 
1553 /*
1554  * Callbacks for recurrence rules without RSCALE (Gregorian only)
1555  */
1556 
icalrecurrencetype_rscale_supported_calendars(void)1557 icalarray *icalrecurrencetype_rscale_supported_calendars(void)
1558 {
1559     return NULL;
1560 }
1561 
set_second(icalrecur_iterator * impl,int second)1562 static void set_second(icalrecur_iterator *impl, int second)
1563 {
1564     impl->last.second = second;
1565 }
1566 
set_minute(icalrecur_iterator * impl,int minute)1567 static void set_minute(icalrecur_iterator *impl, int minute)
1568 {
1569     impl->last.minute = minute;
1570 }
1571 
set_hour(icalrecur_iterator * impl,int hour)1572 static void set_hour(icalrecur_iterator *impl, int hour)
1573 {
1574     impl->last.hour = hour;
1575 }
1576 
set_month(icalrecur_iterator * impl,int month)1577 static int set_month(icalrecur_iterator *impl, int month)
1578 {
1579     return (impl->last.month = month);
1580 }
1581 
1582 #define get_months_in_year(impl, year)  (12)
1583 
get_days_in_year(icalrecur_iterator * impl,int year)1584 static int get_days_in_year(icalrecur_iterator *impl, int year)
1585 {
1586     _unused(impl);
1587 
1588     return icaltime_days_in_year(year);
1589 }
1590 
set_day_of_year(icalrecur_iterator * impl,int doy)1591 static void set_day_of_year(icalrecur_iterator *impl, int doy)
1592 {
1593     struct icaltimetype next;
1594 
1595     if (doy < 0) {
1596         doy += get_days_in_year(impl, impl->last.year) + 1;
1597     }
1598 
1599     next = icaltime_from_day_of_year(doy, impl->last.year);
1600 
1601     impl->last.day = next.day;
1602     impl->last.month = next.month;
1603     impl->last.year = next.year;
1604 }
1605 
get_start_of_week(icalrecur_iterator * impl)1606 static int get_start_of_week(icalrecur_iterator *impl)
1607 {
1608     return icaltime_start_doy_week(impl->last, impl->rule.week_start);
1609 }
1610 
get_day_of_week(icalrecur_iterator * impl)1611 static int get_day_of_week(icalrecur_iterator *impl)
1612 {
1613     return icaltime_day_of_week(impl->last);
1614 }
1615 
1616 /* Calculate ISO weeks per year
1617    https://en.wikipedia.org/wiki/ISO_week_date#Weeks_per_year */
weeks_in_year(int year)1618 static int weeks_in_year(int year)
1619 {
1620     /* Long years occur when year starts on Thu or leap year starts on Wed */
1621     int dow = icaltime_day_of_week(icaltime_from_day_of_year(1, year));
1622     int is_long = (dow == 5 || (dow == 4 && icaltime_is_leap_year(year)));
1623 
1624     return (52 + is_long);
1625 }
1626 
1627 /* Calculate ISO week number
1628    https://en.wikipedia.org/wiki/ISO_week_date#Calculation */
get_week_number(icalrecur_iterator * impl,struct icaltimetype tt)1629 static int get_week_number(icalrecur_iterator *impl, struct icaltimetype tt)
1630 {
1631     int dow, week;
1632 
1633     _unused(impl);
1634 
1635     /* Normalize day of week so that week_start day is 1 */
1636     dow = icaltime_day_of_week(tt) - (impl->rule.week_start - 1);
1637     if (dow <= 0)
1638         dow += 7;
1639 
1640     week = (icaltime_day_of_year(tt) - dow + 10) / 7;
1641     if (week < 1) {
1642         /* Last week of preceding year */
1643         week = weeks_in_year(tt.year - 1);
1644     } else if (week > weeks_in_year(tt.year)) {
1645         /* First week of following year */
1646         week = 1;
1647     }
1648 
1649     return week;
1650 }
1651 
get_days_in_month(icalrecur_iterator * impl,int month,int year)1652 static int get_days_in_month(icalrecur_iterator *impl, int month, int year)
1653 {
1654     _unused(impl);
1655 
1656     return icaltime_days_in_month(month, year);
1657 }
1658 
get_day_of_year(icalrecur_iterator * impl,int year,int month,int day,int * dow)1659 static int get_day_of_year(icalrecur_iterator *impl,
1660                            int year, int month, int day, int *dow)
1661 {
1662     struct icaltimetype t = impl->dtstart;
1663 
1664     t.is_date = 1;
1665     t.year = year;
1666 
1667     if (!month) {
1668         month = impl->dtstart.month;
1669     }
1670     t.month = month;
1671 
1672     if (!day) {
1673         day = impl->dtstart.day;
1674     }
1675     else if (day < 0) {
1676         day += icaltime_days_in_month(month, year) + 1;
1677     }
1678     t.day = day;
1679 
1680     if (dow) {
1681         *dow = icaltime_day_of_week(t);
1682     }
1683 
1684     return icaltime_day_of_year(t);
1685 }
1686 
occurrence_as_icaltime(icalrecur_iterator * impl,int normalize)1687 static struct icaltimetype occurrence_as_icaltime(icalrecur_iterator *impl,
1688                                                   int normalize)
1689 {
1690     return (normalize ? icaltime_normalize(impl->last) : impl->last);
1691 }
1692 
__icaltime_from_day_of_year(icalrecur_iterator * impl,int day,int year,int * weekno)1693 struct icaltimetype __icaltime_from_day_of_year(icalrecur_iterator *impl,
1694                                                 int day, int year, int *weekno)
1695 {
1696     struct icaltimetype tt;
1697 
1698     if (day < 0) {
1699         day += get_days_in_year(impl, year) + 1;
1700     }
1701 
1702     tt = icaltime_from_day_of_year(day, year);
1703 
1704     if (weekno) {
1705         *weekno = get_week_number(impl, tt);
1706     }
1707     return tt;
1708 }
1709 
increment_year(icalrecur_iterator * impl,int inc)1710 static void increment_year(icalrecur_iterator *impl, int inc)
1711 {
1712     impl->last.year += inc;
1713 }
1714 
__increment_month(icalrecur_iterator * impl,int inc)1715 static void __increment_month(icalrecur_iterator *impl, int inc)
1716 {
1717     int years;
1718 
1719     impl->last.month += inc;
1720 
1721     /* Months are offset by one */
1722     impl->last.month--;
1723 
1724     years = impl->last.month / 12;
1725 
1726     impl->last.month = impl->last.month % 12;
1727 
1728     impl->last.month++;
1729 
1730     if (years != 0) {
1731         increment_year(impl, years);
1732     }
1733 }
1734 
increment_monthday(icalrecur_iterator * impl,int inc)1735 static void increment_monthday(icalrecur_iterator *impl, int inc)
1736 {
1737     icaltime_adjust(&impl->last, inc, 0, 0, 0);
1738 }
1739 
increment_hour(icalrecur_iterator * impl,int inc)1740 static void increment_hour(icalrecur_iterator *impl, int inc)
1741 {
1742     icaltime_adjust(&impl->last, 0, inc, 0, 0);
1743 }
1744 
increment_minute(icalrecur_iterator * impl,int inc)1745 static void increment_minute(icalrecur_iterator *impl, int inc)
1746 {
1747     icaltime_adjust(&impl->last, 0, 0, inc, 0);
1748 }
1749 
increment_second(icalrecur_iterator * impl,int inc)1750 static void increment_second(icalrecur_iterator *impl, int inc)
1751 {
1752     icaltime_adjust(&impl->last, 0, 0, 0, inc);
1753 }
1754 
initialize_rscale(icalrecur_iterator * impl)1755 static int initialize_rscale(icalrecur_iterator *impl)
1756 {
1757     impl->rstart = impl->dtstart;
1758 
1759     return 1;
1760 }
1761 
1762 /* Set Gregorian date */
set_datetime(icalrecur_iterator * impl,icaltimetype date)1763 static void set_datetime(icalrecur_iterator *impl, icaltimetype date)
1764 {
1765     impl->last.year = date.year;
1766     impl->last.month = date.month;
1767     impl->last.day = date.day;
1768     impl->last.is_date = impl->dtstart.is_date;
1769     impl->last.zone = impl->dtstart.zone;
1770 
1771     if (!impl->dtstart.is_date) {
1772         impl->last.hour = has_by_data(impl, BY_HOUR) ?
1773             impl->by_ptrs[BY_HOUR][0] : impl->dtstart.hour;
1774         impl->last.minute = has_by_data(impl, BY_MINUTE) ?
1775             impl->by_ptrs[BY_MINUTE][0] : impl->dtstart.minute;
1776         impl->last.second = has_by_data(impl, BY_SECOND) ?
1777             impl->by_ptrs[BY_SECOND][0] : impl->dtstart.second;
1778     }
1779 }
1780 
1781 /* Calculate the number of Gregorian months between 2 dates */
month_diff(icalrecur_iterator * impl,icaltimetype a,icaltimetype b)1782 static int month_diff(icalrecur_iterator *impl, icaltimetype a, icaltimetype b)
1783 {
1784     _unused(impl);
1785 
1786     return __greg_month_diff(a, b);
1787 }
1788 
1789 /* Calculate the number of Gregorian days between 2 dates */
day_diff(icalrecur_iterator * impl,icaltimetype a,icaltimetype b)1790 static int day_diff(icalrecur_iterator *impl, icaltimetype a, icaltimetype b)
1791 {
1792     return __day_diff(impl, a, b);
1793 }
1794 
1795 #endif /* HAVE_LIBICU */
1796 
1797 static int __iterator_set_start(icalrecur_iterator *impl, icaltimetype start);
1798 static void increment_month(icalrecur_iterator *impl);
1799 static int expand_month_days(icalrecur_iterator *impl, int year, int month);
1800 static int expand_year_days(icalrecur_iterator *impl, int year);
1801 static int next_yearday(icalrecur_iterator *impl,
1802                         void (*next_period)(icalrecur_iterator *));
1803 
adjust_to_byday(icalrecur_iterator * impl)1804 static void adjust_to_byday(icalrecur_iterator *impl)
1805 {
1806     /* If there is BY_DAY data, then we need to move the initial
1807        time to the start of the BY_DAY data. That is if the
1808        start time is on a Wednesday, and the rule has
1809        BYDAY=MO,WE,FR, move the initial time back to
1810        monday. Otherwise, jumping to the next week ( jumping 7
1811        days ahead ) will skip over some occurrences in the
1812        second week. */
1813 
1814     /* This depends on impl->by_ptrs[BY_DAY] being correctly sorted by
1815      * day. This should probably be abstracted to make such assumption
1816      * more explicit. */
1817     short this_dow = (short)get_day_of_week(impl);
1818     short dow = (short)(impl->by_ptrs[BY_DAY][0] - this_dow);
1819 
1820     /* Normalize day of week around week start */
1821     if (dow != 0 && this_dow < (short)impl->rule.week_start)
1822         dow -= 7;
1823 
1824     if ((this_dow < impl->by_ptrs[BY_DAY][0] && dow >= 0) || dow < 0) {
1825         /* initial time is after first day of BY_DAY data */
1826         increment_monthday(impl, dow);
1827     }
1828 }
1829 
icalrecur_iterator_new(struct icalrecurrencetype rule,struct icaltimetype dtstart)1830 icalrecur_iterator *icalrecur_iterator_new(struct icalrecurrencetype rule,
1831                                            struct icaltimetype dtstart)
1832 {
1833     icalrecur_iterator *impl;
1834     icalrecurrencetype_frequency freq = rule.freq;
1835     enum byrule byrule;
1836 
1837     icalerror_clear_errno();
1838 
1839     if (freq == ICAL_NO_RECURRENCE) {
1840         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
1841         return 0;
1842     }
1843 
1844 #define IN_RANGE(val, min, max) (val >= min && val <= max)
1845 
1846     /* Make sure that DTSTART is a sane value */
1847     if (!icaltime_is_valid_time(dtstart) ||
1848         !IN_RANGE(dtstart.year, 0, MAX_TIME_T_YEAR) ||
1849         !IN_RANGE(dtstart.month, 1, 12) ||
1850         !IN_RANGE(dtstart.day, 1,
1851                   icaltime_days_in_month(dtstart.month, dtstart.year)) ||
1852         (!dtstart.is_date && (!IN_RANGE(dtstart.hour, 0, 23) ||
1853                               !IN_RANGE(dtstart.minute, 0, 59) ||
1854                               !IN_RANGE(dtstart.second, 0, 59)))) {
1855         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
1856         return 0;
1857     }
1858 
1859     if (!(impl = (icalrecur_iterator *)malloc(sizeof(icalrecur_iterator)))) {
1860         icalerror_set_errno(ICAL_NEWFAILED_ERROR);
1861         return 0;
1862     }
1863 
1864     memset(impl, 0, sizeof(icalrecur_iterator));
1865 
1866     impl->dtstart = dtstart;
1867     impl->rule = rule;
1868 
1869     /* Set up convenience pointers to make the code simpler. Allows
1870        us to iterate through all of the BY* arrays in the rule. */
1871 
1872     impl->by_ptrs[BY_MONTH] = impl->rule.by_month;
1873     impl->by_ptrs[BY_WEEK_NO] = impl->rule.by_week_no;
1874     impl->by_ptrs[BY_YEAR_DAY] = impl->rule.by_year_day;
1875     impl->by_ptrs[BY_MONTH_DAY] = impl->rule.by_month_day;
1876     impl->by_ptrs[BY_DAY] = impl->rule.by_day;
1877     impl->by_ptrs[BY_HOUR] = impl->rule.by_hour;
1878     impl->by_ptrs[BY_MINUTE] = impl->rule.by_minute;
1879     impl->by_ptrs[BY_SECOND] = impl->rule.by_second;
1880     impl->by_ptrs[BY_SET_POS] = impl->rule.by_set_pos;
1881 
1882     memset(impl->orig_data, 0, 9 * sizeof(short));
1883 
1884     /* Note which by rules had data in them when the iterator was
1885        created. We can't use the actual by_x arrays, because the
1886        empty ones will be given default values later in this
1887        routine. The orig_data array will be used later in has_by_data */
1888 
1889     impl->orig_data[BY_MONTH] =
1890       (short)(impl->rule.by_month[0] != ICAL_RECURRENCE_ARRAY_MAX);
1891     impl->orig_data[BY_WEEK_NO] =
1892       (short)(impl->rule.by_week_no[0] != ICAL_RECURRENCE_ARRAY_MAX);
1893     impl->orig_data[BY_YEAR_DAY] =
1894       (short)(impl->rule.by_year_day[0] != ICAL_RECURRENCE_ARRAY_MAX);
1895     impl->orig_data[BY_MONTH_DAY] =
1896       (short)(impl->rule.by_month_day[0] != ICAL_RECURRENCE_ARRAY_MAX);
1897     impl->orig_data[BY_DAY] =
1898       (short)(impl->rule.by_day[0] != ICAL_RECURRENCE_ARRAY_MAX);
1899     impl->orig_data[BY_HOUR] =
1900       (short)(impl->rule.by_hour[0] != ICAL_RECURRENCE_ARRAY_MAX);
1901     impl->orig_data[BY_MINUTE] =
1902       (short)(impl->rule.by_minute[0] != ICAL_RECURRENCE_ARRAY_MAX);
1903     impl->orig_data[BY_SECOND] =
1904       (short)(impl->rule.by_second[0] != ICAL_RECURRENCE_ARRAY_MAX);
1905     impl->orig_data[BY_SET_POS] =
1906       (short)(impl->rule.by_set_pos[0] != ICAL_RECURRENCE_ARRAY_MAX);
1907 
1908     /* Check if the recurrence rule is legal */
1909 
1910     for (byrule = BY_SECOND; byrule <= BY_SET_POS; byrule++) {
1911         if (expand_map[freq].map[byrule] == ILLEGAL &&
1912             impl->by_ptrs[byrule][0] != ICAL_RECURRENCE_ARRAY_MAX) {
1913             icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
1914             free(impl);
1915             return 0;
1916         }
1917     }
1918 
1919     if (initialize_rscale(impl) == 0) {
1920         icalrecur_iterator_free(impl);
1921         return 0;
1922     }
1923 
1924     /* Set up defaults for BY_* arrays */
1925     setup_defaults(impl, BY_SECOND, impl->rstart.second);
1926 
1927     setup_defaults(impl, BY_MINUTE, impl->rstart.minute);
1928 
1929     setup_defaults(impl, BY_HOUR, impl->rstart.hour);
1930 
1931     setup_defaults(impl, BY_MONTH_DAY, impl->rstart.day);
1932 
1933     setup_defaults(impl, BY_MONTH, impl->rstart.month);
1934 
1935     if (!__iterator_set_start(impl, dtstart)) {
1936         icalrecur_iterator_free(impl);
1937         return 0;
1938     }
1939 
1940     return impl;
1941 }
1942 
icalrecur_iterator_free(icalrecur_iterator * i)1943 void icalrecur_iterator_free(icalrecur_iterator *i)
1944 {
1945     icalerror_check_arg_rv((i != 0), "impl");
1946 
1947 #if defined(HAVE_LIBICU)
1948     if (i->greg) {
1949         if (i->rscale && (i->rscale != i->greg)) {
1950             ucal_close(i->rscale);
1951         }
1952 
1953         ucal_close(i->greg);
1954     }
1955 #endif
1956 
1957     free(i);
1958 }
1959 
1960 /* Calculate the number of days between 2 dates */
__day_diff(icalrecur_iterator * impl,icaltimetype a,icaltimetype b)1961 static int __day_diff(icalrecur_iterator *impl, icaltimetype a, icaltimetype b)
1962 {
1963     int diff;
1964 
1965     if (a.year == b.year) {
1966         diff = get_day_of_year(impl, b.year, b.month, b.day, NULL) -
1967             get_day_of_year(impl, a.year, a.month, a.day, NULL);
1968     } else {
1969         /* Swap a and b if a is greater than b */
1970         int flipped = 0;
1971         int year;
1972 
1973         if (a.year > b.year) {
1974             icaltimetype temp = a;
1975 
1976             a = b;
1977             b = temp;
1978             flipped = 1;
1979         }
1980 
1981         /* Count days in each year to account for leap days/months */
1982         year = a.year;
1983 
1984         diff = get_days_in_year(impl, year) -
1985             get_day_of_year(impl, a.year, a.month, a.day, NULL);
1986         while (++year < b.year) diff += get_days_in_year(impl, year);
1987         diff += get_day_of_year(impl, b.year, b.month, b.day, NULL);
1988 
1989         if (flipped) {
1990             /* The difference is negative because a was greater than b */
1991             diff = -diff;
1992         }
1993     }
1994 
1995     return diff;
1996 }
1997 
1998 /** Increment month is different that the other increment_* routines --
1999    it figures out the interval for itself, and uses BYMONTH data if
2000    available. */
increment_month(icalrecur_iterator * impl)2001 static void increment_month(icalrecur_iterator *impl)
2002 {
2003     int inc = impl->rule.interval;
2004 
2005     __increment_month(impl, inc);
2006 
2007     if (has_by_data(impl, BY_MONTH)) {
2008         struct icaltimetype this = occurrence_as_icaltime(impl, 0);
2009 
2010         while (this.year < 20000) {
2011             for (BYMONIDX = 0;
2012                  BYMONPTR[BYMONIDX] != ICAL_RECURRENCE_ARRAY_MAX; BYMONIDX++) {
2013 
2014                 if (this.month == BYMONPTR[BYMONIDX]) return;
2015             }
2016 
2017             __increment_month(impl, inc);
2018             this = occurrence_as_icaltime(impl, 0);
2019         }
2020     }
2021 }
2022 
2023 #if 0
2024 #include "ical.h"
2025 void test_increment()
2026 {
2027     icalrecur_iterator impl;
2028 
2029     impl.last = icaltime_from_string("20000101T000000Z");
2030 
2031     printf("Orig: %s\n", icaltime_as_ctime(impl.last));
2032 
2033     increment_second(&impl, 5);
2034     printf("+ 5 sec    : %s\n", icaltime_as_ctime(impl.last));
2035 
2036     increment_second(&impl, 355);
2037     printf("+ 355 sec  : %s\n", icaltime_as_ctime(impl.last));
2038 
2039     increment_minute(&impl, 5);
2040     printf("+ 5 min    : %s\n", icaltime_as_ctime(impl.last));
2041 
2042     increment_minute(&impl, 360);
2043     printf("+ 360 min  : %s\n", icaltime_as_ctime(impl.last));
2044     increment_hour(&impl, 5);
2045     printf("+ 5 hours  : %s\n", icaltime_as_ctime(impl.last));
2046     increment_hour(&impl, 43);
2047     printf("+ 43 hours : %s\n", icaltime_as_ctime(impl.last));
2048     increment_monthday(&impl, 3);
2049     printf("+ 3 days   : %s\n", icaltime_as_ctime(impl.last));
2050     increment_monthday(&impl, 600);
2051     printf("+ 600 days  : %s\n", icaltime_as_ctime(impl.last));
2052 }
2053 
2054 #endif
2055 
next_second(icalrecur_iterator * impl)2056 static int next_second(icalrecur_iterator *impl)
2057 {
2058     int has_by_second =
2059         (impl->by_ptrs[BY_SECOND][0] != ICAL_RECURRENCE_ARRAY_MAX);
2060     int this_frequency = (impl->rule.freq == ICAL_SECONDLY_RECURRENCE);
2061 
2062     int end_of_data = 0;
2063 
2064     assert(has_by_second || this_frequency);
2065 
2066     if (has_by_second) {
2067         /* Ignore the frequency and use the byrule data */
2068 
2069         impl->by_indices[BY_SECOND]++;
2070 
2071         if (impl->by_ptrs[BY_SECOND][impl->by_indices[BY_SECOND]] ==
2072             ICAL_RECURRENCE_ARRAY_MAX) {
2073             impl->by_indices[BY_SECOND] = 0;
2074 
2075             end_of_data = 1;
2076         }
2077 
2078         set_second(impl, impl->by_ptrs[BY_SECOND][impl->by_indices[BY_SECOND]]);
2079 
2080     } else if (!has_by_second && this_frequency) {
2081         /* Compute the next value from the last time and the freq interval */
2082         increment_second(impl, impl->rule.interval);
2083     }
2084 
2085     /* If we have gone through all of the seconds on the BY list, then we
2086        need to move to the next minute */
2087 
2088     if (has_by_second && end_of_data && this_frequency) {
2089         increment_minute(impl, 1);
2090     }
2091 
2092     return end_of_data;
2093 }
2094 
next_minute(icalrecur_iterator * impl)2095 static int next_minute(icalrecur_iterator *impl)
2096 {
2097     int has_by_minute =
2098         (impl->by_ptrs[BY_MINUTE][0] != ICAL_RECURRENCE_ARRAY_MAX);
2099     int this_frequency = (impl->rule.freq == ICAL_MINUTELY_RECURRENCE);
2100 
2101     int end_of_data = 0;
2102 
2103     assert(has_by_minute || this_frequency);
2104 
2105     if (next_second(impl) == 0) {
2106         return 0;
2107     }
2108 
2109     if (has_by_minute) {
2110         /* Ignore the frequency and use the byrule data */
2111 
2112         impl->by_indices[BY_MINUTE]++;
2113 
2114         if (impl->by_ptrs[BY_MINUTE][impl->by_indices[BY_MINUTE]]
2115             == ICAL_RECURRENCE_ARRAY_MAX) {
2116 
2117             impl->by_indices[BY_MINUTE] = 0;
2118 
2119             end_of_data = 1;
2120         }
2121 
2122         set_minute(impl, impl->by_ptrs[BY_MINUTE][impl->by_indices[BY_MINUTE]]);
2123 
2124     } else if (!has_by_minute && this_frequency) {
2125         /* Compute the next value from the last time and the freq interval */
2126         increment_minute(impl, impl->rule.interval);
2127     }
2128 
2129     /* If we have gone through all of the minutes on the BY list, then we
2130        need to move to the next hour */
2131 
2132     if (has_by_minute && end_of_data && this_frequency) {
2133         increment_hour(impl, 1);
2134     }
2135 
2136     return end_of_data;
2137 }
2138 
next_hour(icalrecur_iterator * impl)2139 static int next_hour(icalrecur_iterator *impl)
2140 {
2141     int has_by_hour = (impl->by_ptrs[BY_HOUR][0] != ICAL_RECURRENCE_ARRAY_MAX);
2142     int this_frequency = (impl->rule.freq == ICAL_HOURLY_RECURRENCE);
2143 
2144     int end_of_data = 0;
2145 
2146     assert(has_by_hour || this_frequency);
2147 
2148     if (next_minute(impl) == 0) {
2149         return 0;
2150     }
2151 
2152     if (has_by_hour) {
2153         /* Ignore the frequency and use the byrule data */
2154 
2155         impl->by_indices[BY_HOUR]++;
2156 
2157         if (impl->by_ptrs[BY_HOUR][impl->by_indices[BY_HOUR]] ==
2158             ICAL_RECURRENCE_ARRAY_MAX) {
2159             impl->by_indices[BY_HOUR] = 0;
2160 
2161             end_of_data = 1;
2162         }
2163 
2164         set_hour(impl, impl->by_ptrs[BY_HOUR][impl->by_indices[BY_HOUR]]);
2165 
2166     } else if (!has_by_hour && this_frequency) {
2167         /* Compute the next value from the last time and the freq interval */
2168         increment_hour(impl, impl->rule.interval);
2169     }
2170 
2171     /* If we have gone through all of the hours on the BY list, then we
2172        need to move to the next day */
2173 
2174     if (has_by_hour && end_of_data && this_frequency) {
2175         increment_monthday(impl, 1);
2176     }
2177 
2178     return end_of_data;
2179 }
2180 
next_day(icalrecur_iterator * impl)2181 static int next_day(icalrecur_iterator *impl)
2182 {
2183     int has_by_day = (impl->by_ptrs[BY_DAY][0] != ICAL_RECURRENCE_ARRAY_MAX);
2184     int this_frequency = (impl->rule.freq == ICAL_DAILY_RECURRENCE);
2185 
2186     assert(has_by_day || this_frequency);
2187     _unused(has_by_day);
2188 
2189     if (next_hour(impl) == 0) {
2190         return 0;
2191     }
2192 
2193     /* Always increment through the interval, since this routine is not
2194        called by any other next_* routine, and the days that are
2195        excluded will be taken care of by restriction filtering */
2196 
2197     if (this_frequency) {
2198         increment_monthday(impl, impl->rule.interval);
2199     } else {
2200         increment_monthday(impl, 1);
2201     }
2202 
2203     return 0;
2204 }
2205 
check_set_position(icalrecur_iterator * impl,int set_pos)2206 static int check_set_position(icalrecur_iterator *impl, int set_pos)
2207 {
2208     int i;
2209     int found = 0;
2210 
2211     for (i = 0;
2212          i < ICAL_BY_SETPOS_SIZE &&
2213              impl->rule.by_set_pos[i] != ICAL_RECURRENCE_ARRAY_MAX;
2214          i++) {
2215         if (impl->rule.by_set_pos[i] == set_pos) {
2216             found = 1;
2217             break;
2218         }
2219     }
2220     return found;
2221 }
2222 
2223 /* Add each BYMONTHDAY to the year days bitmask */
expand_bymonth_days(icalrecur_iterator * impl,int year,int month)2224 static int expand_bymonth_days(icalrecur_iterator *impl, int year, int month)
2225 {
2226     int i, set_pos_total = 0;
2227     int days_in_month = get_days_in_month(impl, month, year);
2228 
2229     for (i = 0; BYMDPTR[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) {
2230         short doy = 0, mday = BYMDPTR[i];
2231         int this_month = month;
2232 
2233         if (abs(mday) > days_in_month) {
2234             int days_in_year = get_days_in_year(impl, year);
2235 
2236             switch (impl->rule.skip) {
2237             default:
2238                 /* Should never get here! */
2239 
2240             case ICAL_SKIP_OMIT:
2241                 continue;
2242 
2243             case ICAL_SKIP_FORWARD:
2244                 if (mday > 0) this_month++;  /* Next month */
2245 
2246                 if (this_month > get_months_in_year(impl, year)) {
2247                     doy = days_in_year + 1;  /* First day of next year */
2248                 } else {
2249                     mday = 1;                /* First day of month */
2250                 }
2251                 break;
2252 
2253             case ICAL_SKIP_BACKWARD:
2254                 if (mday < 0) {
2255                     this_month--;  /* Prev month */
2256                 }
2257 
2258                 if (this_month == 0) {
2259                     doy = -1;      /* Last day of prev year */
2260                 } else {
2261                     mday = -1;     /* Last day of month */
2262                 }
2263                 break;
2264             }
2265         }
2266 
2267         if (!doy) {
2268             doy = get_day_of_year(impl, year, this_month, mday, NULL);
2269         }
2270 
2271         daysmask_setbit(impl->days, doy, 1);
2272         set_pos_total++;
2273         if (doy < impl->days_index) impl->days_index = doy;
2274     }
2275 
2276     return set_pos_total;
2277 }
2278 
2279 /* Expand the BYDAY rule part and apply it to the year days map. */
expand_by_day(icalrecur_iterator * impl,int year,int doy_offset,int last_day,int first_dow,int last_dow,int is_limiting)2280 static int expand_by_day(icalrecur_iterator *impl, int year,
2281                          int doy_offset, int last_day,
2282                          int first_dow, int last_dow,
2283                          int is_limiting)
2284 {
2285     /* Try to calculate each of the occurrences. */
2286     unsigned long bydays[LONGS_PER_BITS(ICAL_YEARDAYS_MASK_SIZE)];
2287     int i, set_pos_total = 0;
2288     short doy;
2289 
2290     daysmask_clearall(bydays);
2291 
2292     for (i = 0; BYDAYPTR[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) {
2293         /* This is 1 (Sun) to 7 (Sat). */
2294         int dow = icalrecurrencetype_day_day_of_week(BYDAYPTR[i]);
2295         int pos = icalrecurrencetype_day_position(BYDAYPTR[i]);
2296         int first_matching_day, last_matching_day;
2297         int day, this_weekno;
2298 
2299         /* Calculate the first day in the period
2300            with the given weekday, and the last day. */
2301         first_matching_day = ((dow + 7 - first_dow) % 7) + 1;
2302         last_matching_day = last_day - ((last_dow + 7 - dow) % 7);
2303 
2304         if (pos == 0) {
2305             /* First instance of the weekday within the period.
2306                (Remaining instances added by loop below. */
2307             day = first_matching_day;
2308 
2309         } else if (pos > 0) {
2310             /* nth instance of the weekday within the period. */
2311             day = first_matching_day + (pos - 1) * 7;
2312 
2313             if (day > last_matching_day) {
2314                 continue;
2315             }
2316 
2317         } else { /* pos < 0 */
2318             /* -nth instance of the weekday within the period. */
2319             day = last_matching_day + (pos + 1) * 7;
2320 
2321             if (day < first_matching_day) {
2322                 continue;
2323             }
2324         }
2325 
2326         (void)__icaltime_from_day_of_year(impl, day + doy_offset, year,
2327                                           &this_weekno);
2328 
2329         /* Add instance(s) of the weekday within the period */
2330         do {
2331             int valid = 1;
2332 
2333             if (has_by_data(impl, BY_WEEK_NO)) {
2334                 /* Make sure our day falls in one of the BYWEEKNO */
2335                 int j;
2336 
2337                 for (j = 0; BYWEEKPTR[j] != ICAL_RECURRENCE_ARRAY_MAX; j++) {
2338                     int weekno = BYWEEKPTR[j];
2339 
2340                     if (weekno != this_weekno) valid = 0;
2341                 }
2342             }
2343 
2344             if (valid) {
2345                 daysmask_setbit(bydays, day + doy_offset, 1);
2346             }
2347 
2348         } while (!pos && ((day += 7) <= last_day) && ++this_weekno);
2349     }
2350 
2351     /* Apply bydays map to the year days bitmask */
2352     for (doy = doy_offset+1; doy <= doy_offset + last_day; doy++) {
2353         int valid;
2354 
2355         if (is_limiting) {
2356             /* "Filter" the year days bitmask with the bydays bitmask */
2357             valid = (int)(daysmask_getbit(impl->days, doy) &
2358                           daysmask_getbit(bydays, doy));
2359         } else {
2360             /* Add each BYDAY to the year days bitmask */
2361             valid = (int)daysmask_getbit(bydays, doy);
2362         }
2363 
2364         daysmask_setbit(impl->days, doy, valid);
2365 
2366         if (valid) {
2367             set_pos_total++;
2368             if (doy < impl->days_index) impl->days_index = doy;
2369         }
2370     }
2371 
2372     return set_pos_total;
2373 }
2374 
2375 /* "Filter" the year days bitmask with each BYSETPOS */
filter_bysetpos(icalrecur_iterator * impl,int pos_total,int start_doy,int end_doy)2376 static void filter_bysetpos(icalrecur_iterator *impl, int pos_total,
2377                             int start_doy, int end_doy)
2378 {
2379     int pos_count = 0;
2380     short doy;
2381 
2382     impl->days_index = ICAL_YEARDAYS_MASK_SIZE;
2383 
2384     for (doy = start_doy; doy <= end_doy; doy++) {
2385         if (daysmask_getbit(impl->days, doy)) {
2386 
2387             daysmask_setbit(impl->days, doy,
2388                             (check_set_position(impl, pos_count + 1) ||
2389                              check_set_position(impl, pos_count - pos_total)));
2390 
2391             if (daysmask_getbit(impl->days, doy) && doy < impl->days_index) {
2392                 impl->days_index = doy;
2393             }
2394             pos_count++;
2395         }
2396     }
2397 }
2398 
2399 /* For INTERVAL=MONTHLY, set up the year days bitmask in the iterator to
2400    list all of the days of the current month that are specified in this
2401    rule. */
expand_month_days(icalrecur_iterator * impl,int year,int month)2402 static int expand_month_days(icalrecur_iterator *impl, int year, int month)
2403 {
2404     int doy_offset, days_in_month, first_dow, set_pos_total;
2405 
2406     daysmask_clearall(impl->days);
2407 
2408     /* We may end up skipping fwd/bwd a month during expansion.
2409        Mark our current start date so next_month() can increment from here */
2410     impl->period_start = occurrence_as_icaltime(impl, 0);
2411 
2412     doy_offset = get_day_of_year(impl, year, month, 1, &first_dow) - 1;
2413     days_in_month = get_days_in_month(impl, month, year);
2414 
2415     /* Add each BYMONTHDAY to the year days bitmask */
2416     set_pos_total = expand_bymonth_days(impl, year, month);
2417 
2418     if (has_by_data(impl, BY_DAY)) {
2419         /* Apply each BYDAY to the year days bitmask */
2420         int last_dow;
2421 
2422         impl->days_index = ICAL_YEARDAYS_MASK_SIZE;
2423 
2424         (void)get_day_of_year(impl, year, month, days_in_month, &last_dow);
2425 
2426         set_pos_total = expand_by_day(impl, year, doy_offset, days_in_month,
2427                                       first_dow, last_dow,
2428                                       has_by_data(impl, BY_MONTH_DAY));
2429     }
2430 
2431     if (has_by_data(impl, BY_SET_POS)) {
2432         /* "Filter" the year days bitmask with each BYSETPOS */
2433         filter_bysetpos(impl, set_pos_total,
2434                         doy_offset + 1, doy_offset + days_in_month);
2435     }
2436 
2437     return 0;
2438 }
2439 
__next_month(icalrecur_iterator * impl)2440 static void __next_month(icalrecur_iterator *impl)
2441 {
2442     struct icaltimetype this;
2443 
2444     /* Increment to and expand the next month */
2445     increment_month(impl);
2446     this = occurrence_as_icaltime(impl, 0);
2447     expand_month_days(impl, this.year, this.month);
2448 }
2449 
next_month(icalrecur_iterator * impl)2450 static int next_month(icalrecur_iterator *impl)
2451 {
2452     return next_yearday(impl, &__next_month);
2453 }
2454 
next_weekday_by_week(icalrecur_iterator * impl)2455 static int next_weekday_by_week(icalrecur_iterator *impl)
2456 {
2457     int end_of_data = 0;
2458     int start_of_week, dow;
2459 
2460     if (next_hour(impl) == 0) {
2461         return 0;
2462     }
2463 
2464     if (!has_by_data(impl, BY_DAY)) {
2465         return 1;
2466     }
2467 
2468     /* If we get here, we need to step to the next day */
2469 
2470     for (;;) {
2471         BYDAYIDX++;     /* Look at next elem in BYDAY array */
2472 
2473         /* Are we at the end of the BYDAY array? */
2474         if (BYDAYPTR[BYDAYIDX] == ICAL_RECURRENCE_ARRAY_MAX) {
2475             BYDAYIDX = 0;       /* Reset to 0 */
2476             end_of_data = 1;    /* Signal that we're at the end */
2477         }
2478 
2479         /* Add the day of week offset to to the start of this week, and use
2480            that to get the next day */
2481         /* ignore position of dow ("4FR"), only use dow ("FR") */
2482         dow = icalrecurrencetype_day_day_of_week(BYDAYPTR[BYDAYIDX]);
2483         dow -= impl->rule.week_start;   /* Set Sunday to be 0 */
2484         if (dow < 0) {
2485             dow += 7;
2486         }
2487 
2488         start_of_week = get_start_of_week(impl);
2489 
2490         if (dow + start_of_week < 1) {
2491             /* The selected date is in the previous year. */
2492             if (!end_of_data) {
2493                 continue;
2494             }
2495 
2496             increment_year(impl, -1);
2497             start_of_week--;    /* set_day_of_year() assumes last doy == -1 */
2498         }
2499 
2500         set_day_of_year(impl, start_of_week + dow);
2501 
2502         return end_of_data;
2503     }
2504 }
2505 
next_week(icalrecur_iterator * impl)2506 static int next_week(icalrecur_iterator *impl)
2507 {
2508     int end_of_data = 0;
2509 
2510     /* Increment to the next week day,
2511        if there is data at a level less than a week */
2512     if (next_weekday_by_week(impl) == 0) {
2513         return 0;       /* Have not reached end of week yet */
2514     }
2515 
2516     /* If we get here, we have incremented through the entire week, and
2517        can increment to the next week */
2518 
2519     /* Jump to the next week */
2520     increment_monthday(impl, 7 * impl->rule.interval);
2521 
2522     return end_of_data;
2523 }
2524 
2525 /* For INTERVAL=YEARLY, set up the year days bitmask in the iterator to
2526    list all of the days of the current year that are specified in this
2527    rule. */
expand_year_days(icalrecur_iterator * impl,int year)2528 static int expand_year_days(icalrecur_iterator *impl, int year)
2529 {
2530     int i;
2531     int set_pos_total = 0;
2532     short days_in_year = (short)get_days_in_year(impl, year);
2533     short doy;
2534 
2535     daysmask_clearall(impl->days);
2536 
2537     /* We may end up skipping fwd/bwd a year during expansion.
2538        Mark our current start date so next_year() can increment from here */
2539     impl->period_start = occurrence_as_icaltime(impl, 0);
2540 
2541     if (has_by_data(impl, BY_YEAR_DAY)) {
2542         /* We only support BYYEARDAY + BYDAY */
2543         if (has_by_data(impl, BY_WEEK_NO) ||
2544             has_by_data(impl, BY_MONTH) || has_by_data(impl, BY_MONTH_DAY)) {
2545             icalerror_set_errno(ICAL_UNIMPLEMENTED_ERROR);
2546             return 0;
2547         }
2548 
2549         /* Add each BYYEARDAY to the year days bitmask */
2550         for (i = 0; BYYDPTR[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) {
2551             doy = BYYDPTR[i];
2552 
2553             if (abs(doy) > days_in_year) {
2554                 switch (impl->rule.skip) {
2555                 default:
2556                     /* Should never get here! */
2557 
2558                 case ICAL_SKIP_OMIT:
2559                     /* Invalid day */
2560                     continue;
2561 
2562                 case ICAL_SKIP_FORWARD:
2563                     if (doy < 0) {
2564                         doy = 1;                 /* First day of this year */
2565                     } else {
2566                         doy = days_in_year + 1;  /* First day of next year */
2567                     }
2568                     break;
2569 
2570                 case ICAL_SKIP_BACKWARD:
2571                     if (doy < 0) {
2572                         doy = -1;                /* Last day of prev year */
2573                     } else {
2574                         doy = days_in_year;      /* Last day of this year */
2575                     }
2576                     break;
2577                 }
2578             } else if (doy < 0) {
2579                 doy += days_in_year + 1;
2580             }
2581 
2582             daysmask_setbit(impl->days, doy, 1);
2583             set_pos_total++;
2584             if (doy < impl->days_index) impl->days_index = doy;
2585         }
2586     }
2587     else if (has_by_data(impl, BY_WEEK_NO)) {
2588         int weekno, start_doy;
2589 
2590         /* We only support BYWEEKNO + BYDAY */
2591         if (has_by_data(impl, BY_YEAR_DAY) ||
2592             has_by_data(impl, BY_MONTH_DAY) ||
2593             (has_by_data(impl, BY_MONTH) && !has_by_data(impl, BY_DAY))) {
2594             icalerror_set_errno(ICAL_UNIMPLEMENTED_ERROR);
2595             return 0;
2596         }
2597 
2598         /* Calculate location of DTSTART day in weekno 1 */
2599         doy = get_day_of_year(impl, year,
2600                               impl->dtstart.month, impl->dtstart.day, NULL);
2601         (void)__icaltime_from_day_of_year(impl, doy, year, &weekno);
2602         if (weekno > doy) weekno = 0;
2603         start_doy = doy - 7 * (weekno - 1);
2604 
2605         /* Add day of week in each BYWEEKNO to the year days bitmask */
2606         for (i = 0; BYWEEKPTR[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) {
2607             weekno = BYWEEKPTR[i];
2608 
2609             doy = start_doy + 7 * (weekno - 1);
2610 
2611             daysmask_setbit(impl->days, doy, 1);
2612             set_pos_total++;
2613             if (doy < impl->days_index) impl->days_index = doy;
2614         }
2615     } else {
2616         /* Add each BYMONTHDAY in each BYMONTH to the year days bitmask */
2617         for (i = 0; BYMONPTR[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) {
2618             int month = set_month(impl, BYMONPTR[i]);
2619 
2620             if (month) set_pos_total += expand_bymonth_days(impl, year, month);
2621         }
2622     }
2623 
2624     if (has_by_data(impl, BY_DAY)) {
2625         /* Apply each BYDAY to the year days bitmask */
2626         int limiting =
2627             has_by_data(impl, BY_YEAR_DAY) || has_by_data(impl, BY_MONTH_DAY);
2628         int first_dow, last_dow;
2629 
2630         impl->days_index = ICAL_YEARDAYS_MASK_SIZE;
2631         set_pos_total = 0;
2632 
2633         if (has_by_data(impl, BY_MONTH)) {
2634             /* Numeric BYDAY are within each month */
2635 
2636             for (i = 0; BYMONPTR[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) {
2637                 short month = BYMONPTR[i];
2638                 int doy_offset, days_in_month;
2639 
2640                 /* Get offset within year & day of week of first day of month */
2641                 doy_offset =
2642                     get_day_of_year(impl, year, month, 1, &first_dow) - 1;
2643 
2644                 /* Get day of week of last day of month */
2645                 days_in_month = get_days_in_month(impl, month, year);
2646                 (void)get_day_of_year(impl, year,
2647                                       month, days_in_month, &last_dow);
2648 
2649                 set_pos_total += expand_by_day(impl, year,
2650                                                doy_offset, days_in_month,
2651                                                first_dow, last_dow, limiting);
2652             }
2653         } else {
2654             /* Numeric BYDAY are within the year */
2655 
2656             /* Get day of week of first day of year */
2657             (void)get_day_of_year(impl, year, 1, 1, &first_dow);
2658 
2659             /* Get day of week of last day of year */
2660             set_day_of_year(impl, days_in_year);
2661             last_dow = get_day_of_week(impl);
2662 
2663             set_pos_total += expand_by_day(impl, year, 0, days_in_year,
2664                                            first_dow, last_dow, limiting);
2665         }
2666     }
2667 
2668     if (has_by_data(impl, BY_SET_POS)) {
2669         /* "Filter" the year days bitmask with each BYSETPOS */
2670         filter_bysetpos(impl, set_pos_total, 1, days_in_year);
2671     }
2672 
2673     return 0;
2674 }
2675 
__next_year(icalrecur_iterator * impl)2676 static void __next_year(icalrecur_iterator *impl)
2677 {
2678     struct icaltimetype this;
2679 
2680     /* Increment to and expand the next year */
2681     increment_year(impl, impl->rule.interval);
2682     this = occurrence_as_icaltime(impl, 0);
2683     expand_year_days(impl, this.year);
2684 }
2685 
next_year(icalrecur_iterator * impl)2686 static int next_year(icalrecur_iterator *impl)
2687 {
2688     return next_yearday(impl, &__next_year);
2689 }
2690 
next_yearday(icalrecur_iterator * impl,void (* next_period)(icalrecur_iterator *))2691 static int next_yearday(icalrecur_iterator *impl,
2692                         void (*next_period)(icalrecur_iterator *))
2693 {
2694     struct icaltimetype start = impl->period_start;
2695 
2696     if (next_hour(impl) == 0) {
2697         return 0;
2698     }
2699 
2700     /* We may have skipped fwd/bwd a month/year with previous occurrence.
2701        Reset the period start date so we can increment properly */
2702     (void)get_day_of_year(impl, start.year, start.month, start.day, NULL);
2703 
2704     /* Find next year day that is set */
2705     while (++impl->days_index < ICAL_YEARDAYS_MASK_SIZE &&
2706            !daysmask_getbit(impl->days, impl->days_index));
2707 
2708     if (impl->days_index >= ICAL_YEARDAYS_MASK_SIZE) {
2709 
2710         for (;;) {
2711             /* Increment to and expand the next period */
2712             next_period(impl);
2713 
2714             if (impl->days_index < ICAL_YEARDAYS_MASK_SIZE) {
2715                 break;  /* break when a matching day is found */
2716             }
2717         }
2718     }
2719 
2720     if (impl->days_index < 0) {
2721         /* Day is in previous year */
2722         increment_year(impl, -1);
2723     }
2724 
2725     set_day_of_year(impl, impl->days_index);
2726 
2727     return 1;
2728 }
2729 
icalrecur_check_rulepart(icalrecur_iterator * impl,int v,enum byrule byrule)2730 int icalrecur_check_rulepart(icalrecur_iterator *impl,
2731                              int v, enum byrule byrule)
2732 {
2733     int itr;
2734 
2735     if (impl->by_ptrs[byrule][0] != ICAL_RECURRENCE_ARRAY_MAX) {
2736         for (itr = 0;
2737              impl->by_ptrs[byrule][itr] != ICAL_RECURRENCE_ARRAY_MAX; itr++) {
2738             if (impl->by_ptrs[byrule][itr] == v) {
2739                 return 1;
2740             }
2741         }
2742     }
2743 
2744     return 0;
2745 }
2746 
check_contract_restriction(icalrecur_iterator * impl,enum byrule byrule,int v)2747 static int check_contract_restriction(icalrecur_iterator *impl,
2748                                       enum byrule byrule, int v)
2749 {
2750     int pass = 0;
2751     int itr;
2752     icalrecurrencetype_frequency freq = impl->rule.freq;
2753 
2754     if (impl->by_ptrs[byrule][0] != ICAL_RECURRENCE_ARRAY_MAX &&
2755         expand_map[freq].map[byrule] == CONTRACT) {
2756         for (itr = 0;
2757              impl->by_ptrs[byrule][itr] != ICAL_RECURRENCE_ARRAY_MAX; itr++) {
2758             if (impl->by_ptrs[byrule][itr] == v) {
2759                 pass = 1;
2760                 break;
2761             }
2762         }
2763 
2764         return pass;
2765     } else {
2766         /* This is not a contracting byrule, or it has no data, so the
2767            test passes */
2768         return 1;
2769     }
2770 }
2771 
check_contracting_rules(icalrecur_iterator * impl)2772 static int check_contracting_rules(icalrecur_iterator *impl)
2773 {
2774     struct icaltimetype last = occurrence_as_icaltime(impl, 0);
2775     int day_of_week;
2776     int week_no = get_week_number(impl, last);
2777     int year_day =
2778         get_day_of_year(impl, last.year, last.month, last.day, &day_of_week);
2779 
2780     if (check_contract_restriction(impl, BY_SECOND, last.second) &&
2781         check_contract_restriction(impl, BY_MINUTE, last.minute) &&
2782         check_contract_restriction(impl, BY_HOUR, last.hour) &&
2783         check_contract_restriction(impl, BY_DAY, day_of_week) &&
2784         check_contract_restriction(impl, BY_WEEK_NO, week_no) &&
2785         check_contract_restriction(impl, BY_MONTH_DAY, last.day) &&
2786         check_contract_restriction(impl, BY_MONTH, last.month) &&
2787         check_contract_restriction(impl, BY_YEAR_DAY, year_day)) {
2788 
2789         return 1;
2790     } else {
2791         return 0;
2792     }
2793 }
2794 
icalrecur_iterator_next(icalrecur_iterator * impl)2795 struct icaltimetype icalrecur_iterator_next(icalrecur_iterator *impl)
2796 {
2797     /* Quit if we reached COUNT or if last time is after the UNTIL time */
2798     if (!impl ||
2799         (impl->rule.count != 0 && impl->occurrence_no >= impl->rule.count) ||
2800         (!icaltime_is_null_time(impl->rule.until) &&
2801          icaltime_compare(impl->last, impl->rule.until) > 0)) {
2802         return icaltime_null_time();
2803     }
2804 
2805     /* If initial time is valid, return it */
2806     if ((impl->occurrence_no == 0) &&
2807         (icaltime_compare(impl->last, impl->istart) >= 0) &&
2808         check_contracting_rules(impl)) {
2809 
2810         impl->occurrence_no++;
2811         return impl->last;
2812     }
2813 
2814     /* Iterate until we get the next valid time */
2815     do {
2816         switch (impl->rule.freq) {
2817 
2818         case ICAL_SECONDLY_RECURRENCE:
2819             next_second(impl);
2820             break;
2821 
2822         case ICAL_MINUTELY_RECURRENCE:
2823             next_minute(impl);
2824             break;
2825 
2826         case ICAL_HOURLY_RECURRENCE:
2827             next_hour(impl);
2828             break;
2829 
2830         case ICAL_DAILY_RECURRENCE:
2831             next_day(impl);
2832             break;
2833 
2834         case ICAL_WEEKLY_RECURRENCE:
2835             next_week(impl);
2836             break;
2837 
2838         case ICAL_MONTHLY_RECURRENCE:
2839             next_month(impl);
2840             break;
2841 
2842         case ICAL_YEARLY_RECURRENCE:
2843             next_year(impl);
2844             break;
2845 
2846         default:
2847             icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
2848             return icaltime_null_time();
2849         }
2850 
2851         impl->last = occurrence_as_icaltime(impl, 1);
2852 
2853         /* Ignore times that are after the MAX year or the UNTIL time */
2854         if (impl->last.year > MAX_TIME_T_YEAR ||
2855             (!icaltime_is_null_time(impl->rule.until) &&
2856              icaltime_compare(impl->last, impl->rule.until) > 0)) {
2857             return icaltime_null_time();
2858         }
2859         if (impl->last.year > MAX_TIME_T_YEAR) {
2860             /* HACK */
2861             return icaltime_null_time();
2862         }
2863 
2864     } while (icaltime_compare(impl->last, impl->istart) < 0 ||
2865              !check_contracting_rules(impl));
2866 
2867     impl->occurrence_no++;
2868 
2869     return impl->last;
2870 }
2871 
__iterator_set_start(icalrecur_iterator * impl,icaltimetype start)2872 static int __iterator_set_start(icalrecur_iterator *impl, icaltimetype start)
2873 {
2874     icalrecurrencetype_frequency freq = impl->rule.freq;
2875     short interval = impl->rule.interval;
2876     int diff;
2877 
2878     impl->istart = start;
2879     impl->occurrence_no = 0;
2880     impl->days_index = ICAL_YEARDAYS_MASK_SIZE;
2881 
2882     /* Set Gregorian start date */
2883     set_datetime(impl, start);
2884 
2885     switch (freq) {
2886     case ICAL_YEARLY_RECURRENCE:
2887         /* For YEARLY rule, begin by setting up the year days array.
2888            The YEARLY rules work by expanding one year at a time. */
2889 
2890         if ((interval > 1) &&
2891             (diff = (impl->istart.year - impl->rstart.year) % interval)) {
2892             /* Specified start year doesn't match interval -
2893                bump start to first day of next year that matches interval */
2894             set_day_of_year(impl, 1);
2895             increment_year(impl, interval - diff);
2896         }
2897 
2898         /* Get (adjusted) start date as RSCALE date */
2899         start = occurrence_as_icaltime(impl, 0);
2900 
2901         /* Expand days array for (adjusted) start year -
2902            fail after hitting the year 20000 if no expanded days match */
2903         while (start.year < 20000) {
2904             expand_year_days(impl, start.year);
2905             if (icalerrno != ICAL_NO_ERROR) {
2906                 icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
2907                 return 0;
2908             }
2909             if (impl->days_index < ICAL_YEARDAYS_MASK_SIZE) {
2910                 break;  /* break when a matching day is found */
2911             }
2912             increment_year(impl, interval);
2913             start = occurrence_as_icaltime(impl, 0);
2914         }
2915 
2916         /* Copy the first day into last */
2917         set_day_of_year(impl, impl->days_index);
2918 
2919         break;
2920 
2921     case ICAL_MONTHLY_RECURRENCE:
2922         /* For MONTHLY rule, begin by setting up the year days array.
2923            The MONTHLY rules work by expanding one month at a time. */
2924 
2925         if ((interval > 1) &&
2926             (diff = month_diff(impl, impl->rstart, impl->istart) % interval)) {
2927             /* Specified month doesn't match interval -
2928                bump start to first day of next month that matches interval */
2929             increment_monthday(impl, -impl->istart.day + 1);
2930             __increment_month(impl, interval - diff);
2931         }
2932 
2933         /* Get (adjusted) start date as RSCALE date */
2934         start = occurrence_as_icaltime(impl, 0);
2935 
2936         /* Expand days array for (adjusted) start month -
2937            fail after hitting the year 20000 if no expanded days match */
2938         while (start.year < 20000) {
2939             expand_month_days(impl, start.year, start.month);
2940             if (impl->days_index < ICAL_YEARDAYS_MASK_SIZE) {
2941                 break;  /* break when a matching day is found */
2942             }
2943             increment_month(impl);
2944             start = occurrence_as_icaltime(impl, 0);
2945         }
2946 
2947         /* Copy the first day into last */
2948         set_day_of_year(impl, impl->days_index);
2949 
2950         break;
2951 
2952     case ICAL_WEEKLY_RECURRENCE:
2953         if (impl->by_ptrs[BY_DAY][0] == ICAL_RECURRENCE_ARRAY_MAX) {
2954             /* Weekly recurrences with no BY_DAY data should occur on the
2955                same day of the week as the start time . */
2956             impl->by_ptrs[BY_DAY][0] = (short)get_day_of_week(impl);
2957 
2958         } else {
2959             adjust_to_byday(impl);
2960 
2961             /* If start == DTSTART, adjust rstart */
2962             if (icaltime_compare(start, impl->dtstart) == 0) {
2963                 impl->rstart = occurrence_as_icaltime(impl, 0);
2964             }
2965 
2966             /* Get (adjusted) start date as RSCALE date */
2967             start = occurrence_as_icaltime(impl, 0);
2968 
2969             if ((interval > 1) &&
2970                 (diff = (day_diff(impl, impl->rstart, start) + 6) / 7) % interval) {
2971                 /* Specified week doesn't match interval -
2972                    bump start to next week that matches interval */
2973                 increment_monthday(impl, 7 * (interval - diff));
2974             }
2975         }
2976         break;
2977 
2978     case ICAL_DAILY_RECURRENCE:
2979         if ((interval > 1) &&
2980             (diff = day_diff(impl, impl->rstart, impl->istart) % interval)) {
2981             /* Specified day doesn't match interval -
2982                bump start to next day that matches interval */
2983             increment_monthday(impl, interval - diff);
2984         }
2985         break;
2986 
2987     default:
2988         break;
2989     }
2990 
2991     /* Get start date as Gregorian date */
2992     impl->last = occurrence_as_icaltime(impl, 1);
2993 
2994     /* Fail if first instance exceeds MAX_TIME_T_YEAR */
2995     if (impl->last.year > MAX_TIME_T_YEAR) {
2996         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
2997         return 0;
2998     }
2999 
3000     return 1;
3001 }
3002 
icalrecur_iterator_set_start(icalrecur_iterator * impl,struct icaltimetype start)3003 int icalrecur_iterator_set_start(icalrecur_iterator *impl,
3004                                  struct icaltimetype start)
3005 {
3006     /* We can't adjust start date if we need to count occurrences */
3007     if (impl->rule.count > 0) {
3008         icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR);
3009         return 0;
3010     }
3011 
3012     /* Convert start to same time zone as DTSTART */
3013     start = icaltime_convert_to_zone(start, (icaltimezone *) impl->dtstart.zone);
3014 
3015     if (icaltime_compare(start, impl->dtstart) < 0) {
3016         /* If start is before DTSTART, use DTSTART */
3017         start = impl->dtstart;
3018     }
3019     else if (!icaltime_is_null_time(impl->rule.until) &&
3020         icaltime_compare(start, impl->rule.until) > 0) {
3021         /* If start is after UNTIL, we're done */
3022         impl->last = start;
3023         return 1;
3024     }
3025 
3026     return __iterator_set_start(impl, start);
3027 }
3028 
3029 /************************** Type Routines **********************/
3030 
icalrecurrencetype_clear(struct icalrecurrencetype * recur)3031 void icalrecurrencetype_clear(struct icalrecurrencetype *recur)
3032 {
3033     memset(recur,
3034            ICAL_RECURRENCE_ARRAY_MAX_BYTE, sizeof(struct icalrecurrencetype));
3035 
3036     recur->week_start = ICAL_MONDAY_WEEKDAY;
3037     recur->freq = ICAL_NO_RECURRENCE;
3038     recur->interval = 1;
3039     memset(&(recur->until), 0, sizeof(struct icaltimetype));
3040     recur->count = 0;
3041     recur->rscale = NULL;
3042     recur->skip = ICAL_SKIP_OMIT;
3043 }
3044 
3045 /** The 'day' element of icalrecurrencetype_weekday is encoded to
3046  * allow representation of both the day of the week ( Monday, Tuesday),
3047  * but also the Nth day of the week ( First tuesday of the month, last
3048  * thursday of the year) These routines decode the day values.
3049  *
3050  * The day's position in the period ( Nth-ness) and the numerical
3051  * value of the day are encoded together as: pos*7 + dow
3052  *
3053  * A position of 0 means 'any' or 'every'
3054  */
3055 
icalrecurrencetype_day_day_of_week(short day)3056 enum icalrecurrencetype_weekday icalrecurrencetype_day_day_of_week(short day)
3057 {
3058     return abs(day) % 8;
3059 }
3060 
icalrecurrencetype_day_position(short day)3061 int icalrecurrencetype_day_position(short day)
3062 {
3063     int wd, pos;
3064 
3065     wd = icalrecurrencetype_day_day_of_week(day);
3066 
3067     pos = (abs(day) - wd) / 8 * ((day < 0) ? -1 : 1);
3068 
3069     return pos;
3070 }
3071 
3072 /**
3073  * The 'month' element of the by_month array is encoded to allow
3074  * representation of the "L" leap suffix (RFC 7529).
3075  * These routines decode the month values.
3076  *
3077  * The "L" suffix is encoded by setting a high-order bit
3078  */
3079 
icalrecurrencetype_month_is_leap(short month)3080 int icalrecurrencetype_month_is_leap(short month)
3081 {
3082     return (month & LEAP_MONTH);
3083 }
3084 
icalrecurrencetype_month_month(short month)3085 int icalrecurrencetype_month_month(short month)
3086 {
3087     return (month & ~LEAP_MONTH);
3088 }
3089 
3090 /** Fill an array with the 'count' number of occurrences generated by
3091  * the rrule. Note that the times are returned in UTC, but the times
3092  * are calculated in local time. YOu will have to convert the results
3093  * back into local time before using them.
3094  */
3095 
icalrecur_expand_recurrence(const char * rule,time_t start,int count,time_t * array)3096 int icalrecur_expand_recurrence(const char *rule,
3097                                 time_t start, int count, time_t *array)
3098 {
3099     struct icalrecurrencetype recur;
3100     icalrecur_iterator *ritr;
3101     time_t tt;
3102     struct icaltimetype icstart, next;
3103     int i = 0;
3104 
3105     memset(array, 0, count * sizeof(time_t));
3106 
3107     icstart = icaltime_from_timet_with_zone(start, 0, 0);
3108 
3109     recur = icalrecurrencetype_from_string(rule);
3110     ritr = icalrecur_iterator_new(recur, icstart);
3111     if (ritr) {
3112         for (next = icalrecur_iterator_next(ritr);
3113              !icaltime_is_null_time(next) && i < count;
3114              next = icalrecur_iterator_next(ritr)) {
3115 
3116             tt = icaltime_as_timet(next);
3117 
3118             if (tt >= start) {
3119                 array[i++] = tt;
3120             }
3121         }
3122         icalrecur_iterator_free(ritr);
3123     }
3124     if(recur.rscale)
3125         free(recur.rscale);
3126 
3127     return 1;
3128 }
3129