Esempio n. 1
0
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,
        )
Esempio n. 2
0
    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)
Esempio n. 3
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,
        )
Esempio n. 4
0
    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
Esempio n. 5
0
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)
Esempio n. 6
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


    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,
        )
Esempio n. 7
0
    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)
Esempio n. 8
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())
Esempio n. 9
0
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,
        )