calcurse icon indicating copy to clipboard operation
calcurse copied to clipboard

iCal: Ignore duration if dtstart and dtend exist

Open Cybolic opened this issue 3 years ago • 7 comments

Calcurse Version: 5.7.0

Description. When syncing events from the Synology Calendar that have originally been created by Calcurse, they contain both dtstart and dtend as well as duration. From what I can see in RFC 2445, it doesn't seem to be against spec to have both, so It'd be nice to have the option to just ignore the duration instead of failing.

Reproduce. Create an event with a start and end time in calcurse. Sync calendar with Synology Calender using calcurse-caldev. Edit anything in the resulting event in the Synology Calender web-app. Observe that calcurse will now refuse to sync the event, instead giving the error VEVENT [4]: either end or duration.

Expected Behavior. Since the needed information is there (dtstart and dtend), just ignore duration.

ical example

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//calcurse//NONSGML v4.7.0//EN
BEGIN:VEVENT
UID:3100228cff30eaf8d2a0c522d61b17e462b154e4
DTSTART;TZID=Europe/Athens:20210413T150000
DURATION:P0DT3H0M0S
SUMMARY:Very cool test summary
SEQUENCE:1
DESCRIPTION:This is a description alright
LOCATION:
TRANSP:TRANSPARENT
DTEND;TZID=Europe/Athens:20210413T180000
END:VEVENT
...

Cybolic avatar Apr 12 '21 15:04 Cybolic

When syncing events from the Synology Calendar that have originally been created by Calcurse, they contain both dtstart and dtend as well as duration. From what I can see in RFC 2445, it doesn't seem to be against spec to have both ...

RFC 2445 is obsolete, the current document is RFC 5545. It explicitly forbids the simultaneous occurrence of DTEND and DURATION in an event, see section 3.6.1:

...
; Either 'dtend' or 'duration' MAY appear in
; a 'eventprop', but 'dtend' and 'duration'
; MUST NOT occur in the same 'eventprop'.
...

If your example was created as described in Reproduce, it seems that the web calendar is the culprit.

lhca avatar Apr 12 '21 21:04 lhca

Thank you for the explanation. Given that it's against spec and it's doubtful Synology is willing to change their software in any decent timespan, is there a way to filter or process the output from calcurse-caldav before it's passed to calcurse?

Cybolic avatar Apr 12 '21 23:04 Cybolic

To answer my own question and for anyone else using the Synology calendar, I ended up pointing the Binary config setting in caldav/config to a short python script containing the following:

#!/usr/bin/env python3
import icalendar
import sys
import subprocess

def fixCalData(data):
    cal = icalendar.Calendar.from_ical(data)
    for component in cal.walk():
        if component.name == 'VEVENT' and component.get('DTSTART') and component.get('DTEND') and component.get('DURATION'):
            del component['DURATION']
    return cal.to_ical(sorted=True)

if (len(sys.argv) > 1 and sys.argv[1] == '-i'):
    data = sys.stdin.read()
    fixed_data = fixCalData(data)
    calcurse = subprocess.Popen(['calcurse', *sys.argv[1:]], stdin=subprocess.PIPE, stdout=sys.stdout)
    calcurse.communicate(fixed_data)
else:
    subprocess.run(['calcurse', *sys.argv[1:]], stdin=sys.stdin, stdout=sys.stdout)

This is quick and dirty, so I don't think it'll handle edge cases or errors very well, but it works for now.

Cybolic avatar Apr 13 '21 08:04 Cybolic

@Cybolic Thank you for this python script.

Also, when I push the event from local computer calcurse to synology calendar, instead of showing it for particular time, the event shows up as an 'All-day event' in synology calendar. Any help regarding this?

asvattha avatar Jan 03 '22 06:01 asvattha

~~@asvattha I don't actually use the Synology Calendar for anything other than syncing, so I haven't bumped in to this. If I were to venture a guess, I think changing the line del component['DURATION'] to del component['DTEND'] might fix it as duration seems to be the property that the Synology Calendar actually uses.~~ EDIT: Never mind, this obviously doesn't work. The change needs to happen on export, not import.

Cybolic avatar Mar 14 '22 09:03 Cybolic

@Cybolic I didn't understand what you mean on export, not import. Do you know why the event from calcurse to synology calendar, when exported to synology calendar, becomes an all-day event, and loses the time duration window? What variables do I need to change, or make change to your python script, to fix this issue? To test this: you can create an event at calcurse with certain time duration, and then try to sync it to your synology calendar. Now if you go to your synology calendar, it will show you as a full day event. It loses the time duration information.

asvattha avatar Jun 21 '22 04:06 asvattha

@asvattha Finally got some time to look at this properly :smiley: By export, I mean that the all-day issue happens when exporting from calcurse to Synology, not on import, so my idea above didn't make any sense.

The good news is that I managed to fix the issue. What happens is that Synology Calendar expects times to be in UTC or have a defined timezone, so I've updated my script above to convert all times to UTC.

Another issue I ran into, is that calcurse will export some durations without the necessary T separator that indicates that a following M means minutes, not months (e.g. it'll export TRIGGER:-P300S instead of TRIGGER:-PT300S and while no M is there, some software still considers the T required).

You can find the updated script in my dotfiles repo here: https://git.sr.ht/~cybolic/Dotfiles/blob/chezmoi/dot_local/bin/executable_calcurse-ical-filter.py

Note that I've added a --debugFiles option, so you can test the script before doing a sync by running calcurse-ical-filter.py -xical --export-uid --debugFiles and check the /tmp/calcurse-export-in.ics and /tmp/calcurse-export-out.ics files to see what calcurse exported (the -in file) and what the script would send to Synology (the -out file).

Cybolic avatar Jan 19 '23 17:01 Cybolic