def __call__(self): request = self.context.REQUEST event_uid = request.get('event') if event_uid: event_uid = event_uid.split('UID_')[1] brains = self.context.portal_catalog(UID=event_uid) obj = brains[0].getObject() startDate, endDate = starts(obj), ends(obj) dayDelta, minuteDelta = float(request.get('dayDelta', 0)), float( request.get('minuteDelta', 0)) startDate = startDate + dayDelta + minuteDelta / 1440.0 endDate = endDate + dayDelta + minuteDelta / 1440.0 if HAS_PAE and not hasattr(obj, 'setStartDate'): # non-Archetypes duck type: use properties for start/end, # along with UTC-normalized datetime.datetime values from plone.event.utils import pydt obj.start = pydt(startDate) obj.end = pydt(endDate) else: obj.setStartDate(startDate) obj.setEndDate(endDate) adapted = interfaces.ISFBaseEventFields(obj, None) if adapted: if request.get('allDay', None) == 'true': setattr(adapted, 'allDay', True) if request.get('allDay', None) == 'false': setattr(adapted, 'allDay', False) obj.reindexObject() return True
def get_events_by_date(context, range_start=None, range_end=None, **kw): """ Return a dictionary with dates in a given timeframe as keys and the actual events for that date. """ # TODO: factor out, use for get_portal_events and whole_day_handler too. tz = default_tzinfo(context) range_start = pydt(range_start, missing_zone=tz) if isinstance(range_end, date): # set range_end to the next day, time will be 0:00 # so the whole previous day is also used for search range_end = range_end + timedelta(days=1) range_end = pydt(range_end, missing_zone=tz) events = get_portal_events(context, range_start, range_end, **kw) # TODO: catalog brains are timezone'd. shouldn't they be in UTC? ## example catalog entry: 2011/09/16 16:35:00 Brazil/West events_by_date = {} for event in events: obj = event.getObject() occurrences = obj.occurrences(range_start, range_end) for occ in occurrences: # occ: (start, end) start_str = datetime.strftime(occ[0], '%Y-%m-%d') # TODO: add span_events parameter to include dates btw. start # and end also. for events lasting longer than a day... if start_str not in events_by_date: events_by_date[start_str] = [event] else: events_by_date[start_str].append(event) return events_by_date
def _prepare_range(context, start, end): """Prepare a date-range to contain timezone info and set end to next day, if end is a date. :param context: [required] Context object. :type context: Content object :param start: [required] Range start. :type start: Python date or datetime :param end: [required] Range end. :type end: Python date or datetime :returns: Localized start and end datetime. :rtype: tuple """ tz = default_timezone(context, as_tzinfo=True) start = pydt(start, missing_zone=tz) if is_date(end): # set range_end to the next day, time will be 0:00 # so the whole previous day is also used for search end = end + timedelta(days=1) end = pydt(end, missing_zone=tz) return start, end
def __call__(self): request = self.context.REQUEST event_uid = request.get('event') if event_uid: event_uid = event_uid.split('UID_')[1] brains = self.context.portal_catalog(UID=event_uid) obj = brains[0].getObject() startDate, endDate = starts(obj), ends(obj) dayDelta, minuteDelta = float(request.get('dayDelta', 0)), float(request.get('minuteDelta', 0)) startDate = startDate + dayDelta + minuteDelta / 1440.0 endDate = endDate + dayDelta + minuteDelta / 1440.0 if HAS_PAE and not hasattr(obj, 'setStartDate'): # non-Archetypes duck type: use properties for start/end, # along with UTC-normalized datetime.datetime values from plone.event.utils import pydt obj.start = pydt(startDate) obj.end = pydt(endDate) else: obj.setStartDate(startDate) obj.setEndDate(endDate) adapted = interfaces.ISFBaseEventFields(obj, None) if adapted: if request.get('allDay', None) == 'true': setattr(adapted, 'allDay', True) if request.get('allDay', None) == 'false': setattr(adapted, 'allDay', False) obj.reindexObject() return True
def __init__(self, uid=None, title=None, description=None, start=None, end=None, whole_day=None, created=None, modified=None, location=None, subject=[], attendees=[], contact_name=None, contact_phone=None, contact_email=None, event_url=None, recurrence=None, start_date=None, end_date=None): # start_date and end_date can be directly set with datetime values, if # no start or end DateTime is set. self.uid = uid self.title = title self.description = description self._start = start and DateTime(start) or None self.start_date = start and pydt(self._start) or start_date self._end = end and DateTime(end) or None self.end_date = end and pydt(self._end) or end_date self._whole_day = whole_day self.created = created self.modified = modified self.location = location self.subject = subject self.attendees = attendees self.cname = contact_name self.cphone = contact_phone self.cemail = contact_email self.eurl = event_url self.recurrence = recurrence self.duration = self.start_date and self.end_date and\ self.end_date-self.start_date or None
def test_edit(self): new = self.obj self._edit_atevent(new) self.assertEqual(new.start_date, pydt(new.start())) self.assertEqual(new.end_date, pydt(new.end())) self.assertEqual(new.start_date, pydt(OBJ_DATA['startDate'])) self.assertEqual(new.end_date, pydt(OBJ_DATA['endDate'])) self.assertEqual(new.duration, new.end_date - new.start_date)
def __iter__(self): """Iterate over items.""" default_timezone = self.options.get('default_timezone') or 'UTC' if HAS_PAC: try: tz = api.portal.get_registry_record('plone.portal_timezone') except InvalidParameterError: tz = None if tz is not None: tz = timezone(tz) else: tz = timezone(default_timezone) for item in self.previous: if self.condition(item): pathkey = self.pathkey(*item.keys())[0] if not pathkey: # not enough info yield item continue path = item[pathkey] obj = traverse(self.context, str(path).lstrip('/'), None) if obj is None: yield item continue # object not found print("SetAndFixKnownDates") if 'creation_date' in item: # try: # obj.setCreationDate(item['creation_date']) # except AttributeError: # # dexterity content does not have setCreationDate # obj.creation_date = item['creation_date'] obj.creation_date = item['creation_date'] if 'modification_date' in item: obj.setModificationDate(item['modification_date']) effectiveDate = item.get('effectiveDate', None) if effectiveDate and effectiveDate != u"None": obj.setEffectiveDate(effectiveDate) else: obj.setEffectiveDate(item.get('modification_date', None)) # if item['_path']==u'/organisation/gv/arbeitshilfen/exuperantius/exuperantius-05-oeffentlichkeitsarbeit': # import pdb; pdb.set_trace() print(item.get('effectiveDate', None) or item.get('modification_date', None)) if 'expirationDate' in item: obj.setExpirationDate(item['expirationDate']) # Fix issue where expiration date was before effective date effective = obj.effective() expires = obj.expires() if effective and expires and expires < effective: obj.setExpirationDate(effective) if HAS_PAC and item.get('_type') == 'Event': # obj = resolve_object(context, item) obj.start = pydt(DateTime(obj.start)).astimezone(tz) obj.end = pydt(DateTime(obj.end)).astimezone(tz) yield item
def recurrence_sequence_timedelta(start, delta=None, until=None, count=None, dst=DSTAUTO): """ Calculates a sequence of datetime objects from a timedelta integer, which defines the minutes between each occurence. :param start: datetime or DateTime instance of the date from which the recurrence sequence is calculated. :type start: datetime :param delta: Integer which defines the minutes between each date occurence. :type delta: integer :param until: datetime or DateTime instance of the date, until the recurrence is calculated. If not given, count or MAXDATE limit the recurrence calculation. :type until: datetime :param count: Integer which defines the number of occurences. If not given, until or MAXDATE limits the recurrence calculation. :param count: integer :param dst: Daylight Saving Time crossing behavior. DSTAUTO, DSTADJUST or DSTKEEP. For more information, see plone.event.utils.utcoffset_normalize. :param dst: string :return: A generator which generates a sequence of datetime instances. :rtype: generator """ start = pydt(start) yield start if delta is None or delta < 1 or until is None: return until = pydt(until) before = start delta = datetime.timedelta(minutes=delta) cnt = 0 while True: after = before + delta after = utcoffset_normalize(after, delta, dst) # Limit number of recurrences otherwise calculations take too long if MAXCOUNT and cnt + 1 > MAXCOUNT: break if count and cnt + 1 > count: break if until and utc(after) > utc(until): break cnt += 1 yield after before = after
def __call__(self): msg = PLMF(u'Copy or cut one or more items to paste.') self.request.response.setHeader("Content-type", "application/json") if self.context.cb_dataValid(): try: baseObject = self.portal.restrictedTraverse( self.copyDict['url']) baseId = 'UID_' + get_uid(baseObject) intervalle = ends(baseObject) - starts(baseObject) cb_copy_data = self.request['__cp'] pasteList = self.context.manage_pasteObjects( cb_copy_data=cb_copy_data) newObject = getattr(self.context, pasteList[0]['new_id']) startDate = self.startDate if self.EventAllDay: startDate = DateTime(self.startDate).strftime( '%Y-%m-%d ') + starts(baseObject).strftime('%H:%M') if HAS_PAE and not hasattr(newObject, 'setStartDate'): # non-Archetypes duck type: use properties for start/end, # along with UTC-normalized datetime.datetime values from plone.event.utils import pydt import pytz local_start = DateTime(startDate) _utc = lambda dt: pytz.UTC.normalize(dt) newObject.start = _utc(pydt(local_start)) newObject.end = _utc(pydt(local_start + intervalle)) newObject.whole_day = self.EventAllDay else: newObject.setStartDate(DateTime(startDate)) newObject.setEndDate( newObject.getField('startDate').get(newObject) + intervalle) newObject.reindexObject() transaction_note('Pasted content to %s' % (self.context.absolute_url())) return json.dumps({ 'status': 'pasted', 'event': self.createJsonEvent(newObject), 'url': newObject.absolute_url(), 'op': self.copyDict['op'], 'id': baseId }) except ConflictError: raise except ValueError: msg = PLMF(u'Disallowed to paste item(s).') except (Unauthorized, 'Unauthorized'): msg = PLMF(u'Unauthorized to paste item(s).') except CopyError: # fallback msg = PLMF(u'Paste could not find clipboard content.') return json.dumps({ 'status': 'failure', 'message': self.context.translate(msg) })
def _get_due(self, value=None): if value is None: value = getattr(self.context, 'due', None) if not value: taskplanner = self._get_taskplanner(self.context) if taskplanner: value = getattr(taskplanner, 'due', None) if not value: return _type = value.get('type') _value = value.get('value') if _type == 'date': return parse_datetime(_value) elif _type == 'computed': field4 = getattr(self.context, _value['field4']) if not field4: return field3 = self._get_value(_value['field3'], TIME_RELATIONS)[2] if safe_callable(field4): field4 = field4() if isinstance(field4, DateTime): field4 = pydt(field4) if field3 is None: return field4 else: field2 = self._get_value(_value['field2'], TIME_UNITS)[2] field1 = int(_value['field1']) return field4 + field2(field3 * field1) elif _type == 'computed_dow': field4 = getattr(self.context, _value['field4']) if not field4: return field3 = self._get_value(_value['field3'], TIME_RELATIONS)[2] if safe_callable(field4): field4 = field4() if isinstance(field4, DateTime): field4 = pydt(field4) if field3 is None: return field4 else: field2 = self._get_value(_value['field2'], DAYS_OF_WEEK)[2] field1 = int(_value['field1']) return field4 + field2(field3 * field1)
def test_pydt__missing_zone_is_None(self, utctz, guesstz): from plone.event.utils import pydt dt = mock.Mock() dt.toZone.return_value = dt dt.parts.return_value = (2011, 11, 24, 11, 39, 00) guesstz.return_value = None import pytz utctz.return_value = pytz.timezone('UTC') pydt(dt) self.assertTrue(utctz.called)
def event_component(context): """ Returns an icalendar object of the event. """ ical_event = icalendar.Event() # TODO: until VTIMETZONE component is added and TZID used, everything is # converted to UTC. use real TZID, when VTIMEZONE is used! ical_event.add('dtstamp', utc(pydt(datetime.now()))) ical_event.add('created', utc(pydt(context.creation_date))) ical_event.add('uid', context.UID()) ical_event.add('last-modified', utc(pydt(context.modification_date))) ical_event.add('summary', context.Title()) ical_event.add('dtstart', utc(pydt(context.start()))) ical_event.add('dtend', utc(pydt(context.end()))) recurrence = context.recurrence if recurrence: if recurrence.startswith('RRULE:'): recurrence = recurrence[6:] ical_event.add('rrule', icalendar.prop.vRecur.from_ical(recurrence)) description = context.Description() if description: ical_event.add('description', description) location = context.getLocation() if location: ical_event.add('location', location) subjects= context.Subject() for subject in subjects: ical_event.add('categories', subject) # TODO: revisit and implement attendee export according to RFC attendees = context.getAttendees() for attendee in attendees: ical_event.add('attendee', attendee) cn = [] contact = context.contact_name() if contact: cn.append(contact) # TODO safe_unicode conversion needed? phone = context.contact_phone() if phone: cn.append(phone) email = context.contact_email() if email: cn.append(email) if cn: ical_event.add('contact', u', '.join(cn)) url = context.event_url() if url: ical_event.add('url', url) return ical_event
def test_import_from_ics(self): # Ical import unit test. self.portal.invokeFactory("Folder", "impfolder1") impfolder = self.portal.impfolder1 directory = os.path.dirname(__file__) icsfile = open(os.path.join(directory, "icaltest.ics"), "rb").read() res = ical_import(impfolder, icsfile, self.event_type) self.assertEqual(res["count"], 5) self.assertEqual(len(impfolder.contentIds()), 5) at = pytz.timezone("Europe/Vienna") utc = pytz.utc # Use pydt to normalize for DST times. # TODO: test for attendees. see note in # plone.app.event.ical.importer.ical_import e1 = IEventAccessor(impfolder.e1) self.assertEqual(e1.start, pydt(datetime(2013, 7, 19, 12, 0, tzinfo=at))) self.assertEqual(e1.end, pydt(datetime(2013, 7, 20, 13, 0, tzinfo=at))) self.assertEqual(e1.description, "A basic event with many properties.") self.assertEqual(e1.whole_day, False) self.assertEqual(e1.open_end, False) self.assertEqual(e1.sync_uid, u"48f1a7ad64e847568d860cd092344970") e2 = IEventAccessor(impfolder.e2) self.assertEqual(e2.start, pydt(datetime(1996, 4, 1, 1, 0, tzinfo=utc))) self.assertEqual(e2.end, pydt(datetime(1996, 4, 1, 2, 0, tzinfo=utc))) self.assertEqual( e2.recurrence, u"RRULE:FREQ=DAILY;COUNT=100\nEXDATE:19960402T010000Z," u"19960403T010000Z,19960404T010000Z" ) e3 = IEventAccessor(impfolder.e3) self.assertEqual(e3.start, pydt(datetime(2012, 3, 27, 10, 0, tzinfo=at))) self.assertEqual(e3.end, pydt(datetime(2012, 3, 27, 18, 0, tzinfo=at))) self.assertEqual( e3.recurrence, u"RRULE:FREQ=WEEKLY;UNTIL=20120703T080000Z;BYDAY=TU\n" u"EXDATE:20120529T100000,20120403T100000,20120410T100000," u"20120501T100000,20120417T100000", ) e4 = IEventAccessor(impfolder.e4) self.assertEqual(e4.start, pydt(datetime(2013, 4, 4, 0, 0, tzinfo=utc))) self.assertEqual(e4.end, pydt(datetime(2013, 4, 4, 23, 59, 59, tzinfo=utc))) self.assertEqual(e4.whole_day, True) self.assertEqual(e4.open_end, False) e5 = IEventAccessor(impfolder.e5) self.assertEqual(e5.start, pydt(datetime(2013, 4, 2, 12, 0, tzinfo=utc))) self.assertEqual(e5.end, pydt(datetime(2013, 4, 2, 23, 59, 59, tzinfo=utc))) self.assertEqual(e5.whole_day, False) self.assertEqual(e5.open_end, True)
def create_g24_event(self, container, node): title = node.getAttribute('pc_title') try: text = node.getElementsByTagName('text')[0].childNodes[1].data except IndexError: text = u'' tags = [] for t in node.getElementsByTagName('tag'): tags.append(t.getAttribute('name')) loc = node.getAttribute('location_name') data = { 'is_event': True, 'title': title, 'text': text, 'subjects': tags, 'timezone': 'Europe/Vienna', 'whole_day': node.getAttribute('pc_alldayevent') == "1", 'open_end': False, 'recurrence': None, 'location': LOC_TO_UUID.get(loc), } createdate = DateTime(node.getAttribute('pc_time')) event_date = DateTime( node.getAttribute('pc_eventDate') + " " + node.getAttribute('pc_startTime') + " Europe/Vienna" ) data['start'] = pydt(event_date) # calc / use end date , fallback is same as start data['end'] = pydt(event_date) if node.hasAttribute('pc_endDate'): end_date = DateTime( node.getAttribute('pc_endDate') + " " + node.getAttribute('pc_endTime') + " Europe/Vienna" ) data['end'] = pydt(end_date) elif node.getAttribute('pc_duration'): end_date = DateTime( int(event_date) + int(node.getAttribute('pc_duration')) ) data['end'] = pydt(end_date) obj = create(container, G24_BASETYPE) # set the creators by loginname. if more than one, # seperate by whitespace obj.setCreators(node.getAttribute('pc_informant')) obj.creation_date = createdate edit(obj, data, order=FEATURES) obj = add(obj, container) return obj
def get_recurring_events(request, event): if isinstance(event.start, datetime): tz = event.start.tzinfo start = datetime.fromtimestamp(request.get('start')).replace(tzinfo=tz) end = datetime.fromtimestamp(request.get('end')).replace(tzinfo=tz) else: start = pydt(DateTime(request.get('start'))) end = pydt(DateTime(request.get('end'))) events = IRecurrenceSupport(event).occurrences(range_start=start, range_end=end) return events
def test_pydt__missing_zone_is_not_None(self, utctz, guesstz): from plone.event.utils import pydt dt = mock.Mock() import pytz utctz.return_value = pytz.timezone('UTC') missing_zone = utctz() dt.toZone.return_value = dt dt.parts.return_value = (2011, 11, 24, 11, 39, 00) guesstz.return_value = None pydt(dt, missing_zone=missing_zone) self.assertEqual(utctz.call_count, 1)
def to_icalendar(self): """ Returns an icalendar object of the event. """ event = self ical_event = icalendar.Event() ical_event.add('dtstamp', datetime.now()) ical_event.add('created', pydt(event.creation_date)) ical_event.add('uid', event.UID()) ical_event.add('last-modified', pydt(event.modification_date)) ical_event.add('summary', event.Title()) ical_event.add('dtstart', pydt(event.start())) ical_event.add('dtend', pydt(event.end())) recurrence = event.recurrence if recurrence: ical_event.add('rrule', icalendar.prop.vRecur.from_ical(recurrence)) description = event.Description() if description: ical_event.add('description', description) location = event.getLocation() if location: ical_event.add('location', location) subjects= event.Subject() for subject in subjects: ical_event.add('categories', subject) # TODO: revisit and implement attendee export according to RFC attendees = event.getAttendees() for attendee in attendees: ical_event.add('attendee', attendee) cn = [] contact = event.contact_name() if contact: cn.append(contact) # TODO safe_unicode conversion needed? phone = event.contact_phone() if phone: cn.append(phone) email = event.contact_email() if email: cn.append(email) if cn: ical_event.add('contact', u', '.join(cn)) url = event.event_url() if url: ical_event.add('url', url) return ical_event
def test_edit(self): new = self.obj self._edit_atevent(new) self.assertEqual(new.start_date, pydt(new.start())) self.assertEqual(new.end_date, pydt(new.end())) # TODO: sometimes the following tests fails, because of comparison of # microseconds. Is it an rounding error? # AssertionError: datetime.datetime(2012, 10, 25, 14, 2, 11, 855640, # tzinfo=<DstTzInfo 'Europe/Vienna' CEST+2:00:00 DST>) != # datetime.datetime(2012, 10, 25, 14, 2, 11, 85564, tzinfo=<DstTzInfo # 'Europe/Vienna' CEST+2:00:00 DST>) self.assertEqual(new.start_date, pydt(OBJ_DATA['startDate'])) self.assertEqual(new.end_date, pydt(OBJ_DATA['endDate'])) self.assertEqual(new.duration, new.end_date - new.start_date)
def start_date(self): """ Return start date as Python datetime. :returns: Start of the event. :rtype: Python datetime """ return pydt(self.start(), exact=False)
def _get_compare_attr(obj, attr): val = getattr(obj, attr, None) if safe_callable(val): val = val() if isinstance(val, DateTime): val = pydt(val) return val
def json_config(self): events = [] for brain in self.results(): event = { 'title': brain.Title, 'url': brain.getURL() } if brain.portal_type == 'Event': event.update({ 'start': brain.start and format_date(brain.start), 'end': brain.end and format_date(brain.end) }) else: event['start'] = format_date(brain.effective) if brain.expires and brain.expires.year() != 2499: event['end'] = format_date(brain.expires) else: event['end'] = format_date(pydt(brain.effective) + timedelta(hours=1)) events.append(event) return json.dumps({ 'header': { 'left': 'prev,next today', 'center': 'title', 'right': 'month,agendaWeek,agendaDay' }, 'events': events })
def __call__(self): msg=PLMF(u'Copy or cut one or more items to paste.') self.request.response.setHeader("Content-type","application/json") if self.context.cb_dataValid(): try: baseObject = self.portal.restrictedTraverse(self.copyDict['url']) baseId = 'UID_' + get_uid(baseObject) intervalle = ends(baseObject) - starts(baseObject) cb_copy_data = self.request['__cp'] pasteList = self.context.manage_pasteObjects(cb_copy_data=cb_copy_data) newObject = getattr(self.context, pasteList[0]['new_id']) startDate = self.startDate if self.EventAllDay: startDate = DateTime(self.startDate).strftime('%Y-%m-%d ')+starts(baseObject).strftime('%H:%M') if HAS_PAE and not hasattr(newObject, 'setStartDate'): # non-Archetypes duck type: use properties for start/end, # along with UTC-normalized datetime.datetime values from plone.event.utils import pydt import pytz local_start = DateTime(startDate) _utc = lambda dt: pytz.UTC.normalize(dt) newObject.start = _utc(pydt(local_start)) newObject.end = _utc(pydt(local_start + intervalle)) newObject.whole_day = self.EventAllDay else: newObject.setStartDate(DateTime(startDate)) newObject.setEndDate(newObject.getField('startDate').get(newObject) + intervalle) newObject.reindexObject() transaction_note('Pasted content to %s' % (self.context.absolute_url())) return json.dumps({'status':'pasted', 'event':self.createJsonEvent(newObject), 'url':newObject.absolute_url(), 'op':self.copyDict['op'], 'id':baseId}) except ConflictError: raise except ValueError: msg=PLMF(u'Disallowed to paste item(s).') except (Unauthorized, 'Unauthorized'): msg=PLMF(u'Unauthorized to paste item(s).') except CopyError: # fallback msg=PLMF(u'Paste could not find clipboard content.') return json.dumps({'status':'failure', 'message':self.context.translate(msg)})
def end_date(self): """ Return end date as Python datetime. Please note, the end date marks only the end of an individual occurrence and not the end of a recurrence sequence. :returns: End of the event. :rtype: Python datetime """ return pydt(self.end(), exact=False)
def _get_compare_attr(obj, attr): """Return an compare attribute, supporting AT, DX and IEventAccessor objects. """ val = getattr(obj, attr, None) if safe_callable(val): val = val() if isinstance(val, DateTime): val = pydt(val) return val
def timestamp(self): item = self.latest_blogentry() date = item.getObject().effective() date = pydt(date) timestamp = {} timestamp['day'] = date.strftime("%d") timestamp['month'] = date.strftime("%B") timestamp['year'] = date.strftime("%Y") timestamp['date'] = date return timestamp
def timestamp(self, uid): item = api.content.get(UID=uid) date = item.created() date = pydt(date) timestamp = {} timestamp['day'] = date.strftime("%d") timestamp['month'] = date.strftime("%m") timestamp['year'] = date.strftime("%Y") timestamp['date'] = date return timestamp
def timestamp(self): context = aq_inner(self.context) date = context.effective() date = pydt(date) timestamp = {} timestamp['day'] = date.strftime("%d") timestamp['month'] = date.strftime("%B") timestamp['year'] = date.strftime("%Y") timestamp['date'] = date return timestamp
def get_obj(start): if pydt(event.start.replace(microsecond=0)) == start: # If the occurrence date is the same as the event object, the # occurrence is the event itself. return it as such. # Dates from recurrence_sequence_ical are explicitly without # microseconds, while event.start may contain it. So we have to # remove it for a valid comparison. return self.context return Occurrence(id=str(start.date()), start=start, end=start + duration).__of__(self.context)
def time_stamp(self): context = aq_inner(self.context) date = context.effective() date = pydt(date) timestamp = { "day": date.strftime("%d"), "month": get_localized_month_name(date.strftime("%B")), "year": date.strftime("%Y"), "date": date, } return timestamp
def timestamp(self): item = self.latest_blogentry() date = item.getObject().effective() date = pydt(date) timestamp = { 'day': date.strftime("%d"), 'month': get_localized_month_name(date.strftime("%B")), 'year': date.strftime("%Y"), 'date': date } return timestamp
def __iter__(self): context = self.transmogrifier.context default_timezone = self.options.get('default_timezone') or 'UTC' if HAS_PAC: try: tz = api.portal.get_registry_record('plone.portal_timezone') except InvalidParameterError: tz = None if tz is not None: tz = timezone(tz) else: tz = timezone(default_timezone) for item in self.previous: if self.condition(item): obj = resolve_object(context, item) if 'creation_date' in item: try: obj.setCreationDate(item['creation_date']) except AttributeError: # dexterity content does not have setCreationDate obj.creation_date = item['creation_date'] if 'modification_date' in item: obj.setModificationDate(item['modification_date']) if 'effective_date' in item: obj.setEffectiveDate(item['effective_date']) if 'expiration_date' in item: obj.setExpirationDate(item['expiration_date']) # Fix issue where expiration date was before effective date effective = obj.effective() expires = obj.expires() if effective and expires and expires < effective: obj.setExpirationDate(effective) if HAS_PAC and item.get('_type') == 'Event': obj = resolve_object(context, item) obj.start = pydt(DateTime(obj.start)).astimezone(tz) obj.end = pydt(DateTime(obj.end)).astimezone(tz) yield item
def get_obj(start): if pydt(event.start.replace(microsecond=0)) == start: # If the occurrence date is the same as the event object, the # occurrence is the event itself. return it as such. # Dates from recurrence_sequence_ical are explicitly without # microseconds, while event.start may contain it. So we have to # remove it for a valid comparison. return self.context return Occurrence( id=str(start.date()), start=start, end=start + duration).__of__(self.context)
def test_traverse_occurrence(self): self.at.setRecurrence('RRULE:FREQ=WEEKLY;COUNT=10') transaction.commit() qdatedt = pydt(self.at.start() + 7) url = '/'.join([self.portal['at'].absolute_url(), str(qdatedt.date())]) browser = Browser(self.layer['app']) browser.addHeader('Authorization', 'Basic %s:%s' % ( TEST_USER_ID, TEST_USER_PASSWORD)) browser.open(url) self.assertTrue( self.portal['at'].title.encode('ascii') in browser.contents)
def test_occurrence(self): self.at.setRecurrence('RRULE:FREQ=WEEKLY;COUNT=10') # does not match occurrence date qdate = datetime.date.today() + datetime.timedelta(days=4) self.assertRaises( AttributeError, self.at_traverser.publishTraverse, self.layer['request'], str(qdate)) qdatedt = pydt(self.at.start() + 7) item = self.at_traverser.publishTraverse(self.layer['request'], str(qdatedt.date())) self.assertTrue(IOccurrence.providedBy(item)) self.assertTrue(IATEvent.providedBy(item.aq_parent))
def time_stamp(date_value): date = pydt(date_value) timestamp = { 'day': format_datetime(date, 'dd', locale='de'), 'day_name': format_datetime(date, 'EEEE', locale='de'), 'month': date.strftime("%m"), 'month_name': format_datetime(date, 'LLLL', locale='de'), 'year': date.strftime("%Y"), 'hour': date.strftime('%H'), 'minute': date.strftime('%M'), 'time': format_datetime(date, 'H:mm', locale='de'), 'date': date, 'date_short': format_datetime(date, format='short', locale='de'), 'date_long': format_datetime(date, format='full', locale='de') } return timestamp
def notifications(self): dates = [] rules = None # if assignee is asking for notifications look first if there are # customezed ones current = api.user.get_current().getId() annotations = self._setup_annotations() assignee = getattr(self.context, 'assignee', None) if assignee and current in assignee and \ current in annotations[TASK_NOTIFICATIONS_KEY]: rules = annotations[TASK_NOTIFICATIONS_KEY][current] # in all other cases just return notifications else: rules = getattr(self.context, 'notifications', None) if not rules: taskplanner = self._get_taskplanner(self.context) if taskplanner: rules = getattr(taskplanner, 'notifications', None) if rules: for rule in rules: if rule['field4'] == 'due': field4 = self._get_due( getattr(self.context, rule['field4'])) else: field4 = getattr(self.context, rule['field4']) if not field4: continue field3 = self._get_value(rule['field3'], TIME_RELATIONS)[2] if safe_callable(field4): field4 = field4() if isinstance(field4, DateTime): field4 = pydt(field4) if field3 is None: dates.append(field4) else: field2 = self._get_value(rule['field2'], TIME_UNITS)[2] field1 = int(rule['field1']) dates.append(field4 + field2(field3 * field1)) return dates
def json_config(self): events = [] for brain in self.results(): event = {'title': brain.Title, 'url': brain.getURL()} if brain.portal_type == 'Event': event.update({ 'start': brain.start and format_date(brain.start), 'end': brain.end and format_date(brain.end) }) if hasattr(brain, 'recurrence') and brain.recurrence is not None: time_delta = brain.end - brain.start recurring_events = recurrences(brain.start, brain.recurrence) for new_event in recurring_events: if new_event != brain.start: # skip the actual event events.append({ 'title': brain.Title, 'url': brain.getURL(), 'start': format_date(new_event), 'end': format_date(new_event + time_delta) }) else: event['start'] = format_date(brain.effective) if brain.expires and brain.expires.year() != 2499: event['end'] = format_date(brain.expires) else: event['end'] = format_date( pydt(brain.effective) + timedelta(hours=1)) events.append(event) return json.dumps({ 'header': { 'left': 'prev,next today', 'center': 'title', 'right': 'month,agendaWeek,agendaDay' }, 'events': events })
def __call__(self): request = self.context.REQUEST event_uid = request.get('event') if event_uid: event_uid = event_uid.split('UID_')[1] brains = self.context.portal_catalog(UID=event_uid) obj = brains[0].getObject() endDate = ends(obj) dayDelta, minuteDelta = float(request.get('dayDelta', 0)), float(request.get('minuteDelta', 0)) endDate = endDate + dayDelta + minuteDelta / 1440.0 if HAS_PAE and not hasattr(obj, 'setEndDate'): # duck-typed: dexterity-based plone.app.event type UTC datetime from plone.event.utils import pydt obj.end = pydt(endDate) # endDate is already in UTC else: obj.setEndDate(endDate) obj.reindexObject() return True
def __call__(self): request = self.context.REQUEST event_uid = request.get('event') if event_uid: event_uid = event_uid.split('UID_')[1] brains = self.context.portal_catalog(UID=event_uid) obj = brains[0].getObject() endDate = ends(obj) dayDelta, minuteDelta = float(request.get('dayDelta', 0)), float( request.get('minuteDelta', 0)) endDate = endDate + dayDelta + minuteDelta / 1440.0 if HAS_PAE and not hasattr(obj, 'setEndDate'): # duck-typed: dexterity-based plone.app.event type UTC datetime from plone.event.utils import pydt obj.end = pydt(endDate) # endDate is already in UTC else: obj.setEndDate(endDate) obj.reindexObject() return True
def last_modified(self, value): tz = default_timezone(self.context, as_tzinfo=True) mod = DT(pydt(value, missing_zone=tz)) setattr(self.context, 'modification_date', mod)
def test_pydt__wrong_type(self): from plone.event.utils import pydt self.assertEqual(pydt('wrongtype'), None)
def index_object(self, documentId, obj, threshold=None): """index an object, normalizing the indexed value to an integer o Normalized value has granularity of one minute. o Objects which have 'None' as indexed value are *omitted*, by design. o Repeat by recurdef - a RFC2445 reccurence definition string """ returnStatus = 0 try: date_attr = getattr(obj, self.id) if safe_callable(date_attr): date_attr = date_attr() except AttributeError: return returnStatus recurdef = getattr(obj, self.attr_recurdef, None) if safe_callable(recurdef): recurdef = recurdef() if not recurdef: dates = [pydt(date_attr)] else: until = getattr(obj, self.attr_until, None) if safe_callable(until): until = until() dates = recurrence_sequence_ical(date_attr, recrule=recurdef, until=until) newvalues = IISet(map(dt2int, dates)) oldvalues = self._unindex.get(documentId, _marker) if oldvalues is not _marker: oldvalues = IISet(oldvalues) if oldvalues is not _marker and newvalues is not _marker\ and not difference(newvalues, oldvalues)\ and not difference(oldvalues, newvalues): # difference is calculated relative to first argument, so we have to # use it twice here return returnStatus if oldvalues is not _marker: for oldvalue in oldvalues: self.removeForwardIndexEntry(oldvalue, documentId) if newvalues is _marker: try: del self._unindex[documentId] except ConflictError: raise except: LOG.error("Should not happen: oldvalues was there," " now it's not, for document with id %s" % documentId) if newvalues is not _marker: inserted = False for value in newvalues: self.insertForwardIndexEntry(value, documentId) inserted = True if inserted: # store tuple values in reverse index entries for sorting self._unindex[documentId] = tuple(newvalues) returnStatus = 1 return returnStatus
def test_import_from_ics(self): # Ical import unit test. self.portal.invokeFactory('Folder', 'impfolder1') impfolder = self.portal.impfolder1 directory = os.path.dirname(__file__) icsfile = open(os.path.join(directory, 'icaltest.ics'), 'rb').read() res = ical_import(impfolder, icsfile, self.event_type) self.assertEqual(res['count'], 5) self.assertEqual(len(impfolder.contentIds()), 5) at = pytz.timezone('Europe/Vienna') utc = pytz.utc # Use pydt to normalize for DST times. # TODO: test for attendees. see note in # plone.app.event.ical.importer.ical_import e1 = IEventAccessor(impfolder.e1) self.assertEqual(e1.start, pydt(datetime(2013, 7, 19, 12, 0, tzinfo=at))) self.assertEqual(e1.end, pydt(datetime(2013, 7, 20, 13, 0, tzinfo=at))) self.assertEqual(e1.description, 'A basic event with many properties.') self.assertEqual(e1.whole_day, False) self.assertEqual(e1.open_end, False) self.assertEqual( e1.sync_uid, u'48f1a7ad64e847568d860cd092344970', ) e2 = IEventAccessor(impfolder.e2) self.assertEqual(e2.start, pydt(datetime(1996, 4, 1, 1, 0, tzinfo=utc))) self.assertEqual(e2.end, pydt(datetime(1996, 4, 1, 2, 0, tzinfo=utc))) self.assertEqual( e2.recurrence, u'RRULE:FREQ=DAILY;COUNT=100\nEXDATE:19960402T010000Z,' u'19960403T010000Z,19960404T010000Z') e3 = IEventAccessor(impfolder.e3) self.assertEqual(e3.start, pydt(datetime(2012, 3, 27, 10, 0, tzinfo=at))) self.assertEqual(e3.end, pydt(datetime(2012, 3, 27, 18, 0, tzinfo=at))) self.assertEqual( e3.recurrence, u'RRULE:FREQ=WEEKLY;UNTIL=20120703T080000Z;BYDAY=TU\n' u'EXDATE:20120529T100000,20120403T100000,20120410T100000,' u'20120501T100000,20120417T100000') e4 = IEventAccessor(impfolder.e4) self.assertEqual(e4.start, pydt(datetime(2013, 4, 4, 0, 0, tzinfo=utc))) self.assertEqual(e4.end, pydt(datetime(2013, 4, 4, 23, 59, 59, tzinfo=utc))) self.assertEqual(e4.whole_day, True) self.assertEqual(e4.open_end, False) e5 = IEventAccessor(impfolder.e5) self.assertEqual(e5.start, pydt(datetime(2013, 4, 2, 12, 0, tzinfo=utc))) self.assertEqual(e5.end, pydt(datetime(2013, 4, 2, 23, 59, 59, tzinfo=utc))) self.assertEqual(e5.whole_day, False) self.assertEqual(e5.open_end, True)
return find_context(context, iface=IPloneSiteRoot, as_url=as_url) def find_navroot(context, as_url=False): return find_context(context, iface=INavigationRoot, as_url=as_url) def find_event_listing(context, as_url=False): return find_context(context, viewname='event_listing', iface=ISite, as_url=as_url, append_view=True) # Workaround for buggy strftime with timezone handling in DateTime. # See: https://github.com/plone/plone.app.event/pull/47 # TODO: should land in CMFPlone or fixed in DateTime. _strftime = lambda v, fmt: pydt(v).strftime(fmt) class PatchedDateTime(DateTime): def strftime(self, fmt): return _strftime(self, fmt) def ulocalized_time(time, *args, **kwargs): """Corrects for DateTime bugs doing wrong thing with timezones""" wrapped_time = PatchedDateTime(time) return orig_ulocalized_time(wrapped_time, *args, **kwargs)
def end(self, value): value = pydt(value) self._behavior_map['end'](self.context).end = value
def start(self, value): value = pydt(value) self._behavior_map['start'](self.context).start = value
def test_get_events(self): # whole range res = get_events(self.portal) self.assertEqual(len(res), 4) res = get_events(self.portal, start=self.past, end=self.future) self.assertEqual(len(res), 4) res = get_events(self.portal, end=self.future) self.assertEqual(len(res), 4) res = get_events(self.portal, start=self.past) self.assertEqual(len(res), 4) # Limit res = get_events(self.portal, limit=2) self.assertEqual(len(res), 2) # Return objects res = get_events(self.portal, ret_mode=RET_MODE_OBJECTS) self.assertTrue(IEvent.providedBy(res[0])) # Return IEventAccessor res = get_events(self.portal, ret_mode=RET_MODE_ACCESSORS) self.assertTrue(IEventAccessor.providedBy(res[0])) # Test sorting self.assertTrue(res[0].start < res[-1].start) # Test reversed sorting res = get_events(self.portal, ret_mode=RET_MODE_ACCESSORS, sort_reverse=True) self.assertTrue(res[0].start > res[-1].start) # Test sort_on res = get_events(self.portal, ret_mode=RET_MODE_ACCESSORS, sort="start") self.assertEqual( [it.title for it in res][2:], [u'Now Event', u'Future Event'] ) res = get_events(self.portal, ret_mode=RET_MODE_ACCESSORS, sort="end") self.assertEqual( [it.title for it in res], [u'Past Event', u'Now Event', u'Future Event', u'Long Event'] ) # Test expansion res = get_events(self.portal, ret_mode=RET_MODE_OBJECTS, expand=True) self.assertEqual(len(res), 8) res = get_events(self.portal, ret_mode=RET_MODE_ACCESSORS, expand=True) self.assertEqual(len(res), 8) # Test sorting self.assertTrue(res[0].start < res[-1].start) res = get_events(self.portal, ret_mode=RET_MODE_ACCESSORS, expand=True, sort_reverse=True) # Test sorting self.assertTrue(res[0].start > res[-1].start) # only on now-date res = get_events(self.portal, start=self.now, end=self.now) self.assertEqual(len(res), 2) # only on now-date as date # NOTE: converting self.now to python datetime to allow testing also # with dates as Zope DateTime objects. res = get_events(self.portal, start=pydt(self.now).date(), end=pydt(self.now).date()) self.assertEqual(len(res), 2) # only on past date res = get_events(self.portal, start=self.past, end=self.past) self.assertEqual(len(res), 2) # one recurrence occurrence in far future res = get_events(self.portal, start=self.far, end=self.far) self.assertEqual(len(res), 1) # from now on res = get_events(self.portal, start=self.now) self.assertEqual(len(res), 3) # until now res = get_events(self.portal, end=self.now) self.assertEqual(len(res), 3) # in subfolder path = '/'.join(self.portal.sub.getPhysicalPath()) res = get_events(self.portal, path=path) self.assertEqual(len(res), 1)
def post_creation(self, obj, pdb_if_exception=False, post_creation_data=None): if obj is None: return field_data = self.field_data bdata = ILayoutAware(obj, None) if bdata: try: bdata.contentLayout = self.layout except Exception: bdata.contentLayout = '++contentlayout++default/document.html' bdata = IRichText(obj, None) if bdata: try: bdata.text = RichTextValue(field_data['text'], 'text/html', 'text/html') except Exception: try: bdata.text = RichTextValue( field_data[ 'plone.app.contenttypes.behaviors.richtext.IRichText'] ['text'], # noqa 'text/html', 'text/html').raw except Exception: bdata.text = '' bdata = IBasic(obj, None) if bdata: try: bdata.title = field_data['title'] except Exception: try: bdata.title = field_data[ 'plone.app.content.interfaces.INameFromTitle']['title'] except Exception: bdata.description = field_data[dublin]['title'] try: bdata.description = field_data['description'] except Exception: try: bdata.description = field_data[dublin]['description'] except Exception: bdata.description = field_data[basic]['description'] else: try: obj.title = field_data['title'] obj.description = field_data['description'] except Exception: obj.title = field_data[dublin]['title'] obj.description = field_data[dublin]['description'] bdata = ICategorization(obj, None) if bdata: try: bdata.subjects = field_data['subject'] except Exception: try: bdata.subjects = self.field_data[dublin]['subjects'] except Exception: try: bdata.subjects = self.field_data[categorization][ 'subjects'] except Exception: pass # no keywords found bdata = IPublication(obj) try: if field_data['effectiveDate']: bdata.effective = pydt(field_data['effectiveDate']) except Exception: try: if field_data[dublin]['effective']: bdata.effective = pydt(field_data[dublin]['effective']) except Exception: try: if field_data[publication]['effective']: bdata.effective = pydt( field_data[publication]['effective']) except Exception: bdata.effective = None ldata = ILocation(obj, None) if ldata: if field_data.get('location'): ldata.locations = [field_data['location']] if field_data.get('newsLocation'): if ldata.locations: ldata.locations.append(field_data['newsLocation']) else: ldata.locations = [field_data['newsLocation']] try: obj.modification_date = field_data['modification_date'] except Exception: try: obj.modification_date = obj.modified() except Exception: obj.modification_date = None try: obj.creation_date = field_data['creation_date'] except Exception: try: obj.creation_date = obj.created() except Exception: obj.creation_date = None bdata = IDublinCore(obj, None) if bdata: if IDublinCore.__identifier__ in field_data: dublin_core = field_data[IDublinCore.__identifier__] bdata.expires = dublin_core['expires'] bdata.rights = dublin_core['rights'] bdata.creators = tuple(dublin_core['creators']) bdata.language = dublin_core['language'] bdata.effective = pydt(dublin_core['effective']) bdata.subjects = dublin_core['subjects'] bdata.contributors = tuple(dublin_core['contributors']) else: bdata.expires = pydt(field_data.get('expirationDate')) bdata.rights = field_data.get('rights') creators = field_data.get('creators') bdata.creators = tuple(creators) if creators else () language = field_data.get('language') bdata.language = language if language is not None else "" bdata.effective = pydt(field_data.get('effectiveDate')) bdata.subjects = field_data.get('subject') contributors = field_data.get('contributors') bdata.contributors = tuple(contributors) if contributors else ( ) bdata = ILayoutAware(obj, None) if bdata: if self.data['portal_type'] == 'Folder' and ( self.field_data.get('text') or '').strip(): bdata.content = FOLDER_DEFAULT_PAGE_LAYOUT % self.field_data[ 'text'] # need to explicitly reset contentLayout value because this data # could be overwritten bdata.contentLayout = None elif self.layout: if layoutaware in field_data and 'contentLayout' in field_data[ layoutaware]: bdata.contentLayout = field_data[layoutaware][ 'contentLayout'] if layoutaware in field_data and 'content' in field_data[ layoutaware]: bdata.content = field_data[ 'plone.app.blocks.layoutbehavior.ILayoutAware'][ 'content'] if 'rendered_layout' in self.data['data']: bdata.rendered_layout = self.data['data'][ 'rendered_layout'] inv_field_mapping = {v: k for k, v in self.fields_mapping.iteritems()} for IBehavior, field_name in self.behavior_data_mappers: original_field_name = inv_field_mapping.get(field_name, field_name) if original_field_name not in self.field_data: # data not here... continue behavior = IBehavior(obj, None) if behavior is None: # behavior not valid for obj type continue val = self.field_data[original_field_name] if field_name in self.data_converters: val = self.data_converters[field_name](val) setattr(behavior, field_name, val) # handle lead images for field_name in self.lead_image_field_names: if self.field_data.get(field_name): if field_name == 'plone.app.contenttypes.behaviors.leadimage.ILeadImage': im_obj = self.field_data.get(field_name)['image'] else: im_obj = self.field_data.get(field_name) if hasattr(im_obj, 'read'): im_data = im_obj.read() else: im_data = im_obj if not im_data: continue filename = self.field_data.get('image_filename') if not filename: if hasattr(im_obj, 'filename'): filename = im_obj.filename else: filename = self.field_data['id'] obj.image = im_data if not isinstance(obj.image, NamedBlobImage): is_stringio = isinstance(im_obj, StringIO) if is_stringio: namedblobimage_data = im_data elif isinstance(im_obj, Image): namedblobimage_data = im_data.data else: if pdb_if_exception: pdb.set_trace() logger.info(" lead image is type %s" % type(im_obj)) obj.image = NamedBlobImage(data=namedblobimage_data, contentType='', filename=filename) if hasattr(obj.image, 'contentType') and isinstance( obj.image.contentType, unicode): obj.image.contentType = obj.image.contentType.encode( 'ascii') else: if isinstance(im_obj, Image): data = im_obj.data elif hasattr(im_obj, 'buf'): data = im_obj.buf elif hasattr(im_obj, '_blob'): if hasattr(im_obj._blob, '_p_blob_uncommitted'): f = open(im_obj._blob._p_blob_uncommitted, 'r') data = f.read() f.close() else: raise Exception( "no _p_blob_uncommitted attr in im_obj._blob") else: raise Exception("no _blob attr in im_obj") if data == '' or data is None: data = base64.b64decode( 'R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' ) image_type = what('', h=data) if image_type in [ 'png', 'bmp', 'jpeg', 'xbm', 'tiff', 'gif' ]: obj.image.contentType = 'image/%s' % image_type elif image_type == 'rast': obj.image.contentType = 'image/cmu-raster' elif image_type == 'ppm': obj.image.contentType = 'image/x-portable-pixmap' elif image_type == 'pgm': obj.image.contentType = 'image/x-portable-greymap' elif image_type == 'pbm': obj.image.contentType = 'image/x-portable-bitmap' elif image_type == 'rgb': obj.image.contentType = 'image/x-rgb' else: # look at filename extension contentType, encoding = guess_type(obj.image.filename, strict=False) if contentType: obj.image.contentType = contentType else: logger.info( "Unknown image type {};" " defaulting to jpeg".format(image_type)) pdb.set_trace() obj.image.contentType = 'image/jpeg' # default for caption_field_name in self.lead_image_caption_field_names: if caption_field_name in self.field_data: obj.imageCaption = self.field_data.get( caption_field_name)
def DateTime_to_datetime(val): if hasattr(val, 'asdatetime'): return pydt(val) return val
def recurrence_sequence_ical( start, recrule=None, from_=None, until=None, count=None, duration=None, ): """Calculates a sequence of datetime objects from a recurrence rule following the RFC2445 specification, using python-dateutil recurrence rules. The resolution of the resulting datetime objects is one second, since python-dateutil rrulestr doesn't support microseconds. :param start: datetime or DateTime instance of the date from which the recurrence sequence is calculated. :type start: datetime.datetime :param recrule: Optional string with RFC2445 compatible recurrence definition, dateutil.rrule or dateutil.rruleset instances. :type recrule: string :param from_: Optional datetime or DateTime instance of the date, to limit the result within a timespan. :type from_: datetime.datetime :param until: Optional datetime or DateTime instance of the date, until the recurrence is calculated. If not given, count or MAXDATE limit the recurrence calculation. :type until: datetime.datetime :param count: Optional integer which defines the number of occurences. If not given, until or MAXDATE limits the recurrence calculation. :type count: integer :param duration: Optional timedelta instance, which is used to calculate if a occurence datetime plus duration is within the queried timerange. :type duration: datetime.timedelta :returns: A generator which generates a sequence of datetime instances. :rtype: generator """ # Always use python datetime objects and remove the microseconds start = pydt(start, exact=False) from_ = pydt(from_, exact=False) until = pydt(until, exact=False) tz = start.tzinfo start = tzdel(start) # tznaive | start defines tz _from = tzdel(from_) _until = tzdel(until) if duration: assert (isinstance(duration, datetime.timedelta)) else: duration = datetime.timedelta(0) if recrule: # TODO BUGFIX WRONG TIME DEFINITIONS # THIS HACK ensures, that UNTIL, RDATE and EXDATE definitions with # incorrect time (currently always set to 0:00 by the recurrence # widget) are handled correctly. # # Following fixes are made: # - The UNTIL date should be included in the recurrence set, as defined # by RFC5545 (fix sets it to the end of the day) # - RDATE definitions should have the same time as the start date. # - EXDATE definitions should exclude occurrences on the specific date # only the same time as the start date. # In the long term ,the recurrence widget should be able to set the # time for UNTIL, RDATE and EXDATE. t0 = start.time() # set initial time information. # First, replace all times in the recurring rule with starttime t0str = 'T{0:02d}{1:02d}{2:02d}'.format(t0.hour, t0.minute, t0.second) # Replace any times set to 000000 with start time, not all # rrules are set by a specific broken widget. Don't waste time # subbing if the start time is already 000000. if t0str != 'T000000': recrule = re.sub(r'T000000', t0str, recrule) # Then, replace incorrect until times with the end of the day recrule = re.sub( r'(UNTIL[^T]*[0-9]{8})T(000000)', r'\1T235959', recrule, ) # RFC2445 string # forceset: always return a rruleset # dtstart: optional used when no dtstart is in RFC2445 string # dtstart is given as timezone naive time. timezones are # applied afterwards, since rrulestr doesn't normalize # timezones over DST boundaries rset = rrule.rrulestr( recrule, dtstart=start, forceset=True, ignoretz=True, # compatible=True # RFC2445 compatibility ) else: rset = rrule.rruleset() rset.rdate(start) # RCF2445: always include start date # limit if _from and _until: # between doesn't add a ruleset but returns a list rset = rset.between(_from - duration, _until, inc=True) for cnt, date in enumerate(rset): # Localize tznaive dates from rrulestr sequence date = tz.localize(date) # Limit number of recurrences otherwise calculations take too long if MAXCOUNT and cnt + 1 > MAXCOUNT: break if count and cnt + 1 > count: break if from_ and utc(date) + duration < utc(from_): continue if until and utc(date) > utc(until): break yield date return
def to_ical(self): ical = icalendar.Event() event = self.event # TODO: event.text # must be in utc ical.add('dtstamp', utc(pydt(datetime.now()))) ical.add('created', utc(pydt(event.created))) ical.add('last-modified', utc(pydt(event.last_modified))) ical.add('uid', event.uid) ical.add('url', event.url) ical.add('summary', event.title) if event.description: ical.add('description', event.description) if event.whole_day: ical.add('dtstart', event.start.date()) # RFC5545, 3.6.1 # For cases where a "VEVENT" calendar component # specifies a "DTSTART" property with a DATE value type but no # "DTEND" nor "DURATION" property, the event's duration is taken to # be one day. # # RFC5545 doesn't define clearly, if all-day events should have # a end date on the same date or one day after the start day at # 0:00. Most icalendar libraries use the latter method. # Internally, whole_day events end on the same day one second # before midnight. Using the RFC5545 preferred method for # plone.app.event seems not appropriate, since we would have to fix # the date to end a day before for displaying. # For exporting, we let whole_day events end on the next day at # midnight. # See: # http://stackoverflow.com/questions/1716237/single-day-all-day-appointments-in-ics-files # http://icalevents.com/1778-all-day-events-adding-a-day-or-not/ # http://www.innerjoin.org/iCalendar/all-day-events.html ical.add('dtend', event.end.date() + timedelta(days=1)) elif event.open_end: # RFC5545, 3.6.1 # For cases where a "VEVENT" calendar component # specifies a "DTSTART" property with a DATE-TIME value type but no # "DTEND" property, the event ends on the same calendar date and # time of day specified by the "DTSTART" property. ical.add('dtstart', event.start) else: ical.add('dtstart', event.start) ical.add('dtend', event.end) if event.recurrence: for recdef in event.recurrence.split(): prop, val = recdef.split(':') if prop == 'RRULE': ical.add(prop, icalendar.prop.vRecur.from_ical(val)) elif prop in ('EXDATE', 'RDATE'): factory = icalendar.prop.vDDDLists # localize ex/rdate # TODO: should better already be localized by event object tzid = event.timezone # get list of datetime values from ical string try: dtlist = factory.from_ical(val, timezone=tzid) except ValueError: # TODO: caused by a bug in plone.formwidget.recurrence, # where the recurrencewidget or plone.event fails with # COUNT=1 and a extra RDATE. # TODO: REMOVE this workaround, once this failure is # fixed in recurrence widget. continue ical.add(prop, dtlist) if event.location: ical.add('location', event.location) # TODO: revisit and implement attendee export according to RFC if event.attendees: for attendee in event.attendees: att = icalendar.prop.vCalAddress(attendee) att.params['cn'] = icalendar.prop.vText(attendee) att.params['ROLE'] = icalendar.prop.vText('REQ-PARTICIPANT') ical.add('attendee', att) cn = [] if event.contact_name: cn.append(event.contact_name) if event.contact_phone: cn.append(event.contact_phone) if event.contact_email: cn.append(event.contact_email) if event.event_url: cn.append(event.event_url) if cn: ical.add('contact', u', '.join(cn)) if event.subjects: for subject in event.subjects: ical.add('categories', subject) return ical