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