aboutsummaryrefslogtreecommitdiffstats
path: root/src/recur.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/recur.c')
-rw-r--r--src/recur.c1185
1 files changed, 991 insertions, 194 deletions
diff --git a/src/recur.c b/src/recur.c
index 7376536..10523ad 100644
--- a/src/recur.c
+++ b/src/recur.c
@@ -1,7 +1,7 @@
/*
* Calcurse - text-based organizer
*
- * Copyright (c) 2004-2017 calcurse Development Team <misc@calcurse.org>
+ * Copyright (c) 2004-2023 calcurse Development Team <misc@calcurse.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -46,12 +46,45 @@
llist_ts_t recur_alist_p;
llist_t recur_elist;
+static void free_int(int *i)
+{
+ mem_free(i);
+}
+
+void recur_free_int_list(llist_t *ilist)
+{
+ LLIST_FREE_INNER(ilist, free_int);
+ LLIST_FREE(ilist);
+}
+
+void recur_int_list_dup(llist_t *l, llist_t *ilist)
+{
+ llist_item_t *i;
+ int *o, *p;
+
+ LLIST_INIT(l);
+
+ if (ilist->head) {
+ LLIST_FOREACH(ilist, i) {
+ p = LLIST_GET_DATA(i);
+ o = mem_malloc(sizeof(int));
+ *o = *p;
+ LLIST_ADD(l, o);
+ }
+ }
+}
+
+static int int_cmp(int *list, int *i)
+{
+ return *list == *i;
+}
+
static void free_exc(struct excp *exc)
{
mem_free(exc);
}
-static void free_exc_list(llist_t * exc)
+void recur_free_exc_list(llist_t * exc)
{
LLIST_FREE_INNER(exc, free_exc);
LLIST_FREE(exc);
@@ -62,6 +95,11 @@ static int exc_cmp_day(struct excp *a, struct excp *b)
return a->st < b->st ? -1 : (a->st == b->st ? 0 : 1);
}
+static int exc_inday(struct excp *exc, time_t *day_start)
+{
+ return (date_cmp_day(exc->st, *day_start) == 0);
+}
+
static void recur_add_exc(llist_t * exc, time_t day)
{
struct excp *o = mem_malloc(sizeof(struct excp));
@@ -70,7 +108,7 @@ static void recur_add_exc(llist_t * exc, time_t day)
LLIST_ADD_SORTED(exc, o, exc_cmp_day);
}
-static void exc_dup(llist_t * in, llist_t * exc)
+void recur_exc_dup(llist_t * in, llist_t * exc)
{
llist_item_t *i;
@@ -103,10 +141,10 @@ char *recur_exc2str(llist_t *exc)
}
/*
- * Update the list of exceptions from a string of days. Any positive number of
+ * Update a list of exceptions from a string of days. Any positive number of
* spaces are allowed before, between and after the days.
*/
-int recur_update_exc(llist_t *exc, char *days)
+int recur_str2exc(llist_t *exc, char *days)
{
int updated = 0;
char *d;
@@ -130,11 +168,11 @@ int recur_update_exc(llist_t *exc, char *days)
else
break;
}
- free_exc_list(exc);
- exc_dup(exc, &nexc);
+ recur_free_exc_list(exc);
+ recur_exc_dup(exc, &nexc);
updated = 1;
cleanup:
- free_exc_list(&nexc);
+ recur_free_exc_list(&nexc);
return updated;
}
@@ -149,11 +187,16 @@ struct recur_event *recur_event_dup(struct recur_event *in)
rev->mesg = mem_strdup(in->mesg);
rev->rpt = mem_malloc(sizeof(struct rpt));
+ /* Note. The linked lists are NOT copied and no memory allocated. */
rev->rpt->type = in->rpt->type;
rev->rpt->freq = in->rpt->freq;
rev->rpt->until = in->rpt->until;
+ LLIST_INIT(&rev->rpt->bymonth);
+ LLIST_INIT(&rev->rpt->bywday);
+ LLIST_INIT(&rev->rpt->bymonthday);
+ LLIST_INIT(&rev->rpt->exc);
- exc_dup(&rev->exc, &in->exc);
+ recur_exc_dup(&rev->exc, &in->exc);
if (in->note)
rev->note = mem_strdup(in->note);
@@ -176,11 +219,16 @@ struct recur_apoint *recur_apoint_dup(struct recur_apoint *in)
rapt->mesg = mem_strdup(in->mesg);
rapt->rpt = mem_malloc(sizeof(struct rpt));
+ /* Note. The linked lists are NOT copied and no memory allocated. */
rapt->rpt->type = in->rpt->type;
rapt->rpt->freq = in->rpt->freq;
rapt->rpt->until = in->rpt->until;
+ LLIST_INIT(&rapt->rpt->bymonth);
+ LLIST_INIT(&rapt->rpt->bywday);
+ LLIST_INIT(&rapt->rpt->bymonthday);
+ LLIST_INIT(&rapt->rpt->exc);
- exc_dup(&rapt->exc, &in->exc);
+ recur_exc_dup(&rapt->exc, &in->exc);
if (in->note)
rapt->note = mem_strdup(in->note);
@@ -205,9 +253,14 @@ void recur_apoint_free(struct recur_apoint *rapt)
mem_free(rapt->mesg);
if (rapt->note)
mem_free(rapt->note);
- if (rapt->rpt)
+ if (rapt->rpt) {
+ recur_free_exc_list(&rapt->rpt->exc);
+ recur_free_int_list(&rapt->rpt->bywday);
+ recur_free_int_list(&rapt->rpt->bymonth);
+ recur_free_int_list(&rapt->rpt->bymonthday);
mem_free(rapt->rpt);
- free_exc_list(&rapt->exc);
+ }
+ recur_free_exc_list(&rapt->exc);
mem_free(rapt);
}
@@ -216,9 +269,14 @@ void recur_event_free(struct recur_event *rev)
mem_free(rev->mesg);
if (rev->note)
mem_free(rev->note);
- if (rev->rpt)
+ if (rev->rpt) {
+ recur_free_exc_list(&rev->rpt->exc);
+ recur_free_int_list(&rev->rpt->bywday);
+ recur_free_int_list(&rev->rpt->bymonth);
+ recur_free_int_list(&rev->rpt->bymonthday);
mem_free(rev->rpt);
- free_exc_list(&rev->exc);
+ }
+ recur_free_exc_list(&rev->exc);
mem_free(rev);
}
@@ -261,28 +319,31 @@ static int recur_event_cmp(struct recur_event *a, struct recur_event *b)
/* Insert a new recursive appointment in the general linked list */
struct recur_apoint *recur_apoint_new(char *mesg, char *note, time_t start,
- long dur, char state, int type,
- int freq, time_t until,
- llist_t * except)
+ long dur, char state, struct rpt *rpt)
{
struct recur_apoint *rapt =
mem_malloc(sizeof(struct recur_apoint));
- rapt->rpt = mem_malloc(sizeof(struct rpt));
rapt->mesg = mem_strdup(mesg);
rapt->note = (note != NULL) ? mem_strdup(note) : 0;
rapt->start = start;
- rapt->state = state;
rapt->dur = dur;
- rapt->rpt->type = type;
- rapt->rpt->freq = freq;
- rapt->rpt->until = until;
- if (except) {
- exc_dup(&rapt->exc, except);
- free_exc_list(except);
- } else {
- LLIST_INIT(&rapt->exc);
- }
+ rapt->state = state;
+ rapt->rpt = mem_malloc(sizeof(struct rpt));
+ *rapt->rpt = *rpt;
+ recur_int_list_dup(&rapt->rpt->bymonth, &rpt->bymonth);
+ recur_free_int_list(&rpt->bymonth);
+ recur_int_list_dup(&rapt->rpt->bywday, &rpt->bywday);
+ recur_free_int_list(&rpt->bywday);
+ recur_int_list_dup(&rapt->rpt->bymonthday, &rpt->bymonthday);
+ recur_free_int_list(&rpt->bymonthday);
+ /*
+ * Note. The exception dates are in the list rapt->exc.
+ * The (empty) list rapt->rpt->exc is not used.
+ */
+ recur_exc_dup(&rapt->exc, &rpt->exc);
+ recur_free_exc_list(&rpt->exc);
+ LLIST_INIT(&rapt->rpt->exc);
LLIST_TS_LOCK(&recur_alist_p);
LLIST_TS_ADD_SORTED(&recur_alist_p, rapt, recur_apoint_cmp);
@@ -293,25 +354,26 @@ struct recur_apoint *recur_apoint_new(char *mesg, char *note, time_t start,
/* Insert a new recursive event in the general linked list */
struct recur_event *recur_event_new(char *mesg, char *note, time_t day,
- int id, int type, int freq, time_t until,
- llist_t * except)
+ int id, struct rpt *rpt)
{
struct recur_event *rev = mem_malloc(sizeof(struct recur_event));
- rev->rpt = mem_malloc(sizeof(struct rpt));
rev->mesg = mem_strdup(mesg);
rev->note = (note != NULL) ? mem_strdup(note) : 0;
rev->day = day;
rev->id = id;
- rev->rpt->type = type;
- rev->rpt->freq = freq;
- rev->rpt->until = until;
- if (except) {
- exc_dup(&rev->exc, except);
- free_exc_list(except);
- } else {
- LLIST_INIT(&rev->exc);
- }
+ rev->rpt = mem_malloc(sizeof(struct rpt));
+ *rev->rpt = *rpt;
+ recur_int_list_dup(&rev->rpt->bymonth, &rpt->bymonth);
+ recur_free_int_list(&rpt->bymonth);
+ recur_int_list_dup(&rev->rpt->bywday, &rpt->bywday);
+ recur_free_int_list(&rpt->bywday);
+ recur_int_list_dup(&rev->rpt->bymonthday, &rpt->bymonthday);
+ recur_free_int_list(&rpt->bymonthday);
+ /* Similarly as for recurrent appointment. */
+ recur_exc_dup(&rev->exc, &rpt->exc);
+ recur_free_exc_list(&rpt->exc);
+ LLIST_INIT(&rev->rpt->exc);
LLIST_ADD_SORTED(&recur_elist, rev, recur_event_cmp);
@@ -340,8 +402,7 @@ char recur_def2char(enum recur_type define)
recur_char = 'Y';
break;
default:
- EXIT(_("unknown repetition type"));
- return 0;
+ recur_char = 0;
}
return recur_char;
@@ -375,6 +436,39 @@ int recur_char2def(char type)
return recur_def;
}
+/* Write the bymonthday list. */
+static void bymonthday_append(struct string *s, llist_t *l)
+{
+ llist_item_t *i;
+
+ LLIST_FOREACH(l, i) {
+ int *day = LLIST_GET_DATA(i);
+ string_catf(s, " d%d", *day);
+ }
+}
+
+/* Write the bywday list. */
+static void bywday_append(struct string *s, llist_t *l)
+{
+ llist_item_t *i;
+
+ LLIST_FOREACH(l, i) {
+ int *wday = LLIST_GET_DATA(i);
+ string_catf(s, " w%d", *wday);
+ }
+}
+
+/* Write the bymonth list. */
+static void bymonth_append(struct string *s, llist_t *l)
+{
+ llist_item_t *i;
+
+ LLIST_FOREACH(l, i) {
+ int *mon = LLIST_GET_DATA(i);
+ string_catf(s, " m%d", *mon);
+ }
+}
+
/* Write days for which recurrent items should not be repeated. */
static void recur_exc_append(struct string *s, llist_t *lexc)
{
@@ -395,29 +489,25 @@ static void recur_exc_append(struct string *s, llist_t *lexc)
}
/* Load the recursive appointment description */
-struct recur_apoint *recur_apoint_scan(FILE * f, struct tm start,
- struct tm end, char type, int freq,
- struct tm until, char *note,
- llist_t * exc, char state,
- struct item_filter *filter)
+char *recur_apoint_scan(FILE *f, struct tm start, struct tm end,
+ char state, char *note,
+ struct item_filter *filter,
+ struct rpt *rpt)
{
char buf[BUFSIZ], *nl;
- time_t tstart, tend, tuntil;
+ time_t tstart, tend;
struct recur_apoint *rapt = NULL;
int cond;
- EXIT_IF(!check_date(start.tm_year, start.tm_mon, start.tm_mday) ||
- !check_date(end.tm_year, end.tm_mon, end.tm_mday) ||
- !check_time(start.tm_hour, start.tm_min) ||
- !check_time(end.tm_hour, end.tm_min) ||
- (until.tm_year != 0
- && !check_date(until.tm_year, until.tm_mon,
- until.tm_mday)),
- _("date error in appointment"));
+ if (!check_date(start.tm_year, start.tm_mon, start.tm_mday) ||
+ !check_date(end.tm_year, end.tm_mon, end.tm_mday) ||
+ !check_time(start.tm_hour, start.tm_min) ||
+ !check_time(end.tm_hour, end.tm_min))
+ return _("illegal date in appointment");
/* Read the appointment description */
if (!fgets(buf, sizeof buf, f))
- return NULL;
+ return _("error in appointment description");
nl = strchr(buf, '\n');
if (nl) {
@@ -432,19 +522,15 @@ struct recur_apoint *recur_apoint_scan(FILE * f, struct tm start,
tstart = mktime(&start);
tend = mktime(&end);
- if (until.tm_year != 0) {
- until.tm_hour = 0;
- until.tm_min = 0;
- until.tm_sec = 0;
- until.tm_isdst = -1;
- until.tm_year -= 1900;
- until.tm_mon--;
- tuntil = mktime(&until);
- } else {
- tuntil = 0;
+ if (tstart == -1 || tend == -1 || tstart > tend)
+ return _("date error in appointment");
+
+ /* Does it occur on the start day? */
+ if (!recur_item_find_occurrence(tstart, tend - tstart, rpt, NULL,
+ DAY(tstart), NULL)) {
+ char *fmt = _("recurrence error: not on start day (%s)");
+ return day_ins(&fmt, tstart);
}
- EXIT_IF(tstart == -1 || tend == -1 || tstart > tend
- || tuntil == -1, _("date error in appointment"));
/* Filter item. */
if (filter) {
@@ -458,9 +544,8 @@ struct recur_apoint *recur_apoint_scan(FILE * f, struct tm start,
);
if (filter->hash) {
rapt = recur_apoint_new(buf, note, tstart,
- tend - tstart, state,
- recur_char2def(type),
- freq, tuntil, exc);
+ tend - tstart, state,
+ rpt);
char *hash = recur_apoint_hash(rapt);
cond = cond || !hash_matches(filter->hash, hash);
mem_free(hash);
@@ -473,54 +558,51 @@ struct recur_apoint *recur_apoint_scan(FILE * f, struct tm start,
}
}
if (!rapt)
- rapt = recur_apoint_new(buf, note, tstart, tend - tstart,
- state, recur_char2def(type), freq,
- tuntil, exc);
-
- return rapt;
+ rapt = recur_apoint_new(buf, note, tstart, tend - tstart, state,
+ rpt);
+ return NULL;
}
/* Load the recursive events from file */
-struct recur_event *recur_event_scan(FILE * f, struct tm start, int id,
- char type, int freq, struct tm until,
- char *note, llist_t * exc,
- struct item_filter *filter)
+char *recur_event_scan(FILE * f, struct tm start, int id,
+ char *note, struct item_filter *filter,
+ struct rpt *rpt)
{
char buf[BUFSIZ], *nl;
- time_t tstart, tend, tuntil;
+ time_t tstart, tend;
struct recur_event *rev = NULL;
int cond;
- EXIT_IF(!check_date(start.tm_year, start.tm_mon, start.tm_mday) ||
- !check_time(start.tm_hour, start.tm_min) ||
- (until.tm_year != 0
- && !check_date(until.tm_year, until.tm_mon,
- until.tm_mday)), _("date error in event"));
+ if (!check_date(start.tm_year, start.tm_mon, start.tm_mday) ||
+ !check_time(start.tm_hour, start.tm_min))
+ return _("illegel date in event");
/* Read the event description */
if (!fgets(buf, sizeof buf, f))
- return NULL;
+ return _("error in appointment description");
nl = strchr(buf, '\n');
if (nl) {
*nl = '\0';
}
- start.tm_hour = until.tm_hour = 0;
- start.tm_min = until.tm_min = 0;
- start.tm_sec = until.tm_sec = 0;
- start.tm_isdst = until.tm_isdst = -1;
+ start.tm_hour = 0;
+ start.tm_min = 0;
+ start.tm_sec = 0;
+ start.tm_isdst = -1;
start.tm_year -= 1900;
start.tm_mon--;
- if (until.tm_year != 0) {
- until.tm_year -= 1900;
- until.tm_mon--;
- tuntil = mktime(&until);
- } else {
- tuntil = 0;
- }
+
tstart = mktime(&start);
- EXIT_IF(tstart == -1 || tuntil == -1, _("date error in event"));
- tend = tstart + DAYINSEC - 1;
+ if (tstart == -1)
+ return _("date error in event");
+ tend = ENDOFDAY(tstart);
+
+ /* Does it occur on the start day? */
+ if (!recur_item_find_occurrence(tstart, -1, rpt, NULL,
+ DAY(tstart), NULL)) {
+ char *fmt = _("recurrence error: not on start day (%s)");
+ return day_ins(&fmt, tstart);
+ }
/* Filter item. */
if (filter) {
@@ -534,8 +616,7 @@ struct recur_event *recur_event_scan(FILE * f, struct tm start, int id,
);
if (filter->hash) {
rev = recur_event_new(buf, note, tstart, id,
- recur_char2def(type),
- freq, tuntil, exc);
+ rpt);
char *hash = recur_event_hash(rev);
cond = cond || !hash_matches(filter->hash, hash);
mem_free(hash);
@@ -548,11 +629,8 @@ struct recur_event *recur_event_scan(FILE * f, struct tm start, int id,
}
}
if (!rev)
- rev = recur_event_new(buf, note, tstart, id,
- recur_char2def(type),
- freq, tuntil, exc);
-
- return rev;
+ rev = recur_event_new(buf, note, tstart, id, rpt);
+ return NULL;
}
char *recur_apoint_tostr(struct recur_apoint *o)
@@ -584,6 +662,9 @@ char *recur_apoint_tostr(struct recur_apoint *o)
recur_def2char(o->rpt->type), lt.tm_mon + 1,
lt.tm_mday, 1900 + lt.tm_year);
}
+ bymonthday_append(&s, &o->rpt->bymonthday);
+ bywday_append(&s, &o->rpt->bywday);
+ bymonth_append(&s, &o->rpt->bymonth);
recur_exc_append(&s, &o->exc);
string_catf(&s, "} ");
if (o->note)
@@ -645,6 +726,9 @@ char *recur_event_tostr(struct recur_event *o)
recur_def2char(o->rpt->type), end_mon, end_day,
end_year);
}
+ bymonthday_append(&s, &o->rpt->bymonthday);
+ bywday_append(&s, &o->rpt->bywday);
+ bymonth_append(&s, &o->rpt->bymonth);
recur_exc_append(&s, &o->exc);
string_catf(&s, "} ");
if (o->note)
@@ -690,6 +774,20 @@ void recur_save_data(FILE * f)
}
/*
+ * Return the month day counted from the opposite end of the month.
+ */
+static int opp_mday(int year, int month, int day)
+{
+ EXIT_IF(day == 0, _("month day is zero"));
+
+ int m_days = days[month - 1] + (month == 2 && ISLEAP(year));
+ if (day > 0)
+ return day - 1 - m_days;
+ else
+ return day + 1 + m_days;
+}
+
+/*
* The two following defines together with the diff_days, diff_months and
* diff_years functions were provided by Lukas Fleischer to correct the wrong
* calculation of recurrent dates after a turn of year.
@@ -740,169 +838,739 @@ static long diff_years(struct tm lt_start, struct tm lt_end)
return lt_end.tm_year - lt_start.tm_year;
}
-static int exc_inday(struct excp *exc, time_t *day_start)
+/*
+ * Return true if 'mon' and 'mday' is month and day of t
+ * (after a call of mktime()).
+ */
+static int date_chk(time_t t, int mon, int mday)
{
- return (date_cmp_day(exc->st, *day_start) == 0);
+ struct tm tm;
+
+ localtime_r(&t, &tm);
+
+ return tm.tm_mon == mon && tm.tm_mday == mday;
}
/*
- * Check if the recurrent item belongs to the selected day, and if yes, store
- * the start date of the occurrence that belongs to the day in a buffer.
- *
- * This function was improved thanks to Tony's patch.
- * Thanks also to youshe for reporting daylight saving time related problems.
- * And finally thanks to Lukas for providing a patch to correct the wrong
- * calculation of recurrent dates after a turn of years.
+ * Return true if the rrule (start, dur, rpt, exc) has an occurrence on the
+ * given day. If so, save that occurrence in a (dynamic or static) buffer.
*/
-unsigned
-recur_item_find_occurrence(time_t item_start, long item_dur,
- llist_t * item_exc, int rpt_type, int rpt_freq,
- time_t rpt_until, time_t day_start,
- time_t *occurrence)
+static int find_occurrence(time_t start, long dur, struct rpt *rpt, llist_t *exc,
+ time_t day, time_t *occurrence)
{
-/*
- * Function-internal duration
- * 1) To avoid an item ending on midnight (which belongs to the next day),
- * duration is always diminished by 1 second.
- * 2) An event has no explicit duration, but lasts for an entire day, which
- * in turn depends on DST.
- */
-#define ITEM_DUR(d) ((item_dur == -1 ? DAYLEN(d) : item_dur) - 1)
+ /*
+ * Duration-on-day-d fix.
+ * An item cannot end on midnight or else it is counted towards the next day.
+ * An event (dur == -1) has no explicit duration, but is considered to last for
+ * the entire day (d) which depends on DST.
+ */
+#define DUR(d) (dur == -1 ? DAYLEN((d)) - 1 : dur - 1)
long diff;
- struct tm lt_day, lt_item, lt_item_day;
- time_t occ, item_day_start;
-
- item_day_start = update_time_in_date(item_start, 0, 0);
+ struct tm lt_day, lt_start, lt_occur;
+ time_t t;
+ int mday, order, pwday, nwday, mon;
- if (day_start < item_day_start)
+ /* Is the given day before the day of the first occurence? */
+ if (date_cmp_day(day, start) < 0)
return 0;
- if (rpt_until && day_start >=
- rpt_until + (item_start - item_day_start) + ITEM_DUR(rpt_until))
+ /*
+ * - or after the day of the last occurrence (which may stretch beyond
+ * the until date)? Extraneous days are eliminated later.
+ */
+ if (rpt->until &&
+ date_cmp_day(NEXTDAY(rpt->until) + DUR(rpt->until), day) < 0)
return 0;
- localtime_r(&day_start, &lt_day); /* selected day */
- localtime_r(&item_start, &lt_item); /* first occurrence */
- lt_item_day = lt_item; /* recent occurrence */
+ localtime_r(&day, &lt_day); /* Given day. */
+ localtime_r(&start, &lt_start); /* Original item. */
+ lt_occur = lt_start; /* First occurence. */
/*
* Update to the most recent occurrence before or on the selected day.
*/
- switch (rpt_type) {
+ switch (rpt->type) {
case RECUR_DAILY:
- diff = diff_days(lt_item_day, lt_day) % rpt_freq;
- lt_item_day.tm_mday = lt_day.tm_mday - diff;
- lt_item_day.tm_mon = lt_day.tm_mon;
- lt_item_day.tm_year = lt_day.tm_year;
+ /* Number of days since the most recent occurrence. */
+ diff = diff_days(lt_occur, lt_day) % rpt->freq;
+ lt_occur.tm_mday = lt_day.tm_mday - diff;
+ lt_occur.tm_mon = lt_day.tm_mon;
+ lt_occur.tm_year = lt_day.tm_year;
break;
case RECUR_WEEKLY:
- diff = diff_days(lt_item_day, lt_day) %
- (rpt_freq * WEEKINDAYS);
- lt_item_day.tm_mday = lt_day.tm_mday - diff;
- lt_item_day.tm_mon = lt_day.tm_mon;
- lt_item_day.tm_year = lt_day.tm_year;
+ diff = diff_days(lt_occur, lt_day) %
+ (rpt->freq * WEEKINDAYS);
+ lt_occur.tm_mday = lt_day.tm_mday - diff;
+ lt_occur.tm_mon = lt_day.tm_mon;
+ lt_occur.tm_year = lt_day.tm_year;
break;
case RECUR_MONTHLY:
- diff = diff_months(lt_item_day, lt_day) % rpt_freq;
- if (!diff && lt_day.tm_mday < lt_item_day.tm_mday)
- diff += rpt_freq;
- lt_item_day.tm_mon = lt_day.tm_mon - diff;
- lt_item_day.tm_year = lt_day.tm_year;
+ diff = diff_months(lt_occur, lt_day) % rpt->freq;
+ if (!diff && lt_day.tm_mday < lt_occur.tm_mday)
+ diff += rpt->freq;
+ lt_occur.tm_mon = lt_day.tm_mon - diff;
+ lt_occur.tm_year = lt_day.tm_year;
break;
case RECUR_YEARLY:
- diff = diff_years(lt_item_day, lt_day) % rpt_freq;
+ diff = diff_years(lt_occur, lt_day) % rpt->freq;
if (!diff &&
- (lt_day.tm_mon < lt_item_day.tm_mon ||
- (lt_day.tm_mon == lt_item_day.tm_mon &&
- lt_day.tm_mday < lt_item_day.tm_mday)))
- diff += rpt_freq;
- lt_item_day.tm_year = lt_day.tm_year - diff;
+ (lt_day.tm_mon < lt_occur.tm_mon ||
+ (lt_day.tm_mon == lt_occur.tm_mon &&
+ lt_day.tm_mday < lt_occur.tm_mday)))
+ diff += rpt->freq;
+ lt_occur.tm_year = lt_day.tm_year - diff;
break;
default:
EXIT(_("unknown item type"));
}
/* Switch to calendar (Unix) time. */
- lt_item_day.tm_isdst = -1;
- occ = mktime(&lt_item_day);
+ lt_occur.tm_isdst = -1;
+ t = mktime(&lt_occur);
/*
* Impossible dates must be ignored (according to RFC 5545). Changing
* only the year or the month may lead to dates like 29 February in
* non-leap years or 31 November.
*/
- if (rpt_type == RECUR_MONTHLY || rpt_type == RECUR_YEARLY) {
- localtime_r(&occ, &lt_item_day);
- if (lt_item_day.tm_mday != lt_item.tm_mday)
+ if ((rpt->type == RECUR_MONTHLY || rpt->type == RECUR_YEARLY) &&
+ !date_chk(t, lt_occur.tm_mon, lt_start.tm_mday))
+ return 0;
+
+ /*
+ * BYMONTHDAY reduction
+ * A month day has two possible list forms.
+ */
+ mday = opp_mday(lt_occur.tm_year + 1900, lt_occur.tm_mon + 1,
+ lt_occur.tm_mday);
+ if (rpt->bymonthday.head &&
+ rpt->type == RECUR_DAILY &&
+ !LLIST_FIND_FIRST(&rpt->bymonthday, &lt_occur.tm_mday, int_cmp) &&
+ !LLIST_FIND_FIRST(&rpt->bymonthday, &mday, int_cmp))
+ return 0;
+
+ /* BYDAY reduction for DAILY */
+ if (rpt->bywday.head && rpt->type == RECUR_DAILY &&
+ !LLIST_FIND_FIRST(&rpt->bywday, &lt_occur.tm_wday, int_cmp))
+ return 0;
+
+ /*
+ * BYDAY reduction for MONTHLY
+ * A weekday has three possible list forms.
+ */
+ if (rpt->bywday.head &&
+ rpt->type == RECUR_MONTHLY && rpt->bymonthday.head) {
+ /* positive order */
+ order = (lt_occur.tm_mday + 6) / WEEKINDAYS;
+ pwday = order * WEEKINDAYS + lt_occur.tm_wday;
+ /* negative order */
+ order = order
+ - wday_per_month(lt_occur.tm_mon + 1,
+ lt_occur.tm_year + 1900,
+ lt_occur.tm_wday)
+ - 1;
+ nwday = order * WEEKINDAYS - lt_occur.tm_wday;
+ if (!LLIST_FIND_FIRST(&rpt->bywday, &lt_occur.tm_wday, int_cmp) &&
+ !LLIST_FIND_FIRST(&rpt->bywday, &pwday, int_cmp) &&
+ !LLIST_FIND_FIRST(&rpt->bywday, &nwday, int_cmp))
return 0;
}
+ /*
+ * BYDAY reduction for YEARLY
+ * A weekday has three possible list forms.
+ */
+ if (rpt->bywday.head &&
+ rpt->type == RECUR_YEARLY && rpt->bymonthday.head) {
+ /* positive order */
+ order = lt_occur.tm_yday / WEEKINDAYS;
+ pwday = order * WEEKINDAYS + lt_occur.tm_wday;
+ /* negative order */
+ order = order
+ - wday_per_year(lt_occur.tm_year + 1900,
+ lt_occur.tm_wday)
+ - 1;
+ nwday = order * WEEKINDAYS - lt_occur.tm_wday;
+ if (!LLIST_FIND_FIRST(&rpt->bywday, &lt_occur.tm_wday, int_cmp) &&
+ !LLIST_FIND_FIRST(&rpt->bywday, &pwday, int_cmp) &&
+ !LLIST_FIND_FIRST(&rpt->bywday, &nwday, int_cmp))
+ return 0;
+ }
+
+ /* BYMONTH reduction */
+ mon = lt_occur.tm_mon + 1;
+ if (rpt->bymonth.head &&
+ rpt->type != RECUR_YEARLY &&
+ !LLIST_FIND_FIRST(&rpt->bymonth, &mon, int_cmp))
+ return 0;
+
/* Exception day? */
- if (LLIST_FIND_FIRST(item_exc, &occ, exc_inday))
+ if (exc && LLIST_FIND_FIRST(exc, &t, exc_inday))
return 0;
- /* After until day? */
- if (rpt_until && occ >= NEXTDAY(rpt_until))
+ /* Extraneous day? */
+ if (rpt->until && t >= NEXTDAY(rpt->until))
return 0;
- /* Does it span the selected day? */
- if (occ + ITEM_DUR(occ) < day_start)
+ /* Does it span the given day?
+ *
+ * NOTE: An appointment ending at 00:00 is not considered to span the
+ * given day, unless the appointment is an appointment without
+ * specified end time, which is internally treated as appointment with
+ * duration 0.
+ */
+ if (t + DUR(t) >= day || (t == day && dur == 0)) {
+ if (occurrence)
+ *occurrence = t;
+ return 1;
+ } else {
return 0;
+ }
+}
+#undef DUR
- if (occurrence)
- *occurrence = occ;
+/*
+ * Return true if the rrule (s, d, r, e) has an occurrence, depending
+ * on the frequency, in the year, month or week of day.
+ */
+static int freq_chk(time_t day, time_t s, long d, struct rpt *r, llist_t *e)
+{
+ if (r->type == RECUR_DAILY)
+ EXIT(_("no daily frequency check"));
+
+ struct tm tm_start, tm_day;
+ struct rpt fc_rpt;
+ time_t fc_day, fc_s;
+
+ localtime_r(&s, &tm_start);
+ localtime_r(&day, &tm_day);
+
+ if (r->type == RECUR_WEEKLY) {
+ /* Set day to the weekly occurrence. */
+ fc_day = date_sec_change(
+ day,
+ 0,
+ WDAY(tm_start.tm_wday) - WDAY(tm_day.tm_wday)
+ );
+ fc_s = s;
+ } else {
+ /* The start day may be invalid in some months. */
+ tm_day.tm_mday = tm_start.tm_mday = 1;
+ if (r->type == RECUR_YEARLY)
+ tm_day.tm_mon = tm_start.tm_mon;
+ tm_day.tm_isdst = tm_start.tm_isdst = -1;
+ fc_day = mktime(&tm_day);
+ fc_s = mktime(&tm_start);
+ }
+ /* Turn all reductions off. */
+ fc_rpt = *r;
+ fc_rpt.until = 0;
+ fc_rpt.bymonth.head = fc_rpt.bywday.head = fc_rpt.bymonthday.head = NULL;
- return 1;
-#undef ITEM_DUR
+ return find_occurrence(fc_s, d, &fc_rpt, e, fc_day, NULL);
}
+/*
+ * Return true if the rrule (s, d, r, e) has an occurrence on 'day' after
+ * 'first'; if so, return it in occurrence.
+ */
+static int test_occurrence(time_t s, long d, struct rpt *r, llist_t *e,
+ time_t first, time_t day, time_t *occurrence)
+{
+ time_t occ;
+
+ if (find_occurrence(s, d, r, e, day, &occ)) {
+ if (occ < first)
+ return 0;
+ if (occurrence)
+ *occurrence = occ;
+ return 1;
+ }
+ return 0;
+}
+
+#define NO_EXPANSION -1
+static int expand_weekly(time_t start, long dur, struct rpt *rpt, llist_t *exc,
+ time_t day, time_t *occurrence)
+{
+ struct tm tm_start;
+ llist_item_t *i;
+ int *w;
+ time_t w_start;
+
+ localtime_r(&start, &tm_start);
+
+ /* BYDAY expansion */
+ if (rpt->bywday.head) {
+ LLIST_FOREACH(&rpt->bywday, i) {
+ w = LLIST_GET_DATA(i);
+ if (*w < 0 || *w > 6)
+ continue;
+ /*
+ * Modify rrule start with a new day in the same week as
+ * start - taking first day of the week into account.
+ */
+ w_start = date_sec_change(
+ start,
+ 0,
+ WDAY(*w) - WDAY(tm_start.tm_wday)
+ );
+ if (test_occurrence(w_start, dur, rpt, exc,
+ start, day, occurrence))
+ return 1;
+ }
+ } else
+ return NO_EXPANSION;
+
+ /* No occurrence */
+ return 0;
+}
+
+static int expand_monthly(time_t start, long dur, struct rpt *rpt, llist_t *exc,
+ time_t day, time_t *occurrence)
+{
+ struct tm tm_start, tm_day;
+ llist_item_t *i;
+ int *w, mday, mon, valid;
+ time_t nstart;
+ struct rpt r = *rpt;
+
+ localtime_r(&day, &tm_day);
+
+ /*
+ * The following three conditional alternatives are mutually exclusive
+ * and cover all four cases of two booleans.
+ */
+
+ /* BYMONTHDAY expansion */
+ if (rpt->bymonthday.head) {
+ LLIST_FOREACH(&rpt->bymonthday, i) {
+ mday = *(int *)LLIST_GET_DATA(i);
+
+ if (mday < 0)
+ mday = opp_mday(tm_day.tm_year + 1900,
+ tm_day.tm_mon + 1, mday);
+ /*
+ * Modify rrule start with a new monthday.
+ * If it is invalid (29, 30 or 31) in the start month,
+ * the month is changed to an earlier one matching the
+ * frequency.
+ */
+ localtime_r(&start, &tm_start);
+ mon = tm_start.tm_mon;
+
+ tm_start.tm_mday = mday;
+ tm_start.tm_isdst = -1;
+ nstart = mktime(&tm_start);
+ valid = date_chk(nstart, mon, mday);
+ /* Never valid? */
+ if (!valid && !(rpt->freq % 12))
+ return 0;
+ /* Note. The loop will terminate! */
+ while (!valid) {
+ localtime_r(&start, &tm_start);
+ mon -= rpt->freq;
+ tm_start.tm_mon = mon;
+ tm_start.tm_mday = mday;
+ tm_start.tm_isdst = -1;
+ nstart = mktime(&tm_start);
+ valid = date_chk(nstart, (mon + 12) % 12, mday);
+ }
+ if (test_occurrence(nstart, dur, rpt, exc,
+ start, day, occurrence))
+ return 1;
+ }
+ }
+ /* BYDAY special expansion for MONTHLY */
+ else if (rpt->bywday.head) {
+ /* The frequency is modified later. */
+ if (!freq_chk(day, start, dur, rpt, exc))
+ return 0;
+
+ LLIST_FOREACH(&rpt->bywday, i) {
+ w = LLIST_GET_DATA(i);
+
+ int order, wday, nbwd;
+
+ localtime_r(&start, &tm_start);
+ /*
+ * Construct a weekly rrule; BYMONTH-reduction in
+ * find_occurrence() will reduce to the bymonth list.
+ */
+ r.type = RECUR_WEEKLY;
+ if (*w > 6) {
+ /*
+ * A single occurrence counting forwards from
+ * the start of the month.
+ */
+ order = *w / WEEKINDAYS;
+ wday = *w % WEEKINDAYS;
+ nbwd = wday_per_month(tm_day.tm_mon + 1,
+ tm_day.tm_year + 1900,
+ wday);
+ if (nbwd < order)
+ return 0;
+ r.freq = order;
+ tm_start.tm_mday = 1;
+ tm_start.tm_mon = tm_day.tm_mon;
+ tm_start.tm_year = tm_day.tm_year;
+ tm_start.tm_isdst = -1;
+ /* Start in the week before the month. */
+ nstart = date_sec_change(
+ next_wday(mktime(&tm_start), wday),
+ 0,
+ -WEEKINDAYS
+ );
+ r.until = date_sec_change(
+ DAY(nstart),
+ 0,
+ r.freq * WEEKINDAYS
+ );
+ if (rpt->until && r.until > rpt->until)
+ return 0;
+ } else if (*w > -1) {
+ /* Expansion to each week. */
+ wday = *w % WEEKINDAYS;
+ r.freq = 1;
+ nstart = next_wday(start, wday);
+ } else if (*w < -6) {
+ /*
+ * A single ocurrence counting backwards from
+ * the end of the month.
+ */
+ order = -(*w) / WEEKINDAYS;
+ wday = -(*w) % WEEKINDAYS;
+ nbwd = wday_per_month(tm_day.tm_mon + 1,
+ tm_day.tm_year + 1900,
+ wday);
+ if (nbwd < order)
+ return 0;
+ r.freq = nbwd - order + 1;
+ tm_start.tm_mday = 1;
+ tm_start.tm_mon = tm_day.tm_mon;
+ tm_start.tm_year = tm_day.tm_year;
+ tm_start.tm_isdst = -1;
+ nstart = date_sec_change(
+ next_wday(mktime(&tm_start), wday),
+ 0,
+ -WEEKINDAYS
+ );
+ r.until = date_sec_change(
+ DAY(nstart),
+ 0,
+ r.freq * WEEKINDAYS
+ );
+ if (rpt->until && r.until > rpt->until)
+ return 0;
+ } else
+ EXIT(_("illegal BYDAY value"));
+
+ if (test_occurrence(nstart, dur, &r, exc,
+ start, day, occurrence))
+ return 1;
+ }
+ }
+ else
+ return NO_EXPANSION;
+
+ /* No occurrence */
+ return 0;
+}
+
+static int expand_yearly(time_t start, long dur, struct rpt *rpt, llist_t *exc,
+ time_t day, time_t *occurrence)
+{
+ struct tm tm_start, tm_day;
+ llist_item_t *i, *j;
+ int *m, *w, mday, wday, order, nbwd;
+ time_t nstart;
+ struct rpt r;
+
+ localtime_r(&day, &tm_day);
+ /*
+ * The following five conditional alternatives are mutually exclusive
+ * and cover all eight cases of three booleans.
+ */
+ /* BYMONTH expansion */
+ if (rpt->bymonth.head && !rpt->bymonthday.head && !rpt->bywday.head) {
+ LLIST_FOREACH(&rpt->bymonth, i) {
+ m = LLIST_GET_DATA(i);
+
+ /* Modify rrule start with new month. */
+ localtime_r(&start, &tm_start);
+ tm_start.tm_mon = *m - 1;
+ tm_start.tm_isdst = -1;
+ nstart = mktime(&tm_start);
+ if (!date_chk(nstart, *m - 1, tm_start.tm_mday))
+ continue;
+ if (find_occurrence(nstart, dur, rpt, exc, day,
+ occurrence))
+ return 1;
+ }
+ } else
+ /* BYDAY special expansion for MONTHLY or YEARLY */
+ if (!rpt->bymonthday.head && rpt->bywday.head) {
+ /* Check needed because frequency is modified later. */
+ if (!freq_chk(day, start, dur, rpt, exc))
+ return 0;
+
+ LLIST_FOREACH(&rpt->bywday, i) {
+ w = LLIST_GET_DATA(i);
+
+ localtime_r(&start, &tm_start);
+ /*
+ * Construct a suitable weekly rrule. BYMONTH
+ * reduction in find_occurrence() will limit
+ * occurrences if needed.
+ */
+ r = *rpt;
+ r.type = RECUR_WEEKLY;
+ if (*w > 6) {
+ /*
+ * Special expand: A single ocurrence counting
+ * forward from the start of the month/year.
+ * Start in the week before with a frequency
+ * that matches the ordered weekday and with
+ * until day that allows only one occurrence.
+ */
+ order = *w / WEEKINDAYS;
+ wday = *w % WEEKINDAYS;
+ if (rpt->bymonth.head)
+ nbwd = wday_per_month(
+ tm_day.tm_mon + 1,
+ tm_day.tm_year + 1900,
+ wday
+ );
+ else
+ nbwd = wday_per_year(
+ tm_day.tm_year + 1900,
+ wday
+ );
+ if (nbwd < order)
+ return 0;
+ r.freq = order;
+ tm_start.tm_mday = 1;
+ if (rpt->bymonth.head)
+ tm_start.tm_mon = tm_day.tm_mon;
+ else
+ tm_start.tm_mon = 0;
+ tm_start.tm_year = tm_day.tm_year;
+ tm_start.tm_isdst = -1;
+ nstart = date_sec_change(
+ next_wday(mktime(&tm_start), wday),
+ 0,
+ -WEEKINDAYS
+ );
+ r.until = date_sec_change(
+ DAY(nstart),
+ 0,
+ r.freq * WEEKINDAYS
+ );
+ if (rpt->until && r.until > rpt->until)
+ return 0;
+ } else if (*w > -1) {
+ /* Expand to each week of the month/year. */
+ wday = *w % WEEKINDAYS;
+ r.freq = 1;
+ nstart = next_wday(start, wday);
+ } else if (*w < -6) {
+ /*
+ * Special expand: A single ocurrence counting
+ * backward from the end of the month/year.
+ */
+ order = -(*w) / WEEKINDAYS;
+ wday = -(*w) % WEEKINDAYS;
+ if (rpt->bymonth.head)
+ nbwd = wday_per_month(
+ tm_day.tm_mon + 1,
+ tm_day.tm_year + 1900,
+ wday
+ );
+ else
+ nbwd = wday_per_year(
+ tm_day.tm_year + 1900,
+ wday
+ );
+ if (nbwd < order)
+ return 0;
+ r.freq = nbwd - order + 1;
+ tm_start.tm_mday = 1;
+ if (rpt->bymonth.head)
+ tm_start.tm_mon = tm_day.tm_mon;
+ else
+ tm_start.tm_mon = 0;
+ tm_start.tm_year = tm_day.tm_year;
+ tm_start.tm_isdst = -1;
+ nstart = date_sec_change(
+ next_wday(mktime(&tm_start), wday),
+ 0,
+ -WEEKINDAYS
+ );
+ r.until = date_sec_change(
+ DAY(nstart),
+ 0,
+ r.freq * WEEKINDAYS
+ );
+ if (rpt->until && r.until > rpt->until)
+ return 0;
+ } else
+ EXIT(_("illegal BYDAY value"));
+
+ if (test_occurrence(nstart, dur, &r, exc,
+ start, day, occurrence))
+ return 1;
+ }
+ } else
+ /* BYMONTHDAY expansion */
+ if (!rpt->bymonth.head && rpt->bymonthday.head) {
+ LLIST_FOREACH(&rpt->bymonthday, i) {
+ mday = *(int *)LLIST_GET_DATA(i);
+ if (mday < 0)
+ mday = opp_mday(
+ tm_day.tm_year + 1900,
+ tm_day.tm_mon + 1, mday
+ );
+ /* Modify rrule start with new monthday. */
+ localtime_r(&start, &tm_start);
+ tm_start.tm_mday = mday;
+ tm_start.tm_isdst = -1;
+ nstart = mktime(&tm_start);
+ if (!date_chk(nstart, tm_start.tm_mon, mday))
+ continue;
+ if (find_occurrence(nstart, dur, rpt, exc, day,
+ occurrence))
+ return 1;
+ }
+ } else
+ /* BYMONTH and BYMONTHDAY expansion */
+ if (rpt->bymonth.head && rpt->bymonthday.head) {
+ LLIST_FOREACH(&rpt->bymonth, i) {
+ m = LLIST_GET_DATA(i);
+
+ LLIST_FOREACH(&rpt->bymonthday, j) {
+ mday = *(int *)LLIST_GET_DATA(j);
+ if (mday < 0)
+ mday = opp_mday(
+ tm_day.tm_year + 1900,
+ tm_day.tm_mon + 1, mday
+ );
+ /* Modify start with new monthday and month. */
+ localtime_r(&start, &tm_start);
+ /* Number of days in February! */
+ if (*m == 2 && mday == 29 &&
+ !ISLEAP(tm_start.tm_year + 1900) &&
+ rpt->freq % 4) {
+ if (!freq_chk(day, start, dur, rpt, exc))
+ return 0;
+ tm_start.tm_year -= tm_start.tm_year % 4;
+ }
+ tm_start.tm_mday = mday;
+ tm_start.tm_mon = *m - 1;
+ tm_start.tm_isdst = -1;
+ nstart = mktime(&tm_start);
+ if (!date_chk(nstart, *m - 1, mday))
+ continue;
+ if (find_occurrence(nstart, dur, rpt, exc, day,
+ occurrence))
+ return 1;
+ }
+ }
+ } else
+ return NO_EXPANSION;
+
+ /* No occurrence */
+ return 0;
+}
+
+/*
+ * Membership test for the recurrence set of the rrule (start, dur, rpt, exc).
+ *
+ * Return true if day belongs to the set. If so, the occurrence is saved in a
+ * buffer. A positive result is always the outcome of find_occurrence(), whereas
+ * a negative result may be arrived at in other ways.
+ *
+ * The basic (type, frequency)-check is in find_occurrence(). When recurrence
+ * set expansion and/or reduction (RFC 5545) is needed, expansion is done before
+ * call of find_occurrence(), while reduction takes place in find_occurrence().
+ *
+ * Recurrence set expansion is accomplished by a combination of calls of
+ * find_occurrence(), possibly with change of type, frequency and start.
+ */
+unsigned
+recur_item_find_occurrence(time_t start, long dur, struct rpt *rpt, llist_t *exc,
+ time_t day, time_t *occurrence)
+{
+ int res;
+
+ /* To make it possible to set an earlier start without expanding the
+ * recurrence set. */
+ if (date_cmp_day(day, start) < 0)
+ return 0;
+
+ switch (rpt->type) {
+ case RECUR_DAILY:
+ res = NO_EXPANSION;
+ break;
+ case RECUR_WEEKLY:
+ res = expand_weekly(start, dur, rpt, exc, day, occurrence);
+ break;
+ case RECUR_MONTHLY:
+ res = expand_monthly(start, dur, rpt, exc, day, occurrence);
+ break;
+ case RECUR_YEARLY:
+ res = expand_yearly(start, dur, rpt, exc, day, occurrence);
+ break;
+ default:
+ res = 0;
+ }
+
+ if (res == NO_EXPANSION)
+ return find_occurrence(start, dur, rpt, exc, day, occurrence);
+
+ /* The result of find_occurrence() is passed on. */
+ return res;
+}
+#undef NO_EXPANSION
+
unsigned
recur_apoint_find_occurrence(struct recur_apoint *rapt, time_t day_start,
time_t *occurrence)
{
- return recur_item_find_occurrence(rapt->start, rapt->dur,
- &rapt->exc, rapt->rpt->type,
- rapt->rpt->freq,
- rapt->rpt->until, day_start,
- occurrence);
+ return recur_item_find_occurrence(rapt->start, rapt->dur, rapt->rpt,
+ &rapt->exc, day_start, occurrence);
}
unsigned
recur_event_find_occurrence(struct recur_event *rev, time_t day_start,
time_t *occurrence)
{
- return recur_item_find_occurrence(rev->day, -1, &rev->exc,
- rev->rpt->type, rev->rpt->freq,
- rev->rpt->until, day_start,
- occurrence);
+ return recur_item_find_occurrence(rev->day, -1, rev->rpt, &rev->exc,
+ day_start, occurrence);
}
/* Check if a recurrent item belongs to the selected day. */
unsigned
-recur_item_inday(time_t item_start, long item_dur, llist_t * item_exc,
- int rpt_type, int rpt_freq, time_t rpt_until,
+recur_item_inday(time_t start, long dur,
+ struct rpt *rpt, llist_t * exc,
time_t day_start)
{
/* We do not need the (real) start time of the occurrence here, so just
* ignore the buffer. */
- return recur_item_find_occurrence(item_start, item_dur, item_exc,
- rpt_type, rpt_freq, rpt_until,
+ return recur_item_find_occurrence(start, dur, rpt, exc,
day_start, NULL);
}
unsigned recur_apoint_inday(struct recur_apoint *rapt, time_t *day_start)
{
- return recur_item_inday(rapt->start, rapt->dur, &rapt->exc,
- rapt->rpt->type, rapt->rpt->freq,
- rapt->rpt->until, *day_start);
+ return recur_item_inday(rapt->start, rapt->dur, rapt->rpt, &rapt->exc,
+ *day_start);
}
unsigned recur_event_inday(struct recur_event *rev, time_t *day_start)
{
- return recur_item_inday(rev->day, -1, &rev->exc,
- rev->rpt->type, rev->rpt->freq,
- rev->rpt->until, *day_start);
+ return recur_item_inday(rev->day, -1, rev->rpt, &rev->exc,
+ *day_start);
}
/* Add an exception to a recurrent event. */
@@ -960,6 +1628,62 @@ void recur_apoint_erase(struct recur_apoint *rapt)
LLIST_TS_UNLOCK(&recur_alist_p);
}
+/* Read monthday list. */
+void recur_bymonthday(llist_t *l, FILE *data_file)
+{
+ int c = 0, d;
+
+ LLIST_INIT(l);
+ while ((c = getc(data_file)) == 'd') {
+ ungetc(c, data_file);
+ if (fscanf(data_file, "d%d ", &d) != 1)
+ EXIT(_("syntax error in bymonthday"));
+ int *i = mem_malloc(sizeof(int));
+ *i = d;
+ LLIST_ADD(l, i);
+ }
+ ungetc(c, data_file);
+}
+
+/* Read weekday list. */
+void recur_bywday(enum recur_type type, llist_t *l, FILE *data_file)
+{
+ int c = 0, w;
+
+ type = !(type == RECUR_MONTHLY || type == RECUR_YEARLY);
+
+ LLIST_INIT(l);
+ while ((c = getc(data_file)) == 'w') {
+ ungetc(c, data_file);
+ if (fscanf(data_file, "w%d ", &w) != 1)
+ EXIT(_("syntax error in bywday"));
+ if (type && (w < 0 || w > 6))
+ EXIT(_("illegal BYDAY value"));
+ int *i = mem_malloc(sizeof(int));
+ *i = w;
+ LLIST_ADD(l, i);
+ }
+ ungetc(c, data_file);
+}
+
+/* Read month list. */
+void recur_bymonth(llist_t *l, FILE *data_file)
+{
+ int c = 0, m;
+
+ LLIST_INIT(l);
+ while ((c = getc(data_file)) == 'm') {
+ ungetc(c, data_file);
+ if (fscanf(data_file, "m%d ", &m) != 1)
+ EXIT(_("syntax error in bymonth"));
+ EXIT_IF(m < 1 || m > 12, _("illegal bymonth value"));
+ int *i = mem_malloc(sizeof(int));
+ *i = m;
+ LLIST_ADD(l, i);
+ }
+ ungetc(c, data_file);
+}
+
/*
* Read days for which recurrent items must not be repeated
* (such days are called exceptions).
@@ -989,6 +1713,7 @@ void recur_exc_scan(llist_t * lexc, FILE * data_file)
exc->st = mktime(&day);
LLIST_ADD(lexc, exc);
}
+ ungetc(c, data_file);
}
/*
@@ -1088,3 +1813,75 @@ void recur_apoint_paste_item(struct recur_apoint *rapt, time_t date)
if (notify_bar())
notify_check_repeated(rapt);
}
+
+/*
+ * Finds the next occurrence of a recurrent item and returns it in the provided
+ * buffer. Useful for test of a repeated item.
+ */
+int recur_next_occurrence(time_t s, long d, struct rpt *r, llist_t *e,
+ time_t day, time_t *next)
+{
+ int ret = 0;
+
+ if (r->until && r->until <= day)
+ return ret;
+
+ while (!r->until || day < r->until) {
+ day = NEXTDAY(day);
+ if (!check_sec(&day))
+ break;
+ if (recur_item_find_occurrence(s, d, r, e, day, next)) {
+ /* Multi-day appointment. */
+ if (*next < day)
+ continue;
+ ret = 1;
+ break;
+ }
+ }
+ return ret;
+}
+
+/*
+ * Finds the nth occurrence (incl. start) of a recurrence rule (s, d, r, e)
+ * and returns it in the provided buffer.
+ */
+int recur_nth_occurrence(time_t s, long d, struct rpt *r, llist_t *e, int n,
+ time_t *nth)
+{
+ time_t day;
+
+ if (n <= 0)
+ return 0;
+
+ for (n--, *nth = s; n > 0; n--) {
+ day = DAY(*nth);
+ if (!recur_next_occurrence(s, d, r, e, day, nth))
+ break;
+ }
+ return !n;
+}
+
+/*
+ * Finds the previous occurrence - the most recent before day - and returns it
+ * in the provided buffer.
+ */
+int recur_prev_occurrence(time_t s, long d, struct rpt *r, llist_t *e,
+ time_t day, time_t *prev)
+{
+ int ret = 0;
+
+ if (day <= DAY(s))
+ return ret;
+
+ while (DAY(s) < day) {
+ day = PREVDAY(day);
+ if (recur_item_find_occurrence(s, d, r, e, day, prev)) {
+ /* Multi-day appointment. */
+ if (d != -1 && *prev < day && day < *prev + d)
+ continue;
+ ret = 1;
+ break;
+ }
+ }
+ return ret;
+}