def _parse_time_range(el): start = el.get('start') end = el.get('end') # Either start OR end OR both need to be specified. # https://tools.ietf.org/html/rfc4791, section 9.9 assert start is not None or end is not None if start is None: start = "00010101T000000Z" if end is None: end = "99991231T235959Z" start = vDDDTypes.from_ical(start) end = vDDDTypes.from_ical(end) assert end > start assert end.tzinfo assert start.tzinfo return (start, end)
def AddOrUpdateWebcal(webcal): calString = urllib.urlopen(webcal.webcal_url).read() cal = VCALENDAR.from_string(calString) eventCluster = EventCluster( cluster_title = webcal.webcal_title, cluster_description = webcal.webcal_description, cluster_user_created = webcal.webcal_user_added, cluster_category = webcal.webcal_default_category, cluster_rsvp_enabled = False, cluster_board_enabled = True, cluster_notify_boardpost = False, ) eventCluster.save() for component in cal.walk(): if(component.name == 'VEVENT'): valid = True proplist = {} REQ_PROPS = ('UID','SUMMARY','DTSTART','DTEND') for prop in component.property_items(): proplist[prop[0]] = prop[1] for rprop in REQ_PROPS: if rprop not in proplist: print 'MISSING %s' % rprop valid = False if valid: try: updateEvent = Event.objects.get(event_webcal_uid = proplist['UID']) print 'I found my old friend, %s' % proplist['UID'] except: dtstart = vDDDTypes.from_ical(proplist['DTSTART'].ical()) dtend = vDDDTypes.from_ical(proplist['DTEND'].ical()) add_event = Event( event_webcal_uid = proplist['UID'], event_user_last_modified = CalUser.objects.get(user_netid='yaro'), event_subtitle = proplist['SUMMARY'], event_subdescription = proplist.get('DESCRIPTION','No description provided.'), event_date_time_start = dtstart, event_date_time_end = dtend, event_location_details = proplist.get('LOCATION',''), event_cluster = eventCluster, event_cancelled = False, event_attendee_count = 0,) add_event.save()
def output_dates(bot, target, now, then, filter_location, announce=0): """ Output dates between now and then and filter default location. Set announce greater 0 to add announce message and suppresses the No dates found message. """ config = dates_configuration(bot) try: file = open(config['cache']) r = file.read() except OSError as e: raise Exception(e) try: cal = Calendar.from_ical(r) found = 0 data = [] timezoneEF = timezone('Europe/Berlin') fmt = "%d.%m.%Y %H:%M" # iterate all VEVENTS for ev in cal.walk('VEVENT'): start = vDDDTypes.from_ical(ev["DTSTART"]) """ check if DTSTART could be casted into some instance of dateime. If so, this indicates an event with a given start and stop time. There are other events too, e.g. Events lasting a whole day. Reading DTSTART of such whole day events will result in some instance of date. We will handle this case later. """ if isinstance(start, datetime): rset = rruleset() # create a set of recurrence rules info = "" loc = "" """ Everyone interested in calendar events wants to get some summary about the event. So each event handled here has to have a SUMMARY. If not, we will discard handling the VEVENT here """ if "SUMMARY" in ev: info = ev["SUMMARY"] else: continue # events ohne summary zeigen wir nicht an! """ Printing the location of an event is important too. However, the string containing location info may be too long to be viewed nicely in IRC. We filter our default location and strip every other location to the location name without address. """ if "LOCATION" in ev: if not ev["LOCATION"].startswith(filter_location): loc = ev["LOCATION"].split(', ')[0] """ Recurrence handling starts here. First, we check if there is a recurrence rule (RRULE) inside the VEVENT, If so, we use the ical like expression of DTSTART and RRULE to feed our ruleset with. """ if "RRULE" in ev: # recurrence ical_dtstart = (ev.get("DTSTART")).to_ical().decode() ical_rrule = (ev.get('RRULE')).to_ical().decode() rset.rrule(rrulestr(ical_rrule, dtstart=parse(ical_dtstart), ignoretz=1)) """ Recurrence handling includes exceptions in EXDATE. First we check if there are EXDATE values. If there is only one we will convert this also to a list to simplify handling. We use list entries to feed our ruleset with. """ if "EXDATE" in ev: ical_exdate = ev.get('EXDATE') if isinstance(ical_exdate, vDDDLists): ical_exdate = [ical_exdate] for exdate in ical_exdate: rset.exdate(parse(exdate.to_ical())) """ the ruleset now may be used to calculate any datetime the event happened and will happen. Since we are only interested in upcoming events between now and then, we just use the between() method of the ruleset which will return an array of datetimes. Since timeutils cares about tumezones, no convertion between utc and ME(S)Z needs to be done. We just iterate the array of datetimes and put starting time (DTSTART) info (SUMMARY) and location (LOCATION) into our "database" of events """ for e in rset.between(now, then): found += 1 data.append({ 'datetime': e.strftime(fmt), 'datetime_sort': e.strftime(fmt), 'info': info, 'loc': loc, }) """ Recurrence rules do also know about EXDATEs, handling this should be easy through rset (ruleset)... TODO handling of EXDATE """ else: # no recurrence """ there was no recurrence rule (RRULE), so we do not need to handle recurrece for this VEVENT. We do, however, need to handle conversion between UTC and ME(S)Z, because now timeutils, which would do this for us automatically, is not involved first we check if the DTSTART is between now and then. If so, we put the VEVENTs starttime (DTSTART), info (SUMMARY) and location (LOCATION) into our database. """ if start < utc.localize(now) or start > utc.localize(then): continue found += 1 data.append({ 'datetime': start.astimezone(timezoneEF).strftime(fmt), 'datetime_sort': start.astimezone(timezoneEF).strftime(fmt), 'info': info, 'loc': loc, }) """ So far we only have handled short time events, but there are whole day events too. So lets handle them here... TODO handling of whole day events if isinstance(start, date): """ # lets sort our database, nearest events coming first... data = sorted(data, key=lambda k: time.mktime(datetime.strptime( k['datetime_sort'], "%d.%m.%Y %H:%M").timetuple())) """ Spit out all events in database into IRC. Suppress duplicate lines from nonconforming ics files. Add message on announcing events. If there were no events, print some message about this... """ if found > 0 and announce > 0: bot.privmsg(target, "Please notice the next following event(s):") last_output = None for ev in data: output = " %s - %s" % (ev['datetime'], ev['info']) if ev['loc']: output = "%s (%s)" % (output, ev['loc']) if last_output != output: last_output = output bot.privmsg(target, output) if found == 0 and announce == 0: bot.privmsg( target, "No dates during the next %d days" % config['list_days'] ) except KeyError: bot.privmsg(target, "Error while retrieving dates data") raise Exception()
proplist = {} REQ_PROPS = ('UID','SUMMARY','DTSTART','DTEND') for prop in component.property_items(): proplist[prop[0]] = prop[1] for rprop in REQ_PROPS: if rprop not in proplist: print 'MISSING %s' % rprop valid = False if valid: try: updateEvent = Event.objects.get(event_webcal_uid = proplist['UID']) print 'I found my old friend, %s' % proplist['UID'] except: dtstart = vDDDTypes.from_ical(proplist['DTSTART'].ical()) dtend = vDDDTypes.from_ical(proplist['DTEND'].ical()) add_event = Event( event_webcal_uid = proplist['UID'], event_user_last_modified = CalUser.objects.get(user_netid='yaro'), event_subtitle = proplist['SUMMARY'], event_subdescription = proplist.get('DESCRIPTION','No description provided.'), event_date_time_start = dtstart, event_date_time_end = dtend, event_location_details = proplist.get('LOCATION',''), event_cluster = eventCluster, event_cancelled = False, event_attendee_count = 0,) add_event.save() AddOrUpdateWebcal(webcal);
from pytz import utc, timezone import smtplib f = urlopen(cal_file) cal = Calendar.from_ical(f.read()) mail = ("From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n" % (mail_from, mail_to, mail_subject)) now = datetime.now(utc).replace(hour=0, minute=0, second=0, microsecond=0) nweek = now + timedelta(weeks=1) timezoneEF = timezone('Europe/Berlin') fmt = "%d.%m.%Y, %H:%M" for ev in cal.walk(): if ev.name == 'VEVENT': # Check the start and end dates of the event if len(str(vDDDTypes.from_ical(ev.get('dtstart'))).split(' ')) > 1: start = vDDDTypes.from_ical(ev.get('dtstart')).replace(tzinfo=utc) end = vDDDTypes.from_ical(ev.get('dtend')).replace(tzinfo=utc) if start < now or start > nweek: continue else: start = vDDDTypes.from_ical(ev.get('dtstart')) end = vDDDTypes.from_ical(ev.get('dtend')) if start < now.date() or start > nweek.date(): continue mail += 'Betreff: ' + ev.get('summary').encode('utf-8') + "\n" mail += 'Start: ' + str(ev.get('dtstart').dt. astimezone(timezoneEF). strftime(fmt)) + "\n" mail += 'Ende: ' + str(ev.get('dtend').dt.
def onPrivmsg(self, irc, msg, channel, user): """Looks for a '!dates' command in messages posted to the channel and returns a list of dates within the next week. irc: An instance of the bytebot. Will be passed by the plugin loader msg: The msg sent to the channel channel: The channels name user: The user who sent the message """ if msg.find('!dates') == -1: return f = urlopen(BYTEBOT_PLUGIN_CONFIG['dates']['url']) """ icalender does not like to see any X-APPLE-MAPKIT-HANDLEs, so lets replace all X-APPLE-MAPKIT-HANDLEs with nothing """ applefilter = re.compile(r"X-APPLE-MAPKIT-HANDLE.*?;", re.MULTILINE | re.DOTALL) ical_orig = f.read() ical_filtered = applefilter.sub("", ical_orig) cal = Calendar.from_ical(ical_filtered) now = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) then = now + timedelta( days=BYTEBOT_PLUGIN_CONFIG['dates']['timedelta']) found = 0 data = [] timezoneEF = timezone('Europe/Berlin') fmt = "%d.%m.%Y %H:%M" # iterate all VEVENTS for ev in cal.walk('VEVENT'): start = vDDDTypes.from_ical(ev["DTSTART"]) """ check if DTSTART could be casted into some instance of dateime. If so, this indicates an event with a given start and stop time. There are other events too, e.g. Events lasting a whole day. Reading DTSTART of such whole day events will result in some instance of date. We will handle this case later. """ if isinstance(start, datetime): rset = rruleset() # create a set of recurrence rules info = "" loc = "" """ Everyone interested in calendar events wants to get some summary about the event. So each event handled here has to have a SUMMARY. If not, we will discard handling the VEVENT here """ if "SUMMARY" in ev: found += 1 info = ev["SUMMARY"].encode("utf-8") else: continue # events ohne summary zeigen wir nicht an! """ Printing the location of an event is important too. However, the string containing location info may be too long to be viewed nicely in IRC. Since there exits no good solution for this, this feature is coming soon. if "LOCATION" in ev: loc = ev["LOCATION"] else: loc = "Liebknechtstrasse 8" Recurrence handling starts here. First, we check if there is a recurrence rule (RRULE) inside the VEVENT, If so, we use the ical like expression of DTSTART and RRULE to feed our ruleset with. """ if "RRULE" in ev: # recurrence ical_dtstart = (ev.get("DTSTART")).to_ical() ical_rrule = (ev.get('RRULE')).to_ical() rset.rrule( rrulestr(ical_rrule, dtstart=parse(ical_dtstart), ignoretz=1)) """ the ruleset now may be used to calculate any datetime the event happened and will happen. Since we are only interested in upcoming events between now and then, we just use the between() method of the ruleset which will return an array of datetimes. Since timeutils cares about tumezones, no convertion between utc and ME(S)Z needs to be done. We just iterate the array of datetimes and put starting time (DTSTART) info (SUMMARY) and location (LOCATION) into our "database" of events """ for e in rset.between(now, then): data.append({ 'datetime': e.strftime(fmt), 'datetime_sort': e.strftime(fmt), 'info': info, 'loc': loc, }) """ Recurrence rules do also know about EXDATEs, handling this should be easy through rset (ruleset)... TODO handling of EXDATE """ else: # no recurrence """ there was no recurrence rule (RRULE), so we do not need to handle recurrece for this VEVENT. We do, however, need to handle conversion between UTC and ME(S)Z, because now timeutils, which would do this for us automatically, is not involved first we check if the DTSTART is between now and then. If so, we put the VEVENTs starttime (DTSTART), info (SUMMARY) and location (LOCATION) into our database. """ if start < utc.localize(now) or start > utc.localize(then): continue data.append({ 'datetime': start.astimezone(timezoneEF).strftime(fmt), 'datetime_sort': start.astimezone(timezoneEF).strftime(fmt), 'info': info, 'loc': loc, }) """ So far we only have handled short time events, but there are whole day events too. So lets handle them here... TODO handling of whole day events if isinstance(start, date): """ # lets sort our database, nearest events coming first... data = sorted(data, key=lambda k: time.mktime( datetime.strptime(k['datetime_sort'], "%d.%m.%Y %H:%M").timetuple())) """ spit out all events in database into IRC. If there were no events, print some message about this... """ for ev in data: irc.msg(channel, " %s - %s" % (ev['datetime'], ev['info'])) if found == 0: irc.msg(channel, "No dates during the next week") f.close()
def dates(bot, mask, target, args): """Show the planned dates within the next days %%dates """ """Load configuration""" config = {'url': '', 'timedelta': 21} config.update(bot.config.get(__name__, {})) """Request the ical file.""" with aiohttp.Timeout(10): with aiohttp.ClientSession(loop=bot.loop) as session: resp = yield from session.get(config['url']) if resp.status == 200: """Get text content from http request.""" r = yield from resp.text() else: bot.privmsg(target, "Error while retrieving calendar data") raise Exception() try: cal = Calendar.from_ical(r) now = datetime.now().replace( hour=0, minute=0, second=0, microsecond=0) then = now + timedelta( days=config['timedelta']) found = 0 data = [] timezoneEF = timezone('Europe/Berlin') fmt = "%d.%m.%Y %H:%M" # iterate all VEVENTS for ev in cal.walk('VEVENT'): start = vDDDTypes.from_ical(ev["DTSTART"]) """ check if DTSTART could be casted into some instance of dateime. If so, this indicates an event with a given start and stop time. There are other events too, e.g. Events lasting a whole day. Reading DTSTART of such whole day events will result in some instance of date. We will handle this case later. """ if isinstance(start, datetime): rset = rruleset() # create a set of recurrence rules info = "" loc = "" """ Everyone interested in calendar events wants to get some summary about the event. So each event handled here has to have a SUMMARY. If not, we will discard handling the VEVENT here """ if "SUMMARY" in ev: found += 1 info = ev["SUMMARY"] else: continue # events ohne summary zeigen wir nicht an! """ Printing the location of an event is important too. However, the string containing location info may be too long to be viewed nicely in IRC. Since there exits no good solution for this, this feature is coming soon. if "LOCATION" in ev: loc = ev["LOCATION"] else: loc = "Liebknechtstrasse 8" Recurrence handling starts here. First, we check if there is a recurrence rule (RRULE) inside the VEVENT, If so, we use the ical like expression of DTSTART and RRULE to feed our ruleset with. """ if "RRULE" in ev: # recurrence ical_dtstart = (ev.get("DTSTART")).to_ical().decode() ical_rrule = (ev.get('RRULE')).to_ical().decode() rset.rrule(rrulestr(ical_rrule, dtstart=parse(ical_dtstart), ignoretz=1)) """ Recurrence handling includes exceptions in EXDATE. First we check if there are EXDATE values. If there is only one we will convert this also to a list to simplify handling. We use list entries to feed our ruleset with. """ if "EXDATE" in ev: ical_exdate = ev.get('EXDATE') if isinstance(ical_exdate, vDDDLists): ical_exdate = [ical_exdate] for exdate in ical_exdate: rset.exdate(parse(exdate.to_ical())) """ the ruleset now may be used to calculate any datetime the event happened and will happen. Since we are only interested in upcoming events between now and then, we just use the between() method of the ruleset which will return an array of datetimes. Since timeutils cares about tumezones, no convertion between utc and ME(S)Z needs to be done. We just iterate the array of datetimes and put starting time (DTSTART) info (SUMMARY) and location (LOCATION) into our "database" of events """ for e in rset.between(now, then): data.append({ 'datetime': e.strftime(fmt), 'datetime_sort': e.strftime(fmt), 'info': info, 'loc': loc, }) """ Recurrence rules do also know about EXDATEs, handling this should be easy through rset (ruleset)... TODO handling of EXDATE """ else: # no recurrence """ there was no recurrence rule (RRULE), so we do not need to handle recurrece for this VEVENT. We do, however, need to handle conversion between UTC and ME(S)Z, because now timeutils, which would do this for us automatically, is not involved first we check if the DTSTART is between now and then. If so, we put the VEVENTs starttime (DTSTART), info (SUMMARY) and location (LOCATION) into our database. """ if start < utc.localize(now) or start > utc.localize(then): continue data.append({ 'datetime': start.astimezone(timezoneEF).strftime(fmt), 'datetime_sort': start.astimezone(timezoneEF).strftime(fmt), 'info': info, 'loc': loc, }) """ So far we only have handled short time events, but there are whole day events too. So lets handle them here... TODO handling of whole day events if isinstance(start, date): """ # lets sort our database, nearest events coming first... data = sorted(data, key=lambda k: time.mktime(datetime.strptime( k['datetime_sort'], "%d.%m.%Y %H:%M").timetuple())) """ Spit out all events in database into IRC. Suppress duplicate lines from nonconforming ics files. If there were no events, print some message about this... """ last_output = None for ev in data: output = " %s - %s" % (ev['datetime'], ev['info']) if last_output != output: last_output = output bot.privmsg(target, output) if found == 0: bot.privmsg(target, "No dates during the next week") except KeyError: bot.privmsg(target, "Error while retrieving rss data") raise Exception()
def output_dates(self, now, then, filter_location, room, announce=0): """ Output dates between now and then and filter default location. Set announce greater 0 to add announce message and suppresses the No dates found message. """ try: tmp_dates_cache = Path(self.config['cache']) if tmp_dates_cache.exists(): file = open(str(tmp_dates_cache), encoding='UTF-8') raw_text = file.read() text = raw_text if not text: return else: DATES_LOG.error("%s file not found from %s", tmp_dates_cache, os.getcwd()) return except OSError as error: raise Exception(error) try: cal = Calendar.from_ical(text) found = 0 data = [] timezone_ef = timezone('Europe/Berlin') fmt = "%d.%m.%Y %H:%M" # iterate all VEVENTS for event in cal.walk('VEVENT'): start = vDDDTypes.from_ical(event["DTSTART"]) info = "" loc = "" # Everyone interested in calendar events wants to get # some summary about the event. So each event # handled here has to have a SUMMARY. If not, we will # discard handling the VEVENT here if "SUMMARY" in event: info = event["SUMMARY"] else: continue # events ohne summary zeigen wir nicht an! # Printing the location of an event is important too. # However, # the string containing location info may be too long # to be viewed nicely in IRC. # We filter our default location and strip every other # location to the location name without address. if "LOCATION" in event: if filter_location and not event["LOCATION"].startswith(filter_location): loc = event["LOCATION"].split(', ')[0] # check if DTSTART could be casted into some instance of # dateime. If so, this indicates an event with a given start # and stop time. There are other events too, e.g. Events # lasting a whole day. Reading DTSTART of such whole day # events will result in some instance of date. We will # handle this case later. if isinstance(start, datetime): rset = rruleset() # create a set of recurrence rules # Recurrence handling starts here. # First, we check if there is a recurrence rule (RRULE) # inside the VEVENT, If so, we use the ical like # expression of DTSTART and RRULE to feed # our ruleset with. if "RRULE" in event: # recurrence ical_dtstart = (event.get("DTSTART")).to_ical().decode() ical_rrule = (event.get('RRULE')).to_ical().decode() rset.rrule(rrulestr(ical_rrule, dtstart=parse(ical_dtstart), ignoretz=1)) # Recurrence handling includes exceptions in EXDATE. # First we check if there are EXDATE values. If there # is only one we will convert this also to a list to # simplify handling. We use list entries to feed our # ruleset with. if "EXDATE" in event: ical_exdate = event.get('EXDATE') if isinstance(ical_exdate, vDDDLists): ical_exdate = [ical_exdate] for exdate in ical_exdate: rset.exdate(parse(exdate.to_ical())) # the ruleset now may be used to calculate any datetime # the event happened and will happen. # Since we are only interested # in upcoming events between now and then, we just use # the between() method of the ruleset which will return an # array of datetimes. Since timeutils cares about tumezones, # no convertion between utc and ME(S)Z needs to be done. # We just iterate the array of datetimes and put starting # time (DTSTART) info (SUMMARY) and location (LOCATION) # into our "database" of events for upcoming_event in rset.between(now, then): found += 1 data.append({ 'datetime': upcoming_event.strftime(fmt), 'datetime_sort': upcoming_event.strftime(fmt), 'info': info, 'loc': loc, }) # Recurrence rules do also know about EXDATEs, handling this # should be easy through rset (ruleset)... # TODO handling of EXDATE else: # no recurrence # there was no recurrence rule (RRULE), so we do not need # to handle recurrece for this VEVENT. We do, however, need # to handle conversion between UTC and ME(S)Z, because now # timeutils, which would do this for us automatically, is # not involved # # first we check if the DTSTART is between now and then. # If so, we put the VEVENTs starttime (DTSTART), info # (SUMMARY) and location (LOCATION) into our database. if start < utc.localize(now) or start > utc.localize(then): continue found += 1 data.append({ 'datetime': start.astimezone(timezone_ef).strftime(fmt), 'datetime_sort': start.astimezone(timezone_ef).strftime(fmt), 'info': info, 'loc': loc, }) # So far we only have handled short time events, but there are # whole day events too. So lets handle them here... # # TODO handling of whole day events # # if isinstance(start, date): else: # set starttime at 8am on day one starttime = utc.localize(datetime.combine(start,dtime(hour=8))); end = vDDDTypes.from_ical(event["DTEND"]) endtime = utc.localize(datetime.combine(end,dtime(hour=8))); duration = endtime - starttime durdays = duration.days if starttime < utc.localize(now) or starttime > utc.localize(then): continue for i in range(durdays): found += 1 data.append({ 'datetime': (starttime + timedelta(days=i)).astimezone(timezone_ef).strftime(fmt), 'datetime_sort': (starttime + timedelta(days=i)).astimezone(timezone_ef).strftime(fmt), 'info': "Ganztägig: " + info + " (Tag " + str(i+1) + ")", 'loc': loc, }) # lets sort our database, nearest events coming first... data = sorted(data, key=lambda k: time.mktime(datetime.strptime( k['datetime_sort'], "%d.%m.%Y %H:%M").timetuple())) # Spit out all events in database into IRC. Suppress duplicate lines # from nonconforming ics files. Add message on announcing events. If # there were no events, print some message about this... if found > 0 and announce > 0: room.send_text("Please notice the next following event(s):") last_output = None all_output = '' for event in data: output = " %s - %s" % (event['datetime'], event['info']) if event['loc']: output = "%s (%s)" % (output, event['loc']) output += "\n" if last_output != output: last_output = output all_output += output #room.send_text(output) if all_output: # is > 0 room.send_text(all_output) if found == 0 and announce == 0: look_back = self.config.getint('list_days') room.send_text( "No dates during the next %d days" % look_back ) except KeyError: room.send_text("Error while retrieving dates data") raise Exception()
def output_dates(bot, target, now, then, filter_location, announce=0): """ Output dates between now and then and filter default location. Set announce greater 0 to add announce message and suppresses the No dates found message. """ config = dates_configuration(bot) try: file = open(config['cache']) r = file.read() except OSError as e: raise Exception(e) try: cal = Calendar.from_ical(r) found = 0 data = [] timezoneEF = timezone('Europe/Berlin') fmt = "%d.%m.%Y %H:%M" # iterate all VEVENTS for ev in cal.walk('VEVENT'): start = vDDDTypes.from_ical(ev["DTSTART"]) """ check if DTSTART could be casted into some instance of dateime. If so, this indicates an event with a given start and stop time. There are other events too, e.g. Events lasting a whole day. Reading DTSTART of such whole day events will result in some instance of date. We will handle this case later. """ if isinstance(start, datetime): rset = rruleset() # create a set of recurrence rules info = "" loc = "" """ Everyone interested in calendar events wants to get some summary about the event. So each event handled here has to have a SUMMARY. If not, we will discard handling the VEVENT here """ if "SUMMARY" in ev: info = ev["SUMMARY"] else: continue # events ohne summary zeigen wir nicht an! """ Printing the location of an event is important too. However, the string containing location info may be too long to be viewed nicely in IRC. We filter our default location and strip every other location to the location name without address. """ if "LOCATION" in ev: if not ev["LOCATION"].startswith(filter_location): loc = ev["LOCATION"].split(', ')[0] """ Recurrence handling starts here. First, we check if there is a recurrence rule (RRULE) inside the VEVENT, If so, we use the ical like expression of DTSTART and RRULE to feed our ruleset with. """ if "RRULE" in ev: # recurrence ical_dtstart = (ev.get("DTSTART")).to_ical().decode() ical_rrule = (ev.get('RRULE')).to_ical().decode() rset.rrule( rrulestr(ical_rrule, dtstart=parse(ical_dtstart), ignoretz=1)) """ Recurrence handling includes exceptions in EXDATE. First we check if there are EXDATE values. If there is only one we will convert this also to a list to simplify handling. We use list entries to feed our ruleset with. """ if "EXDATE" in ev: ical_exdate = ev.get('EXDATE') if isinstance(ical_exdate, vDDDLists): ical_exdate = [ical_exdate] for exdate in ical_exdate: rset.exdate(parse(exdate.to_ical())) """ the ruleset now may be used to calculate any datetime the event happened and will happen. Since we are only interested in upcoming events between now and then, we just use the between() method of the ruleset which will return an array of datetimes. Since timeutils cares about tumezones, no convertion between utc and ME(S)Z needs to be done. We just iterate the array of datetimes and put starting time (DTSTART) info (SUMMARY) and location (LOCATION) into our "database" of events """ for e in rset.between(now, then): found += 1 data.append({ 'datetime': e.strftime(fmt), 'datetime_sort': e.strftime(fmt), 'info': info, 'loc': loc, }) """ Recurrence rules do also know about EXDATEs, handling this should be easy through rset (ruleset)... TODO handling of EXDATE """ else: # no recurrence """ there was no recurrence rule (RRULE), so we do not need to handle recurrece for this VEVENT. We do, however, need to handle conversion between UTC and ME(S)Z, because now timeutils, which would do this for us automatically, is not involved first we check if the DTSTART is between now and then. If so, we put the VEVENTs starttime (DTSTART), info (SUMMARY) and location (LOCATION) into our database. """ if start < utc.localize(now) or start > utc.localize(then): continue found += 1 data.append({ 'datetime': start.astimezone(timezoneEF).strftime(fmt), 'datetime_sort': start.astimezone(timezoneEF).strftime(fmt), 'info': info, 'loc': loc, }) """ So far we only have handled short time events, but there are whole day events too. So lets handle them here... TODO handling of whole day events if isinstance(start, date): """ # lets sort our database, nearest events coming first... data = sorted(data, key=lambda k: time.mktime( datetime.strptime(k['datetime_sort'], "%d.%m.%Y %H:%M").timetuple())) """ Spit out all events in database into IRC. Suppress duplicate lines from nonconforming ics files. Add message on announcing events. If there were no events, print some message about this... """ if found > 0 and announce > 0: bot.privmsg(target, "Please notice the next following event(s):") last_output = None for ev in data: output = " %s - %s" % (ev['datetime'], ev['info']) if ev['loc']: output = "%s (%s)" % (output, ev['loc']) if last_output != output: last_output = output bot.privmsg(target, output) if found == 0 and announce == 0: bot.privmsg( target, "No dates during the next %d days" % config['list_days']) except KeyError: bot.privmsg(target, "Error while retrieving dates data") raise Exception()