Django ical with Vobject issue with pytz

791 views Asked by At

I am using vobject to create a ical event in Django. I am having trouble with the lower level code. It looks like ical is trying to grab a timezone with obj.add(TimezoneComponent(tzinfo=getTzid(tzid))). But then I get raise NonExistentTimeError(dt) from pytz. Any suggestions on what to do? The year, month, day show correctly as I viewed them with the print statement in for variable start1.

 File "/home/git/chrono/chrono/requests_app/views.py", line 110, in form_valid
    ics_form = create_ics(data)
  File "/home/git/chrono/chrono/requests_app/views.py", line 126, in create_ics
    response = HttpResponse(cal.serialize(), content_type='text/calendar')
  File "/home/one/.virtualenvs/chronos/local/lib/python2.7/site-packages/vobject/base.py", line 186, in serialize
    return behavior.serialize(self, buf, lineLength, validate)
  File "/home/one/.virtualenvs/chronos/local/lib/python2.7/site-packages/vobject/behavior.py", line 147, in serialize
    cls.generateImplicitParameters(obj)
  File "/home/one/.virtualenvs/chronos/local/lib/python2.7/site-packages/vobject/icalendar.py", line 853, in generateImplicitParameters
    obj.add(TimezoneComponent(tzinfo=getTzid(tzid)))
  File "/home/one/.virtualenvs/chronos/local/lib/python2.7/site-packages/vobject/icalendar.py", line 75, in __init__
    self.tzinfo = tzinfo
  File "/home/one/.virtualenvs/chronos/local/lib/python2.7/site-packages/vobject/base.py", line 468, in __setattr__
    prop.fset(self, value)
  File "/home/one/.virtualenvs/chronos/local/lib/python2.7/site-packages/vobject/icalendar.py", line 145, in settzinfo
    transition = getTransition(transitionTo, year, tzinfo)
  File "/home/one/.virtualenvs/chronos/local/lib/python2.7/site-packages/vobject/icalendar.py", line 1856, in getTransition
    uncorrected = firstTransition(generateDates(year, month, day), test)
  File "/home/one/.virtualenvs/chronos/local/lib/python2.7/site-packages/vobject/icalendar.py", line 1816, in firstTransition
    if not test(dt):
  File "/home/one/.virtualenvs/chronos/local/lib/python2.7/site-packages/vobject/icalendar.py", line 1843, in test
    def test(dt): return tzinfo.dst(dt) != zeroDelta
  File "/home/one/.virtualenvs/chronos/local/lib/python2.7/site-packages/pytz/tzinfo.py", line 445, in dst
    dt = self.localize(dt, is_dst)
  File "/home/one/.virtualenvs/chronos/local/lib/python2.7/site-packages/pytz/tzinfo.py", line 327, in localize
    raise NonExistentTimeError(dt)
NonExistentTimeError: 2000-04-02 02:00:00



def create_ics(data):
    start1 = data['date_due']
    print start1.day
    start2 = datetime.datetime(start1.year, start1.month, start1.day)
    start3 = data['action']
    cal = vobject.iCalendar()
    cal.add('method').value = 'PUBLISH'
    vevent = cal.add('vevent')
    vevent.add('dtstart').value = start1
    vevent.add('dtend').value = start2
    vevent.add('dtstamp').value = datetime.datetime.now()
    vevent.add('summary').value = data['action'].name
    response = HttpResponse(cal.serialize(), content_type='text/calendar')
    response['Filename'] = 'filename.ics'
    response['Content-Disposition'] = 'attachment; filename=filename.ics'
    return response

from models, the datetime field:

date_due = models.DateTimeField()

UPDATE:

Found I had to place:

>>> utc = vobject.icalendar.utc
>>> start = cal.vevent.add('dtstart')
>>> start.value = datetime.datetime(2006, 2, 16, tzinfo = utc)

into it, which worked.

1

There are 1 answers

0
medmunds On

Short answer: vobject is not (as of 0.9.2) compatible with pytz. So make sure every datetime in your vobject iCalendar has been converted to UTC before trying to serialize it, using something like .astimezone(pytz.utc).

(That's every dtstart, dtend, dtstamp, created, last-modified, and maybe some other vevent fields I've forgotten.)

Long answer: vobject tries to do the right thing for non-UTC datetimes, but runs into trouble with pytz. The "right thing" comes from RFC 5545 which specifies iCalendar:

  1. Represent the datetime using DATE-TIME Form #3 "date with local time and time zone reference". That might be something like DTSTART;TZID=America/New_York:20160714T133000 -- notice the TZID for your event's timezone.

  2. Add a VTIMEZONE block to your iCalendar for each unique TZID used in your events. This is a complete definition of that timezone: how to figure out that timezone's offset from UTC for any datetime that might appear, including daylight savings time rules. (RFC 5545 doesn't specify any particular timezone names, so you have to include timezone definitions in the iCalendar itself. vobject does this for you, automatically.)

To figure out the timezone conversion rules, vobject searches through "all time" (default years 2000-2030), looking for changes in the timezone's offset from UTC. And that's where things go wrong, because the vobject code doesn't handle pytz's invalid time errors.

2:00am April 2, 2000 is the first DST transition between 2000-2030, which is why you're getting an error about that time (even though you didn't use it anywhere in your own code).

Options:

  • Use date instead of datetime if you don't want a specific time of day (as in the original question). Dates don't have timezones, so none of this applies. (And vobject handles dates just fine.)
  • Convert all datetime to aware datetimes in UTC. UTC doesn't require a VTIMEZONE definition.
  • Use dateutil timezones instead of pytz. E.g., from dateutil import tz; ... tzinfo=tz.gettz('America/Los_Angeles'). Since dateutil is a vobject dependency, I think this is the format vobject's VTIMEZONE generator is designed against. (But haven't tested extensively. Also, gettz requires tzdb files installed on your machine, so isn't entirely portable.)
  • Add your own VTIMEZONE definitions to the iCalendar for every TZID you use, which should avoid the problematic auto-timezone-generation code in vobject. (Untested. And complicated to get right in the general case.)
  • Submit a PR to fix vobject to work with pytz.