Repository: dateutil/dateutil (Python)

Issue:
ValueError: unsupported DTSTART parm: VALUE=DATE-TIME
I reported this on khal:
https://github.com/pimutils/khal/issues/679
and radicale:
https://github.com/Kozea/Radicale/issues/646

When trying When trying to sync a calendar built from khal to radicale I get

Traceback (most recent call last):
File "/usr/lib/python3.4/site-packages/vobject/base.py", line 183, in transformToNati
ve
return self.behavior.transformToNative(self)
File "/usr/lib/python3.4/site-packages/vobject/icalendar.py", line 989, in transformT
oNative
obj.registerTzinfo(obj.tzinfo)
File "/usr/lib/python3.4/site-packages/vobject/icalendar.py", line 150, in gettzinfo
return tz.tzical(buffer).get()
File "/usr/lib/python3.4/site-packages/dateutil/tz/tz.py", line 1109, in init
self._parse_rfc(fobj.read())
File "/usr/lib/python3.4/site-packages/dateutil/tz/tz.py", line 1230, in _parse_rfc
cache=True)
File "/usr/lib/python3.4/site-packages/dateutil/rrule.py", line 1603, in call
return self._parse_rfc(s, **kwargs)
File "/usr/lib/python3.4/site-packages/dateutil/rrule.py", line 1562, in _parse_rfc
raise ValueError("unsupported DTSTART parm: "+parm)
ValueError: unsupported DTSTART parm: VALUE=DATE-TIME

Which is due to rrule.py not wanting any parameter to DTSTART.

The following patch is ignoring the parameter and solves the issue for me, until someone comes with a better fix. 

```
$ cat /tmp/python-dateutil-fix-dtstart.patch
diff --git a/dateutil/rrule.py b/dateutil/rrule.py
index da94351..b447600 100644
--- a/dateutil/rrule.py
+++ b/dateutil/rrule.py
@@ -1559,7 +1559,8 @@ class _rrulestr(object):
                     exdatevals.append(value)
                 elif name == "DTSTART":
                     for parm in parms:
-                        raise ValueError("unsupported DTSTART parm: "+parm)
+                        if parm != "VALUE=DATE-TIME":
+                            raise ValueError("unsupported DTSTART parm: "+parm)
                     if not parser:
                         from dateutil import parser
                     dtstart = parser.parse(value, ignoretz=ignoretz,
$
```


Hints:
@potuz This does not at all seem to be a bug. If you can show me a valid `RRULE` that doesn't parse correctly, then we can work from there, but it seems to me that your issue is that something you're using is generating `RRULE` objects that set a start date of `DATE-TIME`. Silently ignoring a malformed `RRULE` is not the right behavior (and hard-coding a specific value to ignore is *definitely* not the right thing to do).

Consider that `DATE-TIME` doesn't actually represent a datetime, so what exactly is it supposed to be set as?

If you can show me somewhere in [the iCalendar RFC](https://tools.ietf.org/html/rfc2445) that `DATE-TIME` is a valid value for `DTSTART` (and what exactly it means), I'd be happy to implement the specified behavior. I'll leave this open for a bit to allow for comment.

cc @Unrud @geier @wpercy Do you guys want to make the case for why whatever it is you're doing is the right thing here?
Oh, actually, I think I may have been misreading this. I thought this was something like `DTSTART: DATE-TIME` (which would be nonsense) - I think the relevant part of the RFC might be [this one](https://tools.ietf.org/html/rfc2445#section-4.2.20), which does specify that you can set a value's type as `DATE-TIME`. I'll have to look into the exact details.

Can someone generate one or more valid `RRULE` strings that are causing this bug, so that I can reproduce it?
Sure, this is my typical calendar entry generated by khal. 
```
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VTIMEZONE
TZID:America/Sao_Paulo
BEGIN:STANDARD
DTSTART;VALUE=DATE-TIME:20170218T230000
TZNAME:-03
TZOFFSETFROM:-0200
TZOFFSETTO:-0300
END:STANDARD
BEGIN:DAYLIGHT
DTSTART;VALUE=DATE-TIME:20171015T010000
TZNAME:-02
TZOFFSETFROM:-0300
TZOFFSETTO:-0200
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
UID:2L31OO7QUTARXINV5FIP66XGNDGNO92QZ7Y1
DTSTART;TZID=America/Sao_Paulo;VALUE=DATE-TIME:20170626T113000
DTEND;TZID=America/Sao_Paulo;VALUE=DATE-TIME:20170626T123000
DTSTAMP;VALUE=DATE-TIME:20170623T123900Z
SUMMARY:Dentista Nossa senhora de copacabana 680/Sala 607
END:VEVENT
END:VCALENDAR
```
The relevant quote from the [RFC](https://tools.ietf.org/html/rfc5545#section-3.8.2.4) (RFC5545 has superseeded RFC2445) is probably this:

       dtstart    = "DTSTART" dtstparam ":" dtstval CRLF

       dtstparam  = *(
                  ;
                  ; The following are OPTIONAL,
                  ; but MUST NOT occur more than once.
                  ;
                  (";" "VALUE" "=" ("DATE-TIME" / "DATE")) /
                  (";" tzidparam) /
                  ;
                  ; The following is OPTIONAL,
                  ; and MAY occur more than once.
                  ;
                  (";" other-param)
                  ;
                  )

       dtstval    = date-time / date
       ;Value MUST match value type

In khal we [solve this by extracting DTSTART's datetime and handing it over to rrule()](https://github.com/pimutils/khal/blob/f24506ba3f092df1235a65afd4d438ca8020b1a0/khal/utils.py#L322), I'm not sure if this is an issue you should fix (instead of radicale).
> I'm not sure if this is an issue you should fix

``RDATE`` and ``EXDATE`` already accept the parameter ``VALUE=DATE-TIME``. I guess that implementing it for ``DTSTART`` was just forgotten.
Thanks guys, I'll look into this.

Patch:
diff --git a/dateutil/rrule.py b/dateutil/rrule.py
--- a/dateutil/rrule.py
+++ b/dateutil/rrule.py
@@ -1560,8 +1560,19 @@ def _parse_rfc(self, s,
                             raise ValueError("unsupported EXDATE parm: "+parm)
                     exdatevals.append(value)
                 elif name == "DTSTART":
+                    # RFC 5445 3.8.2.4: The VALUE parameter is optional, but
+                    # may be found only once.
+                    value_found = False
+                    valid_values = {"VALUE=DATE-TIME", "VALUE=DATE"}
                     for parm in parms:
-                        raise ValueError("unsupported DTSTART parm: "+parm)
+                        if parm not in valid_values:
+                            raise ValueError("unsupported DTSTART parm: "+parm)
+                        else:
+                            if value_found:
+                                msg = ("Duplicate value parameter found in " +
+                                       "DTSTART: " + parm)
+                                raise ValueError(msg)
+                            value_found = True
                     if not parser:
                         from dateutil import parser
                     dtstart = parser.parse(value, ignoretz=ignoretz,


Test patch:
diff --git a/dateutil/test/test_rrule.py b/dateutil/test/test_rrule.py
--- a/dateutil/test/test_rrule.py
+++ b/dateutil/test/test_rrule.py
@@ -2831,6 +2831,20 @@ def testStrUntil(self):
                           datetime(1998, 1, 6, 9, 0),
                           datetime(1998, 12, 31, 9, 0)])
 
+    def testStrValueDatetime(self):
+        rr = rrulestr("DTSTART;VALUE=DATE-TIME:19970902T090000\n"
+                       "RRULE:FREQ=YEARLY;COUNT=2")
+
+        self.assertEqual(list(rr), [datetime(1997, 9, 2, 9, 0, 0),
+                                    datetime(1998, 9, 2, 9, 0, 0)])
+
+    def testStrValueDate(self):
+        rr = rrulestr("DTSTART;VALUE=DATE:19970902\n"
+                       "RRULE:FREQ=YEARLY;COUNT=2")
+
+        self.assertEqual(list(rr), [datetime(1997, 9, 2, 0, 0, 0),
+                                    datetime(1998, 9, 2, 0, 0, 0)])
+
     def testStrInvalidUntil(self):
         with self.assertRaises(ValueError):
             list(rrulestr("DTSTART:19970902T090000\n"