--- /dev/null
+/*
+ * ical-dates: library to handle ical dates/times and recurrences.
+ *
+ * Given a list of strings, typically from an ical file, we produce
+ * a data structure which encodes the relevant date info.
+ * Given such a structure, a start date, end date, and max count, we
+ * produce a list of dates.
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <stdio.h>
+#include "ical-dates.h"
+
+static int month_days[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
+static char *days[7] = { "SU","MO","TU","WE","TH","FR","SA"};
+static char *freqs[] = { "SECONDLY","MINUTELY","HOURLY",
+ "DAILY","WEEKLY","MONTHLY","YEARLY"};
+
+static int daysin(int mon, int yr)
+{
+ if (mon != 2)
+ return month_days[mon-1];
+ if (yr%4 > 0)
+ return 28;
+ if (yr%400 > 0)
+ return 29;
+ return 28;
+}
+
+/* ical_times can be modified by adding/subtracting fields, but
+ * must then be normalised
+ */
+void ical_set_wday(struct ical_time *t)
+{
+ int yday = t->day;
+ int m;
+ int yr, wday;
+ for (m = 1; m < t->mon; m++)
+ yday += daysin(m, t->yr);
+ yr = t->yr - 1;
+ wday = yr + yr/4 - yr/400 + yday + 4;
+ t->yday = yday;
+ t->wday = wday % 7;
+}
+
+void ical_norm_mon(struct ical_time *t)
+{
+ while (t->mon < 1) {
+ t->mon += 12;
+ t->yr -= 1;
+ }
+ while (t->mon > 12) {
+ t->mon -= 12;
+ t->yr += 1;
+ }
+ ical_set_wday(t);
+}
+
+void ical_norm_day(struct ical_time *t)
+{
+ if (t->day >= 1 && t->day <= daysin(t->mon, t->yr)) {
+ if (t->wday < 0)
+ ical_set_wday(t);
+ return;
+ }
+ while (t->day < 1) {
+ t->mon -= 1;
+ ical_norm_mon(t);
+ t->day += daysin(t->mon, t->yr);
+ }
+ while (t->day > daysin(t->mon, t->yr)) {
+ t->day -= daysin(t->mon, t->yr);
+ t->mon += 1;
+ ical_norm_mon(t);
+ }
+ ical_set_wday(t);
+}
+
+void ical_norm_hr(struct ical_time *t)
+{
+ if (t->hr >= 0 && t->hr < 24)
+ return;
+ while (t->hr >= 24) {
+ t->hr -= 24;
+ t->day += 1;
+ }
+ while (t->hr < 0) {
+ t->hr += 24;
+ t->day -= 1;
+ }
+ t->wday = -1; // force wday/yday update
+ ical_norm_day(t);
+}
+
+void ical_norm_min(struct ical_time *t)
+{
+ if (t->min >= 0 && t->min < 60)
+ return;
+ while (t->min >= 60) {
+ t->min -= 60;
+ t->hr += 1;
+ }
+ while (t->min < 0) {
+ t->min += 60;
+ t->hr -= 1;
+ }
+ ical_norm_hr(t);
+}
+
+void ical_norm_sec(struct ical_time *t)
+{
+ if (t->sec >= 0 && t->sec < 60)
+ return;
+ while (t->sec >= 60) {
+ t->sec -= 60;
+ t->min += 1;
+ }
+ while (t->sec < 0) {
+ t->sec += 60;
+ t->min -= 1;
+ }
+ ical_norm_min(t);
+}
+
+int ical_ordered(const struct ical_time *a, const struct ical_time *b)
+{
+ if (a->yr != b->yr)
+ return a->yr - b->yr;
+ if (a->yday != b->yday)
+ return a->yday - b->yday;
+ return (a->hr*3600 + a->min*60 + a->sec) -
+ (b->hr*3600 + b->min*60 + b->sec);
+}
+
+static int ical_orderedv(const void *av, const void *bv)
+{
+ const struct ical_time *a = av;
+ const struct ical_time *b = bv;
+ return ical_ordered(a,b);
+}
+
+void ical_next(struct ical_iter *it)
+{
+ switch (it->unit) {
+ case Secondly:
+ it->now.sec += it->step;
+ ical_norm_sec(&it->now);
+ break;
+ case Minutely:
+ it->now.min += it->step;
+ ical_norm_min(&it->now);
+ break;
+ case Hourly:
+ it->now.hr += it->step;
+ ical_norm_hr(&it->now);
+ break;
+ case Daily:
+ it->now.day += it->step;
+ ical_norm_day(&it->now);
+ break;
+ case Weekly:
+ it->now.day += it->step * 7;
+ ical_norm_day(&it->now);
+ break;
+ case Monthly:
+ it->now.mon += it->step;
+ ical_norm_mon(&it->now);
+ break;
+ case Yearly:
+ it->now.yr += it->step;
+ ical_set_wday(&it->now);
+ break;
+ case BadInterval:
+ break;
+ }
+}
+
+static void list_add(struct ical_list *l, int v)
+{
+ if (l->cnt >= l->size) {
+ l->size = l->cnt + 8;
+ l->v = realloc(l->v, l->size * sizeof(v));
+ }
+ l->v[l->cnt++] = v;
+}
+
+void ical_list_free(struct ical_list *l)
+{
+ free(l->v);
+}
+
+static void timelist_add(struct ical_timelist *tl, struct ical_time *tm)
+{
+ if (tl->cnt >= tl->size) {
+ tl->size = tl->cnt + 8;
+ tl->v = realloc(tl->v, tl->size * sizeof(*tm));
+ }
+ tl->v[tl->cnt++] = *tm;
+}
+
+void ical_timelist_free(struct ical_timelist *tl)
+{
+ free(tl->v);
+}
+
+/* When parsing we destroy the argument string */
+static int parse_bysimple(struct ical_list *list, char *arg, int min, int max, int neg)
+{
+ while (*arg) {
+ char *w = arg;
+ int l = strcspn(arg, ",");
+ int v;
+ char *e;
+ if (arg[l])
+ arg[l++] = 0;
+ arg += l;
+ v = strtol(w, &e, 10);
+ if (!*w || *e)
+ return 0;
+ if ((v < 0 && !neg) ||
+ abs(v) < min ||
+ abs(v) > max)
+ return 0;
+ list_add(list, v);
+ }
+ return 1;
+}
+
+static int match_day(char *d)
+{
+ int n;
+ for (n = 0; n < 7; n++)
+ if (strcmp(d, days[n]) == 0)
+ return n;
+ return -1;
+}
+
+static int parse_byday(struct ical_list lists[7], char *arg)
+{
+ while (*arg) {
+ char *w = arg;
+ int d, v;
+ int l = strspn(arg, "0123456789+-SUMOTWEHFRA");
+ arg += l;
+ if (arg[0] == ',')
+ *arg++ = 0;
+ else if (arg[0])
+ return 0;
+ if (l < 2)
+ return 0;
+ d = match_day(w+l-2);
+ if (d < 0)
+ return 0;
+ if (l == 2)
+ v = 0;
+ else {
+ char *e;
+ v = strtol(w, &e, 10);
+ if (e != w+l-2)
+ return 0;
+ }
+ list_add(&lists[d], v);
+ }
+ return 1;
+}
+
+static enum ical_interval parse_freq(char *arg)
+{
+ enum ical_interval rv;
+ for (rv = Secondly; rv < BadInterval; rv++)
+ if (strcmp(arg, freqs[rv]) == 0)
+ return rv;
+ return BadInterval;
+}
+
+int ical_parse_rrule(struct ical_rrule *rr, char *arg, char *tz)
+{
+ /* ';' separated list of X=Y assignments where Y is a
+ * ',' separated list of values
+ */
+ memset(rr, 0, sizeof(*rr));
+ rr->wkst = 1; // MO by default
+ rr->unit = BadInterval;
+ rr->step = 1;
+ while (*arg) {
+ char *w = arg;
+ int l = strcspn(arg, ";");
+ int t;
+ char *b;
+
+ if (arg[l])
+ arg[l++] = 0;
+ arg += l;
+
+ t = strcspn(w, "=");
+ if (w[t] == 0)
+ return 0;
+ w[t++] = 0;
+ b = w+t;
+
+ if (strcmp(w, "FREQ") == 0) {
+ rr->unit = parse_freq(b);
+ } else if (strcmp(w, "INTERVAL") == 0) {
+ char *e;
+ rr->step = strtol(b, &e, 10);
+ if (rr->step < 1 || *b == 0 || *e != 0)
+ return 0;
+ } else if (strcmp(w, "COUNT") == 0) {
+ char *e;
+ rr->count = strtol(b, &e, 10);
+ if (rr->count < 1 || *b == 0 || *e != 0)
+ return 0;
+ } else if (strcmp(w, "UNTIL") == 0) {
+ if (ical_parse_time(&rr->until, b, tz) == 0)
+ return 0;
+ } else if (strcmp(w, "BYMONTH") == 0) {
+ if (parse_bysimple(&rr->bymonth, b, 1, 31, 0) == 0)
+ return 0;
+ } else if (strcmp(w, "BYWEEKNO") == 0) {
+ if (parse_bysimple(&rr->byweekno, b, 0, 53, 1) == 0)
+ return 0;
+ } else if (strcmp(w, "BYYEARDAY") == 0) {
+ if (parse_bysimple(&rr->byyday, b, 1, 366, 1) == 0)
+ return 0;
+ } else if (strcmp(w, "BYMONTHDAY") == 0) {
+ if (parse_bysimple(&rr->bymday, b, 1, 31, 1) == 0)
+ return 0;
+ } else if (strcmp(w, "BYDAY") == 0) {
+ if (parse_byday(rr->bydays, b) == 0)
+ return 0;
+ rr->any_bydays = 1;
+ } else if (strcmp(w, "BYHOUR") == 0) {
+ if (parse_bysimple(&rr->byhr, b, 0, 23, 0) == 0)
+ return 0;
+ } else if (strcmp(w, "BYMINUTE") == 0) {
+ if (parse_bysimple(&rr->bymin, b, 0, 59, 0) == 0)
+ return 0;
+ } else if (strcmp(w, "BYSECOND") == 0) {
+ if (parse_bysimple(&rr->bysec, b, 0, 59, 0) == 0)
+ return 0;
+ } else if (strcmp(w, "BYSETPOS") == 0) {
+ if (parse_bysimple(&rr->setpos, b, 1, 366, 1) == 0)
+ return 0;
+ } else if (strcmp(w, "WKST") == 0) {
+ rr->wkst = match_day(b);
+ if (rr->wkst < 0)
+ return 0;
+ } else
+ return 0;
+ }
+ /* FREQ is required. Everything else is optional */
+ if (rr->unit == BadInterval)
+ return 0;
+ return 1;
+}
+
+void ical_rrule_free(struct ical_rrule *rr)
+{
+ int i;
+ ical_list_free(&rr->bysec);
+ ical_list_free(&rr->bymin);
+ ical_list_free(&rr->byhr);
+ ical_list_free(&rr->bymonth);
+ ical_list_free(&rr->bymday);
+ ical_list_free(&rr->byyday);
+ ical_list_free(&rr->byweekno);
+ ical_list_free(&rr->setpos);
+ for (i=0; i<7; i++)
+ ical_list_free(&rr->bydays[i]);
+}
+
+static int getnum(char *arg, int len)
+{
+ int n = 0;
+ while (len) {
+ n = n*10 + arg[0] - '0';
+ arg++;
+ len--;
+ }
+ return n;
+}
+
+int ical_parse_time(struct ical_time *tm, char *arg, char *tz)
+{
+ /* 'arg' can by a date or datetime possibly followed by
+ * another date-or-datetime or a period.
+ * For now we just parse the start date[time]
+ */
+ int l;
+
+ memset(tm, 0, sizeof(*tm));
+ l = strspn(arg, "0123456789");
+ if (l != 8)
+ return 0;
+ tm->yr = getnum(arg, 4);
+ tm->mon = getnum(arg+4, 2);
+ tm->day = getnum(arg+6, 2);
+ if (tm->mon < 1 || tm->mon > 12 ||
+ tm->day < 1 || tm->day > daysin(tm->mon, tm->yr))
+ return 0;
+ ical_set_wday(tm);
+ if (arg[l] == 0)
+ return 1;
+ if (arg[l] != '/') {
+ if (arg[l] != 'T')
+ /* Need a time now! */
+ return 0;
+ arg += l+1;
+ l = strspn(arg, "0123456789");
+ if (l != 6)
+ return 0;
+ tm->hr = getnum(arg, 2);
+ tm->min = getnum(arg+2, 2);
+ tm->sec = getnum(arg+4, 2);
+ tm->time_set = 1;
+ if (tm->sec == 60)
+ /* Leap-sec. Cannot cope, sorry */
+ tm->sec = 59;
+ if (tm->hr >= 24 || tm->min >= 60 || tm->sec >= 60)
+ return 0;
+ if (arg[l] == 'Z') {
+ tm->zone = "UTC";
+ l++;
+ } else
+ tm->zone = tz;
+ if (arg[l] == 0)
+ return 1;
+ if (arg[l] != '/')
+ return 0;
+ }
+ /* Ignore duration */
+ return 1;
+}
+
+int ical_parse_time_list(struct ical_timelist *tl, char *arg, char *tz)
+{
+ while (*arg) {
+ char *w = arg;
+ int l = strcspn(arg, ",");
+ struct ical_time tm;
+
+ if (arg[l])
+ arg[l++] = 0;
+ arg += l;
+ if (ical_parse_time(&tm, w, tz) == 0)
+ return 0;
+ timelist_add(tl, &tm);
+ }
+ return 1;
+}
+
+int ical_parse_dates_line(struct ical_dates *id, char *arg)
+{
+ int l = strcspn(arg, ":");
+ char *body, *param;
+ char *tz = NULL;
+
+ if (arg[l] == 0)
+ return 0;
+ arg[l++] = 0;
+ body = arg+l;
+
+ param = arg;
+ /* Look at params, but only care about TZID= */
+ while ((param = strchr(param, ';')) != NULL) {
+ *param++ = 0;
+ if (strncmp(param, "TZID=", 5) == 0)
+ tz = param + 5;
+ }
+
+ if (strcmp(arg, "DTSTART") == 0)
+ return ical_parse_time(&id->start, body, tz);
+ if (strcmp(arg, "RRULE") == 0)
+ return ical_parse_rrule(&id->rr, body, tz);
+ if (strcmp(arg, "RDATE") == 0)
+ return ical_parse_time_list(&id->rdate, body, tz);
+ if (strcmp(arg, "EXDATE") == 0)
+ return ical_parse_time_list(&id->exdate, body, tz);
+
+ /*put it back like we found it */
+ arg[--l] = ':';
+ while (l) {
+ l--;
+ if (!arg[l])
+ arg[l] = ';';
+ }
+ return -1;
+}
+
+void ical_dates_free(struct ical_dates *d)
+{
+ ical_rrule_free(&d->rr);
+ ical_timelist_free(&d->rdate);
+ ical_timelist_free(&d->exdate);
+}
+
+static int inlist(struct ical_list *l, int v)
+{
+ int i;
+ for (i = 0; i < l->cnt; i++)
+ if (l->v[i] == v)
+ return 1;
+ return 0;
+}
+
+static int set_mon(struct ical_time *tm, int v, int wkst)
+{
+ if (tm->day > daysin(v, tm->yr))
+ return 0;
+ tm->mon = v;
+ return 1;
+}
+
+static int get_mon(struct ical_time *tm)
+{
+ return tm->mon;
+}
+
+static int set_mday(struct ical_time *tm, int v, int wkst)
+{
+ int d = daysin(tm->mon, tm->yr);
+ if (v < 0)
+ v = d + 1 + v;
+ if (v < 1 || v > d)
+ return 0;
+ tm->day = v;
+ return 1;
+}
+
+static int test_mday(struct ical_time *tm, int v)
+{
+ if (v > 0)
+ return tm->day == v;
+ v = daysin(tm->mon, tm->yr) + 1 + v;
+ return tm->day == v;
+}
+
+static int set_yday(struct ical_time *tm, int v, int wkst)
+{
+ int d = daysin(2, tm->yr) - 28 + 365;
+ int m;
+ if (v < 0)
+ v = d + 1 + v;
+ if (v < 1 || v > d)
+ return 0;
+ tm->yday = v;
+ for (m=1; m<=12; m++) {
+ int md = daysin(m, tm->yr);
+ if (v <= md)
+ break;
+ v -= md;
+ }
+ tm->mon = m;
+ tm->day = v;
+ return 1;
+}
+
+static int test_yday(struct ical_time *tm, int v)
+{
+ if (v < 0) {
+ int d = daysin(2, tm->yr) - 28 + 365;
+ v = d + 1 + v;
+ }
+ return tm->yday == v;
+}
+
+static int set_weekno(struct ical_time *tm, int v, int wkst)
+{
+ struct ical_time st = *tm;
+ int i;
+ if (v >= 0) {
+ st.mon = 1;
+ st.day = -2;
+ ical_norm_day(&st);
+ ical_set_wday(&st);
+ while (st.wday != wkst) {
+ st.day ++;
+ ical_norm_day(&st);
+ ical_set_wday(&st);
+ }
+ st.day += (v-1)*7;
+ } else {
+ st.mon = 12;
+ st.day = 28;
+ ical_set_wday(&st);
+ while (st.wday != wkst) {
+ st.day --;
+ ical_set_wday(&st);
+ }
+ st.day -= (-1-v)*7;
+ }
+ ical_norm_day(&st);
+ ical_set_wday(&st);
+ /* Now at the start of the week */
+ for (i=0; i<7; i++) {
+ if (st.yr == tm->yr &&
+ st.wday == tm->wday) {
+ *tm = st;
+ return 1;
+ }
+ st.day++;
+ ical_norm_day(&st);
+ ical_set_wday(&st);
+ }
+ return 0;
+}
+
+static int set_hr(struct ical_time *tm, int v, int wkst)
+{
+ tm->hr = v;
+ return 1;
+}
+
+static int get_hr(struct ical_time *tm)
+{
+ return tm->hr;
+}
+
+static int set_min(struct ical_time *tm, int v, int wkst)
+{
+ tm->min = v;
+ return 1;
+}
+
+static int get_min(struct ical_time *tm)
+{
+ return tm->min;
+}
+
+static int set_sec(struct ical_time *tm, int v, int wkst)
+{
+ tm->sec = v;
+ return 1;
+}
+
+static int get_sec(struct ical_time *tm)
+{
+ return tm->sec;
+}
+
+static void expand(struct ical_timelist *tl,
+ int (*set)(struct ical_time *tm, int v, int wkst),
+ struct ical_list *l, int wkst)
+{
+ struct ical_timelist newl = {0};
+ int i, j;
+ for (i = 0; i < tl->cnt; i++) {
+ for (j = 0; j < l->cnt; j++) {
+ struct ical_time t = tl->v[i];
+ if (set(&t, l->v[j], wkst)) {
+ ical_set_wday(&t);
+ timelist_add(&newl, &t);
+ }
+ }
+ }
+ ical_timelist_free(tl);
+ *tl = newl;
+}
+
+static void expand_byday(struct ical_timelist *tl,
+ struct ical_list *dl, int wkst, enum ical_interval xtype)
+{
+ struct ical_timelist newl = {0};
+ int i, days, d;
+ for (i = 0; i < tl->cnt; i++) {
+ struct ical_time st;
+
+ st = tl->v[i];
+ if (xtype == Yearly) {
+ /* Every relevant day in this year */
+ st.mon = 1;
+ st.day = 1;
+ days = daysin(2, st.yr) - 28 + 365;
+ } else if (xtype == Monthly) {
+ /* Every relevant day in this month */
+ st.day = 1;
+ days = daysin(st.mon, st.yr);
+ } else {
+ /* The relevant day in this week */
+ while (st.wday != wkst) {
+ st.day -= 1;
+ st.wday = -1;
+ ical_norm_day(&st);
+ }
+ days = 7;
+ }
+ ical_set_wday(&st);
+ for (d = 0 ; d < days; d++) {
+ struct ical_list *l = dl + st.wday;
+ if (inlist(l, 0) || inlist(l, d/7+1) || inlist(l, -((days-d)/7+1)))
+ timelist_add(&newl, &st);
+ st.day++;
+ ical_norm_day(&st);
+ ical_set_wday(&st);
+ }
+ }
+ ical_timelist_free(tl);
+ *tl = newl;
+}
+
+static void filter(struct ical_timelist *tl, int(*get)(struct ical_time *tm),
+ struct ical_list *l)
+{
+ struct ical_timelist newl = {0};
+ int i;
+ for (i = 0; i < tl->cnt; i++)
+ if (inlist(l, get(&tl->v[i])))
+ timelist_add(&newl, &tl->v[i]);
+
+ ical_timelist_free(tl);
+ *tl = newl;
+}
+
+static void filter_byday(struct ical_timelist *tl,
+ struct ical_list *l)
+{
+ struct ical_timelist newl = {0};
+ int i;
+ for (i = 0; i < tl->cnt; i++)
+ if (inlist(l+tl->v[i].wday, 0))
+ timelist_add(&newl, &tl->v[i]);
+
+ ical_timelist_free(tl);
+ *tl = newl;
+}
+
+static void filter_test(struct ical_timelist *tl,
+ int(*test)(struct ical_time *tm, int v),
+ struct ical_list *l)
+{
+ struct ical_timelist newl = {0};
+ int i, j;
+ for (i = 0; i < tl->cnt; i++)
+ for (j = 0; j < l->cnt; j++)
+ if (test(&tl->v[i], l->v[j])) {
+ timelist_add(&newl, &tl->v[i]);
+ break;
+ }
+ ical_timelist_free(tl);
+ *tl = newl;
+}
+
+static void filter_setpos(struct ical_timelist *tl, struct ical_list *l)
+{
+ struct ical_timelist newl = {0};
+ int i;
+ for (i = 0; i < l->cnt; i++) {
+ int j = l->v[i];
+ if (j < 0) {
+ j = tl->cnt + j;
+ if (inlist(l, j))
+ j = -1;
+ } else
+ j -= 1;
+ if (j >= 0 && j < tl->cnt)
+ timelist_add(&newl, &tl->v[i]);
+ }
+ ical_timelist_free(tl);
+ *tl = newl;
+}
+
+static void dedup(struct ical_timelist *tl)
+{
+ int i;
+ for (i = 1; i < tl->cnt; i++) {
+ if (ical_ordered(&tl->v[i-1], &tl->v[i]) == 0) {
+ tl->cnt--;
+ memmove(&tl->v[i], &tl->v[i+1], (tl->cnt - i)*sizeof(tl->v[0]));
+ i--;
+ }
+ }
+}
+
+static void trim(struct ical_timelist *tl, struct ical_time *tm)
+{
+ int i;
+ for (i = 0; i < tl->cnt; i++)
+ if (ical_ordered(&tl->v[i], tm) >= 0)
+ break;
+ if (i == 0)
+ return;
+ tl->cnt -= i;
+ memmove(tl->v, tl->v+i, tl->cnt * sizeof(tl->v[0]));
+}
+
+static void timelist_join(struct ical_timelist *a, struct ical_timelist *b)
+{
+ if (a->size < a->cnt + b->cnt) {
+ a->size = a->cnt + b->cnt;
+ a->v = realloc(a->v, a->size * sizeof(a->v[0]));
+ }
+ memcpy(&a->v[a->cnt], b->v, b->cnt * sizeof(b->v[0]));
+ a->cnt += b->cnt;
+ ical_timelist_free(b);
+}
+
+void ical_rr_dates(struct ical_time *start, struct ical_rrule *rr,
+ struct ical_time *from, int max,
+ struct ical_timelist *rv)
+{
+ /* generate dates from the given info an return some in 'rv'
+ * We only report dates at or after 'from' and at most 'max'
+ * of them.
+ */
+ struct ical_time last;
+ struct ical_iter it;
+
+ if (start->mon == 0 || rr->unit == BadInterval)
+ return;
+
+ it.now = *start;
+ it.unit = rr->unit;
+ it.step = rr->step;
+ last = *start;
+
+ if (rr->count > 0 && rr->count < max)
+ max = rr->count;
+
+ for ( ;
+ (rv->cnt < max &&
+ (rr->until.mon == 0 || ical_ordered(&last, &rr->until) < 0));
+ ical_next(&it)
+ ) {
+ struct ical_timelist nl = {0};
+ timelist_add(&nl, &it.now);
+
+ /* BYMONTH */
+ if (rr->bymonth.cnt) {
+ if (rr->unit > Monthly)
+ expand(&nl, set_mon, &rr->bymonth, rr->wkst);
+ else
+ filter(&nl, get_mon, &rr->bymonth);
+ }
+ /* BYWEEKNO */
+ if (rr->byweekno.cnt) {
+ if (rr->unit == Yearly)
+ expand(&nl, set_weekno, &rr->byweekno, rr->wkst);
+ }
+ /* BYYEARDAY */
+ if (rr->byyday.cnt) {
+ if (rr->unit == Yearly)
+ expand(&nl, set_yday, &rr->byyday, rr->wkst);
+ else if (rr->unit < Daily)
+ filter_test(&nl, test_yday, &rr->byyday);
+ }
+ /* BYMONTHDAY */
+ if (rr->bymday.cnt) {
+ if (rr->unit > Weekly)
+ expand(&nl, set_mday, &rr->bymday, rr->wkst);
+ else if (rr->unit < Weekly)
+ filter_test(&nl, test_mday, &rr->bymday);
+ }
+ /* BYDAY */
+ if (rr->any_bydays) {
+ enum ical_interval xtype = BadInterval;
+ switch (rr->unit) {
+ case Weekly:
+ xtype = Weekly; break;
+ case Monthly:
+ if (rr->bymday.cnt == 0)
+ xtype = Monthly;
+ break;
+ case Yearly:
+ if (rr->byyday.cnt || rr->bymday.cnt)
+ /* select, not expand */;
+ else if (rr->byweekno.cnt)
+ xtype = Weekly;
+ else if (rr->bymonth.cnt)
+ xtype = Monthly;
+ else
+ xtype = Yearly;
+ default:;
+ }
+ if (xtype == BadInterval)
+ filter_byday(&nl, rr->bydays);
+ else
+ expand_byday(&nl, rr->bydays, rr->wkst, xtype);
+ }
+ /* BYHOUR */
+ if (rr->byhr.cnt) {
+ if (rr->unit > Hourly)
+ expand(&nl, set_hr, &rr->byhr, rr->wkst);
+ else
+ filter(&nl, get_hr, &rr->byhr);
+ }
+ /* BYMINUTE */
+ if (rr->bymin.cnt) {
+ if (rr->unit > Minutely)
+ expand(&nl, set_min, &rr->bymin, rr->wkst);
+ else
+ filter(&nl, get_min, &rr->bymin);
+ }
+ /* BYSECOND */
+ if (rr->bysec.cnt) {
+ if (rr->unit > Secondly)
+ expand(&nl, set_sec, &rr->bysec, rr->wkst);
+ else
+ filter(&nl, get_sec, &rr->bysec);
+ }
+ /* BYSETPOS */
+ qsort(nl.v, nl.cnt, sizeof(nl.v[0]), ical_orderedv);
+ dedup(&nl);
+ if (rr->setpos.cnt)
+ filter_setpos(&nl, &rr->setpos);
+ trim(&nl, start);
+ if (rr->count <= 0)
+ trim(&nl, from);
+ timelist_join(rv, &nl);
+ if (rv->cnt)
+ last = rv->v[rv->cnt-1];
+ }
+ trim(rv, from);
+ if (rv->cnt > max)
+ rv->cnt = max;
+}
+
+int ical_strftime(char *buf, int max, const char *fmt, struct ical_time *itm)
+{
+ struct tm tm;
+ tm.tm_sec = itm->sec;
+ tm.tm_min = itm->min;
+ tm.tm_hour = itm->hr;
+ tm.tm_mday = itm->day;
+ tm.tm_mon = itm->mon - 1;
+ tm.tm_year = itm->yr - 1900;
+ tm.tm_wday = itm->wday;
+ tm.tm_yday = itm->yday - 1;
+ tm.tm_isdst = 0;
+ return strftime(buf, max, fmt, &tm);
+}
+
+void ical_localtime(struct ical_time *itm, const time_t *timep)
+{
+ struct tm tm;
+ localtime_r(timep, &tm);
+ itm->time_set = 1;
+ itm->dur_set = 0;
+ itm->sec = tm.tm_sec;
+ itm->min = tm.tm_min;
+ itm->hr = tm.tm_hour;
+ itm->day = tm.tm_mday;
+ itm->mon = tm.tm_mon + 1;
+ itm->yr = tm.tm_year + 1900;
+ itm->wday = tm.tm_wday;
+ itm->yday = tm.tm_yday + 1;
+ itm->zone = tzname[tm.tm_isdst];
+}
+
+time_t ical_mktime(struct ical_time *itm)
+{
+ struct tm tm;
+ tm.tm_sec = itm->sec;
+ tm.tm_min = itm->min;
+ tm.tm_hour = itm->hr;
+ tm.tm_mday = itm->day;
+ tm.tm_mon = itm->mon - 1;
+ tm.tm_year = itm->yr - 1900;
+ tm.tm_wday = itm->wday;
+ tm.tm_yday = itm->yday - 1;
+ tm.tm_isdst = -1;
+ return mktime(&tm);
+}
+
+int ical_fmt_time(char *buf, int size, struct ical_time *tm)
+{
+ int rv = 0;
+ buf[0] = 0;
+ if (tm->mon == 0)
+ return rv;
+ rv += snprintf(buf+rv, size-rv, "%04d%02d%02d", tm->yr, tm->mon, tm->day);
+ if (tm->time_set == 0)
+ return rv;
+ rv += snprintf(buf+rv, size-rv, "T%02d%02d%02d%s", tm->hr, tm->min, tm->sec,
+ (tm->zone && strcmp(tm->zone, "UTC")==0) ? "Z":"");
+ return rv;
+}
+
+int fmt_list(char *buf, int size, char *str, struct ical_list *l)
+{
+ int rv = 0;
+ int i;
+ rv += snprintf(buf+rv, size-rv, "%s", str);
+ for (i = 0; i < l->cnt; i++) {
+ if (i && rv < size)
+ buf[rv++] = ',';
+ rv += snprintf(buf+rv, size-rv, "%d", l->v[i]);
+ }
+ return rv;
+}
+
+int fmt_days(char *buf, int size, char *str, struct ical_list *l)
+{
+ int rv = 0;
+ int d,i;
+ int first = 1;
+ rv += snprintf(buf+rv, size-rv, "%s", str);
+ for (d = 0; d < 7; d++)
+ for (i = 0; i < l[d].cnt; i++) {
+ if (!first && rv < size)
+ buf[rv++] = ',';
+ first = 0;
+ if (l[d].v[i])
+ rv += snprintf(buf+rv, size-rv, "%d%s", l[d].v[i], days[d]);
+ else
+ rv += snprintf(buf+rv, size-rv, "%s", days[d]);
+ }
+ return rv;
+}
+
+int ical_fmt_rr(char *buf, int size, struct ical_rrule *rr)
+{
+ int rv = 0;
+ if (rr->unit == BadInterval)
+ return 0;
+
+ rv += snprintf(buf+rv, size-rv, "FREQ=%s", freqs[rr->unit]);
+ if (rr->step > 1)
+ rv += snprintf(buf+rv, size-rv, ";INTERVAL=%d", rr->step);
+ if (rr->count > 0)
+ rv += snprintf(buf+rv, size-rv, ";COUNT=%d", rr->count);
+ if (rr->until.mon) {
+ rv += snprintf(buf+rv, size-rv, ";UNTIL=");
+ rv += ical_fmt_time(buf+rv, size-rv, &rr->until);
+ }
+ if (rr->wkst != 1)
+ rv += snprintf(buf+rv, size-rv, ";WKST=%s", days[rr->wkst]);
+ if (rr->bymonth.cnt)
+ rv += fmt_list(buf+rv, size-rv, ";BYMONTH=", &rr->bymonth);
+ if (rr->byweekno.cnt)
+ rv += fmt_list(buf+rv, size-rv, ";BYWEEKNO=", &rr->byweekno);
+ if (rr->byyday.cnt)
+ rv += fmt_list(buf+rv, size-rv, ";BYYEARDAY=", &rr->byyday);
+ if (rr->bymday.cnt)
+ rv += fmt_list(buf+rv, size-rv, ";BYMONTHDAY=", &rr->bymday);
+ if (rr->any_bydays)
+ rv += fmt_days(buf+rv, size-rv, ";BYDAY=", rr->bydays);
+ if (rr->byhr.cnt)
+ rv += fmt_list(buf+rv, size-rv, ";BYHOUR=", &rr->byhr);
+ if (rr->bymin.cnt)
+ rv += fmt_list(buf+rv, size-rv, ";BYMINUTE=", &rr->bymin);
+ if (rr->bysec.cnt)
+ rv += fmt_list(buf+rv, size-rv, ";BYSECOND=", &rr->bysec);
+
+ return rv;
+}