From dd864316c671e28b4979c9365b59acc3a9a2d031 Mon Sep 17 00:00:00 2001 From: Lars Henriksen Date: Sun, 16 Jun 2019 08:28:45 +0200 Subject: Recurrence rule extensions Terms and concepts are from RFC 5545 (the iCalendar specification). Overall design -------------- Calcurse is extended with full support for BYMONTH, BYDAY and BYMONTHDAY recurrence rule (rrule) parts. The three rule parts are lists of, respectively, months, weekdays and monthdays. The lists are added to 'struct rpt' as linked lists of integers, and the data file format is extended accordingly (details below). Load and save of the lists follow the pattern of the existing list of exception dates, also in 'struct rpt'. The function recur_item_find_occurence() is split into a front-end and a back-end. The back-end, called find_occurrence(), is the original function extended with rrule reductions; the front-end retains the original name and performs rrule expansions. Front-end plus back-end are backwards compatible and require no changes in calling functions. There is no user interface in this patch. Data file extensions -------------------- The BYMONTH, BYDAY and BYMONTHDAY lists are added to that part of an item line which describes the recurrence rule (the "{...}" part). Each list is - like the list of exception days - a space-separated string of values identified by the initial character. Each list is optional and, if present, must follow the until date and precede the exception day list. The lists must appear in order BYMONTHDAY list, BYDAY list and BYMONTH list. The possible list values are - BYMONTH: m1, m2, ..., m12 - BYDAY: w0, w1, ..., w6, w7, w-7, w8, w-8, ..., w377, w-377 - BYMONTHDAY: d1, d2, ..., d31, d-1, d-2, ..., d-31 which are interpreted as (cf. RFC 5545) - BYMONTH: January, February, ..., December. - BYDAY: SU, MO, ..., SA, +1SU, -1SU, +1MO, -1MO, ..., +53SA, -53SA - BYMONTHDAY: the first, the second, ..., the 31st, the last, the last but one, ..., the last but 30 day of the month Examples: Thursday, TH, is w4; Saturday, SA, is w6. The seventh Thursday, +7TH, is w53 (7 * 7 + 4 = 53); the last but second Saturday, -2SA, is w-20 (2 * 7 + 6 = 20); the last day of the month is d-1. Note that the values w-1, w-2, ..., w-6 are not used. A recurrent appointment with a BYDAY rule part: 06/23/2019 @ 12:00 -> 06/23/2019 @ 13:00 {1W w0 w6} |every week on Sunday and Saturday An event with a BYDAY and a BYMONTH rule part: 10/27/2019 [1] {1Y w-7 m10} every year on last Sunday in October An event with until date, a BYMONTH rule part and an exception day: 06/23/2019 [1] {1Y -> 08/31/2021 m5 m6 m7 !07/23/2020} every year on the 23rd in May, June and July for three years, starting on Sunday, 23 June 2019, but not on 23 July 2020. Recurrence set expansion and reduction --------------------------------------- In calcurse a recurrence rule is a quadruple (s, d, r, e) consisting of start, duration, repetition pattern and exception days and is implemented as: (time_t start, long dur, struct rpt *rpt, llist_t *exc) In RFC 5545 parlance, a recurrence rule defines a recurrence set consisting of all recurrence instances (occurrences) not earlier than start which match the rule pattern. With this concept in mind, recur_item_find_occurremce() may be thought of as a membership function for a recurrence set. The call recur_item_find_occurrence(s, d, r, e, day, occurrence) returns true if day belongs to the recurrence set of (s, d, r, e); if so occurrence points to the recurrence instance (the set member). For a recurrence rule with only the basic DAYLY, WEEKLY, MONTHLY or YEARLY type and frequency the recurrence set consists of periodically repeated instances. The BYxxx rule parts modify the recurrence set by reducing or expanding it as specified by RFC 5545. Expansion is implemented in the front-end by modifications of start and/or frequency of the rule (s, d, r, e), often several times, in such a way that the desired recurrence instances are included in the recurrence set. This is possible because the front-end as the very first thing checks for early days (day < s). When day is known not to be early, start (s) can safely be moved backwards. Likewise, if frequency must be changed, the front-end checks whether the frequency repetition applies to the week, month or year of day. Reduction is easier and is performed in the back-end along with the existing validity checks. It consists in checking whether month, day of month or weekday of a found occurrence is on the appropriate list. Signed-off-by: Lars Henriksen Signed-off-by: Lukas Fleischer --- src/io.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) (limited to 'src/io.c') diff --git a/src/io.c b/src/io.c index b9c2d3b..1caa6d3 100644 --- a/src/io.c +++ b/src/io.c @@ -644,7 +644,7 @@ void io_load_app(struct item_filter *filter) &until.tm_mon, &until.tm_mday, &until.tm_year) != 3) io_load_error(path_apts, line, - _("syntax error in item repetition")); + _("syntax error in until date")); if (!check_date(until.tm_year, until.tm_mon, until.tm_mday)) io_load_error(path_apts, line, @@ -659,6 +659,30 @@ void io_load_app(struct item_filter *filter) c = getc(data_file); } else rpt.until = 0; + /* Optional bymonthday list */ + if (c == 'd') { + if (rpt.type == RECUR_WEEKLY) + io_load_error(path_apts, line, + _("BYMONTHDAY illegal with WEEKLY")); + ungetc(c, data_file); + recur_bymonthday(&rpt.bymonthday, data_file); + c = getc(data_file); + } else + LLIST_INIT(&rpt.bymonthday); + /* Optional bywday list */ + if (c == 'w') { + ungetc(c, data_file); + recur_bywday(rpt.type, &rpt.bywday, data_file); + c = getc(data_file); + } else + LLIST_INIT(&rpt.bywday); + /* Optional bymonth list */ + if (c == 'm') { + ungetc(c, data_file); + recur_bymonth(&rpt.bymonth, data_file); + c = getc(data_file); + } else + LLIST_INIT(&rpt.bymonth); /* Optional exception dates */ if (c == '!') { ungetc(c, data_file); -- cgit v1.2.3