Extend import of recurrence rules

Support has been implemented for recurrence rule parts BYMONTH, BYMONTHDAY and
BYDAY.  A new test has been added.

Signed-off-by: Lars Henriksen <LarsHenriksen@get2net.dk>
Signed-off-by: Lukas Fleischer <lfleischer@calcurse.org>
This commit is contained in:
Lars Henriksen 2020-05-28 14:45:00 +02:00 committed by Lukas Fleischer
parent d2791b046a
commit 382e60ba69
8 changed files with 2284 additions and 29 deletions

View File

@ -68,6 +68,9 @@ typedef struct {
unsigned freq;
time_t until;
unsigned count;
llist_t bymonth;
llist_t bywday;
llist_t bymonthday;
} ical_rpt_t;
static void ical_export_header(FILE *);
@ -377,7 +380,7 @@ static void ical_store_todo(int priority, int completed, char *mesg,
* 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 int
ical_store_event(char *mesg, char *note, time_t day, time_t end,
ical_rpt_t *irpt, llist_t *exc, const char *fmt_ev,
const char *fmt_rev)
@ -395,12 +398,14 @@ ical_store_event(char *mesg, char *note, time_t day, time_t end,
rpt.type = irpt->type;
rpt.freq = irpt->freq;
rpt.until = irpt->until;
LLIST_INIT(&rpt.bymonth);
LLIST_INIT(&rpt.bywday);
LLIST_INIT(&rpt.bymonthday);
rpt.bymonth = irpt->bymonth;
rpt.bywday = irpt->bywday;
rpt.bymonthday = irpt->bymonthday;
rpt.exc = *exc;
rev = recur_event_new(mesg, note, day, EVENTID, &rpt);
if (!recur_item_find_occurrence(day, -1, &rpt, NULL, day, NULL))
return 0;
mem_free(irpt);
rev = recur_event_new(mesg, note, day, EVENTID, &rpt);
if (fmt_rev)
print_recur_event(fmt_rev, day, rev);
goto cleanup;
@ -434,9 +439,10 @@ ical_store_event(char *mesg, char *note, time_t day, time_t end,
cleanup:
mem_free(mesg);
erase_note(&note);
return 1;
}
static void
static int
ical_store_apoint(char *mesg, char *note, time_t start, long dur,
ical_rpt_t * irpt, llist_t * exc, int has_alarm,
const char *fmt_apt, const char *fmt_rapt)
@ -446,6 +452,7 @@ ical_store_apoint(char *mesg, char *note, time_t start, long dur,
struct recur_apoint *rapt;
time_t day;
day = update_time_in_date(start, 0, 0);
if (has_alarm)
state |= APOINT_NOTIFY;
if (irpt) {
@ -453,10 +460,13 @@ ical_store_apoint(char *mesg, char *note, time_t start, long dur,
rpt.type = irpt->type;
rpt.freq = irpt->freq;
rpt.until = irpt->until;
LLIST_INIT(&rpt.bymonth);
LLIST_INIT(&rpt.bywday);
LLIST_INIT(&rpt.bymonthday);
rpt.bymonth = irpt->bymonth;
rpt.bywday = irpt->bywday;
rpt.bymonthday = irpt->bymonthday;
rpt.exc = *exc;
if (!recur_item_find_occurrence(start, dur, &rpt, NULL,
day, NULL))
return 0;
/*
* In calcurse, "until" is interpreted as a day (DATE) - hours,
* minutes and seconds are ignored - whereas in iCal the full
@ -485,6 +495,7 @@ ical_store_apoint(char *mesg, char *note, time_t start, long dur,
}
mem_free(mesg);
erase_note(&note);
return 1;
}
/*
@ -790,9 +801,9 @@ static void ical_count2until(time_t s, long d, ical_rpt_t *i, llist_t *e,
rpt.type = i->type;
rpt.freq = i->freq;
rpt.until = 0;
LLIST_INIT(&rpt.bymonth);
LLIST_INIT(&rpt.bywday);
LLIST_INIT(&rpt.bymonthday);
rpt.bymonth = i->bymonth;
rpt.bywday = i->bywday;
rpt.bymonthday = i->bymonthday;
recur_nth_occurrence(s, d, &rpt, e, i->count, &i->until);
}
@ -813,9 +824,108 @@ static char *ical_get_value(char *p)
return p + 1;
}
/*
* Fill in the bymonth linked list from a comma-separated list of
* unsigned integers terminated by a space or end of string.
*/
static int ical_bymonth(llist_t *ll, char *cl)
{
unsigned mon;
int *i, n;
while (!(*cl == ' ' || *cl == '\0')) {
if (!(sscanf(cl, "%u%n", &mon, &n) == 1))
return 0;
i = mem_malloc(sizeof(int));
*i = mon;
LLIST_ADD(ll, i);
cl += n;
cl += (*cl == ',');
}
return 1;
}
/*
* Fill in the bymonthday linked list from a comma-separated list of
* (signed) integers terminated by a space or end of string.
*/
static int ical_bymonthday(llist_t *ll, char *cl)
{
int mday;
int *i, n;
while (!(*cl == ' ' || *cl == '\0')) {
if (!(sscanf(cl, "%d%n", &mday, &n) == 1))
return 0;
i = mem_malloc(sizeof(int));
*i = mday;
LLIST_ADD(ll, i);
cl += n;
cl += (*cl == ',');
}
return 1;
}
/*
* Fill in the bywday linked list from a comma-separated list of (ordered)
* weekday names (+1SU, MO, -5SA, 25TU, etc.) terminated by a space or end of
* string.
*/
static int ical_bywday(llist_t *ll, char *cl)
{
int sign, order, wday, n, *i;
char *owd;
while (!(*cl == ' ' || *cl == '\0')) {
/* find list separator */
for (owd = cl; !(*cl == ',' || *cl == ' ' || *cl == '\0'); cl++)
;
cl += (*cl == ',');
if (!(sscanf(owd, "%d%n", &order, &n) == 1))
order = n = 0;
sign = (order < 0) ? -1 : 1;
order *= sign;
owd += n;
if (starts_with(owd, "SU"))
wday = 0;
else if (starts_with(owd, "MO"))
wday = 1;
else if (starts_with(owd, "TU"))
wday = 2;
else if (starts_with(owd, "WE"))
wday = 3;
else if (starts_with(owd, "TH"))
wday = 4;
else if (starts_with(owd, "FR"))
wday = 5;
else if (starts_with(owd, "SA"))
wday = 6;
else
return 0;
wday = sign * (wday + order * WEEKINDAYS);
i = mem_malloc(sizeof(int));
*i = wday;
LLIST_ADD(ll, i);
}
return 1;
}
/*
* Read a recurrence rule from an iCalendar RRULE string.
*
* RFC 5545, section 3.8.5.3:
*
* Property Name: RRULE
*
* Purpose: This property defines a rule or repeating pattern for
* recurring events, to-dos, journal entries, or time zone definitions.
*
* Value Type: RECUR
*
* RFC 5545, section 3.3.10:
*
* Value Name: RECUR
*
* Purpose: This value type is used to identify properties that contain
@ -855,7 +965,8 @@ static char *ical_get_value(char *p)
static ical_rpt_t *ical_read_rrule(FILE *log, char *rrulestr,
unsigned *noskipped,
const int itemline,
ical_vevent_e type)
ical_vevent_e type,
time_t start)
{
char freqstr[8];
ical_rpt_t *rpt;
@ -880,6 +991,9 @@ static ical_rpt_t *ical_read_rrule(FILE *log, char *rrulestr,
rpt = mem_malloc(sizeof(ical_rpt_t));
memset(rpt, 0, sizeof(ical_rpt_t));
LLIST_INIT(&rpt->bymonth);
LLIST_INIT(&rpt->bywday);
LLIST_INIT(&rpt->bymonthday);
/* FREQ rule part */
if ((p = strstr(rrulestr, "FREQ="))) {
@ -960,6 +1074,42 @@ static ical_rpt_t *ical_read_rrule(FILE *log, char *rrulestr,
}
}
/* BYMONTH rule part */
if ((p = strstr(rrulestr, "BYMONTH="))) {
p = strchr(p, '=') + 1;
if (!ical_bymonth(&rpt->bymonth, p)) {
ical_log(log, ICAL_VEVENT, itemline,
_("invalid bymonth list."));
(*noskipped)++;
mem_free(rpt);
return NULL;
}
}
/* BYMONTHDAY rule part */
if ((p = strstr(rrulestr, "BYMONTHDAY="))) {
p = strchr(p, '=') + 1;
if (!ical_bymonthday(&rpt->bymonthday, p)) {
ical_log(log, ICAL_VEVENT, itemline,
_("invalid bymonthday list."));
(*noskipped)++;
mem_free(rpt);
return NULL;
}
}
/* BYDAY rule part */
if ((p = strstr(rrulestr, "BYDAY="))) {
p = strchr(p, '=') + 1;
if (!ical_bywday(&rpt->bywday, p)) {
ical_log(log, ICAL_VEVENT, itemline,
_("invalid byday list."));
(*noskipped)++;
mem_free(rpt);
return NULL;
}
}
return rpt;
}
@ -1214,21 +1364,38 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents,
}
vevent.note = generate_note(string_buf(&s));
mem_free(s.buf);
/*
* Necessary to prevent double-free if item
* creation fails below.
*/
vevent.desc = vevent.loc = vevent.comm =
vevent.stat = vevent.imp = NULL;
}
char *msg = _("rrule does not match start day (%s).");
switch (vevent_type) {
case APPOINTMENT:
ical_store_apoint(vevent.mesg, vevent.note,
if (!ical_store_apoint(vevent.mesg, vevent.note,
vevent.start, vevent.dur,
vevent.rpt, &vevent.exc,
vevent.has_alarm, fmt_apt,
fmt_rapt);
vevent.has_alarm,
fmt_apt, fmt_rapt)) {
char *l = day_ins(&msg, vevent.start);
ical_log(log, ICAL_VEVENT, ITEMLINE, l);
mem_free(l);
goto skip;
}
(*noapoints)++;
break;
case EVENT:
ical_store_event(vevent.mesg, vevent.note,
if (!ical_store_event(vevent.mesg, vevent.note,
vevent.start, vevent.end,
vevent.rpt, &vevent.exc,
fmt_ev, fmt_rev);
fmt_ev, fmt_rev)) {
char *l = day_ins(&msg, vevent.start);
ical_log(log, ICAL_VEVENT, ITEMLINE, l);
mem_free(l);
goto skip;
}
(*noevents)++;
break;
case UNDEFINED:
@ -1342,7 +1509,7 @@ ical_read_event(FILE * fdi, FILE * log, unsigned *noevents,
}
} else if (starts_with_ci(buf, "RRULE")) {
vevent.rpt = ical_read_rrule(log, buf, noskipped,
ITEMLINE, vevent_type);
ITEMLINE, vevent_type, vevent.start);
if (!vevent.rpt)
goto cleanup;
} else if (starts_with_ci(buf, "EXDATE")) {

View File

@ -1828,7 +1828,7 @@ int recur_next_occurrence(time_t s, long d, struct rpt *r, llist_t *e,
}
/*
* Finds the nth occurrence of a recurrence rule (s, d, r, e) (incl. the start)
* 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,

View File

@ -434,9 +434,9 @@ time_t tzdate2sec(struct date day, unsigned hour, unsigned min, char *tznew)
tzold = getenv("TZ");
if (tzold)
tzold = mem_strdup(tzold);
setenv("TZ", tznew, 1);
tzset();
t = date2sec(day, hour, min);
if (tzold) {

View File

@ -60,6 +60,7 @@ TESTS = \
ical-010.sh \
ical-011.sh \
ical-012.sh \
ical-013.sh \
next-001.sh \
next-002.sh \
next-003.sh \
@ -138,6 +139,7 @@ EXTRA_DIST = \
data/ical-008.ical \
data/ical-009.ical \
data/ical-012.ical \
data/rfc5545.ical \
data/rfc5545 \
data/todo \
data/todo-export

View File

@ -46,7 +46,25 @@ DTSTAMP:
UID:
DTSTART;VALUE=DATE:20200502
DTEND;VALUE=DATE:20200504
DESCRIPTION:The first weekend in May is a two-day event.
DESCRIPTION:The first weekend in May is a two-day event.\nNon-repeating event.
SUMMARY:First weekend in May
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;VALUE=DATE:20200502
DTEND;VALUE=DATE:20200504
DESCRIPTION:First weekend in May is a two-day event!\nRepeating event\, three years.
SUMMARY:First weekend in May
RRULE:FREQ=YEARLY;BYDAY=1SA;BYMONTH=5;COUNT=3
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART:20200502T000000
DTEND:20200504T000000
DESCRIPTION:First weekend in May is a two-day event!\nRepeating appointment.
SUMMARY:First weekend in May
RRULE:FREQ=YEARLY;BYDAY=1SA;BYMONTH=5;COUNT=3
END:VEVENT
END:VCALENDAR

238
test/data/rfc5545.ical Normal file
View File

@ -0,0 +1,238 @@
BEGIN:VCALENDAR
VERSION:2.0
PRODID:
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970901T090000
SUMMARY:Every other week on Monday\, Wednesday\, and Friday until December 24\, 1997\, starting on Monday\, September 1\, 1997
RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970902T090000
SUMMARY:Every other week on Tuesday and Thursday\, for 8 occurrences
RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970905T090000
SUMMARY:Monthly on the first Friday for 10 occurrences
RRULE:FREQ=MONTHLY;COUNT=10;BYDAY=1FR
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970905T090000
SUMMARY:Monthly on the first Friday until December 24\, 1997
RRULE:FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970907T090000
SUMMARY:Every other month on the first and last Sunday of the month for 10 occurrences
RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970922T090000
SUMMARY:Monthly on the second-to-last Monday of the month for 6 months
RRULE:FREQ=MONTHLY;COUNT=6;BYDAY=-2MO
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970928T090000
SUMMARY:Monthly on the third-to-the-last day of the month\, forever
RRULE:FREQ=MONTHLY;BYMONTHDAY=-3
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970902T090000
SUMMARY:Monthly on the 2nd and 15th of the month for 10 occurrences
RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970930T090000
SUMMARY:Monthly on the first and last day of the month for 10 occurrences
RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970910T090000
SUMMARY:Every 18 months on the 10th thru 15th of the month for 10 occurrences
RRULE:FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,15
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970902T090000
DURATION:PT1H
SUMMARY:Daily for 10 occurrences
RRULE:FREQ=DAILY;COUNT=10
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970902T090000
SUMMARY:Every Tuesday\, every other month
RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970610T090000
SUMMARY:Yearly in June and July for 10 occurrences
RRULE:FREQ=YEARLY;COUNT=10;BYMONTH=6,7
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970310T090000
SUMMARY:Every other year on January\, February\, and March for 10 occurrences
RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970519T090000
SUMMARY:Every 20th Monday of the year\, forever
RRULE:FREQ=YEARLY;BYDAY=20MO
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970313T090000
SUMMARY:Every Thursday in March\, forever
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970605T090000
SUMMARY:Every Thursday\, but only during June\, July\, and August\, forever
RRULE:FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19980213T090000
SUMMARY:Every Friday the 13th\, forever
RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970913T090000
SUMMARY:The first Saturday that follows the first Sunday of the month\, forever
RRULE:FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19961105T090000
SUMMARY:Every 4 years\, the first Tuesday after a Monday in November\, forever (U.S. Presidential Election day)
RRULE:FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970805T090000
SUMMARY:An example where the days generated makes a difference because of WKST
RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970805T090000
SUMMARY:changing only WKST from MO to SU\, yields different results...
RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970902T090000
DURATION:PT30M
SUMMARY:Daily until December 24\, 1997
RRULE:FREQ=DAILY;UNTIL=19971224T000000Z
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:20070115T090000
SUMMARY:An example where an invalid date (i.e.\, February 30) is ignored
RRULE:FREQ=MONTHLY;BYMONTHDAY=15,30;COUNT=5
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970902T090000
DURATION:PT5M
SUMMARY:Every other day - forever
RRULE:FREQ=DAILY;INTERVAL=2
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970902T090000
SUMMARY:Every 10 days\, 5 occurrences:
RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19980101T090000
SUMMARY:(1) Every day in January\, for 3 years:
RRULE:FREQ=YEARLY;UNTIL=20000131T140000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19980101T090000
SUMMARY:(2) Every day in January\, for 3 years:
RRULE:FREQ=DAILY;UNTIL=20000131T140000Z;BYMONTH=1
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970902T090000
SUMMARY:Weekly for 10 occurrences
RRULE:FREQ=WEEKLY;COUNT=10
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970902T090000
SUMMARY:Weekly until December 24\, 1997
RRULE:FREQ=WEEKLY;UNTIL=19971224T000000Z
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970902T090000
SUMMARY:Every other week - forever
RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970902T090000
SUMMARY:Weekly on Tuesday and Thursday for five weeks (UNTIL)
RRULE:FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH
END:VEVENT
BEGIN:VEVENT
DTSTAMP:
UID:
DTSTART;TZID=America/New_York:19970902T090000
SUMMARY:Weekly on Tuesday and Thursday for five weeks (COUNT)
RRULE:FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH
END:VEVENT
END:VCALENDAR

View File

@ -8,13 +8,13 @@ if [ "$1" = 'actual' ]; then
cp "$DATA_DIR/conf" .calcurse || exit 1
"$CALCURSE" -D "$PWD/.calcurse" -i "$DATA_DIR/ical-003.ical"
"$CALCURSE" -D "$PWD/.calcurse" -s01/01/2000 -r365
"$CALCURSE" -D "$PWD/.calcurse" -s05/01/2020 --to 01/01/2022
"$CALCURSE" -D "$PWD/.calcurse" -s05/01/2020 --to 01/01/2023
cat "$PWD/.calcurse/notes"/*
rm -rf .calcurse || exit 1
elif [ "$1" = 'expected' ]; then
cat <<EOD
Import process report: 0052 lines read
6 apps / 1 event / 0 todos / 0 skipped
Import process report: 0070 lines read
7 apps / 2 events / 0 todos / 0 skipped
01/01/00:
- 00:00 -> 01:30
Recurring appointment
@ -136,9 +136,14 @@ Import process report: 0052 lines read
Recurring appointment
05/02/20:
* First weekend in May
* First weekend in May
- 00:00 -> ..:..
First weekend in May
05/03/20:
* First weekend in May
- ..:.. -> 00:00
First weekend in May
05/26/20:
- 12:00 -> 13:17
@ -182,6 +187,15 @@ Import process report: 0052 lines read
- 21:45 -> 22:00
monthly on 31th, count 10, exceptions 31/7/2020 and 31/1/2021
05/01/21:
* First weekend in May
- 00:00 -> ..:..
First weekend in May
05/02/21:
- ..:.. -> 00:00
First weekend in May
05/31/21:
- 21:45 -> 22:00
monthly on 31th, count 10, exceptions 31/7/2020 and 31/1/2021
@ -201,7 +215,23 @@ Import process report: 0052 lines read
12/31/21:
- 21:45 -> 22:00
monthly on 31th, count 10, exceptions 31/7/2020 and 31/1/2021
05/07/22:
* First weekend in May
- 00:00 -> ..:..
First weekend in May
05/08/22:
- ..:.. -> 00:00
First weekend in May
First weekend in May is a two-day event!
Repeating appointment.
The first weekend in May is a two-day event.
Non-repeating event.
--
Import: multi-day event changed to one-day event
First weekend in May is a two-day event!
Repeating event, three years.
--
Import: multi-day event changed to one-day event
EOD

1800
test/ical-013.sh Executable file

File diff suppressed because it is too large Load Diff