1.1 --- a/imip_agent.py Tue Sep 23 23:32:11 2014 +0200
1.2 +++ b/imip_agent.py Wed Sep 24 13:16:49 2014 +0200
1.3 @@ -1,12 +1,15 @@
1.4 #!/usr/bin/env python
1.5
1.6 from bisect import bisect_left, insort_left
1.7 +from datetime import date, datetime
1.8 from email import message_from_file
1.9 from email.mime.multipart import MIMEMultipart
1.10 from email.mime.text import MIMEText
1.11 +from pytz import timezone, UnknownTimeZoneError
1.12 from smtplib import SMTP
1.13 from vCalendar import parse, ParseError, SECTION_TYPES
1.14 import imip_store
1.15 +import re
1.16 import sys
1.17
1.18 try:
1.19 @@ -47,6 +50,18 @@
1.20 "text/x-vcalendar", "application/ics", # other possibilities
1.21 ]
1.22
1.23 +# iCalendar date and datetime parsing (from DateSupport in MoinSupport).
1.24 +
1.25 +date_icalendar_regexp_str = ur'(?P<year>[0-9]{4})(?P<month>[0-9]{2})(?P<day>[0-9]{2})'
1.26 +datetime_icalendar_regexp_str = date_icalendar_regexp_str + \
1.27 + ur'(?:' \
1.28 + ur'T(?P<hour>[0-2][0-9])(?P<minute>[0-5][0-9])(?P<second>[0-6][0-9])' \
1.29 + ur'(?P<utc>Z)?' \
1.30 + ur')?'
1.31 +
1.32 +match_date_icalendar = re.compile(date_icalendar_regexp_str, re.UNICODE).match
1.33 +match_datetime_icalendar = re.compile(datetime_icalendar_regexp_str, re.UNICODE).match
1.34 +
1.35 # Content interpretation.
1.36
1.37 def get_itip_structure(elements):
1.38 @@ -104,12 +119,49 @@
1.39 def get_value(d, name):
1.40 return get_values(d, name, False)
1.41
1.42 +def get_utc_datetime(d, name):
1.43 + value, attr = get_item(d, name)
1.44 + dt = get_datetime(value, attr)
1.45 + if isinstance(dt, datetime):
1.46 + return dt.astimezone(timezone("UTC")).strftime("%Y%m%dT%H%M%SZ")
1.47 + else:
1.48 + return dt.strftime("%Y%m%d")
1.49 +
1.50 def get_address(value):
1.51 return value.startswith("mailto:") and value[7:] or value
1.52
1.53 def get_uri(value):
1.54 return value.startswith("mailto:") and value or "mailto:%s" % value
1.55
1.56 +def get_datetime(value, attr):
1.57 + try:
1.58 + tz = attr.has_key("TZID") and timezone(attr["TZID"]) or None
1.59 + except UnknownTimeZoneError:
1.60 + tz = None
1.61 +
1.62 + m = match_datetime_icalendar(value)
1.63 + if m:
1.64 + dt = datetime(
1.65 + int(m.group("year")), int(m.group("month")), int(m.group("day")),
1.66 + int(m.group("hour")), int(m.group("minute")), int(m.group("second"))
1.67 + )
1.68 +
1.69 + # Impose the indicated timezone.
1.70 + # NOTE: This needs an ambiguity policy for DST changes.
1.71 +
1.72 + tz = m.group("utc") and timezone("UTC") or tz or None
1.73 + if tz is not None:
1.74 + return tz.localize(dt)
1.75 + else:
1.76 + return dt
1.77 +
1.78 + m = match_date_icalendar(value)
1.79 + if m:
1.80 + return date(
1.81 + int(m.group("year")), int(m.group("month")), int(m.group("day"))
1.82 + )
1.83 + return None
1.84 +
1.85 # Time management.
1.86
1.87 def insert_period(freebusy, period):
1.88 @@ -302,6 +354,9 @@
1.89 def get_value(self, name):
1.90 return get_value(self.details, name)
1.91
1.92 + def get_utc_datetime(self, name):
1.93 + return get_utc_datetime(self.details, name)
1.94 +
1.95 def filter_by_recipients(self, values):
1.96 return self.recipients.intersection(map(get_address, values))
1.97
1.98 @@ -374,6 +429,8 @@
1.99
1.100 # Process each attendee separately.
1.101
1.102 + calendar = []
1.103 +
1.104 for attendee, attendee_attr in attendees.items():
1.105
1.106 # Check for event using UID.
1.107 @@ -402,8 +459,8 @@
1.108 # If newer than any old version, discard old details from the
1.109 # free/busy record and check for suitability.
1.110
1.111 - dtstart = self.get_value("DTSTART")
1.112 - dtend = self.get_value("DTEND")
1.113 + dtstart = self.get_utc_datetime("DTSTART")
1.114 + dtend = self.get_utc_datetime("DTEND")
1.115
1.116 conflict = False
1.117 freebusy = self.store.get_freebusy(attendee)
1.118 @@ -432,7 +489,9 @@
1.119 attendee_attr["PARTSTAT"] = "DECLINED"
1.120
1.121 self.details["ATTENDEE"] = [(attendee, attendee_attr)]
1.122 - return [("VEVENT", {}, get_structure_items(self.details))]
1.123 + calendar.append(("VEVENT", {}, get_structure_items(self.details)))
1.124 +
1.125 + return calendar
1.126
1.127 class Freebusy(Handler):
1.128