]> git.neil.brown.name Git - plato.git/blob - lib/ical-dates.c
ical/cal/alarm - minor fixes
[plato.git] / lib / ical-dates.c
1 /*
2  * ical-dates: library to handle ical dates/times and recurrences.
3  *
4  * Given a list of strings, typically from an ical file, we produce
5  * a data structure which encodes the relevant date info.
6  * Given such a structure, a start date, end date, and max count, we
7  * produce a list of dates.
8  */
9
10 #include <unistd.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <time.h>
14 #include <stdio.h>
15 #include "ical-dates.h"
16
17 static int month_days[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
18 static char *days[7] = { "SU","MO","TU","WE","TH","FR","SA"};
19 static char *freqs[] = { "SECONDLY","MINUTELY","HOURLY",
20                          "DAILY","WEEKLY","MONTHLY","YEARLY"};
21
22 static int daysin(int mon, int yr)
23 {
24         if (mon != 2)
25                 return month_days[mon-1];
26         if (yr%4 > 0)
27                 return 28;
28         if (yr%400 > 0)
29                 return 29;
30         return 28;
31 }
32
33 /* ical_times can be modified by adding/subtracting fields, but
34  * must then be normalised
35  */
36 void ical_set_wday(struct ical_time *t)
37 {
38         int yday = t->day;
39         int m;
40         int yr, wday;
41         for (m = 1; m < t->mon; m++)
42                 yday += daysin(m, t->yr);
43         yr = t->yr - 1;
44         wday = yr + yr/4 - yr/400 + yday + 4;
45         t->yday = yday;
46         t->wday = wday % 7;
47 }
48
49 void ical_norm_mon(struct ical_time *t)
50 {
51         while (t->mon < 1) {
52                 t->mon += 12;
53                 t->yr -= 1;
54         }
55         while (t->mon > 12) {
56                 t->mon -= 12;
57                 t->yr += 1;
58         }
59         ical_set_wday(t);
60 }
61
62 void ical_norm_day(struct ical_time *t)
63 {
64         if (t->day >= 1 && t->day <= daysin(t->mon, t->yr)) {
65                 if (t->wday < 0)
66                         ical_set_wday(t);
67                 return;
68         }
69         while (t->day < 1) {
70                 t->mon -= 1;
71                 ical_norm_mon(t);
72                 t->day += daysin(t->mon, t->yr);
73         }
74         while (t->day > daysin(t->mon, t->yr)) {
75                 t->day -= daysin(t->mon, t->yr);
76                 t->mon += 1;
77                 ical_norm_mon(t);
78         }
79         ical_set_wday(t);
80 }
81
82 void ical_norm_hr(struct ical_time *t)
83 {
84         if (t->hr >= 0 && t->hr < 24)
85                 return;
86         while (t->hr >= 24) {
87                 t->hr -= 24;
88                 t->day += 1;
89         }
90         while (t->hr < 0) {
91                 t->hr += 24;
92                 t->day -= 1;
93         }
94         t->wday = -1; // force wday/yday update
95         ical_norm_day(t);
96 }
97
98 void ical_norm_min(struct ical_time *t)
99 {
100         if (t->min >= 0 && t->min < 60)
101                 return;
102         while (t->min >= 60) {
103                 t->min -= 60;
104                 t->hr += 1;
105         }
106         while (t->min < 0) {
107                 t->min += 60;
108                 t->hr -= 1;
109         }
110         ical_norm_hr(t);
111 }
112
113 void ical_norm_sec(struct ical_time *t)
114 {
115         if (t->sec >= 0 && t->sec < 60)
116                 return;
117         while (t->sec >= 60) {
118                 t->sec -= 60;
119                 t->min += 1;
120         }
121         while (t->sec < 0) {
122                 t->sec += 60;
123                 t->min -= 1;
124         }
125         ical_norm_min(t);
126 }
127
128 int ical_ordered(const struct ical_time *a, const struct ical_time *b)
129 {
130         if (a->yr != b->yr)
131                 return a->yr - b->yr;
132         if (a->yday != b->yday)
133                 return a->yday - b->yday;
134         return (a->hr*3600 + a->min*60 + a->sec) -
135                 (b->hr*3600 + b->min*60 + b->sec);
136 }
137
138 static int ical_orderedv(const void *av, const void *bv)
139 {
140         const struct ical_time *a = av;
141         const struct ical_time *b = bv;
142         return ical_ordered(a,b);
143 }
144
145 void ical_next(struct ical_iter *it)
146 {
147         switch (it->unit) {
148         case Secondly:
149                 it->now.sec += it->step;
150                 ical_norm_sec(&it->now);
151                 break;
152         case Minutely:
153                 it->now.min += it->step;
154                 ical_norm_min(&it->now);
155                 break;
156         case Hourly:
157                 it->now.hr += it->step;
158                 ical_norm_hr(&it->now);
159                 break;
160         case Daily:
161                 it->now.day += it->step;
162                 ical_norm_day(&it->now);
163                 break;
164         case Weekly:
165                 it->now.day += it->step * 7;
166                 ical_norm_day(&it->now);
167                 ical_set_wday(&it->now);
168                 break;
169         case Monthly:
170                 it->now.mon += it->step;
171                 ical_norm_mon(&it->now);
172                 ical_set_wday(&it->now);
173                 break;
174         case Yearly:
175                 it->now.yr += it->step;
176                 ical_set_wday(&it->now);
177                 break;
178         case BadInterval:
179                 break;
180         }
181 }
182
183 static void list_add(struct ical_list *l, int v)
184 {
185         if (l->cnt >= l->size) {
186                 l->size = l->cnt + 8;
187                 l->v = realloc(l->v, l->size * sizeof(v));
188         }
189         l->v[l->cnt++] = v;
190 }
191
192 void ical_list_free(struct ical_list *l)
193 {
194         free(l->v);
195 }
196
197 static void timelist_add(struct ical_timelist *tl, struct ical_time *tm)
198 {
199         if (tl->cnt >= tl->size) {
200                 tl->size = tl->cnt + 8;
201                 tl->v = realloc(tl->v, tl->size * sizeof(*tm));
202         }
203         tl->v[tl->cnt++] = *tm;
204 }
205
206 void ical_timelist_free(struct ical_timelist *tl)
207 {
208         free(tl->v);
209 }
210
211 /* When parsing we destroy the argument string */
212 static int parse_bysimple(struct ical_list *list, char *arg, int min, int max, int neg)
213 {
214         while (*arg) {
215                 char *w = arg;
216                 int l = strcspn(arg, ",");
217                 int v;
218                 char *e;
219                 if (arg[l])
220                         arg[l++] = 0;
221                 arg += l;
222                 v = strtol(w, &e, 10);
223                 if (!*w || *e)
224                         return 0;
225                 if ((v < 0 && !neg) ||
226                     abs(v) < min ||
227                     abs(v) > max)
228                         return 0;
229                 list_add(list, v);
230         }
231         return 1;
232 }
233
234 static int match_day(char *d)
235 {
236         int n;
237         for (n = 0; n < 7; n++)
238                 if (strcmp(d, days[n]) == 0)
239                         return n;
240         return -1;
241 }
242
243 static int parse_byday(struct ical_list lists[7], char *arg)
244 {
245         while (*arg) {
246                 char *w = arg;
247                 int d, v;
248                 int l = strspn(arg, "0123456789+-SUMOTWEHFRA");
249                 arg += l;
250                 if (arg[0] == ',')
251                         *arg++ = 0;
252                 else if (arg[0])
253                         return 0;
254                 if (l < 2)
255                         return 0;
256                 d = match_day(w+l-2);
257                 if (d < 0)
258                         return 0;
259                 if (l == 2)
260                         v = 0;
261                 else {
262                         char *e;
263                         v = strtol(w, &e, 10);
264                         if (e != w+l-2)
265                                 return 0;
266                 }
267                 list_add(&lists[d], v);
268         }
269         return 1;
270 }
271
272 static enum ical_interval parse_freq(char *arg)
273 {
274         enum ical_interval rv;
275         for (rv = Secondly; rv < BadInterval; rv++)
276                 if (strcmp(arg, freqs[rv]) == 0)
277                         return rv;
278         return BadInterval;
279 }
280
281 int ical_parse_rrule(struct ical_rrule *rr, char *arg, char *tz)
282 {
283         /* ';' separated list of X=Y assignments where Y is a
284          * ',' separated list of values
285          */
286         memset(rr, 0, sizeof(*rr));
287         rr->wkst = 1; // MO by default
288         rr->unit = BadInterval;
289         rr->step = 1;
290         while (*arg) {
291                 char *w = arg;
292                 int l = strcspn(arg, ";");
293                 int t;
294                 char *b;
295
296                 if (arg[l])
297                         arg[l++] = 0;
298                 arg += l;
299
300                 t = strcspn(w, "=");
301                 if (w[t] == 0)
302                         return 0;
303                 w[t++] = 0;
304                 b = w+t;
305
306                 if (strcmp(w, "FREQ") == 0) {
307                         rr->unit = parse_freq(b);
308                 } else if (strcmp(w, "INTERVAL") == 0) {
309                         char *e;
310                         rr->step = strtol(b, &e, 10);
311                         if (rr->step < 1 || *b == 0 || *e != 0)
312                                 return 0;
313                 } else if (strcmp(w, "COUNT") == 0) {
314                         char *e;
315                         rr->count = strtol(b, &e, 10);
316                         if (rr->count < 1 || *b == 0 || *e != 0)
317                                 return 0;
318                 } else if (strcmp(w, "UNTIL") == 0) {
319                         if (ical_parse_time(&rr->until, b, tz) == 0)
320                                 return 0;
321                 } else if (strcmp(w, "BYMONTH") == 0) {
322                         if (parse_bysimple(&rr->bymonth, b, 1, 31, 0) == 0)
323                                 return 0;
324                 } else if (strcmp(w, "BYWEEKNO") == 0) {
325                         if (parse_bysimple(&rr->byweekno, b, 0, 53, 1) == 0)
326                                 return 0;
327                 } else if (strcmp(w, "BYYEARDAY") == 0) {
328                         if (parse_bysimple(&rr->byyday, b, 1, 366, 1) == 0)
329                                 return 0;
330                 } else if (strcmp(w, "BYMONTHDAY") == 0) {
331                         if (parse_bysimple(&rr->bymday, b, 1, 31, 1) == 0)
332                                 return 0;
333                 } else if (strcmp(w, "BYDAY") == 0) {
334                         if (parse_byday(rr->bydays, b) == 0)
335                                 return 0;
336                         rr->any_bydays = 1;
337                 } else if (strcmp(w, "BYHOUR") == 0) {
338                         if (parse_bysimple(&rr->byhr, b, 0, 23, 0) == 0)
339                                 return 0;
340                 } else if (strcmp(w, "BYMINUTE") == 0) {
341                         if (parse_bysimple(&rr->bymin, b, 0, 59, 0) == 0)
342                                 return 0;
343                 } else if (strcmp(w, "BYSECOND") == 0) {
344                         if (parse_bysimple(&rr->bysec, b, 0, 59, 0) == 0)
345                                 return 0;
346                 } else if (strcmp(w, "BYSETPOS") == 0) {
347                         if (parse_bysimple(&rr->setpos, b, 1, 366, 1) == 0)
348                                 return 0;
349                 } else if (strcmp(w, "WKST") == 0) {
350                         rr->wkst = match_day(b);
351                         if (rr->wkst < 0)
352                                 return 0;
353                 } else
354                         return 0;
355         }
356         /* FREQ is required. Everything else is optional */
357         if (rr->unit == BadInterval)
358                 return 0;
359         return 1;
360 }
361
362 void ical_rrule_free(struct ical_rrule *rr)
363 {
364         int i;
365         ical_list_free(&rr->bysec);
366         ical_list_free(&rr->bymin);
367         ical_list_free(&rr->byhr);
368         ical_list_free(&rr->bymonth);
369         ical_list_free(&rr->bymday);
370         ical_list_free(&rr->byyday);
371         ical_list_free(&rr->byweekno);
372         ical_list_free(&rr->setpos);
373         for (i=0; i<7; i++)
374                 ical_list_free(&rr->bydays[i]);
375 }
376
377 static int getnum(char *arg, int len)
378 {
379         int n = 0;
380         while (len) {
381                 n = n*10 + arg[0] - '0';
382                 arg++;
383                 len--;
384         }
385         return n;
386 }
387
388 int ical_parse_time(struct ical_time *tm, char *arg, char *tz)
389 {
390         /* 'arg' can by a date or datetime possibly followed by
391          * another date-or-datetime or a period.
392          * For now we just parse the start date[time]
393          */
394         int l;
395
396         memset(tm, 0, sizeof(*tm));
397         l = strspn(arg, "0123456789");
398         if (l != 8)
399                 return 0;
400         tm->yr = getnum(arg, 4);
401         tm->mon = getnum(arg+4, 2);
402         tm->day = getnum(arg+6, 2);
403         if (tm->mon < 1 || tm->mon > 12 ||
404             tm->day < 1 || tm->day > daysin(tm->mon, tm->yr))
405                 return 0;
406         ical_set_wday(tm);
407         if (arg[l] == 0)
408                 return 1;
409         if (arg[l] != '/') {
410                 if (arg[l] != 'T')
411                         /* Need a time now! */
412                         return 0;
413                 arg += l+1;
414                 l = strspn(arg, "0123456789");
415                 if (l != 6)
416                         return 0;
417                 tm->hr = getnum(arg, 2);
418                 tm->min = getnum(arg+2, 2);
419                 tm->sec = getnum(arg+4, 2);
420                 tm->time_set = 1;
421                 if (tm->sec == 60)
422                         /* Leap-sec.  Cannot cope, sorry */
423                         tm->sec = 59;
424                 if (tm->hr >= 24 || tm->min >= 60 || tm->sec >= 60)
425                         return 0;
426                 if (arg[l] == 'Z') {
427                         tm->zone = "UTC";
428                         l++;
429                 } else
430                         tm->zone = tz;
431                 if (arg[l] == 0)
432                         return 1;
433                 if (arg[l] != '/')
434                         return 0;
435         }
436         /* Ignore duration */
437         return 1;
438 }
439
440 int ical_parse_time_list(struct ical_timelist *tl, char *arg, char *tz)
441 {
442         while (*arg) {
443                 char *w = arg;
444                 int l = strcspn(arg, ",");
445                 struct ical_time tm;
446
447                 if (arg[l])
448                         arg[l++] = 0;
449                 arg += l;
450                 if (ical_parse_time(&tm, w, tz) == 0)
451                         return 0;
452                 timelist_add(tl, &tm);
453         }
454         return 1;
455 }
456
457 int ical_parse_dates_line(struct ical_dates *id, char *arg)
458 {
459         int l = strcspn(arg, ":");
460         char *body, *param;
461         char *tz = NULL;
462
463         if (arg[l] == 0)
464                 return 0;
465         arg[l++] = 0;
466         body = arg+l;
467
468         param = arg;
469         /* Look at params, but only care about TZID= */
470         while ((param = strchr(param, ';')) != NULL) {
471                 *param++ = 0;
472                 if (strncmp(param, "TZID=", 5) == 0)
473                         tz = param + 5;
474         }
475
476         if (strcmp(arg, "DTSTART") == 0)
477                 return ical_parse_time(&id->start, body, tz);
478         if (strcmp(arg, "RRULE") == 0)
479                 return ical_parse_rrule(&id->rr, body, tz);
480         if (strcmp(arg, "RDATE") == 0)
481                 return ical_parse_time_list(&id->rdate, body, tz);
482         if (strcmp(arg, "EXDATE") == 0)
483                 return ical_parse_time_list(&id->exdate, body, tz);
484
485         /*put it back like we found it */
486         arg[--l] = ':';
487         while (l) {
488                 l--;
489                 if (!arg[l])
490                         arg[l] = ';';
491         }
492         return -1;
493 }
494
495 void ical_dates_free(struct ical_dates *d)
496 {
497         ical_rrule_free(&d->rr);
498         ical_timelist_free(&d->rdate);
499         ical_timelist_free(&d->exdate);
500 }
501
502 static int inlist(struct ical_list *l, int v)
503 {
504         int i;
505         for (i = 0; i < l->cnt; i++)
506                 if (l->v[i] == v)
507                         return 1;
508         return 0;
509 }
510
511 static int set_mon(struct ical_time *tm, int v, int wkst)
512 {
513         if (tm->day > daysin(v, tm->yr))
514                 return 0;
515         tm->mon = v;
516         return 1;
517 }
518
519 static int get_mon(struct ical_time *tm)
520 {
521         return tm->mon;
522 }
523
524 static int set_mday(struct ical_time *tm, int v, int wkst)
525 {
526         int d = daysin(tm->mon, tm->yr);
527         if (v < 0)
528                 v = d + 1 + v;
529         if (v < 1 || v > d)
530                 return 0;
531         tm->day = v;
532         return 1;
533 }
534
535 static int test_mday(struct ical_time *tm, int v)
536 {
537         if (v > 0)
538                 return tm->day == v;
539         v = daysin(tm->mon, tm->yr) + 1 + v;
540         return tm->day == v;
541 }
542
543 static int set_yday(struct ical_time *tm, int v, int wkst)
544 {
545         int d = daysin(2, tm->yr) - 28 + 365;
546         int m;
547         if (v < 0)
548                 v = d + 1 + v;
549         if (v < 1 || v > d)
550                 return 0;
551         tm->yday = v;
552         for (m=1; m<=12; m++) {
553                 int md = daysin(m, tm->yr);
554                 if (v <= md)
555                         break;
556                 v -= md;
557         }
558         tm->mon = m;
559         tm->day = v;
560         return 1;
561 }
562
563 static int test_yday(struct ical_time *tm, int v)
564 {
565         if (v < 0) {
566                 int d = daysin(2, tm->yr) - 28 + 365;
567                 v = d + 1 + v;
568         }
569         return tm->yday == v;
570 }
571
572 static int set_weekno(struct ical_time *tm, int v, int wkst)
573 {
574         struct ical_time st = *tm;
575         int i;
576         if (v >= 0) {
577                 st.mon = 1;
578                 st.day = -2;
579                 ical_norm_day(&st);
580                 ical_set_wday(&st);
581                 while (st.wday != wkst) {
582                         st.day ++;
583                         ical_norm_day(&st);
584                         ical_set_wday(&st);
585                 }
586                 st.day += (v-1)*7;
587         } else {
588                 st.mon = 12;
589                 st.day = 28;
590                 ical_set_wday(&st);
591                 while (st.wday != wkst) {
592                         st.day --;
593                         ical_set_wday(&st);
594                 }
595                 st.day -= (-1-v)*7;
596         }
597         ical_norm_day(&st);
598         ical_set_wday(&st);
599         /* Now at the start of the week */
600         for (i=0; i<7; i++) {
601                 if (st.yr == tm->yr &&
602                     st.wday == tm->wday) {
603                         *tm = st;
604                         return 1;
605                 }
606                 st.day++;
607                 ical_norm_day(&st);
608                 ical_set_wday(&st);
609         }
610         return 0;
611 }
612
613 static int set_hr(struct ical_time *tm, int v, int wkst)
614 {
615         tm->hr = v;
616         return 1;
617 }
618
619 static int get_hr(struct ical_time *tm)
620 {
621         return tm->hr;
622 }
623
624 static int set_min(struct ical_time *tm, int v, int wkst)
625 {
626         tm->min = v;
627         return 1;
628 }
629
630 static int get_min(struct ical_time *tm)
631 {
632         return tm->min;
633 }
634
635 static int set_sec(struct ical_time *tm, int v, int wkst)
636 {
637         tm->sec = v;
638         return 1;
639 }
640
641 static int get_sec(struct ical_time *tm)
642 {
643         return tm->sec;
644 }
645
646 static void expand(struct ical_timelist *tl,
647                    int (*set)(struct ical_time *tm, int v, int wkst),
648                    struct ical_list *l, int wkst)
649 {
650         struct ical_timelist newl = {0};
651         int i, j;
652         for (i = 0; i < tl->cnt; i++) {
653                 for (j = 0; j < l->cnt; j++) {
654                         struct ical_time t = tl->v[i];
655                         if (set(&t, l->v[j], wkst)) {
656                                 ical_set_wday(&t);
657                                 timelist_add(&newl, &t);
658                         }
659                 }
660         }
661         ical_timelist_free(tl);
662         *tl = newl;
663 }
664
665 static void expand_byday(struct ical_timelist *tl,
666                          struct ical_list *dl, int wkst, enum ical_interval xtype)
667 {
668         struct ical_timelist newl = {0};
669         int i, days, d;
670         for (i = 0; i < tl->cnt; i++) {
671                 struct ical_time st;
672
673                 st = tl->v[i];
674                 if (xtype == Yearly) {
675                         /* Every relevant day in this year */
676                         st.mon = 1;
677                         st.day = 1;
678                         days = daysin(2, st.yr) - 28 + 365;
679                 } else if (xtype == Monthly) {
680                         /* Every relevant day in this month */
681                         st.day = 1;
682                         days = daysin(st.mon, st.yr);
683                 } else {
684                         /* The relevant day in this week */
685                         while (st.wday != wkst) {
686                                 st.day -= 1;
687                                 st.wday = -1;
688                                 ical_norm_day(&st);
689                         }
690                         days = 7;
691                 }
692                 ical_set_wday(&st);
693                 for (d = 0 ; d < days; d++) {
694                         struct ical_list *l = dl + st.wday;
695                         if (inlist(l, 0) || inlist(l, d/7+1) || inlist(l, -((days-d)/7+1)))
696                                 timelist_add(&newl, &st);
697                         st.day++;
698                         ical_norm_day(&st);
699                         ical_set_wday(&st);
700                 }
701         }
702         ical_timelist_free(tl);
703         *tl = newl;
704 }
705
706 static void filter(struct ical_timelist *tl, int(*get)(struct ical_time *tm),
707                    struct ical_list *l)
708 {
709         struct ical_timelist newl = {0};
710         int i;
711         for (i = 0; i < tl->cnt; i++)
712                 if (inlist(l, get(&tl->v[i])))
713                         timelist_add(&newl, &tl->v[i]);
714
715         ical_timelist_free(tl);
716         *tl = newl;
717 }
718
719 static void filter_byday(struct ical_timelist *tl,
720                          struct ical_list *l)
721 {
722         struct ical_timelist newl = {0};
723         int i;
724         for (i = 0; i < tl->cnt; i++)
725                 if (inlist(l+tl->v[i].wday, 0))
726                         timelist_add(&newl, &tl->v[i]);
727
728         ical_timelist_free(tl);
729         *tl = newl;
730 }
731
732 static void filter_test(struct ical_timelist *tl,
733                         int(*test)(struct ical_time *tm, int v),
734                         struct ical_list *l)
735 {
736         struct ical_timelist newl = {0};
737         int i, j;
738         for (i = 0; i < tl->cnt; i++)
739                 for (j = 0; j < l->cnt; j++)
740                         if (test(&tl->v[i], l->v[j])) {
741                                 timelist_add(&newl, &tl->v[i]);
742                                 break;
743                         }
744         ical_timelist_free(tl);
745         *tl = newl;
746 }
747
748 static void filter_setpos(struct ical_timelist *tl, struct ical_list *l)
749 {
750         struct ical_timelist newl = {0};
751         int i;
752         for (i = 0; i < l->cnt; i++) {
753                 int j = l->v[i];
754                 if (j < 0) {
755                         j = tl->cnt + j;
756                         if (inlist(l, j))
757                                 j = -1;
758                 } else
759                         j -= 1;
760                 if (j >= 0 && j < tl->cnt)
761                         timelist_add(&newl, &tl->v[i]);
762         }
763         ical_timelist_free(tl);
764         *tl = newl;
765 }
766
767 static void dedup(struct ical_timelist *tl)
768 {
769         int i;
770         for (i = 1; i < tl->cnt; i++) {
771                 if (ical_ordered(&tl->v[i-1], &tl->v[i]) == 0) {
772                         tl->cnt--;
773                         memmove(&tl->v[i], &tl->v[i+1], (tl->cnt - i)*sizeof(tl->v[0]));
774                         i--;
775                 }
776         }
777 }
778
779 static void trim(struct ical_timelist *tl, struct ical_time *tm)
780 {
781         int i;
782         for (i = 0; i < tl->cnt; i++)
783                 if (ical_ordered(&tl->v[i], tm) >= 0)
784                         break;
785         if (i == 0)
786                 return;
787         tl->cnt -= i;
788         memmove(tl->v, tl->v+i, tl->cnt * sizeof(tl->v[0]));
789 }
790
791 static void timelist_join(struct ical_timelist *a, struct ical_timelist *b)
792 {
793         if (a->size < a->cnt + b->cnt) {
794                 a->size = a->cnt + b->cnt;
795                 a->v = realloc(a->v, a->size * sizeof(a->v[0]));
796         }
797         memcpy(&a->v[a->cnt], b->v, b->cnt * sizeof(b->v[0]));
798         a->cnt += b->cnt;
799         ical_timelist_free(b);
800 }
801
802 void ical_rr_dates(struct ical_time *start, struct ical_rrule *rr,
803                    struct ical_time *from, int max,
804                    struct ical_timelist *rv)
805 {
806         /* generate dates from the given info an return some in 'rv'
807          * We only report dates at or after 'from' and at most 'max'
808          * of them.
809          */
810         struct ical_time last;
811         struct ical_iter it;
812
813         if (start->mon == 0 || rr->unit == BadInterval)
814                 return;
815
816         it.now = *start;
817         it.unit = rr->unit;
818         it.step = rr->step;
819         last = *start;
820
821         if (rr->count  > 0 && rr->count < max)
822                 max = rr->count;
823
824         for ( ;
825               (rv->cnt < max &&
826                (rr->until.mon == 0 || ical_ordered(&last, &rr->until) < 0));
827               ical_next(&it)
828                 ) {
829                 struct ical_timelist nl = {0};
830                 timelist_add(&nl, &it.now);
831
832                 /* BYMONTH */
833                 if (rr->bymonth.cnt) {
834                         if (rr->unit > Monthly)
835                                 expand(&nl, set_mon, &rr->bymonth, rr->wkst);
836                         else
837                                 filter(&nl, get_mon, &rr->bymonth);
838                 }
839                 /* BYWEEKNO */
840                 if (rr->byweekno.cnt) {
841                         if (rr->unit == Yearly)
842                                 expand(&nl, set_weekno, &rr->byweekno, rr->wkst);
843                 }
844                 /* BYYEARDAY */
845                 if (rr->byyday.cnt) {
846                         if (rr->unit == Yearly)
847                                 expand(&nl, set_yday, &rr->byyday, rr->wkst);
848                         else if (rr->unit < Daily)
849                                 filter_test(&nl, test_yday, &rr->byyday);
850                 }
851                 /* BYMONTHDAY */
852                 if (rr->bymday.cnt) {
853                         if (rr->unit > Weekly)
854                                 expand(&nl, set_mday, &rr->bymday, rr->wkst);
855                         else if (rr->unit < Weekly)
856                                 filter_test(&nl, test_mday, &rr->bymday);
857                 }
858                 /* BYDAY */
859                 if (rr->any_bydays) {
860                         enum ical_interval xtype = BadInterval;
861                         switch (rr->unit) {
862                         case Weekly:
863                                 xtype = Weekly; break;
864                         case Monthly:
865                                 if (rr->bymday.cnt == 0)
866                                         xtype = Monthly;
867                                 break;
868                         case Yearly:
869                                 if (rr->byyday.cnt || rr->bymday.cnt)
870                                         /* select, not expand */;
871                                 else if (rr->byweekno.cnt)
872                                         xtype = Weekly;
873                                 else if (rr->bymonth.cnt)
874                                         xtype = Monthly;
875                                 else
876                                         xtype = Yearly;
877                         default:;
878                         }
879                         if (xtype == BadInterval)
880                                 filter_byday(&nl, rr->bydays);
881                         else
882                                 expand_byday(&nl, rr->bydays, rr->wkst, xtype);
883                 }
884                 /* BYHOUR */
885                 if (rr->byhr.cnt) {
886                         if (rr->unit > Hourly)
887                                 expand(&nl, set_hr, &rr->byhr, rr->wkst);
888                         else
889                                 filter(&nl, get_hr, &rr->byhr);
890                 }
891                 /* BYMINUTE */
892                 if (rr->bymin.cnt) {
893                         if (rr->unit > Minutely)
894                                 expand(&nl, set_min, &rr->bymin, rr->wkst);
895                         else
896                                 filter(&nl, get_min, &rr->bymin);
897                 }
898                 /* BYSECOND */
899                 if (rr->bysec.cnt) {
900                         if (rr->unit > Secondly)
901                                 expand(&nl, set_sec, &rr->bysec, rr->wkst);
902                         else
903                                 filter(&nl, get_sec, &rr->bysec);
904                 }
905                 /* BYSETPOS */
906                 qsort(nl.v, nl.cnt, sizeof(nl.v[0]), ical_orderedv);
907                 dedup(&nl);
908                 if (rr->setpos.cnt)
909                         filter_setpos(&nl, &rr->setpos);
910                 trim(&nl, start);
911                 if (rr->count <= 0)
912                         trim(&nl, from);
913                 timelist_join(rv, &nl);
914                 if (rv->cnt)
915                         last = rv->v[rv->cnt-1];
916         }
917         trim(rv, from);
918         if (rv->cnt > max)
919                 rv->cnt = max;
920 }
921
922 int ical_strftime(char *buf, int max, const char *fmt, struct ical_time *itm)
923 {
924         struct tm tm;
925         tm.tm_sec = itm->sec;
926         tm.tm_min = itm->min;
927         tm.tm_hour = itm->hr;
928         tm.tm_mday = itm->day;
929         tm.tm_mon = itm->mon - 1;
930         tm.tm_year = itm->yr - 1900;
931         tm.tm_wday = itm->wday;
932         tm.tm_yday = itm->yday - 1;
933         tm.tm_isdst = 0;
934         return strftime(buf, max, fmt, &tm);
935 }
936
937 void ical_localtime(struct ical_time *itm, const time_t *timep)
938 {
939         struct tm tm;
940         localtime_r(timep, &tm);
941         itm->time_set = 1;
942         itm->dur_set = 0;
943         itm->sec = tm.tm_sec;
944         itm->min = tm.tm_min;
945         itm->hr = tm.tm_hour;
946         itm->day = tm.tm_mday;
947         itm->mon = tm.tm_mon + 1;
948         itm->yr = tm.tm_year + 1900;
949         itm->wday = tm.tm_wday;
950         itm->yday = tm.tm_yday + 1;
951         itm->zone = tzname[tm.tm_isdst];
952 }
953
954 time_t ical_mktime(struct ical_time *itm)
955 {
956         struct tm tm;
957         tm.tm_sec = itm->sec;
958         tm.tm_min = itm->min;
959         tm.tm_hour = itm->hr;
960         tm.tm_mday = itm->day;
961         tm.tm_mon = itm->mon - 1;
962         tm.tm_year = itm->yr - 1900;
963         tm.tm_wday = itm->wday;
964         tm.tm_yday = itm->yday - 1;
965         tm.tm_isdst = -1;
966         return mktime(&tm);
967 }
968
969 int ical_fmt_time(char *buf, int size, struct ical_time *tm)
970 {
971         int rv = 0;
972         buf[0] = 0;
973         if (tm->mon == 0)
974                 return rv;
975         rv += snprintf(buf+rv, size-rv, "%04d%02d%02d", tm->yr, tm->mon, tm->day);
976         if (tm->time_set == 0)
977                 return rv;
978         rv += snprintf(buf+rv, size-rv, "T%02d%02d%02d%s", tm->hr, tm->min, tm->sec,
979                      (tm->zone && strcmp(tm->zone, "UTC")==0) ? "Z":"");
980         return rv;
981 }
982
983 int fmt_list(char *buf, int size, char *str, struct ical_list *l)
984 {
985         int rv = 0;
986         int i;
987         rv += snprintf(buf+rv, size-rv, "%s", str);
988         for (i = 0; i < l->cnt; i++) {
989                 if (i && rv < size)
990                         buf[rv++] = ',';
991                 rv += snprintf(buf+rv, size-rv, "%d", l->v[i]);
992         }
993         return rv;
994 }
995
996 int fmt_days(char *buf, int size, char *str, struct ical_list *l)
997 {
998         int rv = 0;
999         int d,i;
1000         int first = 1;
1001         rv += snprintf(buf+rv, size-rv, "%s", str);
1002         for (d = 0; d < 7; d++)
1003                 for (i = 0; i < l[d].cnt; i++) {
1004                         if (!first && rv < size)
1005                                 buf[rv++] = ',';
1006                         first = 0;
1007                         if (l[d].v[i])
1008                                 rv += snprintf(buf+rv, size-rv, "%d%s", l[d].v[i], days[d]);
1009                         else
1010                                 rv += snprintf(buf+rv, size-rv, "%s", days[d]);
1011                 }
1012         return rv;
1013 }
1014
1015 int ical_fmt_rr(char *buf, int size, struct ical_rrule *rr)
1016 {
1017         int rv = 0;
1018         if (rr->unit == BadInterval)
1019                 return 0;
1020
1021         rv += snprintf(buf+rv, size-rv, "FREQ=%s", freqs[rr->unit]);
1022         if (rr->step > 1)
1023                 rv += snprintf(buf+rv, size-rv, ";INTERVAL=%d", rr->step);
1024         if (rr->count > 0)
1025                 rv += snprintf(buf+rv, size-rv, ";COUNT=%d", rr->count);
1026         if (rr->until.mon) {
1027                 rv += snprintf(buf+rv, size-rv, ";UNTIL=");
1028                 rv += ical_fmt_time(buf+rv, size-rv, &rr->until);
1029         }
1030         if (rr->wkst != 1)
1031                 rv += snprintf(buf+rv, size-rv, ";WKST=%s", days[rr->wkst]);
1032         if (rr->bymonth.cnt)
1033                 rv += fmt_list(buf+rv, size-rv, ";BYMONTH=", &rr->bymonth);
1034         if (rr->byweekno.cnt)
1035                 rv += fmt_list(buf+rv, size-rv, ";BYWEEKNO=", &rr->byweekno);
1036         if (rr->byyday.cnt)
1037                 rv += fmt_list(buf+rv, size-rv, ";BYYEARDAY=", &rr->byyday);
1038         if (rr->bymday.cnt)
1039                 rv += fmt_list(buf+rv, size-rv, ";BYMONTHDAY=", &rr->bymday);
1040         if (rr->any_bydays)
1041                 rv += fmt_days(buf+rv, size-rv, ";BYDAY=", rr->bydays);
1042         if (rr->byhr.cnt)
1043                 rv += fmt_list(buf+rv, size-rv, ";BYHOUR=", &rr->byhr);
1044         if (rr->bymin.cnt)
1045                 rv += fmt_list(buf+rv, size-rv, ";BYMINUTE=", &rr->bymin);
1046         if (rr->bysec.cnt)
1047                 rv += fmt_list(buf+rv, size-rv, ";BYSECOND=", &rr->bysec);
1048
1049         return rv;
1050 }