def occurrences(self, limit_start=None, limit_end=None): starts = recurrence_sequence_ical( self.context.start, recrule=self.context.recurrence, from_=limit_start, until=limit_end) ends = recurrence_sequence_ical( self.context.end, recrule=self.context.recurrence, from_=limit_start, until=limit_end) events = map(lambda start,end:(start, end), starts, ends) return events
def occurences_start(obj, propertyname, from_=None, until=None, hours=None, minutes=None): oid = get_oid(obj, None) if oid is not None: index = find_catalog('lac')[dates_mapping.get( propertyname + '_start_date', propertyname + '_start_date')] results = index._rev_index.get(oid, ()) results = occurences_until(until, results, True) results = occurences_from(from_, results, True) results = [int2dt(d, hours, minutes) for d in results] else: start = getattr(obj, propertyname + '_start_date', None) if hours is not None and minutes is not None: start = datetime.datetime.combine( start, datetime.time(hours, minutes, 0, tzinfo=pytz.UTC)) recurrence = getattr(obj, propertyname + '_recurrence', '') if not recurrence: results = [start] else: results = list( recurrence_sequence_ical(start, recrule=recurrence, from_=from_, until=until)) return results
def test_start(self): from plone.event.recurrence import recurrence_sequence_ical from datetime import datetime start = datetime(2011, 11, 23) seq = recurrence_sequence_ical(start) results = [res for res in seq] self.assertEqual(len(results), 1)
def occurences_start(obj, propertyname, from_=None, until=None, hours=None, minutes=None): oid = get_oid(obj, None) if oid is not None: index = find_catalog('lac')[ dates_mapping.get( propertyname + '_start_date', propertyname + '_start_date')] results = index._rev_index.get(oid, ()) results = occurences_until(until, results, True) results = occurences_from(from_, results, True) results = [int2dt(d, hours, minutes) for d in results] else: start = getattr(obj, propertyname + '_start_date', None) if hours is not None and minutes is not None: start = datetime.datetime.combine( start, datetime.time(hours, minutes, 0, tzinfo=pytz.UTC)) recurrence = getattr(obj, propertyname + '_recurrence', '') if not recurrence: results = [start] else: results = list(recurrence_sequence_ical(start, recrule=recurrence, from_=from_, until=until)) return results
def test_recrule_until_with_timezone(self): from plone.event.recurrence import recurrence_sequence_ical from datetime import datetime start = datetime(2011, 11, 24) recrule = "RRULE:FREQ=DAILY;UNTIL=20111130T000000Z" seq = list(recurrence_sequence_ical(start, recrule=recrule)) self.assertEqual(len(seq), 7)
def test_recrule_str_more_than_MAXCOUNT(self): from plone.event.recurrence import recurrence_sequence_ical from datetime import datetime start = datetime(2011, 11, 23) recrule = "FREQ=DAILY;INTERVAL=10;COUNT=1001" seq = recurrence_sequence_ical(start, recrule=recrule) results = [res for res in seq] self.assertEqual(len(results), 1000)
def occurrences_over_limit(rule, start, limit): assert rule and start and limit for i, o in enumerate(recurrence_sequence_ical(start=start, recrule=rule)): if i + 1 > limit: return True return False
def test_recrule_until(self): from plone.event.recurrence import recurrence_sequence_ical from datetime import datetime start = datetime(2011, 11, 24) recrule = "FREQ=DAILY;INTERVAL=1;COUNT=5" from_ = datetime(2011, 11, 23) until = datetime(2011, 11, 27) seq = recurrence_sequence_ical(start, recrule=recrule, from_=from_, until=until) results = [res for res in seq] self.assertEqual(len(results), 4)
def test_recrule_from_until(self): from plone.event.recurrence import recurrence_sequence_ical from datetime import datetime start = datetime(2011, 11, 23) recrule = None from_ = datetime(2011, 11, 01) until = datetime(2011, 12, 31) seq = recurrence_sequence_ical(start, recrule=recrule, from_=from_, until=until) results = [res for res in seq] self.assertEqual(len(results), 1)
def has_future_occurrences(item, reference_date): if not item.recurrence: return reference_date <= item.start futures = recurrence_sequence_ical( item.start, recrule=item.recurrence, from_=reference_date ) try: return bool(futures.next()) except StopIteration: return False
def has_future_occurrences(item, reference_date): if not item.recurrence: return reference_date <= item.start futures = recurrence_sequence_ical(item.start, recrule=item.recurrence, from_=reference_date) try: return bool(futures.next()) except StopIteration: return False
def test_recrule_str_rdate(self): """Test, if an RDATE date has the correct time set. See: "BUGFIX WRONG RDATE TIME" in recurrence.py """ from plone.event.recurrence import recurrence_sequence_ical from datetime import datetime start = datetime(2011, 11, 23, 10, 10) recrule = """FREQ=DAILY;INTERVAL=1;COUNT=3 RDATE:20111129T000000""" seq = recurrence_sequence_ical(start, recrule=recrule) results = [res for res in seq] self.assertEqual(len(results), 4) self.assertEqual(results[0].time(), results[-1].time())
def occurrences(self, range_start=None, range_end=None): """Return all occurrences of an event, possibly within a start and end limit. :param range_start: Optional start datetime, from which you want occurrences be returned. :type range_start: Python datetime :param range_end: Optional start datetime, from which you want occurrences be returned. :type range_end: Python datetime :returns: List of occurrences, including the start event. :rtype: IEvent or IOccurrence based objects Please note: Events beginning before range_start but ending afterwards won't be found. TODO: really? TODO: test with event start = 21st feb, event end = start+36h, recurring for 10 days, range_start = 1st mar, range_end = last Mark """ event = IEventAccessor(self.context) # We get event ends by adding a duration to the start. This way, we # prevent that the start and end lists are of different size if an # event starts before range_start but ends afterwards. duration = event.duration starts = recurrence_sequence_ical(event.start, recrule=event.recurrence, from_=range_start, until=range_end, duration=duration) # XXX potentially occurrence won't need to be wrapped anymore # but doing it for backwards compatibility as views/templates # still rely on acquisition-wrapped objects. 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) for start in starts: yield get_obj(start)
def occurrences(self, range_start=None, range_end=None): """Return all occurrences of an event, possibly within a start and end limit. :param range_start: Optional start datetime, from which you want occurrences be returned. :type range_start: Python datetime :param range_end: Optional start datetime, from which you want occurrences be returned. :type range_end: Python datetime :returns: List of occurrences, including the start event. :rtype: IEvent or IOccurrence based objects Please note: Events beginning before range_start but ending afterwards won't be found. TODO: really? TODO: test with event start = 21st feb, event end = start+36h, recurring for 10 days, range_start = 1st mar, range_end = last Mark """ event = IEventAccessor(self.context) # We get event ends by adding a duration to the start. This way, we # prevent that the start and end lists are of different size if an # event starts before range_start but ends afterwards. duration = event.duration starts = recurrence_sequence_ical(event.start, recrule=event.recurrence, from_=range_start, until=range_end, duration=duration) # XXX potentially occurrence won't need to be wrapped anymore # but doing it for backwards compatibility as views/templates # still rely on acquisition-wrapped objects. 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) for start in starts: yield get_obj(start)
def discriminate(self, obj, default): """ See interface IIndexInjection """ if callable(self.discriminator): value = self.discriminator(obj, _marker) else: value = getattr(obj, self.discriminator, _marker) if value is _marker: return default if isinstance(value, Persistent): raise ValueError('Catalog cannot index persistent object %s' % value) if isinstance(value, Broken): raise ValueError('Catalog cannot index broken object %s' % value) if not isinstance(value, dict): raise ValueError('Catalog can only index dict with ' 'attr and date keys, or date and recurdef keys, given %s' % value) # examples: # {'attr': 'dates', # 'date': datetime.datetime.now()} # will get dates_recurrence attribute on the obj to get iCal string # for recurrence definition # or # {'date': datetime.datetime.now(), # 'recurdef': ICALSTRING} # no access to obj attributes at all date = value.get('date') default_recurdef = value.get('recurdef', _marker) if default_recurdef is not _marker: recurdef = default_recurdef else: attr_recurdef = value.get('attr') + '_recurrence' recurdef = getattr(obj, attr_recurdef, None) if callable(recurdef): recurdef = recurdef() if not recurdef: dates = [date] else: dates = recurrence_sequence_ical(date, recrule=recurdef) # dates is a generator return tuple(dates)
def discriminate(self, obj, default): """ See interface IIndexInjection """ if callable(self.discriminator): value = self.discriminator(obj, _marker) else: value = getattr(obj, self.discriminator, _marker) if value is _marker: return default if isinstance(value, Persistent): raise ValueError('Catalog cannot index persistent object %s' % value) if isinstance(value, Broken): raise ValueError('Catalog cannot index broken object %s' % value) if not isinstance(value, dict): raise ValueError( 'Catalog can only index dict with ' 'attr and date keys, or date and recurdef keys, given %s' % value) # examples: # {'attr': 'dates', # 'date': datetime.datetime.now()} # will get dates_recurrence attribute on the obj to get iCal string # for recurrence definition # or # {'date': datetime.datetime.now(), # 'recurdef': ICALSTRING} # no access to obj attributes at all date = value.get('date') default_recurdef = value.get('recurdef', _marker) if default_recurdef is not _marker: recurdef = default_recurdef else: attr_recurdef = value.get('attr') + '_recurrence' recurdef = getattr(obj, attr_recurdef, None) if callable(recurdef): recurdef = recurdef() if not recurdef: dates = [date] else: dates = recurrence_sequence_ical(date, recrule=recurdef) # dates is a generator return tuple(dates)
def test_recrule_from_until_with_duration(self): """Should include events ranging into the queried timerange. """ from plone.event.recurrence import recurrence_sequence_ical from datetime import datetime from datetime import timedelta start = datetime(2011, 11, 23) recrule = "FREQ=DAILY;INTERVAL=1;COUNT=5" from_ = datetime(2011, 11, 26) until = datetime(2011, 11, 27) seq = recurrence_sequence_ical(start, recrule=recrule, from_=from_, until=until, duration=timedelta(days=2)) results = [res for res in seq] self.assertEqual(len(results), 4)
def _get_ical_dates(obj, name, date_type): attr_recurdef = name + '_recurrence' recurdef = getattr(obj, attr_recurdef, None) date = getattr(obj, name + date_type) if callable(recurdef): recurdef = recurdef() if date: if not recurdef: dates = [date] else: dates = recurrence_sequence_ical(date, recrule=recurdef) return dates return []
def occurrences(self, range_start=None, range_end=None): """ Return all occurrences of an event, possibly within a start and end limit. Please note: Events beginning before range_start but ending afterwards won't be found. TODO: test with event start = 21st feb, event end = start+36h, recurring for 10 days, range_start = 1st mar, range_end = last Mark """ event = IEventAccessor(self.context) starts = recurrence_sequence_ical(event.start, recrule=event.recurrence, from_=range_start, until=range_end) if range_start and\ event.start < range_start and\ event.end >= range_start and\ event.start not in starts: # Include event, which started before range but lasts until it. starts = itertools.chain(starts, [event.start]) # We get event ends by adding a duration to the start. This way, we # prevent that the start and end lists are of different size if an # event starts before range_start but ends afterwards. duration = event.duration # XXX potentially occurrence won't need to be wrapped anymore # but doing it for backwards compatibility as views/templates # still rely on acquisition-wrapped objects. def get_obj(start): if 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) events = map(get_obj, starts) return events
def test_recrule_str_exdate(self): """Test, if an EXDATE date are not in the resulting recurrence set. """ from plone.event.recurrence import recurrence_sequence_ical from datetime import datetime import pytz at = pytz.timezone("Europe/Vienna") start = at.localize(datetime(2013, 6, 29, 10, 10)) recrule = 'RRULE:FREQ=DAILY;COUNT=4\r\nEXDATE:20130630T000000,20130701T000000\r\nRDATE:20130706T000000,20130809T000000' seq = recurrence_sequence_ical(start, recrule=recrule) res = [res for res in seq] res_test = [at.localize(datetime(2013, 6, 29, 10, 10)), at.localize(datetime(2013, 7, 2, 10, 10)), at.localize(datetime(2013, 7, 6, 10, 10)), at.localize(datetime(2013, 8, 9, 10, 10))] self.assertEqual(len(res), 4) self.assertEqual(res, res_test)
def occurrences(self, range_start=None, range_end=None): """ Return all occurrences of an event, possibly within a start and end limit. Please note: Events beginning before range_start but ending afterwards won't be found. TODO: test with event start = 21st feb, event end = start+36h, recurring for 10 days, range_start = 1st mar, range_end = last Mark """ event = IEventAccessor(self.context) starts = recurrence_sequence_ical(event.start, recrule=event.recurrence, from_=range_start, until=range_end) if range_start and\ event.start < range_start and\ event.end >= range_start and\ event.start not in starts: # Include event, which started before range but lasts until it. starts = itertools.chain(starts, [event.start]) # We get event ends by adding a duration to the start. This way, we # prevent that the start and end lists are of different size if an # event starts before range_start but ends afterwards. duration = event.duration # XXX potentially occurrence won't need to be wrapped anymore # but doing it for backwards compatibility as views/templates # still rely on acquisition-wrapped objects. def get_obj(start): if 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) events = map(get_obj, starts) return events
def test_recrule_str_until(self): """Test, if UNTIL stops the sequence at the end of the day, even if it's set to 0:00 by the recurrence widget. """ from plone.event.recurrence import recurrence_sequence_ical from datetime import datetime import pytz at = pytz.timezone("Europe/Vienna") start = at.localize(datetime(2013, 6, 29, 10, 10)) recrule = 'RRULE:FREQ=DAILY;UNTIL=20130702T000000' seq = recurrence_sequence_ical(start, recrule=recrule) res = [res for res in seq] res_test = [at.localize(datetime(2013, 6, 29, 10, 10)), at.localize(datetime(2013, 6, 30, 10, 10)), at.localize(datetime(2013, 7, 1, 10, 10)), at.localize(datetime(2013, 7, 2, 10, 10))] self.assertEqual(len(res), 4) self.assertEqual(res, res_test)
def occurrences(item, min_date, max_date): """ Returns the occurrences for item between min and max date. Will return a list with a single item if the given item has no recurrence. """ if not isinstance(item.start, datetime): item_start = dates.to_utc(datetime.utcfromtimestamp(item.start)) else: item_start = item.start if not isinstance(item.end, datetime): item_end = dates.to_utc(datetime.utcfromtimestamp(item.end)) else: item_end = item.end if not item.recurrence: if not overlaps(min_date, max_date, item_start, item_end): return [] else: return [Occurrence(item, item_start, item_end)] tz = pytz.timezone(item.timezone) local_start = tz.normalize(item_start) _occurrences = recurrence_sequence_ical( start=local_start, recrule=item.recurrence, from_=min_date, until=max_date ) result = [] duration = item_end - item_start for start in _occurrences: start = utcoffset_normalize(start, dstmode=DSTADJUST) result.append(Occurrence(item, start, start + duration)) return result
def occurrences(item, min_date, max_date): """ Returns the occurrences for item between min and max date. Will return a list with a single item if the given item has no recurrence. """ if not isinstance(item.start, datetime): item_start = dates.to_utc(datetime.utcfromtimestamp(item.start)) else: item_start = item.start if not isinstance(item.end, datetime): item_end = dates.to_utc(datetime.utcfromtimestamp(item.end)) else: item_end = item.end if not item.recurrence: if not overlaps(min_date, max_date, item_start, item_end): return [] else: return [Occurrence(item, item_start, item_end)] tz = pytz.timezone(item.timezone) local_start = tz.normalize(item_start) _occurrences = recurrence_sequence_ical(start=local_start, recrule=item.recurrence, from_=min_date, until=max_date) result = [] duration = item_end - item_start for start in _occurrences: start = utcoffset_normalize(start, dstmode=DSTADJUST) result.append(Occurrence(item, start, start + duration)) return result
def occurrences(self, range_start=None, range_end=None): """ Return all occurrences of an event, possibly within a start and end limit. Please note: Events beginning before range_start but ending afterwards won't be found. TODO: test with event start = 21st feb, event end = start+36h, recurring for 10 days, range_start = 1st mar, range_end = last Mark """ event = IEventAccessor(self.context) starts = recurrence_sequence_ical(event.start, recrule=event.recurrence, from_=range_start, until=range_end) # We get event ends by adding a duration to the start. This way, we # prevent that the start and end lists are of different size if an # event starts before range_start but ends afterwards. duration = event.duration # XXX potentially occurrence won't need to be wrapped anymore # but doing it for backwards compatibility as views/templates # still rely on acquisition-wrapped objects. def get_obj(start): if event.start == start: # If the occurrence date is the same as the event object, the # occurrence is the event itself. return it as such. return self.context return Occurrence( id=str(start.date()), start=start, end=start + duration).__of__(self.context) events = map(get_obj, starts) return events
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 Exception: 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 if returnStatus > 0: self._increment_counter() return returnStatus
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