Update import of basic recurrence rules
Conversion of COUNT to UNTIL was a simple calculation which assumed one repetiton per period (day, week, month or year); it does not take exception days and invalid dates into account. Solved by a new function which returns the n'th occurrence of a recurrence rule. In calcurse UNTIL is interpreted as a day (DATE), in RFC 5545 as a time of day (DATE-TIME). This has implications when a recurrence rule has an occurrence on the UNTIL day, see comment in ical.c An "Import:" note is added when a multi-day event is imported and turned into a calcurse all-day event. Icalendar quotes in comments have been updated to RFC 5545. Signed-off-by: Lars Henriksen <LarsHenriksen@get2net.dk> Signed-off-by: Lukas Fleischer <lfleischer@calcurse.org>
This commit is contained in:
parent
d8c1c48e78
commit
d2791b046a
@ -1106,6 +1106,7 @@ void recur_apoint_switch_notify(struct recur_apoint *);
|
|||||||
void recur_event_paste_item(struct recur_event *, time_t);
|
void recur_event_paste_item(struct recur_event *, time_t);
|
||||||
void recur_apoint_paste_item(struct recur_apoint *, time_t);
|
void recur_apoint_paste_item(struct recur_apoint *, time_t);
|
||||||
int recur_next_occurrence(time_t, long, struct rpt *, llist_t *, time_t, time_t *);
|
int recur_next_occurrence(time_t, long, struct rpt *, llist_t *, time_t, time_t *);
|
||||||
|
int recur_nth_occurrence(time_t, long, struct rpt *, llist_t *, int, time_t *);
|
||||||
|
|
||||||
|
|
||||||
/* sigs.c */
|
/* sigs.c */
|
||||||
|
323
src/ical.c
323
src/ical.c
@ -65,8 +65,8 @@ typedef enum {
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
enum recur_type type;
|
enum recur_type type;
|
||||||
int freq;
|
unsigned freq;
|
||||||
long until;
|
time_t until;
|
||||||
unsigned count;
|
unsigned count;
|
||||||
} ical_rpt_t;
|
} ical_rpt_t;
|
||||||
|
|
||||||
@ -373,8 +373,12 @@ static void ical_store_todo(int priority, int completed, char *mesg,
|
|||||||
erase_note(¬e);
|
erase_note(¬e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calcurse limitation: events are one-day (all-day), and all multi-day events
|
||||||
|
* are turned into one-day events; a note has been added by ical_read_event().
|
||||||
|
*/
|
||||||
static void
|
static void
|
||||||
ical_store_event(char *mesg, char *note, long day, long end,
|
ical_store_event(char *mesg, char *note, time_t day, time_t end,
|
||||||
ical_rpt_t *irpt, llist_t *exc, const char *fmt_ev,
|
ical_rpt_t *irpt, llist_t *exc, const char *fmt_ev,
|
||||||
const char *fmt_rev)
|
const char *fmt_rev)
|
||||||
{
|
{
|
||||||
@ -382,6 +386,10 @@ ical_store_event(char *mesg, char *note, long day, long end,
|
|||||||
struct event *ev;
|
struct event *ev;
|
||||||
struct recur_event *rev;
|
struct recur_event *rev;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Repeating event. The end day is ignored, and the event becomes
|
||||||
|
* one-day even if multi-day.
|
||||||
|
*/
|
||||||
if (irpt) {
|
if (irpt) {
|
||||||
struct rpt rpt;
|
struct rpt rpt;
|
||||||
rpt.type = irpt->type;
|
rpt.type = irpt->type;
|
||||||
@ -398,7 +406,8 @@ ical_store_event(char *mesg, char *note, long day, long end,
|
|||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (end == 0 || end - day <= DAYINSEC) {
|
/* Ordinary one-day event. */
|
||||||
|
if (end - day <= DAYINSEC) {
|
||||||
ev = event_new(mesg, note, day, EVENTID);
|
ev = event_new(mesg, note, day, EVENTID);
|
||||||
if (fmt_ev)
|
if (fmt_ev)
|
||||||
print_event(fmt_ev, day, ev);
|
print_event(fmt_ev, day, ev);
|
||||||
@ -406,17 +415,14 @@ ical_store_event(char *mesg, char *note, long day, long end,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Here we have an event that spans over several days.
|
* Ordinary multi-day event. The event is turned into a daily repeating
|
||||||
*
|
* event until the day before the end. In iCal, the end day is
|
||||||
* In iCal, the end specifies when the event is supposed to end, in
|
* exclusive, the until day inclusive.
|
||||||
* calcurse, the end specifies the time that the last occurrence of the
|
|
||||||
* event starts, so we need to do some conversion here.
|
|
||||||
*/
|
*/
|
||||||
end = day + ((end - day - 1) / DAYINSEC) * DAYINSEC;
|
|
||||||
struct rpt rpt;
|
struct rpt rpt;
|
||||||
rpt.type = RECUR_DAILY;
|
rpt.type = RECUR_DAILY;
|
||||||
rpt.freq = 1;
|
rpt.freq = 1;
|
||||||
rpt.until = end;
|
rpt.until = day + ((end - day - 1) / DAYINSEC) * DAYINSEC;
|
||||||
LLIST_INIT(&rpt.bymonth);
|
LLIST_INIT(&rpt.bymonth);
|
||||||
LLIST_INIT(&rpt.bywday);
|
LLIST_INIT(&rpt.bywday);
|
||||||
LLIST_INIT(&rpt.bymonthday);
|
LLIST_INIT(&rpt.bymonthday);
|
||||||
@ -431,13 +437,14 @@ cleanup:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
ical_store_apoint(char *mesg, char *note, long start, long dur,
|
ical_store_apoint(char *mesg, char *note, time_t start, long dur,
|
||||||
ical_rpt_t * irpt, llist_t * exc, int has_alarm,
|
ical_rpt_t * irpt, llist_t * exc, int has_alarm,
|
||||||
const char *fmt_apt, const char *fmt_rapt)
|
const char *fmt_apt, const char *fmt_rapt)
|
||||||
{
|
{
|
||||||
char state = 0L;
|
char state = 0L;
|
||||||
struct apoint *apt;
|
struct apoint *apt;
|
||||||
struct recur_apoint *rapt;
|
struct recur_apoint *rapt;
|
||||||
|
time_t day;
|
||||||
|
|
||||||
if (has_alarm)
|
if (has_alarm)
|
||||||
state |= APOINT_NOTIFY;
|
state |= APOINT_NOTIFY;
|
||||||
@ -450,8 +457,25 @@ ical_store_apoint(char *mesg, char *note, long start, long dur,
|
|||||||
LLIST_INIT(&rpt.bywday);
|
LLIST_INIT(&rpt.bywday);
|
||||||
LLIST_INIT(&rpt.bymonthday);
|
LLIST_INIT(&rpt.bymonthday);
|
||||||
rpt.exc = *exc;
|
rpt.exc = *exc;
|
||||||
rapt = recur_apoint_new(mesg, note, start, dur, state, &rpt);
|
/*
|
||||||
|
* In calcurse, "until" is interpreted as a day (DATE) - hours,
|
||||||
|
* minutes and seconds are ignored - whereas in iCal the full
|
||||||
|
* DATE-TIME value of "until" is taken into account. It follows
|
||||||
|
* that if the event in calcurse has an occurrence on the until
|
||||||
|
* day, and the start time is after the until value, the
|
||||||
|
* calcurse until day must be changed to the day before.
|
||||||
|
*/
|
||||||
|
if (rpt.until) {
|
||||||
|
day = update_time_in_date(rpt.until, 0, 0);
|
||||||
|
if (recur_item_find_occurrence(start, dur, &rpt, NULL,
|
||||||
|
day, NULL) &&
|
||||||
|
get_item_time(rpt.until) < get_item_time(start))
|
||||||
|
rpt.until = date_sec_change(day, 0, -1);
|
||||||
|
else
|
||||||
|
rpt.until = day;
|
||||||
|
}
|
||||||
mem_free(irpt);
|
mem_free(irpt);
|
||||||
|
rapt = recur_apoint_new(mesg, note, start, dur, state, &rpt);
|
||||||
if (fmt_rapt)
|
if (fmt_rapt)
|
||||||
print_recur_apoint(fmt_rapt, start, rapt->start, rapt);
|
print_recur_apoint(fmt_rapt, start, rapt->start, rapt);
|
||||||
} else {
|
} else {
|
||||||
@ -715,6 +739,7 @@ static long ical_durtime2long(char *timestr)
|
|||||||
* dur-second = 1*DIGIT "S"
|
* dur-second = 1*DIGIT "S"
|
||||||
* dur-day = 1*DIGIT "D"
|
* dur-day = 1*DIGIT "D"
|
||||||
*
|
*
|
||||||
|
* For events, duration must be days or weeks.
|
||||||
* Example: A duration of 15 days, 5 hours and 20 seconds would be:
|
* Example: A duration of 15 days, 5 hours and 20 seconds would be:
|
||||||
* P15DT5H0M20S
|
* P15DT5H0M20S
|
||||||
* A duration of 7 weeks would be:
|
* A duration of 7 weeks would be:
|
||||||
@ -743,39 +768,32 @@ static long ical_dur2long(char *durstr, ical_vevent_e type)
|
|||||||
else if (sscanf(p, "%u%c%n", &day, &c, &bytes_read) == 2 && c == 'D') {
|
else if (sscanf(p, "%u%c%n", &day, &c, &bytes_read) == 2 && c == 'D') {
|
||||||
/* dur-date */
|
/* dur-date */
|
||||||
p += bytes_read;
|
p += bytes_read;
|
||||||
if (*p == 'T' && type == APPOINTMENT)
|
return day * DAYINSEC + (*p == 'T' && type == APPOINTMENT ?
|
||||||
return day * DAYINSEC + ical_durtime2long(p);
|
ical_durtime2long(p) :
|
||||||
else if (*p != 'T' && type == EVENT)
|
0);
|
||||||
return day * DAYINSEC;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Compute the vevent repetition end date from the repetition count.
|
* Set repetition until date from repetition count
|
||||||
*
|
* for an ical recurrence rule (s, d, i, e).
|
||||||
* Extract from RFC2445:
|
|
||||||
* The COUNT rule part defines the number of occurrences at which to
|
|
||||||
* range-bound the recurrence. The "DTSTART" property value, if specified,
|
|
||||||
* counts as the first occurrence.
|
|
||||||
*/
|
*/
|
||||||
static long ical_compute_rpt_until(long start, ical_rpt_t * rpt)
|
static void ical_count2until(time_t s, long d, ical_rpt_t *i, llist_t *e,
|
||||||
|
ical_vevent_e type)
|
||||||
{
|
{
|
||||||
switch (rpt->type) {
|
struct rpt rpt;
|
||||||
case RECUR_DAILY:
|
|
||||||
return date_sec_change(start, 0, rpt->freq * (rpt->count - 1));
|
if (type == EVENT)
|
||||||
case RECUR_WEEKLY:
|
d = -1;
|
||||||
return date_sec_change(start, 0,
|
rpt.type = i->type;
|
||||||
rpt->freq * WEEKINDAYS * (rpt->count - 1));
|
rpt.freq = i->freq;
|
||||||
case RECUR_MONTHLY:
|
rpt.until = 0;
|
||||||
return date_sec_change(start, rpt->freq * (rpt->count - 1), 0);
|
LLIST_INIT(&rpt.bymonth);
|
||||||
case RECUR_YEARLY:
|
LLIST_INIT(&rpt.bywday);
|
||||||
return date_sec_change(start,
|
LLIST_INIT(&rpt.bymonthday);
|
||||||
rpt->freq * 12 * (rpt->count - 1), 0);
|
recur_nth_occurrence(s, d, &rpt, e, i->count, &i->until);
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -803,53 +821,52 @@ static char *ical_get_value(char *p)
|
|||||||
* Purpose: This value type is used to identify properties that contain
|
* Purpose: This value type is used to identify properties that contain
|
||||||
* a recurrence rule specification.
|
* a recurrence rule specification.
|
||||||
*
|
*
|
||||||
* Formal Definition: The value type is defined by the following
|
* Format Definition: The value type is defined by the following
|
||||||
* notation:
|
* notation:
|
||||||
*
|
*
|
||||||
* recur = "FREQ"=freq *(
|
* recur = recur-rule-part *( ";" recur-rule-part )
|
||||||
|
* ;
|
||||||
|
* ; The rule parts are not ordered in any particular sequence.
|
||||||
|
* ;
|
||||||
|
* ; The FREQ rule part is REQUIRED,
|
||||||
|
* ; but MUST NOT occur more than once.
|
||||||
|
* ;
|
||||||
|
* ; The UNTIL or COUNT rule parts are OPTIONAL,
|
||||||
|
* ; but they MUST NOT occur in the same 'recur'.
|
||||||
|
* ;
|
||||||
|
* ; The other rule parts are OPTIONAL,
|
||||||
|
* ; but MUST NOT occur more than once.
|
||||||
*
|
*
|
||||||
* ; either UNTIL or COUNT may appear in a 'recur',
|
* recur-rule-part = ( "FREQ"=freq )
|
||||||
* ; but UNTIL and COUNT MUST NOT occur in the same 'recur'
|
* / ( "UNTIL" "=" enddate )
|
||||||
*
|
* / ( "COUNT" "=" 1*DIGIT )
|
||||||
* ( ";" "UNTIL" "=" enddate ) /
|
* / ( "INTERVAL" "=" 1*DIGIT )
|
||||||
* ( ";" "COUNT" "=" 1*DIGIT ) /
|
* / ( "BYSECOND" "=" byseclist )
|
||||||
*
|
* / ( "BYMINUTE" "=" byminlist )
|
||||||
* ; the rest of these keywords are optional,
|
* / ( "BYHOUR" "=" byhrlist )
|
||||||
* ; but MUST NOT occur more than
|
* / ( "BYDAY" "=" bywdaylist )
|
||||||
* ; once
|
* / ( "BYMONTHDAY" "=" bymodaylist )
|
||||||
*
|
* / ( "BYYEARDAY" "=" byyrdaylist )
|
||||||
* ( ";" "INTERVAL" "=" 1*DIGIT ) /
|
* / ( "BYWEEKNO" "=" bywknolist )
|
||||||
* ( ";" "BYSECOND" "=" byseclist ) /
|
* / ( "BYMONTH" "=" bymolist )
|
||||||
* ( ";" "BYMINUTE" "=" byminlist ) /
|
* / ( "BYSETPOS" "=" bysplist )
|
||||||
* ( ";" "BYHOUR" "=" byhrlist ) /
|
* / ( "WKST" "=" weekday )
|
||||||
* ( ";" "BYDAY" "=" bywdaylist ) /
|
*/
|
||||||
* ( ";" "BYMONTHDAY" "=" bymodaylist ) /
|
|
||||||
* ( ";" "BYYEARDAY" "=" byyrdaylist ) /
|
|
||||||
* ( ";" "BYWEEKNO" "=" bywknolist ) /
|
|
||||||
* ( ";" "BYMONTH" "=" bymolist ) /
|
|
||||||
* ( ";" "BYSETPOS" "=" bysplist ) /
|
|
||||||
* ( ";" "WKST" "=" weekday ) /
|
|
||||||
* ( ";" x-name "=" text )
|
|
||||||
* )
|
|
||||||
*/
|
|
||||||
static ical_rpt_t *ical_read_rrule(FILE *log, char *rrulestr,
|
static ical_rpt_t *ical_read_rrule(FILE *log, char *rrulestr,
|
||||||
unsigned *noskipped,
|
unsigned *noskipped,
|
||||||
const int itemline,
|
const int itemline,
|
||||||
ical_vevent_e type)
|
ical_vevent_e type)
|
||||||
{
|
{
|
||||||
const char count[] = "COUNT=";
|
char freqstr[8];
|
||||||
const char interv[] = "INTERVAL=";
|
|
||||||
char freqstr[BUFSIZ];
|
|
||||||
unsigned interval;
|
|
||||||
ical_rpt_t *rpt;
|
ical_rpt_t *rpt;
|
||||||
char *p;
|
char *p, *q;
|
||||||
|
|
||||||
/* See DTSTART. */
|
|
||||||
if (type == UNDEFINED) {
|
if (type == UNDEFINED) {
|
||||||
ical_log(log, ICAL_VEVENT, itemline,
|
ical_log(log, ICAL_VEVENT, itemline,
|
||||||
_("need DTSTART to determine event type."));
|
_("need DTSTART to determine event type."));
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
p = ical_get_value(rrulestr);
|
p = ical_get_value(rrulestr);
|
||||||
if (!p) {
|
if (!p) {
|
||||||
ical_log(log, ICAL_VEVENT, itemline,
|
ical_log(log, ICAL_VEVENT, itemline,
|
||||||
@ -857,69 +874,90 @@ static ical_rpt_t *ical_read_rrule(FILE *log, char *rrulestr,
|
|||||||
(*noskipped)++;
|
(*noskipped)++;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
/* Prepare for scanf(): replace semicolons by spaces. */
|
||||||
|
for (q = p; (q = strchr(q, ';')); *q = ' ', q++)
|
||||||
|
;
|
||||||
|
|
||||||
rpt = mem_malloc(sizeof(ical_rpt_t));
|
rpt = mem_malloc(sizeof(ical_rpt_t));
|
||||||
memset(rpt, 0, sizeof(ical_rpt_t));
|
memset(rpt, 0, sizeof(ical_rpt_t));
|
||||||
if (sscanf(p, "FREQ=%s", freqstr) != 1) {
|
|
||||||
ical_log(log, ICAL_VEVENT, itemline,
|
|
||||||
_("recurrence frequency not found."));
|
|
||||||
(*noskipped)++;
|
|
||||||
mem_free(rpt);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (starts_with(freqstr, "DAILY")) {
|
/* FREQ rule part */
|
||||||
rpt->type = RECUR_DAILY;
|
if ((p = strstr(rrulestr, "FREQ="))) {
|
||||||
} else if (starts_with(freqstr, "WEEKLY")) {
|
if (sscanf(p, "FREQ=%s", freqstr) != 1) {
|
||||||
rpt->type = RECUR_WEEKLY;
|
ical_log(log, ICAL_VEVENT, itemline,
|
||||||
} else if (starts_with(freqstr, "MONTHLY")) {
|
_("frequency not set in rrule."));
|
||||||
rpt->type = RECUR_MONTHLY;
|
(*noskipped)++;
|
||||||
} else if (starts_with(freqstr, "YEARLY")) {
|
mem_free(rpt);
|
||||||
rpt->type = RECUR_YEARLY;
|
return NULL;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ical_log(log, ICAL_VEVENT, itemline,
|
ical_log(log, ICAL_VEVENT, itemline,
|
||||||
_("recurrence frequency not recognized."));
|
_("frequency absent in rrule."));
|
||||||
(*noskipped)++;
|
(*noskipped)++;
|
||||||
mem_free(rpt);
|
mem_free(rpt);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
if (!strcmp(freqstr, "DAILY"))
|
||||||
* The UNTIL rule part defines a date-time value which bounds the
|
rpt->type = RECUR_DAILY;
|
||||||
* recurrence rule in an inclusive manner. If not present, and the
|
else if (!strcmp(freqstr, "WEEKLY"))
|
||||||
* COUNT rule part is also not present, the RRULE is considered to
|
rpt->type = RECUR_WEEKLY;
|
||||||
* repeat forever.
|
else if (!strcmp(freqstr, "MONTHLY"))
|
||||||
|
rpt->type = RECUR_MONTHLY;
|
||||||
|
else if (!strcmp(freqstr, "YEARLY"))
|
||||||
|
rpt->type = RECUR_YEARLY;
|
||||||
|
else {
|
||||||
|
ical_log(log, ICAL_VEVENT, itemline,
|
||||||
|
_("rrule frequency not supported."));
|
||||||
|
(*noskipped)++;
|
||||||
|
mem_free(rpt);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
* The COUNT rule part defines the number of occurrences at which to
|
/* INTERVAL rule part */
|
||||||
* range-bound the recurrence. The "DTSTART" property value, if
|
rpt->freq = 1;
|
||||||
* specified, counts as the first occurrence.
|
if ((p = strstr(rrulestr, "INTERVAL="))) {
|
||||||
*/
|
if (sscanf(p, "INTERVAL=%u", &rpt->freq) != 1) {
|
||||||
if ((p = strstr(rrulestr, "UNTIL")) != NULL) {
|
ical_log(log, ICAL_VEVENT, itemline, _("invalid interval."));
|
||||||
|
(*noskipped)++;
|
||||||
|
mem_free(rpt);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UNTIL and COUNT rule parts */
|
||||||
|
if (strstr(rrulestr, "UNTIL=") && strstr(rrulestr, "COUNT=")) {
|
||||||
|
ical_log(log, ICAL_VEVENT, itemline,
|
||||||
|
_("either until or count."));
|
||||||
|
(*noskipped)++;
|
||||||
|
mem_free(rpt);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((p = strstr(rrulestr, "UNTIL="))) {
|
||||||
rpt->until = ical_datetime2time_t(strchr(p, '=') + 1, NULL, type);
|
rpt->until = ical_datetime2time_t(strchr(p, '=') + 1, NULL, type);
|
||||||
if (!(rpt->until)) {
|
if (!rpt->until) {
|
||||||
ical_log(log, ICAL_VEVENT, itemline,
|
ical_log(log, ICAL_VEVENT, itemline,
|
||||||
_("invalid until format."));
|
_("invalid until format."));
|
||||||
(*noskipped)++;
|
(*noskipped)++;
|
||||||
mem_free(rpt);
|
mem_free(rpt);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
unsigned cnt;
|
|
||||||
char *countstr;
|
|
||||||
|
|
||||||
rpt->until = 0;
|
|
||||||
if ((countstr = strstr(rrulestr, count))) {
|
|
||||||
countstr += sizeof(count) - 1;
|
|
||||||
if (sscanf(countstr, "%u", &cnt) == 1)
|
|
||||||
rpt->count = cnt;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rpt->freq = 1;
|
/*
|
||||||
if ((p = strstr(rrulestr, interv))) {
|
* COUNT is converted to UNTIL in ical_read_event() once all recurrence
|
||||||
p += sizeof(interv) - 1;
|
* parameters are known.
|
||||||
if (sscanf(p, "%u", &interval) == 1)
|
*/
|
||||||
rpt->freq = interval;
|
if ((p = strstr(rrulestr, "COUNT="))) {
|
||||||
|
p = strchr(p, '=') + 1;
|
||||||
|
if (!(sscanf(p, "%u", &rpt->count) == 1 && rpt->count)) {
|
||||||
|
ical_log(log, ICAL_VEVENT, itemline,
|
||||||
|
_("invalid count value."));
|
||||||
|
(*noskipped)++;
|
||||||
|
mem_free(rpt);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return rpt;
|
return rpt;
|
||||||
@ -1083,7 +1121,8 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents,
|
|||||||
llist_t exc;
|
llist_t exc;
|
||||||
ical_rpt_t *rpt;
|
ical_rpt_t *rpt;
|
||||||
char *mesg, *desc, *loc, *comm, *stat, *imp, *note;
|
char *mesg, *desc, *loc, *comm, *stat, *imp, *note;
|
||||||
long start, end, dur;
|
time_t start, end;
|
||||||
|
long dur;
|
||||||
int has_alarm;
|
int has_alarm;
|
||||||
} vevent;
|
} vevent;
|
||||||
int skip_alarm, has_note, separator;
|
int skip_alarm, has_note, separator;
|
||||||
@ -1115,22 +1154,35 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents,
|
|||||||
_("item start date is not defined."));
|
_("item start date is not defined."));
|
||||||
goto skip;
|
goto skip;
|
||||||
}
|
}
|
||||||
if (vevent_type == APPOINTMENT && vevent.dur == 0) {
|
/* An APPOINTMENT must always have a duration. */
|
||||||
if (vevent.end != 0) {
|
if (vevent_type == APPOINTMENT && !vevent.dur) {
|
||||||
vevent.dur = vevent.end - vevent.start;
|
vevent.dur = vevent.end ?
|
||||||
}
|
vevent.end - vevent.start :
|
||||||
|
0;
|
||||||
if (vevent.dur < 0) {
|
}
|
||||||
ical_log(log, ICAL_VEVENT, ITEMLINE,
|
/* An EVENT must always have an end. */
|
||||||
_("item has a negative duration."));
|
if (vevent_type == EVENT) {
|
||||||
goto skip;
|
if (!vevent.end)
|
||||||
|
vevent.end = vevent.start + vevent.dur;
|
||||||
|
vevent.dur = vevent.end - vevent.start;
|
||||||
|
if (vevent.dur > DAYINSEC) {
|
||||||
|
/* Add note on multi-day events. */
|
||||||
|
char *md = _("multi-day event changed "
|
||||||
|
"to one-day event");
|
||||||
|
if (vevent.imp) {
|
||||||
|
asprintf(&tmp, "%s, %s",
|
||||||
|
vevent.imp, md);
|
||||||
|
mem_free(vevent.imp);
|
||||||
|
vevent.imp = tmp;
|
||||||
|
} else
|
||||||
|
asprintf(&vevent.imp, "%s", md);
|
||||||
|
has_note = separator = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (vevent.rpt && vevent.rpt->count) {
|
if (vevent.rpt && vevent.rpt->count)
|
||||||
vevent.rpt->until =
|
ical_count2until(vevent.start, vevent.dur,
|
||||||
ical_compute_rpt_until(vevent.start,
|
vevent.rpt, &vevent.exc,
|
||||||
vevent.rpt);
|
vevent_type);
|
||||||
}
|
|
||||||
if (has_note) {
|
if (has_note) {
|
||||||
/* Construct string with note file contents. */
|
/* Construct string with note file contents. */
|
||||||
string_init(&s);
|
string_init(&s);
|
||||||
@ -1227,7 +1279,11 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents,
|
|||||||
goto skip;
|
goto skip;
|
||||||
}
|
}
|
||||||
} else if (starts_with_ci(buf, "DTEND")) {
|
} else if (starts_with_ci(buf, "DTEND")) {
|
||||||
/* See DTSTART. */
|
if (vevent.dur) {
|
||||||
|
ical_log(log, ICAL_VEVENT, ITEMLINE,
|
||||||
|
_("either end or duration."));
|
||||||
|
goto skip;
|
||||||
|
}
|
||||||
if (vevent_type == UNDEFINED) {
|
if (vevent_type == UNDEFINED) {
|
||||||
ical_log(log, ICAL_VEVENT, ITEMLINE,
|
ical_log(log, ICAL_VEVENT, ITEMLINE,
|
||||||
_("need DTSTART to determine "
|
_("need DTSTART to determine "
|
||||||
@ -1255,8 +1311,17 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents,
|
|||||||
_("malformed event end time."));
|
_("malformed event end time."));
|
||||||
goto skip;
|
goto skip;
|
||||||
}
|
}
|
||||||
|
if (vevent.end <= vevent.start) {
|
||||||
|
ical_log(log, ICAL_VEVENT, ITEMLINE,
|
||||||
|
_("end must be later than start."));
|
||||||
|
goto skip;
|
||||||
|
}
|
||||||
} else if (starts_with_ci(buf, "DURATION")) {
|
} else if (starts_with_ci(buf, "DURATION")) {
|
||||||
/* See DTSTART. */
|
if (vevent.end) {
|
||||||
|
ical_log(log, ICAL_VEVENT, ITEMLINE,
|
||||||
|
_("either end or duration."));
|
||||||
|
goto skip;
|
||||||
|
}
|
||||||
if (vevent_type == UNDEFINED) {
|
if (vevent_type == UNDEFINED) {
|
||||||
ical_log(log, ICAL_VEVENT, ITEMLINE,
|
ical_log(log, ICAL_VEVENT, ITEMLINE,
|
||||||
_("need DTSTART to determine "
|
_("need DTSTART to determine "
|
||||||
|
20
src/recur.c
20
src/recur.c
@ -1826,3 +1826,23 @@ int recur_next_occurrence(time_t s, long d, struct rpt *r, llist_t *e,
|
|||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Finds the nth occurrence of a recurrence rule (s, d, r, e) (incl. the start)
|
||||||
|
* 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 = update_time_in_date(*nth, 0, 0);
|
||||||
|
if (!recur_next_occurrence(s, d, r, e, day, nth))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return !n;
|
||||||
|
}
|
||||||
|
@ -22,4 +22,31 @@ EXDATE:20000215T000000
|
|||||||
EXDATE:20000223T000000
|
EXDATE:20000223T000000
|
||||||
SUMMARY:Recurring appointment
|
SUMMARY:Recurring appointment
|
||||||
END:VEVENT
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART:20200526T120000
|
||||||
|
DURATION:PT1H17M0S
|
||||||
|
RRULE:FREQ=DAILY;UNTIL=20200529T130000
|
||||||
|
SUMMARY: until May 29 2020\, 13:00
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART:20200526T120000
|
||||||
|
DURATION:PT1H17M0S
|
||||||
|
RRULE:FREQ=DAILY;UNTIL=20200529T110000
|
||||||
|
SUMMARY: until May 29 2020\, 11:00
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART:20200531T214500
|
||||||
|
DURATION:PT15M0S
|
||||||
|
RRULE:FREQ=MONTHLY;COUNT=10
|
||||||
|
EXDATE:20200731T214500,20210131T214500
|
||||||
|
SUMMARY:monthly on 31th\, count 10\, exceptions 31/7/2020 and 31/1/2021
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTAMP:
|
||||||
|
UID:
|
||||||
|
DTSTART;VALUE=DATE:20200502
|
||||||
|
DTEND;VALUE=DATE:20200504
|
||||||
|
DESCRIPTION:The first weekend in May is a two-day event.
|
||||||
|
SUMMARY:First weekend in May
|
||||||
|
END:VEVENT
|
||||||
END:VCALENDAR
|
END:VCALENDAR
|
||||||
|
@ -111,6 +111,17 @@ RRULE:FREQ=MONTHLY;UNTIL=20201030
|
|||||||
EXDATE;VALUE=DATE:20200606T120000Z
|
EXDATE;VALUE=DATE:20200606T120000Z
|
||||||
SUMMARY:Invalid EXDATE value
|
SUMMARY:Invalid EXDATE value
|
||||||
END:VEVENT
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART:20200527T163000
|
||||||
|
DTEND:20200528T163000
|
||||||
|
DURATION:P1D
|
||||||
|
SUMMARY:Both end time and duration
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART:20200527T163000
|
||||||
|
DTEND:20200526T163000
|
||||||
|
SUMMARY:End time before start
|
||||||
|
END:VEVENT
|
||||||
BEGIN:VTODO
|
BEGIN:VTODO
|
||||||
SUMMARY:finally\, missing end of item
|
SUMMARY:finally\, missing end of item
|
||||||
END:VCALENDAR
|
END:VCALENDAR
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
# Recurrence rules.
|
||||||
|
|
||||||
. "${TEST_INIT:-./test-init.sh}"
|
. "${TEST_INIT:-./test-init.sh}"
|
||||||
|
|
||||||
@ -7,11 +8,13 @@ if [ "$1" = 'actual' ]; then
|
|||||||
cp "$DATA_DIR/conf" .calcurse || exit 1
|
cp "$DATA_DIR/conf" .calcurse || exit 1
|
||||||
"$CALCURSE" -D "$PWD/.calcurse" -i "$DATA_DIR/ical-003.ical"
|
"$CALCURSE" -D "$PWD/.calcurse" -i "$DATA_DIR/ical-003.ical"
|
||||||
"$CALCURSE" -D "$PWD/.calcurse" -s01/01/2000 -r365
|
"$CALCURSE" -D "$PWD/.calcurse" -s01/01/2000 -r365
|
||||||
|
"$CALCURSE" -D "$PWD/.calcurse" -s05/01/2020 --to 01/01/2022
|
||||||
|
cat "$PWD/.calcurse/notes"/*
|
||||||
rm -rf .calcurse || exit 1
|
rm -rf .calcurse || exit 1
|
||||||
elif [ "$1" = 'expected' ]; then
|
elif [ "$1" = 'expected' ]; then
|
||||||
cat <<EOD
|
cat <<EOD
|
||||||
Import process report: 0025 lines read
|
Import process report: 0052 lines read
|
||||||
3 apps / 0 events / 0 todos / 0 skipped
|
6 apps / 1 event / 0 todos / 0 skipped
|
||||||
01/01/00:
|
01/01/00:
|
||||||
- 00:00 -> 01:30
|
- 00:00 -> 01:30
|
||||||
Recurring appointment
|
Recurring appointment
|
||||||
@ -131,6 +134,76 @@ Import process report: 0025 lines read
|
|||||||
02/29/00:
|
02/29/00:
|
||||||
- 00:00 -> 01:30
|
- 00:00 -> 01:30
|
||||||
Recurring appointment
|
Recurring appointment
|
||||||
|
05/02/20:
|
||||||
|
* First weekend in May
|
||||||
|
|
||||||
|
05/03/20:
|
||||||
|
* First weekend in May
|
||||||
|
|
||||||
|
05/26/20:
|
||||||
|
- 12:00 -> 13:17
|
||||||
|
until May 29 2020, 11:00
|
||||||
|
- 12:00 -> 13:17
|
||||||
|
until May 29 2020, 13:00
|
||||||
|
|
||||||
|
05/27/20:
|
||||||
|
- 12:00 -> 13:17
|
||||||
|
until May 29 2020, 11:00
|
||||||
|
- 12:00 -> 13:17
|
||||||
|
until May 29 2020, 13:00
|
||||||
|
|
||||||
|
05/28/20:
|
||||||
|
- 12:00 -> 13:17
|
||||||
|
until May 29 2020, 11:00
|
||||||
|
- 12:00 -> 13:17
|
||||||
|
until May 29 2020, 13:00
|
||||||
|
|
||||||
|
05/29/20:
|
||||||
|
- 12:00 -> 13:17
|
||||||
|
until May 29 2020, 13:00
|
||||||
|
|
||||||
|
05/31/20:
|
||||||
|
- 21:45 -> 22:00
|
||||||
|
monthly on 31th, count 10, exceptions 31/7/2020 and 31/1/2021
|
||||||
|
|
||||||
|
08/31/20:
|
||||||
|
- 21:45 -> 22:00
|
||||||
|
monthly on 31th, count 10, exceptions 31/7/2020 and 31/1/2021
|
||||||
|
|
||||||
|
10/31/20:
|
||||||
|
- 21:45 -> 22:00
|
||||||
|
monthly on 31th, count 10, exceptions 31/7/2020 and 31/1/2021
|
||||||
|
|
||||||
|
12/31/20:
|
||||||
|
- 21:45 -> 22:00
|
||||||
|
monthly on 31th, count 10, exceptions 31/7/2020 and 31/1/2021
|
||||||
|
|
||||||
|
03/31/21:
|
||||||
|
- 21:45 -> 22:00
|
||||||
|
monthly on 31th, count 10, exceptions 31/7/2020 and 31/1/2021
|
||||||
|
|
||||||
|
05/31/21:
|
||||||
|
- 21:45 -> 22:00
|
||||||
|
monthly on 31th, count 10, exceptions 31/7/2020 and 31/1/2021
|
||||||
|
|
||||||
|
07/31/21:
|
||||||
|
- 21:45 -> 22:00
|
||||||
|
monthly on 31th, count 10, exceptions 31/7/2020 and 31/1/2021
|
||||||
|
|
||||||
|
08/31/21:
|
||||||
|
- 21:45 -> 22:00
|
||||||
|
monthly on 31th, count 10, exceptions 31/7/2020 and 31/1/2021
|
||||||
|
|
||||||
|
10/31/21:
|
||||||
|
- 21:45 -> 22:00
|
||||||
|
monthly on 31th, count 10, exceptions 31/7/2020 and 31/1/2021
|
||||||
|
|
||||||
|
12/31/21:
|
||||||
|
- 21:45 -> 22:00
|
||||||
|
monthly on 31th, count 10, exceptions 31/7/2020 and 31/1/2021
|
||||||
|
The first weekend in May is a two-day event.
|
||||||
|
--
|
||||||
|
Import: multi-day event changed to one-day event
|
||||||
EOD
|
EOD
|
||||||
else
|
else
|
||||||
./run-test "$0"
|
./run-test "$0"
|
||||||
|
@ -7,6 +7,7 @@ if [ "$1" = 'actual' ]; then
|
|||||||
cp "$DATA_DIR/conf" .calcurse || exit 1
|
cp "$DATA_DIR/conf" .calcurse || exit 1
|
||||||
"$CALCURSE" -D "$PWD/.calcurse" -i "$DATA_DIR/ical-005.ical"
|
"$CALCURSE" -D "$PWD/.calcurse" -i "$DATA_DIR/ical-005.ical"
|
||||||
"$CALCURSE" -D "$PWD/.calcurse" -s10/03/2013 -r3
|
"$CALCURSE" -D "$PWD/.calcurse" -s10/03/2013 -r3
|
||||||
|
cat "$PWD/.calcurse/notes"/*
|
||||||
rm -rf .calcurse || exit 1
|
rm -rf .calcurse || exit 1
|
||||||
elif [ "$1" = 'expected' ]; then
|
elif [ "$1" = 'expected' ]; then
|
||||||
cat <<EOD
|
cat <<EOD
|
||||||
@ -20,6 +21,7 @@ Import process report: 0023 lines read
|
|||||||
|
|
||||||
10/04/13:
|
10/04/13:
|
||||||
* Two days
|
* Two days
|
||||||
|
Import: multi-day event changed to one-day event
|
||||||
EOD
|
EOD
|
||||||
else
|
else
|
||||||
./run-test "$0"
|
./run-test "$0"
|
||||||
|
@ -17,10 +17,10 @@ if [ "$1" = 'actual' ]; then
|
|||||||
rm -rf .calcurse || exit 1
|
rm -rf .calcurse || exit 1
|
||||||
elif [ "$1" = 'expected' ]; then
|
elif [ "$1" = 'expected' ]; then
|
||||||
cat <<EOD
|
cat <<EOD
|
||||||
Import process report: 0116 lines read
|
Import process report: 0127 lines read
|
||||||
2 apps / 0 events / 1 todo / 18 skipped
|
2 apps / 0 events / 1 todo / 20 skipped
|
||||||
VEVENT [12]: invalid or malformed event start time.
|
VEVENT [12]: invalid or malformed event start time.
|
||||||
VEVENT [17]: recurrence frequency not recognized.
|
VEVENT [17]: rrule frequency not supported.
|
||||||
VEVENT [23]: malformed summary line.
|
VEVENT [23]: malformed summary line.
|
||||||
VTODO [28]: item priority is invalid (must be between 0 and 9).
|
VTODO [28]: item priority is invalid (must be between 0 and 9).
|
||||||
VEVENT [32]: malformed exceptions line.
|
VEVENT [32]: malformed exceptions line.
|
||||||
@ -36,7 +36,9 @@ VEVENT [89]: invalid end time value type.
|
|||||||
VEVENT [94]: invalid until format.
|
VEVENT [94]: invalid until format.
|
||||||
VEVENT [100]: invalid exception date value type.
|
VEVENT [100]: invalid exception date value type.
|
||||||
VEVENT [107]: invalid exception.
|
VEVENT [107]: invalid exception.
|
||||||
VTODO [114]: The ical file seems to be malformed. The end of item was not found.
|
VEVENT [114]: either end or duration.
|
||||||
|
VEVENT [120]: end must be later than start.
|
||||||
|
VTODO [125]: The ical file seems to be malformed. The end of item was not found.
|
||||||
101
|
101
|
||||||
EOD
|
EOD
|
||||||
else
|
else
|
||||||
|
Loading…
x
Reference in New Issue
Block a user