def ical_init(): cal = Calendar() cal.add('prodid', '-//CSPay calendar//EN/') cal.add('version', '2.0') TZ = Timezone() TZ.add('tzid','Europe/Bucharest') TZS = StandardT() TZS.add('TZOFFSETFROM',timedelta(hours=3)) TZS.add('TZOFFSETTO',timedelta(hours=2)) TZS.add('TZNAME','EET') TZS.add('DTSTART',datetime(1997,10,26)) TZS.add('rrule',vRecur.from_ical('FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU')) TZ.add_component(TZS) TZS = DaylightT() TZS.add('TZOFFSETFROM',timedelta(hours=2)) TZS.add('TZOFFSETTO',timedelta(hours=3)) TZS.add('TZNAME','EEST') TZS.add('DTSTART',datetime(1997,03,30)) TZS.add('rrule',vRecur.from_ical('FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU')) TZ.add_component(TZS) cal.add_component(TZ) return cal
def testMultipleRRules(self): props = Event() addProps( props, summary="Event", uid="1234", dtstart=dt.datetime(2016, 4, 5, 9), dtend=dt.datetime(2016, 4, 5, 13), dtstamp=dt.datetime(2016, 4, 5, 13), rrule=vRecur.from_ical("FREQ=WEEKLY;WKST=SU;BYDAY=MO,TU,WE,TH,FR")) props.add('rrule', vRecur.from_ical("FREQ=MONTHLY;WKST=SU;BYDAY=-1SA,-1SU")) with self.assertRaises(CalendarTypeError) as expected: self.factory.makeFromProps(props, None) self.assertEqual(str(expected.exception), "Multiple RRULEs")
def testLunchTimes(self): tz = pytz.timezone("Pacific/Auckland") vev = SimpleVEvent() addProps( vev, summary="All lunch times", uid="999", dtstart=timezone.make_aware(dt.datetime(1996, 3, 1, 12), tz), dtend=timezone.make_aware(dt.datetime(1996, 3, 1, 13), tz), rrule=vRecur.from_ical("FREQ=WEEKLY;WKST=SU;BYDAY=MO,TU,WE,TH,FR")) span = TimeZoneSpan(vev) vev = SimpleVEvent() addProps(vev, summary="Saturday Brunch", uid="2000", dtstart=timezone.make_aware(dt.datetime(2019, 6, 4, 10), tz), dtend=timezone.make_aware(dt.datetime(2019, 6, 4, 11, 30), tz)) span.add(vev) vtz = span.createVTimeZone(tz) self.assertEqual(vtz['TZID'], "Pacific/Auckland") export = vtz.to_ical() nzdt = b"\r\n".join([ b"BEGIN:DAYLIGHT", b"DTSTART;VALUE=DATE-TIME:19951001T030000", b"RDATE:19961006T030000,19971005T030000,19981004T030000,19991003T030000,2000", b" 1001T030000,20011007T030000,20021006T030000,20031005T030000,20041003T03000", b" 0,20051002T030000,20061001T030000,20070930T030000,20080928T030000,20090927", b" T030000,20100926T030000,20110925T030000,20120930T030000,20130929T030000,20", b" 140928T030000,20150927T030000,20160925T030000,20170924T030000,20180930T030", b" 000,20190929T030000,20200927T030000,20210926T030000,20220925T030000,202309", b" 24T030000,20240929T030000,20250928T030000,20260927T030000,20270926T030000,", b" 20280924T030000,20290930T030000,20300929T030000,20310928T030000,20320926T0", b" 30000,20330925T030000,20340924T030000,20350930T030000,20360928T030000,2037", b" 0927T030000", b"TZNAME:NZDT", b"TZOFFSETFROM:+1200", b"TZOFFSETTO:+1300", b"END:DAYLIGHT", ]) self.assertIn(nzdt, export) nzst = b"\r\n".join([ b"BEGIN:STANDARD", b"DTSTART;VALUE=DATE-TIME:19960317T020000", b"RDATE:19970316T020000,19980315T020000,19990321T020000,20000319T020000,2001", b" 0318T020000,20020317T020000,20030316T020000,20040321T020000,20050320T02000", b" 0,20060319T020000,20070318T020000,20080406T020000,20090405T020000,20100404", b" T020000,20110403T020000,20120401T020000,20130407T020000,20140406T020000,20", b" 150405T020000,20160403T020000,20170402T020000,20180401T020000,20190407T020", b" 000,20200405T020000,20210404T020000,20220403T020000,20230402T020000,202404", b" 07T020000,20250406T020000,20260405T020000,20270404T020000,20280402T020000,", b" 20290401T020000,20300407T020000,20310406T020000,20320404T020000,20330403T0", b" 20000,20340402T020000,20350401T020000,20360406T020000,20370405T020000", b"TZNAME:NZST", b"TZOFFSETFROM:+1300", b"TZOFFSETTO:+1200", b"END:STANDARD", ]) self.assertIn(nzst, export)
def parse_recurring_rules(self, item, component): """Extracts ICS RRULE into the Event item :param item: The Event item :param component: An ICS VEVENT component """ # parse ics RRULE to fit eventsML recurring_rule r_rule = component.get('rrule') if not isinstance(r_rule, vRecur): return r_rule_dict = vRecur.from_ical(r_rule) recurring_rule: Dict[str, Union[str, int, datetime.date, datetime.datetime]] = {} if r_rule.get('FREQ'): recurring_rule['frequency'] = ''.join(r_rule_dict['FREQ']) if len(r_rule.get('INTERVAL') or []): recurring_rule['interval'] = r_rule_dict['INTERVAL'][0] if len(r_rule.get('UNTIL') or []): recurring_rule['until'] = r_rule_dict['UNTIL'][0] if r_rule.get('COUNT'): recurring_rule['count'] = r_rule_dict['COUNT'] if r_rule.get('BYMONTH'): recurring_rule['bymonth'] = ' '.join(r_rule_dict['BYMONTH']) if r_rule.get('BYDAY'): recurring_rule['byday'] = ' '.join(r_rule_dict['BYDAY']) if r_rule.get('BYHOUR'): recurring_rule['byhour'] = ' '.join(r_rule_dict['BYHOUR']) if r_rule.get('BYMIN'): recurring_rule['bymin'] = ' '.join(r_rule_dict['BYMIN']) if recurring_rule.get('count'): recurring_rule['endRepeatMode'] = 'count' elif recurring_rule.get('until'): recurring_rule['endRepeatMode'] = 'until' # If the `until` attribute is just a date # then copy the time/tzinfo from `dates.end` attribute if isinstance(recurring_rule['until'], datetime.date): end_date = item['dates']['end'] recurring_rule['until'] = datetime.datetime.combine( recurring_rule['until'], end_date.time(), end_date.tzinfo) else: # If the calendar does not provide an end date # then set `count` to MAX_RECURRENT_EVENTS settings recurring_rule['count'] = get_max_recurrent_events() recurring_rule['endRepeatMode'] = 'count' item['dates']['recurring_rule'] = recurring_rule
def fromPage(cls, page): vevent = super().fromPage(page) minDt = pytz.utc.localize(dt.datetime.min) # FIXME support Anniversary date type events? dtstart = page._getMyFirstDatetimeFrom() or minDt dtend = page._getMyFirstDatetimeTo() or minDt vevent.set('UID', page.uid) vevent.set('DTSTART', vDatetime(dtstart)) vevent.set('DTEND', vDatetime(dtend)) vevent.set('DESCRIPTION', page.details) vevent.set('LOCATION', page.location) vevent.vchildren, exDates = cls.__getExceptions(page) if exDates: vevent.set('EXDATE', exDates) vevent.set('RRULE', vRecur.from_ical(page.repeat._getRrule())) return vevent
def fromPage(cls, page): vevent = super().fromPage(page) minDt = pytz.utc.localize(dt.datetime.min) dtstart = page._getMyFirstDatetimeFrom() or minDt dtend = page._getMyFirstDatetimeTo() or minDt vevent.set('UID', page.uid) vevent.set('DTSTART', vDatetime(dtstart)) vevent.set('DTEND', vDatetime(dtend)) vevent._setDesc(page.details) vevent.set('LOCATION', page.location) vevent.vchildren, exDates = cls.__getExceptions(page) if exDates: vevent.set('EXDATE', exDates) until = page.repeat.until if until: until = getAwareDatetime(until, dt.time.max, dtend.tzinfo) until = until.astimezone(pytz.utc) vevent.set('RRULE', vRecur.from_ical(page.repeat._getRrule(until))) return vevent
def testOverlapping(self): tz = pytz.timezone("Pacific/Auckland") vev = SimpleVEvent() addProps(vev, summary="Storytelling", uid="1000", dtstart=timezone.make_aware(dt.datetime(2016, 4, 5, 10), tz), dtend=timezone.make_aware(dt.datetime(2016, 4, 5, 11), tz), rrule=vRecur.from_ical( "FREQ=WEEKLY;WKST=SU;BYDAY=TU;UNTIL=20160519")) span = TimeZoneSpan(vev) vev = SimpleVEvent() addProps(vev, summary="Parade", uid="1002", dtstart=timezone.make_aware(dt.datetime(2016, 4, 5, 9), tz), dtend=timezone.make_aware(dt.datetime(2016, 4, 5, 13), tz)) span.add(vev) vtz = span.createVTimeZone(tz) self.assertEqual(vtz['TZID'], "Pacific/Auckland") export = vtz.to_ical() nzdt = b"\r\n".join([ b"BEGIN:DAYLIGHT", b"DTSTART;VALUE=DATE-TIME:20160925T030000", b"TZNAME:NZDT", b"TZOFFSETFROM:+1200", b"TZOFFSETTO:+1300", b"END:DAYLIGHT", ]) self.assertIn(nzdt, export) nzst = b"\r\n".join([ b"BEGIN:STANDARD", b"DTSTART;VALUE=DATE-TIME:20160403T020000", b"TZNAME:NZST", b"TZOFFSETFROM:+1300", b"TZOFFSETTO:+1200", b"END:STANDARD", ]) self.assertIn(nzst, export)
def add_end(self, d): end = vDatetime(datetime(d.year, d. month, d.day, 12, 0, 0, tzinfo = UTC)) self.rpt += end.ical() self.ev.add('rrule',vRecur.from_ical(self.rpt))
def parse(self, cal, provider=None): try: items = [] for component in cal.walk(): if component.name == "VEVENT": item = { ITEM_TYPE: CONTENT_TYPE.TEXT, GUID_FIELD: generate_guid(type=GUID_NEWSML), FORMAT: FORMATS.PRESERVED } item['name'] = component.get('summary') item['definition_short'] = component.get('summary') item['definition_long'] = component.get('description') item['original_source'] = component.get('uid') # add dates # check if component .dt return date instead of datetime, if so, convert to datetime dtstart = component.get('dtstart').dt dates_start = dtstart if isinstance(dtstart, datetime.datetime) \ else datetime.datetime.combine(dtstart, datetime.datetime.min.time()) if not dates_start.tzinfo: dates_start = utc.localize(dates_start) try: dtend = component.get('dtend').dt dates_end = dtend if isinstance(dtend, datetime.datetime) \ else datetime.datetime.combine(dtend, datetime.datetime.min.time()) if not dates_end.tzinfo: dates_end = utc.localize(dates_end) except AttributeError as e: dates_end = None item['dates'] = { 'start': dates_start, 'end': dates_end, 'tz': '', 'recurring_rule': {} } # parse ics RRULE to fit eventsML recurring_rule r_rule = component.get('rrule') if isinstance(r_rule, vRecur): r_rule_dict = vRecur.from_ical(r_rule) if 'FREQ' in r_rule_dict.keys(): item['dates']['recurring_rule'][ 'frequency'] = ''.join(r_rule_dict.get('FREQ')) if 'INTERVAL' in r_rule_dict.keys(): item['dates']['recurring_rule'][ 'interval'] = r_rule_dict.get('INTERVAL')[0] if 'UNTIL' in r_rule_dict.keys(): item['dates']['recurring_rule'][ 'until'] = r_rule_dict.get('UNTIL')[0] if 'COUNT' in r_rule_dict.keys(): item['dates']['recurring_rule'][ 'count'] = r_rule_dict.get('COUNT') if 'BYMONTH' in r_rule_dict.keys(): item['dates']['recurring_rule'][ 'bymonth'] = ' '.join( r_rule_dict.get('BYMONTH')) if 'BYDAY' in r_rule_dict.keys(): item['dates']['recurring_rule'][ 'byday'] = ' '.join(r_rule_dict.get('BYDAY')) if 'BYHOUR' in r_rule_dict.keys(): item['dates']['recurring_rule'][ 'byhour'] = ' '.join(r_rule_dict.get('BYHOUR')) if 'BYMIN' in r_rule_dict.keys(): item['dates']['recurring_rule'][ 'bymin'] = ' '.join(r_rule_dict.get('BYMIN')) # set timezone info if date is a datetime if isinstance( component.get('dtstart').dt, datetime.datetime): item['dates']['tz'] = tzid_from_dt( component.get('dtstart').dt) # add participants item['participant'] = [] if component.get('attendee'): for attendee in component.get('attendee'): if isinstance(attendee, vCalAddress): item['participant'].append({ 'name': vCalAddress.from_ical(attendee), 'qcode': '' }) # add organizers item['organizer'] = [{ 'name': component.get('organizer', ''), 'qcode': '' }] # add location item['location'] = [{ 'name': component.get('location', ''), 'qcode': '', 'geo': '' }] if component.get('geo'): item['location'][0]['geo'] = vGeo.from_ical( component.get('geo').to_ical()) # IMPORTANT: firstcreated must be less than 2 days past # we must preserve the original event created and updated in some other fields if component.get('created'): item['event_created'] = component.get('created').dt if component.get('last-modified'): item['event_lastmodified'] = component.get( 'last-modified').dt item['firstcreated'] = utcnow() item['versioncreated'] = utcnow() items.append(item) original_source_ids = [ _['original_source'] for _ in items if _.get('original_source', None) ] existing_items = list( get_resource_service('events').get_from_mongo( req=None, lookup={'original_source': { '$in': original_source_ids }})) def original_source_exists(item): """Return true if the item exists in `existing_items`""" for c in existing_items: if c['original_source'] == item['original_source']: if c['dates']['start'] == item['dates']['start']: return True return False def is_future(item): """Return true if the item is reccuring or in the future""" if not item['dates'].get('recurring_rule'): if item['dates']['start'] < utcnow() - datetime.timedelta( days=1): return False return True items = [_ for _ in items if is_future(_)] items = [_ for _ in items if not original_source_exists(_)] return items except Exception as ex: raise ParserError.parseMessageError(ex, provider)