collective / icalendar

icalendar parser library for Python
https://icalendar.readthedocs.io/en/latest/
Other
981 stars 168 forks source link

[BUG] VTIMEZONE to_tz results in ValueError: RRULE UNTIL values must be specified in UTC when DTSTART is timezone-aware #708

Open 5ila5 opened 1 week ago

5ila5 commented 1 week ago

Describe the bug

Getting timezone from a VTIMEZONE tag using to_tz() I get ValueError: RRULE UNTIL values must be specified in UTC when DTSTART is timezone-aware I noticed this while using icalevents but thought this might belong here and not to icalevents

I used the ICAL file: https://eilenburg.de/fileadmin/2024/Abfall_EG_1_Ost_2024.ics maybe they do not follow the spec but at least https://icalendar.org/validator.html did not report any issues

To Reproduce

import requests
import icalendar
from icalendar import Timezone

r = requests.get("https://eilenburg.de/fileadmin/2024/Abfall_EG_1_Ost_2024.ics")
r.encoding = "utf-8"
c = icalendar.Calendar.from_ical(r.text)
part: Timezone
for part in c.walk('VTIMEZONE'):
    print(isinstance(part, Timezone))
    tz = part.to_tz()
    print(tz)
...

Output:

True

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[1], line 13
     10 for part in c.walk('VTIMEZONE'):
     11     # print(event['SUMMARY'])
     12     print(isinstance(part, Timezone))
---> 13     tz = part.to_tz()
     14     print(tz)

File ~/Documents/coding/python/hacs_waste_collection_schedule/.venv/lib/python3.12/site-packages/icalendar/cal.py:653, in Timezone.to_tz(self)
    648         tzname = f"{zone}_{component['DTSTART'].to_ical().decode('utf-8')}_" + \
    649                  f"{component['TZOFFSETFROM'].to_ical()}_" + \
    650                  f"{component['TZOFFSETTO'].to_ical()}"
    651         tzname = self._make_unique_tzname(tzname, tznames)
--> 653     dst[tzname], component_transitions = self._extract_offsets(
    654         component, tzname
    655     )
    656     transitions.extend(component_transitions)
    658 transitions.sort()

File ~/Documents/coding/python/hacs_waste_collection_schedule/.venv/lib/python3.12/site-packages/icalendar/cal.py:582, in Timezone._extract_offsets(component, tzname)
    579 rrstart = dtstart.replace (tzinfo=tzi)
    581 rrulestr = component['RRULE'].to_ical().decode('utf-8')
--> 582 rrule = dateutil.rrule.rrulestr(rrulestr, dtstart=rrstart)
    583 if not {'UNTIL', 'COUNT'}.intersection(component['RRULE'].keys()):
    584     # pytz.timezones don't know any transition dates after 2038
    585     # either
    586     rrule._until = datetime(2038, 12, 31, tzinfo=pytz.UTC)

File ~/Documents/coding/python/hacs_waste_collection_schedule/.venv/lib/python3.12/site-packages/dateutil/rrule.py:1732, in _rrulestr.__call__(self, s, **kwargs)
   1731 def __call__(self, s, **kwargs):
-> 1732     return self._parse_rfc(s, **kwargs)

File ~/Documents/coding/python/hacs_waste_collection_schedule/.venv/lib/python3.12/site-packages/dateutil/rrule.py:1652, in _rrulestr._parse_rfc(self, s, dtstart, cache, unfold, forceset, compatible, ignoretz, tzids, tzinfos)
   1649     lines = s.split()
   1650 if (not forceset and len(lines) == 1 and (s.find(':') == -1 or
   1651                                           s.startswith('RRULE:'))):
-> 1652     return self._parse_rfc_rrule(lines[0], cache=cache,
   1653                                  dtstart=dtstart, ignoretz=ignoretz,
   1654                                  tzinfos=tzinfos)
   1655 else:
   1656     rrulevals = []

File ~/Documents/coding/python/hacs_waste_collection_schedule/.venv/lib/python3.12/site-packages/dateutil/rrule.py:1561, in _rrulestr._parse_rfc_rrule(self, line, dtstart, cache, ignoretz, tzinfos)
   1559     except (KeyError, ValueError):
   1560         raise ValueError("invalid '%s': %s" % (name, value))
-> 1561 return rrule(dtstart=dtstart, cache=cache, **rrkwargs)

File ~/Documents/coding/python/hacs_waste_collection_schedule/.venv/lib/python3.12/site-packages/dateutil/rrule.py:470, in rrule.__init__(self, freq, dtstart, interval, wkst, count, until, bysetpos, bymonth, bymonthday, byyearday, byeaster, byweekno, byweekday, byhour, byminute, bysecond, cache)
    461 if self._dtstart and self._until:
    462     if (self._dtstart.tzinfo is not None) != (self._until.tzinfo is not None):
    463         # According to RFC5545 Section 3.3.10:
    464         # https://tools.ietf.org/html/rfc5545#section-3.3.10
   (...)
    468         # > then the UNTIL rule part MUST be specified as a date with
    469         # > UTC time.
--> 470         raise ValueError(
    471             'RRULE UNTIL values must be specified in UTC when DTSTART '
    472             'is timezone-aware'
    473         )
    475 if count is not None and until:
    476     warn("Using both 'count' and 'until' is inconsistent with RFC 5545"
    477          " and has been deprecated in dateutil. Future versions will "
    478          "raise an error.", DeprecationWarning)

ValueError: RRULE UNTIL values must be specified in UTC when DTSTART is timezone-aware

Expected behavior

Environment

Additional context

EDIT:

ICS file from link above

As txt file as GitHub does not allow me to upload a ics file

Abfall_EG_1_Ost_2024.txt

niccokunzmann commented 1 week ago

Hi, thanks for reporting this!

I think, it might be worth opening an issue in dateutil, too. I have fixed that in the recurring-ical-events for rrules of events. So, there is code that we could use and push upstream into dateutil to fix this.

5ila5 commented 5 days ago

I don't think this is an issue with dateutil https://github.com/dateutil/dateutil/blob/9eaa5de584f9f374c6e4943069925cc53522ad61/src/dateutil/rrule.py#L463-L469

I just noticed this seems to work when using the git version or the 6.0.0a0 release

So I probably need to wait for a 6.0 release and a new version from icalevents to fully resolve my problem

edit: fixed grammar issue

niccokunzmann commented 5 days ago

I would keep this open until the error really vanishes. Calendars in the wild sometimes bring on edge cases.

I just noticed this seems to be using the git version or the 6.0.0a0

Yes, we use dateutil then to generate the timezone. So, it is actually good to track, I think.