class VFreeBusy(Component): propertyCardinality_1 = ( definitions.cICalProperty_DTSTAMP, definitions.cICalProperty_UID, ) propertyCardinality_0_1 = ( definitions.cICalProperty_CONTACT, definitions.cICalProperty_DTSTART, definitions.cICalProperty_DTEND, definitions.cICalProperty_ORGANIZER, definitions.cICalProperty_URL, ) propertyValueChecks = ICALENDAR_VALUE_CHECKS def __init__(self, parent=None): super(VFreeBusy, self).__init__(parent=parent) self.mStart = DateTime() self.mHasStart = False self.mEnd = DateTime() self.mHasEnd = False self.mDuration = False self.mCachedBusyTime = False self.mSpanPeriod = None self.mBusyTime = None def duplicate(self, parent=None): other = super(VFreeBusy, self).duplicate(parent=parent) other.mStart = self.mStart.duplicate() other.mHasStart = self.mHasStart other.mEnd = self.mEnd.duplicate() other.mHasEnd = self.mHasEnd other.mDuration = self.mDuration other.mCachedBusyTime = False other.mBusyTime = None return other def getType(self): return definitions.cICalComponent_VFREEBUSY def getMimeComponentName(self): return itipdefinitions.cICalMIMEComponent_VFREEBUSY def finalise(self): # Do inherited super(VFreeBusy, self).finalise() # Get DTSTART temp = self.loadValueDateTime(definitions.cICalProperty_DTSTART) self.mHasStart = temp is not None if self.mHasStart: self.mStart = temp # Get DTEND temp = self.loadValueDateTime(definitions.cICalProperty_DTEND) if temp is None: # Try DURATION instead temp = self.loadValueDuration(definitions.cICalProperty_DURATION) if temp is not None: self.mEnd = self.mStart + temp self.mDuration = True else: # Force end to start, which will then be fixed to sensible # value later self.mEnd = self.mStart else: self.mHasEnd = True self.mDuration = False self.mEnd = temp def fixStartEnd(self): # End is always greater than start if start exists if self.mHasStart and self.mEnd <= self.mStart: # Use the start self.mEnd = self.mStart.duplicate() self.mDuration = False # Adjust to appropiate non-inclusive end point if self.mStart.isDateOnly(): self.mEnd.offsetDay(1) # For all day events it makes sense to use duration self.mDuration = True else: # Use end of current day self.mEnd.offsetDay(1) self.mEnd.setHHMMSS(0, 0, 0) def getStart(self): return self.mStart def hasStart(self): return self.mHasStart def getEnd(self): return self.mEnd def hasEnd(self): return self.mHasEnd def useDuration(self): return self.mDuration def getSpanPeriod(self): return self.mSpanPeriod def getBusyTime(self): return self.mBusyTime def editTiming(self): # Updated cached values self.mHasStart = False self.mHasEnd = False self.mDuration = False self.mStart.setToday() self.mEnd.setToday() # Remove existing DTSTART & DTEND & DURATION & DUE items self.removeProperties(definitions.cICalProperty_DTSTART) self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) def editTimingStartEnd(self, start, end): # Updated cached values self.mHasStart = self.mHasEnd = True self.mStart = start self.mEnd = end self.mDuration = False self.fixStartEnd() # Remove existing DTSTART & DTEND & DURATION & DUE items self.removeProperties(definitions.cICalProperty_DTSTART) self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) # Now create properties prop = Property(definitions.cICalProperty_DTSTART, start) self.addProperty(prop) # If its an all day event and the end one day after the start, ignore it temp = start.duplicate() temp.offsetDay(1) if not start.isDateOnly() or end != temp: prop = Property(definitions.cICalProperty_DTEND, end) self.addProperty(prop) def editTimingStartDuration(self, start, duration): # Updated cached values self.mHasStart = True self.mHasEnd = False self.mStart = start self.mEnd = start + duration self.mDuration = True # Remove existing DTSTART & DTEND & DURATION & DUE items self.removeProperties(definitions.cICalProperty_DTSTART) self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) self.removeProperties(definitions.cICalProperty_DUE) # Now create properties prop = Property(definitions.cICalProperty_DTSTART, start) self.addProperty(prop) # If its an all day event and the duration is one day, ignore it if (not start.isDateOnly() or (duration.getWeeks() != 0) or (duration.getDays() > 1)): prop = Property(definitions.cICalProperty_DURATION, duration) self.addProperty(prop) # Generating info def expandPeriodComp(self, period, list): # Cache the busy-time details if not done already if not self.mCachedBusyTime: self.cacheBusyTime() # See if period intersects the busy time span range if (self.mBusyTime is not None) and period.isPeriodOverlap( self.mSpanPeriod): list.append(self) def expandPeriodFB(self, period, list): # Cache the busy-time details if not done already if not self.mCachedBusyTime: self.cacheBusyTime() # See if period intersects the busy time span range if (self.mBusyTime is not None) and period.isPeriodOverlap( self.mSpanPeriod): for fb in self.mBusyTime: list.append(FreeBusy(fb)) def cacheBusyTime(self): # Clear out any existing cache self.mBusyTime = [] # Get all FREEBUSY items and add those that are BUSY min_start = DateTime() max_end = DateTime() props = self.getProperties() result = props.get(definitions.cICalProperty_FREEBUSY, ()) for iter in result: # Check the properties FBTYPE parameter type = 0 is_busy = False if iter.hasParameter(definitions.cICalParameter_FBTYPE): fbyype = iter.getParameterValue( definitions.cICalParameter_FBTYPE) if fbyype.upper() == definitions.cICalParameter_FBTYPE_BUSY: is_busy = True type = FreeBusy.BUSY elif fbyype.upper( ) == definitions.cICalParameter_FBTYPE_BUSYUNAVAILABLE: is_busy = True type = FreeBusy.BUSYUNAVAILABLE elif fbyype.upper( ) == definitions.cICalParameter_FBTYPE_BUSYTENTATIVE: is_busy = True type = FreeBusy.BUSYTENTATIVE else: is_busy = False type = FreeBusy.FREE else: # Default is busy when no parameter is_busy = True type = FreeBusy.BUSY # Add this period if is_busy: multi = iter.getMultiValue() if (multi is not None) and (multi.getType() == Value.VALUETYPE_PERIOD): for o in multi.getValues(): # Double-check type period = None if isinstance(o, PeriodValue): period = o # Double-check type if period is not None: self.mBusyTime.append( FreeBusy(type, period.getValue())) if len(self.mBusyTime) == 1: min_start = period.getValue().getStart() max_end = period.getValue().getEnd() else: if min_start > period.getValue().getStart(): min_start = period.getValue().getStart() if max_end < period.getValue().getEnd(): max_end = period.getValue().getEnd() # If nothing present, empty the list if len(self.mBusyTime) == 0: self.mBusyTime = None else: # Sort the list by period self.mBusyTime.sort(cmp=lambda x, y: x.getPeriod().getStart(). compareDateTime(y.getPeriod().getStart())) # Determine range start = DateTime() end = DateTime() if self.mHasStart: start = self.mStart else: start = min_start if self.mHasEnd: end = self.mEnd else: end = max_end self.mSpanPeriod = Period(start, end) self.mCachedBusyTime = True def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, definitions.cICalProperty_DTSTART, definitions.cICalProperty_DURATION, definitions.cICalProperty_DTEND, )
def testCachePreserveOnAdjustment(self): # UTC first dt = DateTime(2012, 6, 7, 12, 0, 0, Timezone(tzid="utc")) dt.getPosixTime() # check existing cache is complete self.assertTrue(dt.mPosixTimeCached) self.assertNotEqual(dt.mPosixTime, 0) self.assertEqual(dt.mTZOffset, None) # duplicate preserves cache details dt2 = dt.duplicate() self.assertTrue(dt2.mPosixTimeCached) self.assertEqual(dt2.mPosixTime, dt.mPosixTime) self.assertEqual(dt2.mTZOffset, dt.mTZOffset) # adjust preserves cache details dt2.adjustToUTC() self.assertTrue(dt2.mPosixTimeCached) self.assertEqual(dt2.mPosixTime, dt.mPosixTime) self.assertEqual(dt2.mTZOffset, dt.mTZOffset) # Now timezone tzdata = """BEGIN:VCALENDAR CALSCALE:GREGORIAN PRODID:-//calendarserver.org//Zonal//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:America/Pittsburgh BEGIN:STANDARD DTSTART:18831118T120358 RDATE:18831118T120358 TZNAME:EST TZOFFSETFROM:-045602 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19180331T020000 RRULE:FREQ=YEARLY;UNTIL=19190330T070000Z;BYDAY=-1SU;BYMONTH=3 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19181027T020000 RRULE:FREQ=YEARLY;UNTIL=19191026T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:STANDARD DTSTART:19200101T000000 RDATE:19200101T000000 RDATE:19420101T000000 RDATE:19460101T000000 RDATE:19670101T000000 TZNAME:EST TZOFFSETFROM:-0500 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19200328T020000 RDATE:19200328T020000 RDATE:19740106T020000 RDATE:19750223T020000 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19201031T020000 RDATE:19201031T020000 RDATE:19450930T020000 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19210424T020000 RRULE:FREQ=YEARLY;UNTIL=19410427T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19210925T020000 RRULE:FREQ=YEARLY;UNTIL=19410928T060000Z;BYDAY=-1SU;BYMONTH=9 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19420209T020000 RDATE:19420209T020000 TZNAME:EWT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19450814T190000 RDATE:19450814T190000 TZNAME:EPT TZOFFSETFROM:-0400 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19460428T020000 RRULE:FREQ=YEARLY;UNTIL=19660424T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19460929T020000 RRULE:FREQ=YEARLY;UNTIL=19540926T060000Z;BYDAY=-1SU;BYMONTH=9 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:STANDARD DTSTART:19551030T020000 RRULE:FREQ=YEARLY;UNTIL=19661030T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19670430T020000 RRULE:FREQ=YEARLY;UNTIL=19730429T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19671029T020000 RRULE:FREQ=YEARLY;UNTIL=20061029T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19760425T020000 RRULE:FREQ=YEARLY;UNTIL=19860427T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19870405T020000 RRULE:FREQ=YEARLY;UNTIL=20060402T070000Z;BYDAY=1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:20070311T020000 RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20071104T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE END:VCALENDAR """.replace("\n", "\r\n") Calendar.parseText(tzdata) dt = DateTime(2012, 6, 7, 12, 0, 0, Timezone(tzid="America/Pittsburgh")) dt.getPosixTime() # check existing cache is complete self.assertTrue(dt.mPosixTimeCached) self.assertNotEqual(dt.mPosixTime, 0) self.assertEqual(dt.mTZOffset, -14400) # duplicate preserves cache details dt2 = dt.duplicate() self.assertTrue(dt2.mPosixTimeCached) self.assertEqual(dt2.mPosixTime, dt.mPosixTime) self.assertEqual(dt2.mTZOffset, dt.mTZOffset) # adjust preserves cache details dt2.adjustToUTC() self.assertTrue(dt2.mPosixTimeCached) self.assertEqual(dt2.mPosixTime, dt.mPosixTime) self.assertEqual(dt2.mTZOffset, 0)
class VToDo(ComponentRecur): OVERDUE = 0 DUE_NOW = 1 DUE_LATER = 2 DONE = 3 CANCELLED = 4 @staticmethod def sort_for_display(e1, e2): s1 = e1.getMaster() s2 = e2.getMaster() # Check status first (convert None -> Needs action for tests) status1 = s1.self.mStatus status2 = s2.self.mStatus if status1 == definitions.eStatus_VToDo_None: status1 = definitions.eStatus_VToDo_NeedsAction if status2 == definitions.eStatus_VToDo_None: status2 = definitions.eStatus_VToDo_NeedsAction if status1 != status2: # More important ones at the top return status1 < status2 # At this point the status of each is the same # If status is cancelled sort by start time if s1.self.mStatus == definitions.eStatus_VToDo_Cancelled: # Older ones at the bottom return s1.mStart > s2.mStart # If status is completed sort by completion time if s1.self.mStatus == definitions.eStatus_VToDo_Completed: # Older ones at the bottom return s1.self.mCompleted > s2.self.mCompleted # Check due date exists if s1.mHasEnd != s2.mHasEnd: now = DateTime() now.setToday() # Ones with due dates after today below ones without due dates if s1.hasEnd(): return s1.mEnd <= now elif s2.hasEnd(): return now < s2.mEnd # Check due dates if present if s1.mHasEnd: if s1.mEnd != s2.mEnd: # Soonest dues dates above later ones return s1.mEnd < s2.mEnd # Check priority next if s1.self.mPriority != s2.self.mPriority: # Higher priority above lower ones return s1.self.mPriority < s2.self.mPriority # Just use start time - older ones at the top return s1.mStart < s2.mStart propertyCardinality_1 = ( definitions.cICalProperty_DTSTAMP, definitions.cICalProperty_UID, ) propertyCardinality_0_1 = ( definitions.cICalProperty_CLASS, definitions.cICalProperty_COMPLETED, definitions.cICalProperty_CREATED, definitions.cICalProperty_DESCRIPTION, definitions.cICalProperty_DTSTART, definitions.cICalProperty_GEO, definitions.cICalProperty_LAST_MODIFIED, definitions.cICalProperty_LOCATION, definitions.cICalProperty_ORGANIZER, definitions.cICalProperty_PERCENT_COMPLETE, definitions.cICalProperty_PRIORITY, definitions.cICalProperty_RECURRENCE_ID, definitions.cICalProperty_SEQUENCE, # definitions.cICalProperty_STATUS, # Special fix done for multiple STATUS definitions.cICalProperty_SUMMARY, definitions.cICalProperty_URL, definitions.cICalProperty_RRULE, definitions.cICalProperty_DUE, definitions.cICalProperty_DURATION, ) propertyValueChecks = ICALENDAR_VALUE_CHECKS def __init__(self, parent=None): super(VToDo, self).__init__(parent=parent) self.mPriority = 0 self.mStatus = definitions.eStatus_VToDo_None self.mPercentComplete = 0 self.mCompleted = DateTime() self.mHasCompleted = False def duplicate(self, parent=None): other = super(VToDo, self).duplicate(parent=parent) other.mPriority = self.mPriority other.mStatus = self.mStatus other.mPercentComplete = self.mPercentComplete other.mCompleted = self.mCompleted.duplicate() other.mHasCompleted = self.mHasCompleted return other def getType(self): return definitions.cICalComponent_VTODO def getMimeComponentName(self): return itipdefinitions.cICalMIMEComponent_VTODO def addComponent(self, comp): # We can embed the alarm components only if comp.getType() == definitions.cICalComponent_VALARM: super(VToDo, self).addComponent(comp) else: raise ValueError("Only 'VALARM' components allowed in 'VTODO'") def getStatus(self): return self.mStatus def setStatus(self, status): self.mStatus = status def getStatusText(self): sout = StringIO() if self.mStatus in (definitions.eStatus_VToDo_NeedsAction, definitions.eStatus_VToDo_InProcess): if self.hasEnd(): # Check due date today = DateTime() today.setToday() if self.getEnd() > today: sout.append("Due: ") whendue = self.getEnd() - today if (whendue.getDays() > 0) and (whendue.getDays() <= 7): sout.write(whendue.getDays()) sout.write(" days") else: sout.write(self.getEnd().getLocaleDate( DateTime.NUMERICDATE)) elif self.getEnd() == today: sout.write("Due today") else: sout.write("Overdue: ") overdue = today - self.getEnd() if overdue.getWeeks() != 0: sout.write(overdue.getWeeks()) sout.write(" weeks") else: sout.write(overdue.getDays() + 1) sout.write(" days") else: sout.write("Not Completed") elif self.mStatus == definitions.eStatus_VToDo_Completed: if self.hasCompleted(): sout.write("Completed: ") sout.write(self.getCompleted().getLocaleDate( DateTime.NUMERICDATE)) else: sout.write("Completed") elif definitions.eStatus_VToDo_Cancelled: sout.write("Cancelled") return sout.toString() def getCompletionState(self): if self.mStatus in (definitions.eStatus_VToDo_NeedsAction, definitions.eStatus_VToDo_InProcess): if self.hasEnd(): # Check due date today = DateTime() today.setToday() if self.getEnd() > today: return VToDo.DUE_LATER elif self.getEnd() == today: return VToDo.DUE_NOW else: return VToDo.OVERDUE else: return VToDo.DUE_NOW elif self.mStatus == definitions.eStatus_VToDo_Completed: return VToDo.DONE elif self.mStatus == definitions.eStatus_VToDo_Cancelled: return VToDo.CANCELLED def getPriority(self): return self.mPriority def setPriority(self, priority): self.mPriority = priority def getCompleted(self): return self.mCompleted def hasCompleted(self): return self.mHasCompleted def finalise(self): # Do inherited super(VToDo, self).finalise() # Get DUE temp = self.loadValueDateTime(definitions.cICalProperty_DUE) if temp is None: # Try DURATION instead temp = self.loadValueDuration(definitions.cICalProperty_DURATION) if temp is not None: self.mEnd = self.mStart + temp self.mHasEnd = True else: self.mHasEnd = False else: self.mHasEnd = True self.mEnd = temp # Get PRIORITY self.mPriority = self.loadValueInteger( definitions.cICalProperty_PRIORITY) # Get STATUS temp = self.loadValueString(definitions.cICalProperty_STATUS) if temp is not None: if temp == definitions.cICalProperty_STATUS_NEEDS_ACTION: self.mStatus = definitions.eStatus_VToDo_NeedsAction elif temp == definitions.cICalProperty_STATUS_COMPLETED: self.mStatus = definitions.eStatus_VToDo_Completed elif temp == definitions.cICalProperty_STATUS_IN_PROCESS: self.mStatus = definitions.eStatus_VToDo_InProcess elif temp == definitions.cICalProperty_STATUS_CANCELLED: self.mStatus = definitions.eStatus_VToDo_Cancelled # Get PERCENT-COMPLETE self.mPercentComplete = self.loadValueInteger( definitions.cICalProperty_PERCENT_COMPLETE) # Get COMPLETED temp = self.loadValueDateTime(definitions.cICalProperty_COMPLETED) self.mHasCompleted = temp is not None if self.mHasCompleted: self.mCompleted = temp def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems, else raise. If loggedProblems is not None it must be a C{list} and problem descriptions are appended to that. """ fixed, unfixed = super(VToDo, self).validate(doFix) # Extra constraint: only one of DUE or DURATION if self.hasProperty( definitions.cICalProperty_DUE) and self.hasProperty( definitions.cICalProperty_DURATION): # Fix by removing the DURATION logProblem = "[%s] Properties must not both be present: %s, %s" % ( self.getType(), definitions.cICalProperty_DUE, definitions.cICalProperty_DURATION, ) if doFix: self.removeProperties(definitions.cICalProperty_DURATION) fixed.append(logProblem) else: unfixed.append(logProblem) # Extra constraint: DTSTART must be present if DURATION is present if self.hasProperty( definitions.cICalProperty_DURATION) and not self.hasProperty( definitions.cICalProperty_DTSTART): # Cannot fix this one logProblem = "[%s] Property must be present: %s with %s" % ( self.getType(), definitions.cICalProperty_DTSTART, definitions.cICalProperty_DURATION, ) unfixed.append(logProblem) return fixed, unfixed # Editing def editStatus(self, status): # Only if it is different if self.mStatus != status: # Updated cached values self.mStatus = status # Remove existing STATUS & COMPLETED items self.removeProperties(definitions.cICalProperty_STATUS) self.removeProperties(definitions.cICalProperty_COMPLETED) self.mHasCompleted = False # Now create properties value = None if status == definitions.eStatus_VToDo_NeedsAction: value = definitions.cICalProperty_STATUS_NEEDS_ACTION if status == definitions.eStatus_VToDo_Completed: value = definitions.cICalProperty_STATUS_COMPLETED # Add the completed item self.mCompleted.setNowUTC() self.mHasCompleted = True prop = Property(definitions.cICalProperty_STATUS_COMPLETED, self.mCompleted) self.addProperty(prop) elif status == definitions.eStatus_VToDo_InProcess: value = definitions.cICalProperty_STATUS_IN_PROCESS elif status == definitions.eStatus_VToDo_Cancelled: value = definitions.cICalProperty_STATUS_CANCELLED prop = Property(definitions.cICalProperty_STATUS, value) self.addProperty(prop) def editCompleted(self, completed): # Remove existing COMPLETED item self.removeProperties(definitions.cICalProperty_COMPLETED) self.mHasCompleted = False # Always UTC self.mCompleted = completed.duplicate() self.mCompleted.adjustToUTC() self.mHasCompleted = True prop = Property(definitions.cICalProperty_STATUS_COMPLETED, self.mCompleted) self.addProperty(prop) def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, definitions.cICalProperty_RECURRENCE_ID, definitions.cICalProperty_DTSTART, definitions.cICalProperty_DURATION, definitions.cICalProperty_DUE, definitions.cICalProperty_COMPLETED, )
def expand(self, rules, minYear, maxYear): """ Expand this zone into a set of transitions. @param rules: parsed Rules for the tzdb @type rules: C{dict} @param minYear: starting year @type minYear: C{int} @param maxYear: ending year @type maxYear: C{int} @return: C{list} of C{tuple} for ( transition date-time, offset to, offset from, associated rule, ) """ # Start at 1/1/1800 with the offset from the initial zone rule start = DateTime(year=1800, month=1, day=1, hours=0, minutes=0, seconds=0) start_offset = self.rules[0].getUTCOffset() start_stdoffset = self.rules[0].getUTCOffset() startdt = start.duplicate() # Now add each zone rules dates transitions = [] lastUntilDateUTC = start.duplicate() last_offset = start_offset last_stdoffset = start_stdoffset first = True for zonerule in self.rules: last_offset, last_stdoffset = zonerule.expand(rules, transitions, lastUntilDateUTC, last_offset, last_stdoffset, maxYear) lastUntilDate = zonerule.getUntilDate() lastUntilDateUTC = lastUntilDate.getUTC(last_offset, last_stdoffset) # We typically don't care about the initial one if first and len(self.rules) > 1: transitions = [] first = False # Sort the results by date transitions.sort(cmp=lambda x, y: x[0].compareDateTime(y[0])) # Now scan transitions looking for real changes and note those results = [] last_transition = (startdt, start_offset, start_offset) for transition in transitions: dtutc, to_offset, zonerule, rule = transition dt = dtutc.duplicate() dt.offsetSeconds(last_transition[1]) if dtutc.getYear() >= minYear: if dt > last_transition[0]: results.append((dt, last_transition[1], to_offset, zonerule, rule)) elif dt <= last_transition[0]: if len(results): results[-1] = ((results[-1][0], results[-1][1], to_offset, zonerule, None)) else: results.append((last_transition[0], last_transition[1], last_transition[2], zonerule, None)) last_transition = (dt, to_offset, last_transition[2], rule) return results
class ComponentRecur(Component): propertyCardinality_STATUS_Fix = ( definitions.cICalProperty_STATUS, ) @staticmethod def mapKey(uid, rid=None): if uid: result = "u:" + uid if rid is not None: result += rid return result else: return None @staticmethod def sort_by_dtstart_allday(e1, e2): if e1.self.mStart.isDateOnly() and e2.self.mStart.isDateOnly(): return e1.self.mStart < e2.self.mStart elif e1.self.mStart.isDateOnly(): return True elif (e2.self.mStart.isDateOnly()): return False elif e1.self.mStart == e2.self.mStart: if e1.self.mEnd == e2.self.mEnd: # Put ones created earlier in earlier columns in day view return e1.self.mStamp < e2.self.mStamp else: # Put ones that end later in earlier columns in day view return e1.self.mEnd > e2.self.mEnd else: return e1.self.mStart < e2.self.mStart @staticmethod def sort_by_dtstart(e1, e2): if e1.self.mStart == e2.self.mStart: if ( e1.self.mStart.isDateOnly() and e2.self.mStart.isDateOnly() or not e1.self.mStart.isDateOnly() and not e2.self.mStart.isDateOnly() ): return False else: return e1.self.mStart.isDateOnly() else: return e1.self.mStart < e2.self.mStart def __init__(self, parent=None): super(ComponentRecur, self).__init__(parent=parent) self.mMaster = self self.mMapKey = None self.mSummary = None self.mStamp = DateTime() self.mHasStamp = False self.mStart = DateTime() self.mHasStart = False self.mEnd = DateTime() self.mHasEnd = False self.mDuration = False self.mHasRecurrenceID = False self.mAdjustFuture = False self.mAdjustPrior = False self.mRecurrenceID = None self.mRecurrences = None # This is a special check we do only for STATUS due to a calendarserver bug self.cardinalityChecks += ( self.check_cardinality_STATUS_Fix, ) def duplicate(self, parent=None): other = super(ComponentRecur, self).duplicate(parent=parent) # Special determination of master other.mMaster = self.mMaster if self.recurring() else self other.mMapKey = self.mMapKey other.mSummary = self.mSummary if (self.mStamp is not None): other.mStamp = self.mStamp.duplicate() other.mHasStamp = self.mHasStamp other.mStart = self.mStart.duplicate() other.mHasStart = self.mHasStart other.mEnd = self.mEnd.duplicate() other.mHasEnd = self.mHasEnd other.mDuration = self.mDuration other.mHasRecurrenceID = self.mHasRecurrenceID other.mAdjustFuture = self.mAdjustFuture other.mAdjustPrior = self.mAdjustPrior if self.mRecurrenceID is not None: other.mRecurrenceID = self.mRecurrenceID.duplicate() other._resetRecurrenceSet() return other def canGenerateInstance(self): return not self.mHasRecurrenceID def recurring(self): return (self.mMaster is not None) and (self.mMaster is not self) def setMaster(self, master): self.mMaster = master self.initFromMaster() def getMaster(self): return self.mMaster def getMapKey(self): if self.mMapKey is None: self.mMapKey = str(uuid.uuid4()) return self.mMapKey def getMasterKey(self): return ComponentRecur.mapKey(self.mUID) def initDTSTAMP(self): # Save new one super(ComponentRecur, self).initDTSTAMP() # Get the new one temp = self.loadValueDateTime(definitions.cICalProperty_DTSTAMP) self.mHasStamp = temp is not None if self.mHasStamp: self.mStamp = temp def getStamp(self): return self.mStamp def hasStamp(self): return self.mHasStamp def getStart(self): return self.mStart def hasStart(self): return self.mHasStart def getEnd(self): return self.mEnd def hasEnd(self): return self.mHasEnd def useDuration(self): return self.mDuration def isRecurrenceInstance(self): return self.mHasRecurrenceID def isAdjustFuture(self): return self.mAdjustFuture def isAdjustPrior(self): return self.mAdjustPrior def getRecurrenceID(self): return self.mRecurrenceID def isRecurring(self): return (self.mRecurrences is not None) and self.mRecurrences.hasRecurrence() def getRecurrenceSet(self): return self.mRecurrences def setUID(self, uid): super(ComponentRecur, self).setUID(uid) # Update the map key if self.mHasRecurrenceID: self.mMapKey = self.mapKey(self.mUID, self.mRecurrenceID.getText()) else: self.mMapKey = self.mapKey(self.mUID) def getSummary(self): return self.mSummary def setSummary(self, summary): self.mSummary = summary def getDescription(self): # Get DESCRIPTION txt = self.loadValueString(definitions.cICalProperty_DESCRIPTION) if txt is not None: return txt else: return "" def getLocation(self): # Get LOCATION txt = self.loadValueString(definitions.cICalProperty_LOCATION) if txt is not None: return txt else: return "" def finalise(self): super(ComponentRecur, self).finalise() # Get DTSTAMP temp = self.loadValueDateTime(definitions.cICalProperty_DTSTAMP) self.mHasStamp = temp is not None if self.mHasStamp: self.mStamp = temp # Get DTSTART temp = self.loadValueDateTime(definitions.cICalProperty_DTSTART) self.mHasStart = temp is not None if self.mHasStart: self.mStart = temp # Get DTEND temp = self.loadValueDateTime(definitions.cICalProperty_DTEND) if temp is None: # Try DURATION instead temp = self.loadValueDuration(definitions.cICalProperty_DURATION) if temp is not None: self.mHasEnd = False self.mEnd = self.mStart + temp self.mDuration = True else: # If no end or duration then use the start (bumped by one day for # an all day event) self.mHasEnd = False self.mEnd = self.mStart.duplicate() if self.mEnd.isDateOnly(): self.mEnd.offsetDay(1) self.mDuration = False else: self.mHasEnd = True self.mEnd = temp self.mDuration = False # Get SUMMARY temp = self.loadValueString(definitions.cICalProperty_SUMMARY) if temp is not None: self.mSummary = temp # Get RECURRENCE-ID self.mHasRecurrenceID = (self.countProperty(definitions.cICalProperty_RECURRENCE_ID) != 0) if self.mHasRecurrenceID: self.mRecurrenceID = self.loadValueDateTime(definitions.cICalProperty_RECURRENCE_ID) # Update the map key if self.mHasRecurrenceID: self.mMapKey = self.mapKey(self.mUID, self.mRecurrenceID.getText()) # Also get the RANGE parameter attrs = self.findFirstProperty(definitions.cICalProperty_RECURRENCE_ID).getParameters() if definitions.cICalParameter_RANGE in attrs: self.mAdjustFuture = (attrs[definitions.cICalParameter_RANGE][0].getFirstValue() == definitions.cICalParameter_RANGE_THISANDFUTURE) self.mAdjustPrior = (attrs[definitions.cICalParameter_RANGE][0].getFirstValue() == definitions.cICalParameter_RANGE_THISANDPRIOR) else: self.mAdjustFuture = False self.mAdjustPrior = False else: self.mMapKey = self.mapKey(self.mUID) self._resetRecurrenceSet() def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems. Return a tuple containing two lists: the first describes problems that were fixed, the second problems that were not fixed. Caller can then decide what to do with unfixed issues. """ # Do normal checks fixed, unfixed = super(ComponentRecur, self).validate(doFix) # Check that any UNTIL value matches that for DTSTART if self.mHasStart and self.mRecurrences: dtutc = self.mStart.duplicateAsUTC() for rrule in self.mRecurrences.getRules(): if rrule.getUseUntil(): if rrule.getUntil().isDateOnly() ^ self.mStart.isDateOnly(): logProblem = "[%s] Value types must match: %s, %s" % ( self.getType(), definitions.cICalProperty_DTSTART, definitions.cICalValue_RECUR_UNTIL, ) if doFix: rrule.getUntil().setDateOnly(self.mStart.isDateOnly()) if not self.mStart.isDateOnly(): rrule.getUntil().setHHMMSS(dtutc.getHours(), dtutc.getMinutes(), dtutc.getSeconds()) rrule.getUntil().setTimezone(Timezone(utc=True)) self.mRecurrences.changed() fixed.append(logProblem) else: unfixed.append(logProblem) return fixed, unfixed def check_cardinality_STATUS_Fix(self, fixed, unfixed, doFix): """ Special for bug with STATUS where STATUS:CANCELLED is added alongside another STATUS. In this case we want STATUS:CANCELLED to win. """ for propname in self.propertyCardinality_STATUS_Fix: if self.countProperty(propname) > 1: logProblem = "[%s] Too many properties: %s" % (self.getType(), propname) if doFix: # Check that one of them is STATUS:CANCELLED for prop in self.getProperties(propname): if prop.getTextValue().getValue().upper() == definitions.cICalProperty_STATUS_CANCELLED: self.removeProperties(propname) self.addProperty(Property(propname, definitions.cICalProperty_STATUS_CANCELLED)) fixed.append(logProblem) break else: unfixed.append(logProblem) else: unfixed.append(logProblem) def _resetRecurrenceSet(self): # May need to create items self.mRecurrences = None if ( (self.countProperty(definitions.cICalProperty_RRULE) != 0) or (self.countProperty(definitions.cICalProperty_RDATE) != 0) or (self.countProperty(definitions.cICalProperty_EXRULE) != 0) or (self.countProperty(definitions.cICalProperty_EXDATE) != 0) ): self.mRecurrences = RecurrenceSet() # Get RRULEs self.loadValueRRULE(definitions.cICalProperty_RRULE, self.mRecurrences, True) # Get RDATEs self.loadValueRDATE(definitions.cICalProperty_RDATE, self.mRecurrences, True) # Get EXRULEs self.loadValueRRULE(definitions.cICalProperty_EXRULE, self.mRecurrences, False) # Get EXDATEs self.loadValueRDATE(definitions.cICalProperty_EXDATE, self.mRecurrences, False) def FixStartEnd(self): # End is always greater than start if start exists if self.mHasStart and self.mEnd <= self.mStart: # Use the start self.mEnd = self.mStart.duplicate() self.mDuration = False # Adjust to approriate non-inclusive end point if self.mStart.isDateOnly(): self.mEnd.offsetDay(1) # For all day events it makes sense to use duration self.mDuration = True else: # Use end of current day self.mEnd.offsetDay(1) self.mEnd.setHHMMSS(0, 0, 0) def expandPeriod(self, period, results): # Check for recurrence and True master if ((self.mRecurrences is not None) and self.mRecurrences.hasRecurrence() and not self.isRecurrenceInstance()): # Expand recurrences within the range items = [] self.mRecurrences.expand(self.mStart, period, items) # Look for overridden recurrence items cal = self.mParentComponent if cal is not None: # Remove recurrence instances from the list of items recurs = [] cal.getRecurrenceInstancesIds(definitions.cICalComponent_VEVENT, self.getUID(), recurs) recurs.sort() if len(recurs) != 0: temp = [] temp = set_difference(items, recurs) items = temp # Now get actual instances instances = [] cal.getRecurrenceInstancesItems(definitions.cICalComponent_VEVENT, self.getUID(), instances) # Get list of each ones with RANGE prior = [] future = [] for iter in instances: if iter.isAdjustPrior(): prior.append(iter) if iter.isAdjustFuture(): future.append(iter) # Check for special behaviour if len(prior) + len(future) == 0: # Add each expanded item for iter in items: results.append(self.createExpanded(self, iter)) else: # Sort each list first prior.sort(self.sort_by_dtstart) future.sort(self.sort_by_dtstart) # Add each expanded item for iter1 in items: # Now step through each using the slave item # instead of the master as appropriate slave = None # Find most appropriate THISANDPRIOR item for i in range(len(prior) - 1, 0, -1): riter2 = prior[i] if riter2.getStart() > iter1: slave = riter2 break # Find most appropriate THISANDFUTURE item for i in range(len(future) - 1, 0, -1): riter2 = future.elementAt(i) if riter2.getStart() < iter1: slave = riter2 break if slave is None: slave = self results.append(self.createExpanded(slave, iter1)) else: # Add each expanded item for iter in items: results.append(self.createExpanded(self, iter)) elif self.withinPeriod(period): if self.isRecurrenceInstance(): rid = self.mRecurrenceID else: rid = None results.append(ComponentExpanded(self, rid)) def withinPeriod(self, period): # Check for recurrence if ((self.mRecurrences is not None) and self.mRecurrences.hasRecurrence()): items = [] self.mRecurrences.expand(self.mStart, period, items) return len(items) != 0 else: # Does event span the period (assume self.mEnd > self.mStart) # Check start (inclusive) and end (exclusive) if self.mEnd <= period.getStart() or self.mStart >= period.getEnd(): return False else: return True def changedRecurrence(self): # Clear cached values if self.mRecurrences is not None: self.mRecurrences.changed() # Editing def editSummary(self, summary): # Updated cached value self.mSummary = summary # Remove existing items self.editProperty(definitions.cICalProperty_SUMMARY, summary) def editDetails(self, description, location): # Edit existing items self.editProperty(definitions.cICalProperty_DESCRIPTION, description) self.editProperty(definitions.cICalProperty_LOCATION, location) def editTiming(self): # Updated cached values self.mHasStart = False self.mHasEnd = False self.mDuration = False self.mStart.setToday() self.mEnd.setToday() # Remove existing DTSTART & DTEND & DURATION & DUE items self.removeProperties(definitions.cICalProperty_DTSTART) self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) self.removeProperties(definitions.cICalProperty_DUE) def editTimingDue(self, due): # Updated cached values self.mHasStart = False self.mHasEnd = True self.mDuration = False self.mStart = due self.mEnd = due # Remove existing DUE & DTSTART & DTEND & DURATION items self.removeProperties(definitions.cICalProperty_DUE) self.removeProperties(definitions.cICalProperty_DTSTART) self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) # Now create properties prop = Property(definitions.cICalProperty_DUE, due) self.addProperty(prop) def editTimingStartEnd(self, start, end): # Updated cached values self.mHasStart = self.mHasEnd = True self.mStart = start self.mEnd = end self.mDuration = False self.FixStartEnd() # Remove existing DTSTART & DTEND & DURATION & DUE items self.removeProperties(definitions.cICalProperty_DTSTART) self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) self.removeProperties(definitions.cICalProperty_DUE) # Now create properties prop = Property(definitions.cICalProperty_DTSTART, start) self.addProperty(prop) # If its an all day event and the end one day after the start, ignore it temp = start.duplicate() temp.offsetDay(1) if not start.isDateOnly() or end != temp: prop = Property(definitions.cICalProperty_DTEND, end) self.addProperty(prop) def editTimingStartDuration(self, start, duration): # Updated cached values self.mHasStart = True self.mHasEnd = False self.mStart = start self.mEnd = start + duration self.mDuration = True # Remove existing DTSTART & DTEND & DURATION & DUE items self.removeProperties(definitions.cICalProperty_DTSTART) self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) self.removeProperties(definitions.cICalProperty_DUE) # Now create properties prop = Property(definitions.cICalProperty_DTSTART, start) self.addProperty(prop) # If its an all day event and the duration is one day, ignore it if (not start.isDateOnly() or (duration.getWeeks() != 0) or (duration.getDays() > 1)): prop = Property(definitions.cICalProperty_DURATION, duration) self.addProperty(prop) def editRecurrenceSet(self, recurs): # Must have items if self.mRecurrences is None: self.mRecurrences = RecurrenceSet() # Updated cached values self.mRecurrences = recurs # Remove existing RRULE, EXRULE, RDATE & EXDATE self.removeProperties(definitions.cICalProperty_RRULE) self.removeProperties(definitions.cICalProperty_EXRULE) self.removeProperties(definitions.cICalProperty_RDATE) self.removeProperties(definitions.cICalProperty_EXDATE) # Now create properties for iter in self.mRecurrences.getRules(): prop = Property(definitions.cICalProperty_RRULE, iter) self.addProperty(prop) for iter in self.getExrules(): prop = Property(definitions.cICalProperty_EXRULE, iter) self.addProperty(prop) for iter in self.mRecurrences.getDates(): prop = Property(definitions.cICalProperty_RDATE, iter) self.addProperty(prop) for iter in self.mRecurrences.getExdates(): prop = Property(definitions.cICalProperty_EXDATE, iter) self.addProperty(prop) def excludeRecurrence(self, start): # Must have items if self.mRecurrences is None: return # Add to recurrence set and clear cache self.mRecurrences.subtract(start) # Add property prop = Property(definitions.cICalProperty_EXDATE, start) self.addProperty(prop) def excludeFutureRecurrence(self, start): # Must have items if self.mRecurrences is None: return # Adjust RRULES to end before start self.mRecurrences.excludeFutureRecurrence(start) # Remove existing RRULE & RDATE self.removeProperties(definitions.cICalProperty_RRULE) self.removeProperties(definitions.cICalProperty_RDATE) # Now create properties for iter in self.mRecurrences.getRules(): prop = Property(definitions.cICalProperty_RRULE, iter) self.addProperty(prop) for iter in self.mRecurrences.getDates(): prop = Property(definitions.cICalProperty_RDATE, iter) self.addProperty(prop) def initFromMaster(self): # Only if not master if self.recurring(): # Redo this to get cached values from master self.finalise() # If this component does not have its own start property, use the # recurrence id # i.e. the start time of this instance has not changed - something # else has if not self.hasProperty(definitions.cICalProperty_DTSTART): self.mStart = self.mRecurrenceID # If this component does not have its own end/duration property, # the determine # the end from the master duration if ( not self.hasProperty(definitions.cICalProperty_DTEND) and not self.hasProperty(definitions.cICalProperty_DURATION) ): # End is based on original events settings self.mEnd = self.mStart + (self.mMaster.getEnd() - self.mMaster.getStart()) # If this instance has a duration, but no start of its own, then we # need to readjust the end # to account for the start being changed to the recurrence id elif (self.hasProperty(definitions.cICalProperty_DURATION) and not self.hasProperty(definitions.cICalProperty_DTSTART)): temp = self.loadValueDuration(definitions.cICalProperty_DURATION) self.mEnd = self.mStart + temp def createExpanded(self, master, recurid): return ComponentExpanded(master, recurid)
class VToDo(ComponentRecur): OVERDUE = 0 DUE_NOW = 1 DUE_LATER = 2 DONE = 3 CANCELLED = 4 @staticmethod def sort_for_display(e1, e2): s1 = e1.getMaster() s2 = e2.getMaster() # Check status first (convert None -> Needs action for tests) status1 = s1.self.mStatus status2 = s2.self.mStatus if status1 == definitions.eStatus_VToDo_None: status1 = definitions.eStatus_VToDo_NeedsAction if status2 == definitions.eStatus_VToDo_None: status2 = definitions.eStatus_VToDo_NeedsAction if status1 != status2: # More important ones at the top return status1 < status2 # At this point the status of each is the same # If status is cancelled sort by start time if s1.self.mStatus == definitions.eStatus_VToDo_Cancelled: # Older ones at the bottom return s1.mStart > s2.mStart # If status is completed sort by completion time if s1.self.mStatus == definitions.eStatus_VToDo_Completed: # Older ones at the bottom return s1.self.mCompleted > s2.self.mCompleted # Check due date exists if s1.mHasEnd != s2.mHasEnd: now = DateTime() now.setToday() # Ones with due dates after today below ones without due dates if s1.hasEnd(): return s1.mEnd <= now elif s2.hasEnd(): return now < s2.mEnd # Check due dates if present if s1.mHasEnd: if s1.mEnd != s2.mEnd: # Soonest dues dates above later ones return s1.mEnd < s2.mEnd # Check priority next if s1.self.mPriority != s2.self.mPriority: # Higher priority above lower ones return s1.self.mPriority < s2.self.mPriority # Just use start time - older ones at the top return s1.mStart < s2.mStart propertyCardinality_1 = ( definitions.cICalProperty_DTSTAMP, definitions.cICalProperty_UID, ) propertyCardinality_0_1 = ( definitions.cICalProperty_CLASS, definitions.cICalProperty_COMPLETED, definitions.cICalProperty_CREATED, definitions.cICalProperty_DESCRIPTION, definitions.cICalProperty_DTSTART, definitions.cICalProperty_GEO, definitions.cICalProperty_LAST_MODIFIED, definitions.cICalProperty_LOCATION, definitions.cICalProperty_ORGANIZER, definitions.cICalProperty_PERCENT_COMPLETE, definitions.cICalProperty_PRIORITY, definitions.cICalProperty_RECURRENCE_ID, definitions.cICalProperty_SEQUENCE, # definitions.cICalProperty_STATUS, # Special fix done for multiple STATUS definitions.cICalProperty_SUMMARY, definitions.cICalProperty_URL, definitions.cICalProperty_RRULE, definitions.cICalProperty_DUE, definitions.cICalProperty_DURATION, ) propertyValueChecks = ICALENDAR_VALUE_CHECKS def __init__(self, parent=None): super(VToDo, self).__init__(parent=parent) self.mPriority = 0 self.mStatus = definitions.eStatus_VToDo_None self.mPercentComplete = 0 self.mCompleted = DateTime() self.mHasCompleted = False def duplicate(self, parent=None): other = super(VToDo, self).duplicate(parent=parent) other.mPriority = self.mPriority other.mStatus = self.mStatus other.mPercentComplete = self.mPercentComplete other.mCompleted = self.mCompleted.duplicate() other.mHasCompleted = self.mHasCompleted return other def getType(self): return definitions.cICalComponent_VTODO def getMimeComponentName(self): return itipdefinitions.cICalMIMEComponent_VTODO def addComponent(self, comp): # We can embed the alarm components only if comp.getType() == definitions.cICalComponent_VALARM: super(VToDo, self).addComponent(comp) else: raise ValueError def getStatus(self): return self.mStatus def setStatus(self, status): self.mStatus = status def getStatusText(self): sout = StringIO() if self.mStatus in (definitions.eStatus_VToDo_NeedsAction, definitions.eStatus_VToDo_InProcess): if self.hasEnd(): # Check due date today = DateTime() today.setToday() if self.getEnd() > today: sout.append("Due: ") whendue = self.getEnd() - today if (whendue.getDays() > 0) and (whendue.getDays() <= 7): sout.write(whendue.getDays()) sout.write(" days") else: sout.write(self.getEnd().getLocaleDate(DateTime.NUMERICDATE)) elif self.getEnd() == today: sout.write("Due today") else: sout.write("Overdue: ") overdue = today - self.getEnd() if overdue.getWeeks() != 0: sout.write(overdue.getWeeks()) sout.write(" weeks") else: sout.write(overdue.getDays() + 1) sout.write(" days") else: sout.write("Not Completed") elif self.mStatus == definitions.eStatus_VToDo_Completed: if self.hasCompleted(): sout.write("Completed: ") sout.write(self.getCompleted().getLocaleDate(DateTime.NUMERICDATE)) else: sout.write("Completed") elif definitions.eStatus_VToDo_Cancelled: sout.write("Cancelled") return sout.toString() def getCompletionState(self): if self.mStatus in (definitions.eStatus_VToDo_NeedsAction, definitions.eStatus_VToDo_InProcess): if self.hasEnd(): # Check due date today = DateTime() today.setToday() if self.getEnd() > today: return VToDo.DUE_LATER elif self.getEnd() == today: return VToDo.DUE_NOW else: return VToDo.OVERDUE else: return VToDo.DUE_NOW elif self.mStatus == definitions.eStatus_VToDo_Completed: return VToDo.DONE elif self.mStatus == definitions.eStatus_VToDo_Cancelled: return VToDo.CANCELLED def getPriority(self): return self.mPriority def setPriority(self, priority): self.mPriority = priority def getCompleted(self): return self.mCompleted def hasCompleted(self): return self.mHasCompleted def finalise(self): # Do inherited super(VToDo, self).finalise() # Get DUE temp = self.loadValueDateTime(definitions.cICalProperty_DUE) if temp is None: # Try DURATION instead temp = self.loadValueDuration(definitions.cICalProperty_DURATION) if temp is not None: self.mEnd = self.mStart + temp self.mHasEnd = True else: self.mHasEnd = False else: self.mHasEnd = True self.mEnd = temp # Get PRIORITY self.mPriority = self.loadValueInteger(definitions.cICalProperty_PRIORITY) # Get STATUS temp = self.loadValueString(definitions.cICalProperty_STATUS) if temp is not None: if temp == definitions.cICalProperty_STATUS_NEEDS_ACTION: self.mStatus = definitions.eStatus_VToDo_NeedsAction elif temp == definitions.cICalProperty_STATUS_COMPLETED: self.mStatus = definitions.eStatus_VToDo_Completed elif temp == definitions.cICalProperty_STATUS_IN_PROCESS: self.mStatus = definitions.eStatus_VToDo_InProcess elif temp == definitions.cICalProperty_STATUS_CANCELLED: self.mStatus = definitions.eStatus_VToDo_Cancelled # Get PERCENT-COMPLETE self.mPercentComplete = self.loadValueInteger(definitions.cICalProperty_PERCENT_COMPLETE) # Get COMPLETED temp = self.loadValueDateTime(definitions.cICalProperty_COMPLETED) self.mHasCompleted = temp is not None if self.mHasCompleted: self.mCompleted = temp def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems, else raise. If loggedProblems is not None it must be a C{list} and problem descriptions are appended to that. """ fixed, unfixed = super(VToDo, self).validate(doFix) # Extra constraint: only one of DUE or DURATION if self.hasProperty(definitions.cICalProperty_DUE) and self.hasProperty(definitions.cICalProperty_DURATION): # Fix by removing the DURATION logProblem = "[%s] Properties must not both be present: %s, %s" % ( self.getType(), definitions.cICalProperty_DUE, definitions.cICalProperty_DURATION, ) if doFix: self.removeProperties(definitions.cICalProperty_DURATION) fixed.append(logProblem) else: unfixed.append(logProblem) # Extra constraint: DTSTART must be present if DURATION is present if self.hasProperty(definitions.cICalProperty_DURATION) and not self.hasProperty(definitions.cICalProperty_DTSTART): # Cannot fix this one logProblem = "[%s] Property must be present: %s with %s" % ( self.getType(), definitions.cICalProperty_DTSTART, definitions.cICalProperty_DURATION, ) unfixed.append(logProblem) return fixed, unfixed # Editing def editStatus(self, status): # Only if it is different if self.mStatus != status: # Updated cached values self.mStatus = status # Remove existing STATUS & COMPLETED items self.removeProperties(definitions.cICalProperty_STATUS) self.removeProperties(definitions.cICalProperty_COMPLETED) self.mHasCompleted = False # Now create properties value = None if status == definitions.eStatus_VToDo_NeedsAction: value = definitions.cICalProperty_STATUS_NEEDS_ACTION if status == definitions.eStatus_VToDo_Completed: value = definitions.cICalProperty_STATUS_COMPLETED # Add the completed item self.mCompleted.setNowUTC() self.mHasCompleted = True prop = Property(definitions.cICalProperty_STATUS_COMPLETED, self.mCompleted) self.addProperty(prop) elif status == definitions.eStatus_VToDo_InProcess: value = definitions.cICalProperty_STATUS_IN_PROCESS elif status == definitions.eStatus_VToDo_Cancelled: value = definitions.cICalProperty_STATUS_CANCELLED prop = Property(definitions.cICalProperty_STATUS, value) self.addProperty(prop) def editCompleted(self, completed): # Remove existing COMPLETED item self.removeProperties(definitions.cICalProperty_COMPLETED) self.mHasCompleted = False # Always UTC self.mCompleted = completed.duplicate() self.mCompleted.adjustToUTC() self.mHasCompleted = True prop = Property(definitions.cICalProperty_STATUS_COMPLETED, self.mCompleted) self.addProperty(prop) def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, definitions.cICalProperty_RECURRENCE_ID, definitions.cICalProperty_DTSTART, definitions.cICalProperty_DURATION, definitions.cICalProperty_DUE, definitions.cICalProperty_COMPLETED, )
def testCachePreserveOnAdjustment(self): # UTC first dt = DateTime(2012, 6, 7, 12, 0, 0, Timezone(tzid="utc")) dt.getPosixTime() # check existing cache is complete self.assertTrue(dt.mPosixTimeCached) self.assertNotEqual(dt.mPosixTime, 0) self.assertEqual(dt.mTZOffset, None) # duplicate preserves cache details dt2 = dt.duplicate() self.assertTrue(dt2.mPosixTimeCached) self.assertEqual(dt2.mPosixTime, dt.mPosixTime) self.assertEqual(dt2.mTZOffset, dt.mTZOffset) # adjust preserves cache details dt2.adjustToUTC() self.assertTrue(dt2.mPosixTimeCached) self.assertEqual(dt2.mPosixTime, dt.mPosixTime) self.assertEqual(dt2.mTZOffset, dt.mTZOffset) # Now timezone tzdata = """BEGIN:VCALENDAR CALSCALE:GREGORIAN PRODID:-//calendarserver.org//Zonal//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:America/Pittsburgh BEGIN:STANDARD DTSTART:18831118T120358 RDATE:18831118T120358 TZNAME:EST TZOFFSETFROM:-045602 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19180331T020000 RRULE:FREQ=YEARLY;UNTIL=19190330T070000Z;BYDAY=-1SU;BYMONTH=3 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19181027T020000 RRULE:FREQ=YEARLY;UNTIL=19191026T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:STANDARD DTSTART:19200101T000000 RDATE:19200101T000000 RDATE:19420101T000000 RDATE:19460101T000000 RDATE:19670101T000000 TZNAME:EST TZOFFSETFROM:-0500 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19200328T020000 RDATE:19200328T020000 RDATE:19740106T020000 RDATE:19750223T020000 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19201031T020000 RDATE:19201031T020000 RDATE:19450930T020000 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19210424T020000 RRULE:FREQ=YEARLY;UNTIL=19410427T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19210925T020000 RRULE:FREQ=YEARLY;UNTIL=19410928T060000Z;BYDAY=-1SU;BYMONTH=9 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19420209T020000 RDATE:19420209T020000 TZNAME:EWT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19450814T190000 RDATE:19450814T190000 TZNAME:EPT TZOFFSETFROM:-0400 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19460428T020000 RRULE:FREQ=YEARLY;UNTIL=19660424T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19460929T020000 RRULE:FREQ=YEARLY;UNTIL=19540926T060000Z;BYDAY=-1SU;BYMONTH=9 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:STANDARD DTSTART:19551030T020000 RRULE:FREQ=YEARLY;UNTIL=19661030T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19670430T020000 RRULE:FREQ=YEARLY;UNTIL=19730429T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:19671029T020000 RRULE:FREQ=YEARLY;UNTIL=20061029T060000Z;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD BEGIN:DAYLIGHT DTSTART:19760425T020000 RRULE:FREQ=YEARLY;UNTIL=19860427T070000Z;BYDAY=-1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:19870405T020000 RRULE:FREQ=YEARLY;UNTIL=20060402T070000Z;BYDAY=1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:DAYLIGHT DTSTART:20070311T020000 RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20071104T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE END:VCALENDAR """.replace("\n", "\r\n") Calendar.parseText(tzdata) dt = DateTime(2012, 6, 7, 12, 0, 0, Timezone(tzid="America/Pittsburgh")) dt.getPosixTime() # check existing cache is complete self.assertTrue(dt.mPosixTimeCached) self.assertNotEqual(dt.mPosixTime, 0) self.assertEqual(dt.mTZOffset, -14400) # duplicate preserves cache details dt2 = dt.duplicate() self.assertTrue(dt2.mPosixTimeCached) self.assertEqual(dt2.mPosixTime, dt.mPosixTime) self.assertEqual(dt2.mTZOffset, dt.mTZOffset) # adjust preserves cache details dt2.adjustToUTC() self.assertTrue(dt2.mPosixTimeCached) self.assertEqual(dt2.mPosixTime, dt.mPosixTime) self.assertEqual(dt2.mTZOffset, 0)
class VAlarm(Component): sActionMap = { definitions.cICalProperty_ACTION_AUDIO: definitions.eAction_VAlarm_Audio, definitions.cICalProperty_ACTION_DISPLAY: definitions.eAction_VAlarm_Display, definitions.cICalProperty_ACTION_EMAIL: definitions.eAction_VAlarm_Email, definitions.cICalProperty_ACTION_PROCEDURE: definitions.eAction_VAlarm_Procedure, definitions.cICalProperty_ACTION_URI: definitions.eAction_VAlarm_URI, definitions.cICalProperty_ACTION_NONE: definitions.eAction_VAlarm_None, } sActionValueMap = { definitions.eAction_VAlarm_Audio: definitions.cICalProperty_ACTION_AUDIO, definitions.eAction_VAlarm_Display: definitions.cICalProperty_ACTION_DISPLAY, definitions.eAction_VAlarm_Email: definitions.cICalProperty_ACTION_EMAIL, definitions.eAction_VAlarm_Procedure: definitions.cICalProperty_ACTION_PROCEDURE, definitions.eAction_VAlarm_URI: definitions.cICalProperty_ACTION_URI, definitions.eAction_VAlarm_None: definitions.cICalProperty_ACTION_NONE, } # Classes for each action encapsulating action-specific data class VAlarmAction(object): propertyCardinality_1 = () propertyCardinality_1_Fix_Empty = () propertyCardinality_0_1 = () propertyCardinality_1_More = () def __init__(self, type): self.mType = type def duplicate(self): return VAlarm.VAlarmAction(self.mType) def load(self, valarm): pass def add(self, valarm): pass def remove(self, valarm): pass def getType(self): return self.mType class VAlarmAudio(VAlarmAction): propertyCardinality_1 = ( definitions.cICalProperty_ACTION, definitions.cICalProperty_TRIGGER, ) propertyCardinality_0_1 = ( definitions.cICalProperty_DURATION, definitions.cICalProperty_REPEAT, definitions.cICalProperty_ATTACH, definitions.cICalProperty_ACKNOWLEDGED, ) def __init__(self, speak=None): super(VAlarm.VAlarmAudio, self).__init__(type=definitions.eAction_VAlarm_Audio) self.mSpeakText = speak def duplicate(self): return VAlarm.VAlarmAudio(self.mSpeakText) def load(self, valarm): # Get properties self.mSpeakText = valarm.loadValueString(definitions.cICalProperty_ACTION_X_SPEAKTEXT) def add(self, valarm): # Delete existing then add self.remove(valarm) prop = Property(definitions.cICalProperty_ACTION_X_SPEAKTEXT, self.mSpeakText) valarm.addProperty(prop) def remove(self, valarm): valarm.removeProperties(definitions.cICalProperty_ACTION_X_SPEAKTEXT) def isSpeakText(self): return len(self.mSpeakText) != 0 def getSpeakText(self): return self.mSpeakText class VAlarmDisplay(VAlarmAction): propertyCardinality_1 = ( definitions.cICalProperty_ACTION, definitions.cICalProperty_TRIGGER, ) propertyCardinality_1_Fix_Empty = ( definitions.cICalProperty_DESCRIPTION, ) propertyCardinality_0_1 = ( definitions.cICalProperty_DURATION, definitions.cICalProperty_REPEAT, definitions.cICalProperty_ACKNOWLEDGED, ) def __init__(self, description=None): super(VAlarm.VAlarmDisplay, self).__init__(type=definitions.eAction_VAlarm_Display) self.mDescription = description def duplicate(self): return VAlarm.VAlarmDisplay(self.mDescription) def load(self, valarm): # Get properties self.mDescription = valarm.loadValueString(definitions.cICalProperty_DESCRIPTION) def add(self, valarm): # Delete existing then add self.remove(valarm) prop = Property(definitions.cICalProperty_DESCRIPTION, self.mDescription) valarm.addProperty(prop) def remove(self, valarm): valarm.removeProperties(definitions.cICalProperty_DESCRIPTION) def getDescription(self): return self.mDescription class VAlarmEmail(VAlarmAction): propertyCardinality_1 = ( definitions.cICalProperty_ACTION, definitions.cICalProperty_TRIGGER, ) propertyCardinality_1_Fix_Empty = ( definitions.cICalProperty_DESCRIPTION, definitions.cICalProperty_SUMMARY, ) propertyCardinality_0_1 = ( definitions.cICalProperty_DURATION, definitions.cICalProperty_REPEAT, definitions.cICalProperty_ACKNOWLEDGED, ) propertyCardinality_1_More = ( definitions.cICalProperty_ATTENDEE, ) def __init__(self, description=None, summary=None, attendees=None): super(VAlarm.VAlarmEmail, self).__init__(type=definitions.eAction_VAlarm_Email) self.mDescription = description self.mSummary = summary self.mAttendees = attendees def duplicate(self): return VAlarm.VAlarmEmail(self.mDescription, self.mSummary, self.mAttendees) def load(self, valarm): # Get properties self.mDescription = valarm.loadValueString(definitions.cICalProperty_DESCRIPTION) self.mSummary = valarm.loadValueString(definitions.cICalProperty_SUMMARY) self.mAttendees = [] if valarm.hasProperty(definitions.cICalProperty_ATTENDEE): # Get each attendee range = valarm.getProperties().get(definitions.cICalProperty_ATTENDEE, ()) for iter in range: # Get the attendee value attendee = iter.getCalAddressValue() if attendee is not None: self.mAttendees.append(attendee.getValue()) def add(self, valarm): # Delete existing then add self.remove(valarm) prop = Property(definitions.cICalProperty_DESCRIPTION, self.mDescription) valarm.addProperty(prop) prop = Property(definitions.cICalProperty_SUMMARY, self.mSummary) valarm.addProperty(prop) for iter in self.mAttendees: prop = Property(definitions.cICalProperty_ATTENDEE, iter, Value.VALUETYPE_CALADDRESS) valarm.addProperty(prop) def remove(self, valarm): valarm.removeProperties(definitions.cICalProperty_DESCRIPTION) valarm.removeProperties(definitions.cICalProperty_SUMMARY) valarm.removeProperties(definitions.cICalProperty_ATTENDEE) def getDescription(self): return self.mDescription def getSummary(self): return self.mSummary def getAttendees(self): return self.mAttendees class VAlarmUnknown(VAlarmAction): propertyCardinality_1 = ( definitions.cICalProperty_ACTION, definitions.cICalProperty_TRIGGER, ) propertyCardinality_0_1 = ( definitions.cICalProperty_DURATION, definitions.cICalProperty_REPEAT, definitions.cICalProperty_ACKNOWLEDGED, ) def __init__(self): super(VAlarm.VAlarmUnknown, self).__init__(type=definitions.eAction_VAlarm_Unknown) def duplicate(self): return VAlarm.VAlarmUnknown() class VAlarmURI(VAlarmAction): propertyCardinality_1 = ( definitions.cICalProperty_ACTION, definitions.cICalProperty_TRIGGER, definitions.cICalProperty_URL, ) propertyCardinality_0_1 = ( definitions.cICalProperty_DURATION, definitions.cICalProperty_REPEAT, definitions.cICalProperty_ACKNOWLEDGED, ) def __init__(self, uri=None): super(VAlarm.VAlarmURI, self).__init__(type=definitions.eAction_VAlarm_URI) self.mURI = uri def duplicate(self): return VAlarm.VAlarmURI(self.mURI) def load(self, valarm): # Get properties self.mURI = valarm.loadValueString(definitions.cICalProperty_URL) def add(self, valarm): # Delete existing then add self.remove(valarm) prop = Property(definitions.cICalProperty_URL, self.mURI) valarm.addProperty(prop) def remove(self, valarm): valarm.removeProperties(definitions.cICalProperty_URL) def getURI(self): return self.mURI class VAlarmNone(VAlarmAction): propertyCardinality_1 = ( definitions.cICalProperty_ACTION, ) def __init__(self): super(VAlarm.VAlarmNone, self).__init__(type=definitions.eAction_VAlarm_None) def duplicate(self): return VAlarm.VAlarmNone() def getMimeComponentName(self): # Cannot be sent as a separate MIME object return None sActionToAlarmMap = { definitions.eAction_VAlarm_Audio: VAlarmAudio, definitions.eAction_VAlarm_Display: VAlarmDisplay, definitions.eAction_VAlarm_Email: VAlarmEmail, definitions.eAction_VAlarm_URI: VAlarmURI, definitions.eAction_VAlarm_None: VAlarmNone, } propertyValueChecks = ICALENDAR_VALUE_CHECKS def __init__(self, parent=None): super(VAlarm, self).__init__(parent=parent) self.mAction = definitions.eAction_VAlarm_Display self.mTriggerAbsolute = False self.mTriggerOnStart = True self.mTriggerOn = DateTime() # Set duration default to 1 hour self.mTriggerBy = Duration() self.mTriggerBy.setDuration(60 * 60) # Does not repeat by default self.mRepeats = 0 self.mRepeatInterval = Duration() self.mRepeatInterval.setDuration(5 * 60) # Five minutes # Status self.mStatusInit = False self.mAlarmStatus = definitions.eAlarm_Status_Pending self.mLastTrigger = DateTime() self.mNextTrigger = DateTime() self.mDoneCount = 0 # Create action data self.mActionData = VAlarm.VAlarmDisplay("") def duplicate(self, parent=None): other = super(VAlarm, self).duplicate(parent=parent) other.mAction = self.mAction other.mTriggerAbsolute = self.mTriggerAbsolute other.mTriggerOn = self.mTriggerOn.duplicate() other.mTriggerBy = self.mTriggerBy.duplicate() other.mTriggerOnStart = self.mTriggerOnStart other.mRepeats = self.mRepeats other.mRepeatInterval = self.mRepeatInterval.duplicate() other.mAlarmStatus = self.mAlarmStatus if self.mLastTrigger is not None: other.mLastTrigger = self.mLastTrigger.duplicate() if self.mNextTrigger is not None: other.mNextTrigger = self.mNextTrigger.duplicate() other.mDoneCount = self.mDoneCount other.mActionData = self.mActionData.duplicate() return other def getType(self): return definitions.cICalComponent_VALARM def getAction(self): return self.mAction def getActionData(self): return self.mActionData def isTriggerAbsolute(self): return self.mTriggerAbsolute def getTriggerOn(self): return self.mTriggerOn def getTriggerDuration(self): return self.mTriggerBy def isTriggerOnStart(self): return self.mTriggerOnStart def getRepeats(self): return self.mRepeats def getInterval(self): return self.mRepeatInterval def added(self): # Added to calendar so add to calendar notifier # calstore::CCalendarNotifier::sCalendarNotifier.AddAlarm(this) # Do inherited super(VAlarm, self).added() def removed(self): # Removed from calendar so add to calendar notifier # calstore::CCalendarNotifier::sCalendarNotifier.RemoveAlarm(this) # Do inherited super(VAlarm, self).removed() def changed(self): # Always force recalc of trigger status self.mStatusInit = False # Changed in calendar so change in calendar notifier # calstore::CCalendarNotifier::sCalendarNotifier.ChangedAlarm(this) # Do not do inherited as this is always a sub-component and we do not # do top-level component changes # super.changed() def finalise(self): # Do inherited super(VAlarm, self).finalise() # Get the ACTION temp = self.loadValueString(definitions.cICalProperty_ACTION) if temp is not None: self.mAction = VAlarm.sActionMap.get(temp, definitions.eAction_VAlarm_Unknown) self.loadAction() # Get the trigger if self.hasProperty(definitions.cICalProperty_TRIGGER): # Determine the type of the value temp1 = self.loadValueDateTime(definitions.cICalProperty_TRIGGER) temp2 = self.loadValueDuration(definitions.cICalProperty_TRIGGER) if temp1 is not None: self.mTriggerAbsolute = True self.mTriggerOn = temp1 elif temp2 is not None: self.mTriggerAbsolute = False self.mTriggerBy = temp2 # Get the property prop = self.findFirstProperty(definitions.cICalProperty_TRIGGER) # Look for RELATED parameter if prop.hasParameter(definitions.cICalParameter_RELATED): temp = prop.getParameterValue(definitions.cICalParameter_RELATED) if temp == definitions.cICalParameter_RELATED_START: self.mTriggerOnStart = True elif temp == definitions.cICalParameter_RELATED_END: self.mTriggerOnStart = False else: self.mTriggerOnStart = True # Get repeat & interval temp = self.loadValueInteger(definitions.cICalProperty_REPEAT) if temp is not None: self.mRepeats = temp temp = self.loadValueDuration(definitions.cICalProperty_DURATION) if temp is not None: self.mRepeatInterval = temp # Set a map key for sorting self.mMapKey = "%s:%s" % (self.mAction, self.mTriggerOn if self.mTriggerAbsolute else self.mTriggerBy,) # Alarm status - private to Mulberry status = self.loadValueString(definitions.cICalProperty_ALARM_X_ALARMSTATUS) if status is not None: if status == definitions.cICalProperty_ALARM_X_ALARMSTATUS_PENDING: self.mAlarmStatus = definitions.eAlarm_Status_Pending elif status == definitions.cICalProperty_ALARM_X_ALARMSTATUS_COMPLETED: self.mAlarmStatus = definitions.eAlarm_Status_Completed elif status == definitions.cICalProperty_ALARM_X_ALARMSTATUS_DISABLED: self.mAlarmStatus = definitions.eAlarm_Status_Disabled else: self.mAlarmStatus = definitions.eAlarm_Status_Pending # Last trigger time - private to Mulberry temp = self.loadValueDateTime(definitions.cICalProperty_ALARM_X_LASTTRIGGER) if temp is not None: self.mLastTrigger = temp def validate(self, doFix=False): """ Validate the data in this component and optionally fix any problems, else raise. If loggedProblems is not None it must be a C{list} and problem descriptions are appended to that. """ # Validate using action specific constraints self.propertyCardinality_1 = self.mActionData.propertyCardinality_1 self.propertyCardinality_1_Fix_Empty = self.mActionData.propertyCardinality_1_Fix_Empty self.propertyCardinality_0_1 = self.mActionData.propertyCardinality_0_1 self.propertyCardinality_1_More = self.mActionData.propertyCardinality_1_More fixed, unfixed = super(VAlarm, self).validate(doFix) # Extra constraint: both DURATION and REPEAT must be present togethe if self.hasProperty(definitions.cICalProperty_DURATION) ^ self.hasProperty(definitions.cICalProperty_REPEAT): # Cannot fix this logProblem = "[%s] Properties must be present together: %s, %s" % ( self.getType(), definitions.cICalProperty_DURATION, definitions.cICalProperty_REPEAT, ) unfixed.append(logProblem) return fixed, unfixed def editStatus(self, status): # Remove existing self.removeProperties(definitions.cICalProperty_ALARM_X_ALARMSTATUS) # Updated cached values self.mAlarmStatus = status # Add new status_txt = "" if self.mAlarmStatus == definitions.eAlarm_Status_Pending: status_txt = definitions.cICalProperty_ALARM_X_ALARMSTATUS_PENDING elif self.mAlarmStatus == definitions.eAlarm_Status_Completed: status_txt = definitions.cICalProperty_ALARM_X_ALARMSTATUS_COMPLETED elif self.mAlarmStatus == definitions.eAlarm_Status_Disabled: status_txt = definitions.cICalProperty_ALARM_X_ALARMSTATUS_DISABLED self.addProperty(Property(definitions.cICalProperty_ALARM_X_ALARMSTATUS, status_txt)) def editAction(self, action, data): # Remove existing self.removeProperties(definitions.cICalProperty_ACTION) self.mActionData.remove(self) self.mActionData = None # Updated cached values self.mAction = action self.mActionData = data # Add new properties to alarm action_txt = VAlarm.sActionValueMap.get(self.mAction, definitions.cICalProperty_ACTION_PROCEDURE) prop = Property(definitions.cICalProperty_ACTION, action_txt) self.addProperty(prop) self.mActionData.add(self) def editTriggerOn(self, dt): # Remove existing self.removeProperties(definitions.cICalProperty_TRIGGER) # Updated cached values self.mTriggerAbsolute = True self.mTriggerOn = dt # Add new prop = Property(definitions.cICalProperty_TRIGGER, dt) self.addProperty(prop) def editTriggerBy(self, duration, trigger_start): # Remove existing self.removeProperties(definitions.cICalProperty_TRIGGER) # Updated cached values self.mTriggerAbsolute = False self.mTriggerBy = duration self.mTriggerOnStart = trigger_start # Add new (with parameter) prop = Property(definitions.cICalProperty_TRIGGER, duration) attr = Parameter( definitions.cICalParameter_RELATED, ( definitions.cICalParameter_RELATED_START, definitions.cICalParameter_RELATED_END )[not trigger_start] ) prop.addParameter(attr) self.addProperty(prop) def editRepeats(self, repeat, interval): # Remove existing self.removeProperties(definitions.cICalProperty_REPEAT) self.removeProperties(definitions.cICalProperty_DURATION) # Updated cached values self.mRepeats = repeat self.mRepeatInterval = interval # Add new if self.mRepeats > 0: self.addProperty(Property(definitions.cICalProperty_REPEAT, repeat)) self.addProperty(Property(definitions.cICalProperty_DURATION, interval)) def getAlarmStatus(self): return self.mAlarmStatus def getNextTrigger(self, dt): if not self.mStatusInit: self.initNextTrigger() dt.copy(self.mNextTrigger) def alarmTriggered(self, dt): # Remove existing self.removeProperties(definitions.cICalProperty_ALARM_X_LASTTRIGGER) self.removeProperties(definitions.cICalProperty_ALARM_X_ALARMSTATUS) # Updated cached values self.mLastTrigger.copy(dt) if self.mDoneCount < self.mRepeats: self.mNextTrigger = self.mLastTrigger + self.mRepeatInterval dt.copy(self.mNextTrigger) self.mDoneCount += 1 self.mAlarmStatus = definitions.eAlarm_Status_Pending else: self.mAlarmStatus = definitions.eAlarm_Status_Completed # Add new self.addProperty(Property(definitions.cICalProperty_ALARM_X_LASTTRIGGER, dt)) status = "" if self.mAlarmStatus == definitions.eAlarm_Status_Pending: status = definitions.cICalProperty_ALARM_X_ALARMSTATUS_PENDING elif self.mAlarmStatus == definitions.eAlarm_Status_Completed: status = definitions.cICalProperty_ALARM_X_ALARMSTATUS_COMPLETED elif self.mAlarmStatus == definitions.eAlarm_Status_Disabled: status = definitions.cICalProperty_ALARM_X_ALARMSTATUS_DISABLED self.addProperty(Property(definitions.cICalProperty_ALARM_X_ALARMSTATUS, status)) # Now update dt to the next alarm time return self.mAlarmStatus == definitions.eAlarm_Status_Pending def loadAction(self): # Delete current one self.mActionData = None self.mActionData = VAlarm.sActionToAlarmMap.get(self.mAction, VAlarm.VAlarmUnknown)() self.mActionData.load(self) def initNextTrigger(self): # Do not bother if its completed if self.mAlarmStatus == definitions.eAlarm_Status_Completed: return self.mStatusInit = True # Look for trigger immediately preceeding or equal to utc now nowutc = DateTime.getNowUTC() # Init done counter self.mDoneCount = 0 # Determine the first trigger trigger = DateTime() self.getFirstTrigger(trigger) while self.mDoneCount < self.mRepeats: # See if next trigger is later than now next_trigger = trigger + self.mRepeatInterval if next_trigger > nowutc: break self.mDoneCount += 1 trigger = next_trigger # Check for completion if trigger == self.mLastTrigger or (nowutc - trigger).getTotalSeconds() > 24 * 60 * 60: if self.mDoneCount == self.mRepeats: self.mAlarmStatus = definitions.eAlarm_Status_Completed return else: trigger = trigger + self.mRepeatInterval self.mDoneCount += 1 self.mNextTrigger = trigger def getFirstTrigger(self, dt): # If absolute trigger, use that if self.isTriggerAbsolute(): # Get the trigger on dt.copy(self.getTriggerOn()) else: # Get the parent embedder class (must be CICalendarComponentRecur type) owner = self.getEmbedder() if owner is not None: # Determine time at which alarm will trigger trigger = (owner.getStart(), owner.getEnd())[not self.isTriggerOnStart()] # Offset by duration dt.copy(trigger + self.getTriggerDuration())
class VFreeBusy(Component): propertyCardinality_1 = ( definitions.cICalProperty_DTSTAMP, definitions.cICalProperty_UID, ) propertyCardinality_0_1 = ( definitions.cICalProperty_CONTACT, definitions.cICalProperty_DTSTART, definitions.cICalProperty_DTEND, definitions.cICalProperty_ORGANIZER, definitions.cICalProperty_URL, ) propertyValueChecks = ICALENDAR_VALUE_CHECKS def __init__(self, parent=None): super(VFreeBusy, self).__init__(parent=parent) self.mStart = DateTime() self.mHasStart = False self.mEnd = DateTime() self.mHasEnd = False self.mDuration = False self.mCachedBusyTime = False self.mSpanPeriod = None self.mBusyTime = None def duplicate(self, parent=None): other = super(VFreeBusy, self).duplicate(parent=parent) other.mStart = self.mStart.duplicate() other.mHasStart = self.mHasStart other.mEnd = self.mEnd.duplicate() other.mHasEnd = self.mHasEnd other.mDuration = self.mDuration other.mCachedBusyTime = False other.mBusyTime = None return other def getType(self): return definitions.cICalComponent_VFREEBUSY def getMimeComponentName(self): return itipdefinitions.cICalMIMEComponent_VFREEBUSY def finalise(self): # Do inherited super(VFreeBusy, self).finalise() # Get DTSTART temp = self.loadValueDateTime(definitions.cICalProperty_DTSTART) self.mHasStart = temp is not None if self.mHasStart: self.mStart = temp # Get DTEND temp = self.loadValueDateTime(definitions.cICalProperty_DTEND) if temp is None: # Try DURATION instead temp = self.loadValueDuration(definitions.cICalProperty_DURATION) if temp is not None: self.mEnd = self.mStart + temp self.mDuration = True else: # Force end to start, which will then be fixed to sensible # value later self.mEnd = self.mStart else: self.mHasEnd = True self.mDuration = False self.mEnd = temp def fixStartEnd(self): # End is always greater than start if start exists if self.mHasStart and self.mEnd <= self.mStart: # Use the start self.mEnd = self.mStart.duplicate() self.mDuration = False # Adjust to appropiate non-inclusive end point if self.mStart.isDateOnly(): self.mEnd.offsetDay(1) # For all day events it makes sense to use duration self.mDuration = True else: # Use end of current day self.mEnd.offsetDay(1) self.mEnd.setHHMMSS(0, 0, 0) def getStart(self): return self.mStart def hasStart(self): return self.mHasStart def getEnd(self): return self.mEnd def hasEnd(self): return self.mHasEnd def useDuration(self): return self.mDuration def getSpanPeriod(self): return self.mSpanPeriod def getBusyTime(self): return self.mBusyTime def editTiming(self): # Updated cached values self.mHasStart = False self.mHasEnd = False self.mDuration = False self.mStart.setToday() self.mEnd.setToday() # Remove existing DTSTART & DTEND & DURATION & DUE items self.removeProperties(definitions.cICalProperty_DTSTART) self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) def editTimingStartEnd(self, start, end): # Updated cached values self.mHasStart = self.mHasEnd = True self.mStart = start self.mEnd = end self.mDuration = False self.fixStartEnd() # Remove existing DTSTART & DTEND & DURATION & DUE items self.removeProperties(definitions.cICalProperty_DTSTART) self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) # Now create properties prop = Property(definitions.cICalProperty_DTSTART, start) self.addProperty(prop) # If its an all day event and the end one day after the start, ignore it temp = start.duplicate() temp.offsetDay(1) if not start.isDateOnly() or end != temp: prop = Property(definitions.cICalProperty_DTEND, end) self.addProperty(prop) def editTimingStartDuration(self, start, duration): # Updated cached values self.mHasStart = True self.mHasEnd = False self.mStart = start self.mEnd = start + duration self.mDuration = True # Remove existing DTSTART & DTEND & DURATION & DUE items self.removeProperties(definitions.cICalProperty_DTSTART) self.removeProperties(definitions.cICalProperty_DTEND) self.removeProperties(definitions.cICalProperty_DURATION) self.removeProperties(definitions.cICalProperty_DUE) # Now create properties prop = Property(definitions.cICalProperty_DTSTART, start) self.addProperty(prop) # If its an all day event and the duration is one day, ignore it if (not start.isDateOnly() or (duration.getWeeks() != 0) or (duration.getDays() > 1)): prop = Property(definitions.cICalProperty_DURATION, duration) self.addProperty(prop) # Generating info def expandPeriodComp(self, period, list): # Cache the busy-time details if not done already if not self.mCachedBusyTime: self.cacheBusyTime() # See if period intersects the busy time span range if (self.mBusyTime is not None) and period.isPeriodOverlap(self.mSpanPeriod): list.append(self) def expandPeriodFB(self, period, list): # Cache the busy-time details if not done already if not self.mCachedBusyTime: self.cacheBusyTime() # See if period intersects the busy time span range if (self.mBusyTime is not None) and period.isPeriodOverlap(self.mSpanPeriod): for fb in self.mBusyTime: list.append(FreeBusy(fb)) def cacheBusyTime(self): # Clear out any existing cache self.mBusyTime = [] # Get all FREEBUSY items and add those that are BUSY min_start = DateTime() max_end = DateTime() props = self.getProperties() result = props.get(definitions.cICalProperty_FREEBUSY, ()) for iter in result: # Check the properties FBTYPE parameter type = 0 is_busy = False if iter.hasParameter(definitions.cICalParameter_FBTYPE): fbyype = iter.getParameterValue(definitions.cICalParameter_FBTYPE) if fbyype.upper() == definitions.cICalParameter_FBTYPE_BUSY: is_busy = True type = FreeBusy.BUSY elif fbyype.upper() == definitions.cICalParameter_FBTYPE_BUSYUNAVAILABLE: is_busy = True type = FreeBusy.BUSYUNAVAILABLE elif fbyype.upper() == definitions.cICalParameter_FBTYPE_BUSYTENTATIVE: is_busy = True type = FreeBusy.BUSYTENTATIVE else: is_busy = False type = FreeBusy.FREE else: # Default is busy when no parameter is_busy = True type = FreeBusy.BUSY # Add this period if is_busy: multi = iter.getMultiValue() if (multi is not None) and (multi.getType() == Value.VALUETYPE_PERIOD): for o in multi.getValues(): # Double-check type period = None if isinstance(o, PeriodValue): period = o # Double-check type if period is not None: self.mBusyTime.append(FreeBusy(type, period.getValue())) if len(self.mBusyTime) == 1: min_start = period.getValue().getStart() max_end = period.getValue().getEnd() else: if min_start > period.getValue().getStart(): min_start = period.getValue().getStart() if max_end < period.getValue().getEnd(): max_end = period.getValue().getEnd() # If nothing present, empty the list if len(self.mBusyTime) == 0: self.mBusyTime = None else: # Sort the list by period self.mBusyTime.sort(cmp=lambda x, y: x.getPeriod().getStart().compareDateTime(y.getPeriod().getStart())) # Determine range start = DateTime() end = DateTime() if self.mHasStart: start = self.mStart else: start = min_start if self.mHasEnd: end = self.mEnd else: end = max_end self.mSpanPeriod = Period(start, end) self.mCachedBusyTime = True def sortedPropertyKeyOrder(self): return ( definitions.cICalProperty_UID, definitions.cICalProperty_DTSTART, definitions.cICalProperty_DURATION, definitions.cICalProperty_DTEND, )