Пример #1
0
    def test_workdistribution(self):
        tzname = "US/Eastern"
        dist = WorkDistribution(["mon", "wed", "thu", "sat"], 10, 20, tzname)
        dist._helperDistribution = UniformDiscreteDistribution(
            [35 * 60 * 60 + 30 * 60])
        dist.now = lambda tzname=None: DateTime(
            2011, 5, 29, 18, 5, 36, tzid=tzname)
        value = dist.sample()
        self.assertEqual(
            # Move past three workdays - monday, wednesday, thursday - using 30
            # of the hours, and then five and a half hours into the fourth
            # workday, saturday.  Workday starts at 10am, so the sample value
            # is 3:30pm, ie 1530 hours.
            DateTime(2011, 6, 4, 15, 30, 0, tzid=Timezone(tzid=tzname)),
            value)

        dist = WorkDistribution(["mon", "tue", "wed", "thu", "fri"], 10, 20,
                                tzname)
        dist._helperDistribution = UniformDiscreteDistribution(
            [35 * 60 * 60 + 30 * 60])
        value = dist.sample()
        self.assertTrue(isinstance(value, DateTime))
Пример #2
0
    def test_query_timerange(self):
        """
        Basic query test - with time range
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        *[
                            caldavxml.TimeRange(
                                **{
                                    "start": "20060605T160000Z",
                                    "end": "20060605T170000Z"
                                })
                        ],
                        **{"name": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")})
                ], **{"name": "VCALENDAR"}))
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))

        expression = buildExpression(filter, self._queryFields)
        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
        select, args, usedtimerange = sql.generate()

        self.assertEqual(
            select.toSQL(),
            SQLFragment(
                "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT, TIME_RANGE where ICALENDAR_TYPE in (?, ?, ?) and (FLOATING = ? and START_DATE < ? and END_DATE > ? or FLOATING = ? and START_DATE < ? and END_DATE > ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?",
                [
                    Parameter('arg1', 3), False,
                    datetime.datetime(2006, 6, 5, 17, 0),
                    datetime.datetime(2006, 6, 5, 16, 0), True,
                    datetime.datetime(2006, 6, 5, 13, 0),
                    datetime.datetime(2006, 6, 5, 12, 0), 1234
                ]))
        self.assertEqual(args,
                         {"arg1": ("VEVENT", "VFREEBUSY", "VAVAILABILITY")})
        self.assertEqual(usedtimerange, True)
Пример #3
0
    def test_query_extended(self):
        """
        Extended query test - two terms with anyof
        """

        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                *[
                    caldavxml.ComponentFilter(
                        *[
                            caldavxml.TimeRange(**{
                                "start": "20060605T160000Z",
                            })
                        ], **{"name": ("VEVENT")}),
                    caldavxml.ComponentFilter(**{"name": ("VTODO")}),
                ], **{
                    "name": "VCALENDAR",
                    "test": "anyof"
                }))
        filter = Filter(filter)
        filter.child.settzinfo(Timezone(tzid="America/New_York"))

        expression = buildExpression(filter, self._queryFields)
        sql = CalDAVSQLQueryGenerator(expression, self, 1234)
        select, args, usedtimerange = sql.generate()

        self.assertEqual(
            select.toSQL(),
            SQLFragment(
                "select distinct RESOURCE_NAME, ICALENDAR_UID, ICALENDAR_TYPE from CALENDAR_OBJECT, TIME_RANGE where (ICALENDAR_TYPE = ? and (FLOATING = ? and END_DATE > ? or FLOATING = ? and END_DATE > ?) or ICALENDAR_TYPE = ?) and CALENDAR_OBJECT_RESOURCE_ID = RESOURCE_ID and TIME_RANGE.CALENDAR_RESOURCE_ID = ?",
                [
                    'VEVENT', False,
                    datetime.datetime(2006, 6, 5, 16, 0, tzinfo=tzutc()), True,
                    datetime.datetime(2006, 6, 5, 12, 0, tzinfo=tzutc()),
                    'VTODO', 1234
                ]))
        self.assertEqual(args, {})
        self.assertEqual(usedtimerange, True)
Пример #4
0
    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
Пример #5
0
    def test_normalizeToUTC(self):
        """
        Test that dateops.normalizeToUTC works correctly on all four types of date/time: date only, floating, UTC and local time.
        """

        data = (
            (DateTime(2012, 1, 1),
             DateTime(2012, 1, 1, 0, 0, 0, tzid=Timezone.UTCTimezone)),
            (DateTime(2012, 1, 1, 10, 0, 0),
             DateTime(2012, 1, 1, 10, 0, 0, tzid=Timezone.UTCTimezone)),
            (DateTime(2012, 1, 1, 11, 0, 0, tzid=Timezone.UTCTimezone),
             DateTime(2012, 1, 1, 11, 0, 0, tzid=Timezone.UTCTimezone)),
            (DateTime(2012,
                      1,
                      1,
                      12,
                      0,
                      0,
                      tzid=Timezone(tzid="America/New_York")),
             DateTime(2012, 1, 1, 17, 0, 0, tzid=Timezone.UTCTimezone)),
        )

        for value, result in data:
            self.assertEqual(normalizeToUTC(value), result)
Пример #6
0
    def testDeriveComponent(self):

        data = (
            (
                "1.1 Recurring no VTIMEZONE",
                """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//mulberrymail.com//Mulberry v4.0//EN
BEGIN:VEVENT
UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
DTSTART;VALUE=DATE:20110601
DURATION:P1D
DTSTAMP:20020101T000000Z
RRULE:FREQ=DAILY
SUMMARY:New Year's Day
END:VEVENT
BEGIN:VEVENT
UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
RECURRENCE-ID;VALUE=DATE:20110602
DTSTART;VALUE=DATE:20110602
DURATION:P1D
DTSTAMP:20020101T000000Z
SUMMARY:New Year's Day
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n"),
                DateTime(2011, 6, 3),
                """BEGIN:VEVENT
UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
RECURRENCE-ID;VALUE=DATE:20110603
DTSTART;VALUE=DATE:20110603
DURATION:P1D
DTSTAMP:20020101T000000Z
SUMMARY:New Year's Day
END:VEVENT
""".replace("\n", "\r\n"),
            ),
            (
                "2.2 Recurring with VTIMEZONE",
                """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//mulberrymail.com//Mulberry v4.0//EN
BEGIN:VTIMEZONE
TZID:Etc/GMT+1
X-LIC-LOCATION:Etc/GMT+1
BEGIN:STANDARD
DTSTART:18000101T000000
RDATE:18000101T000000
TZNAME:GMT+1
TZOFFSETFROM:-0100
TZOFFSETTO:-0100
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
DTSTART;TZID=Etc/GMT+1:20110601T000000
DURATION:P1D
DTSTAMP:20020101T000000Z
SUMMARY:New Year's Day
RRULE:FREQ=DAILY
END:VEVENT
BEGIN:VEVENT
UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
RECURRENCE-ID;TZID=Etc/GMT+1:20110602T000000
DTSTART;TZID=Etc/GMT+1:20110602T000000
DURATION:P1D
DTSTAMP:20020101T000000Z
SUMMARY:New Year's Day
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n"),
                DateTime(2011, 6, 3, 1, 0, 0, Timezone(utc=True)),
                """BEGIN:VEVENT
UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
RECURRENCE-ID;TZID=Etc/GMT+1:20110603T000000
DTSTART;TZID=Etc/GMT+1:20110603T000000
DURATION:P1D
DTSTAMP:20020101T000000Z
SUMMARY:New Year's Day
END:VEVENT
""".replace("\n", "\r\n"),
            ),
            (
                "2.3 Recurring with VTIMEZONE, DTEND",
                """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//mulberrymail.com//Mulberry v4.0//EN
BEGIN:VTIMEZONE
TZID:Etc/GMT+1
X-LIC-LOCATION:Etc/GMT+1
BEGIN:STANDARD
DTSTART:18000101T000000
RDATE:18000101T000000
TZNAME:GMT+1
TZOFFSETFROM:-0100
TZOFFSETTO:-0100
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
DTSTART;TZID=Etc/GMT+1:20110601T000000
DTEND;TZID=Etc/GMT+1:20110601T020000
DTSTAMP:20020101T000000Z
SUMMARY:New Year's Day
RRULE:FREQ=DAILY
END:VEVENT
BEGIN:VEVENT
UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
RECURRENCE-ID;TZID=Etc/GMT+1:20110602T000000
DTSTART;TZID=Etc/GMT+1:20110602T000000
DTEND;TZID=Etc/GMT+1:20110602T020000
DTSTAMP:20020101T000000Z
SUMMARY:New Year's Day
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n"),
                DateTime(2011, 6, 3, 1, 0, 0, Timezone(utc=True)),
                """BEGIN:VEVENT
UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
RECURRENCE-ID;TZID=Etc/GMT+1:20110603T000000
DTSTART;TZID=Etc/GMT+1:20110603T000000
DTEND;TZID=Etc/GMT+1:20110603T020000
DTSTAMP:20020101T000000Z
SUMMARY:New Year's Day
END:VEVENT
""".replace("\n", "\r\n"),
            ),
            (
                "2.1 Recurring no master, no VTIMEZONE",
                """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//mulberrymail.com//Mulberry v4.0//EN
BEGIN:VEVENT
UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
RECURRENCE-ID;VALUE=DATE:20110602
DTSTART;VALUE=DATE:20110602
DURATION:P1D
DTSTAMP:20020101T000000Z
SUMMARY:New Year's Day
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n"),
                DateTime(2011, 6, 3),
                "",
            ),
            (
                "2.2 Recurring no master, with VTIMEZONE",
                """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//mulberrymail.com//Mulberry v4.0//EN
BEGIN:VTIMEZONE
TZID:Etc/GMT+1
X-LIC-LOCATION:Etc/GMT+1
BEGIN:STANDARD
DTSTART:18000101T000000
RDATE:18000101T000000
TZNAME:GMT+1
TZOFFSETFROM:-0100
TZOFFSETTO:-0100
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
RECURRENCE-ID;TZID=Etc/GMT+1:20110602T000000
DTSTART;TZID=Etc/GMT+1:20110602T000000
DURATION:P1D
DTSTAMP:20020101T000000Z
SUMMARY:New Year's Day
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n"),
                DateTime(2011, 6, 3, 1, 0, 0, Timezone(utc=True)),
                "",
            ),
        )

        for title, caldata, rid, result in data:
            calendar = Calendar.parseText(caldata)
            master = calendar.deriveComponent(rid)
            if master is None:
                master = ""
            self.assertEqual(
                str(master), result,
                "Failed in %s: got %s, expected %s" % (title, master, result))
Пример #7
0
    def doCapabilities(self, request):
        """
        Return a list of all timezones known to the server.
        """

        # Determine min/max date-time for iSchedule
        now = DateTime.getNowUTC()
        minDateTime = DateTime(now.getYear(), 1, 1, 0, 0, 0, Timezone(utc=True))
        minDateTime.offsetYear(-1)
        maxDateTime = DateTime(now.getYear(), 1, 1, 0, 0, 0, Timezone(utc=True))
        maxDateTime.offsetYear(10)

        dataTypes = []
        dataTypes.append(
            ischedulexml.CalendarDataType(**{
                "content-type": "text/calendar",
                "version": "2.0",
            })
        )
        if config.EnableJSONData:
            dataTypes.append(
                ischedulexml.CalendarDataType(**{
                    "content-type": "application/calendar+json",
                    "version": "2.0",
                })
            )

        componentTypes = []
        from twistedcaldav.ical import allowedSchedulingComponents
        for name in allowedSchedulingComponents:
            if name == "VFREEBUSY":
                componentTypes.append(
                    ischedulexml.Component(
                        ischedulexml.Method(name="REQUEST"),
                        name=name
                    )
                )
            else:
                componentTypes.append(
                    ischedulexml.Component(
                        ischedulexml.Method(name="REQUEST"),
                        ischedulexml.Method(name="CANCEL"),
                        ischedulexml.Method(name="REPLY"),
                        name=name
                    )
                )

        result = ischedulexml.QueryResult(

            ischedulexml.Capabilities(
                ischedulexml.Version.fromString(config.Scheduling.iSchedule.SerialNumber),
                ischedulexml.Versions(
                    ischedulexml.Version.fromString("1.0"),
                ),
                ischedulexml.SchedulingMessages(*componentTypes),
                ischedulexml.CalendarDataTypes(*dataTypes),
                ischedulexml.Attachments(
                    ischedulexml.External(),
                ),
                ischedulexml.MaxContentLength.fromString(config.MaxResourceSize),
                ischedulexml.MinDateTime.fromString(minDateTime.getText()),
                ischedulexml.MaxDateTime.fromString(maxDateTime.getText()),
                ischedulexml.MaxInstances.fromString(config.MaxAllowedInstances),
                ischedulexml.MaxRecipients.fromString(config.MaxAttendeesPerInstance),
                ischedulexml.Administrator.fromString(request.unparseURL(params="", querystring="", fragment="")),
            ),
        )
        response = XMLResponse(responsecode.OK, result)
        response.headers.addRawHeader(ISCHEDULE_CAPABILITIES, str(config.Scheduling.iSchedule.SerialNumber))
        return response
Пример #8
0
    def testWeeklyTwice(self):

        recur = Recurrence()
        recur.parse("FREQ=WEEKLY")
        start = DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(utc=True))
        end = DateTime(2014, 2, 1, 0, 0, 0, tzid=Timezone(utc=True))
        items = []
        range = Period(start, end)
        recur.expand(
            DateTime(2014,
                     1,
                     1,
                     12,
                     0,
                     0,
                     tzid=Timezone(tzid="America/New_York")), range, items)
        self.assertEqual(
            items,
            [
                DateTime(2014,
                         1,
                         1,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         1,
                         8,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         1,
                         15,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         1,
                         22,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         1,
                         29,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
            ],
        )

        start = DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(utc=True))
        end = DateTime(2014, 3, 1, 0, 0, 0, tzid=Timezone(utc=True))
        items = []
        range = Period(start, end)
        recur.expand(
            DateTime(2014,
                     1,
                     1,
                     12,
                     0,
                     0,
                     tzid=Timezone(tzid="America/New_York")), range, items)
        self.assertEqual(
            items,
            [
                DateTime(2014,
                         1,
                         1,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         1,
                         8,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         1,
                         15,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         1,
                         22,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         1,
                         29,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         2,
                         5,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         2,
                         12,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         2,
                         19,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         2,
                         26,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
            ],
        )
Пример #9
0
    def initManager(self):
        # TODO: - read in timezones from vtimezones.ics file

        # Eventually we need to read these from prefs - for now they are
        # hard-coded to my personal prefs!
        self.setDefaultTimezone(Timezone(utc=False, tzid="US/Eastern"))
Пример #10
0
    def test_timeRangesOverlap(self):

        data = (
            # Timed
            (
                "Start within, end within - overlap",
                DateTime(2012, 1, 1, 11, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 1, 12, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 1, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 2, 0, 0, 0, tzid=Timezone(utc=True)),
                True,
            ),
            (
                "Start before, end before - no overlap",
                DateTime(2012, 1, 1, 11, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 1, 12, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 2, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 3, 0, 0, 0, tzid=Timezone(utc=True)),
                False,
            ),
            (
                "Start before, end right before - no overlap",
                DateTime(2012, 1, 1, 23, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 2, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 2, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 3, 0, 0, 0, tzid=Timezone(utc=True)),
                False,
            ),
            (
                "Start before, end within - overlap",
                DateTime(2012, 1, 1, 11, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 2, 11, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 2, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 3, 0, 0, 0, tzid=Timezone(utc=True)),
                True,
            ),
            (
                "Start after, end after - no overlap",
                DateTime(2012, 1, 2, 11, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 2, 12, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 1, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 2, 0, 0, 0, tzid=Timezone(utc=True)),
                False,
            ),
            (
                "Start right after, end after - no overlap",
                DateTime(2012, 1, 2, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 2, 1, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 1, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 2, 0, 0, 0, tzid=Timezone(utc=True)),
                False,
            ),
            (
                "Start within, end after - overlap",
                DateTime(2012, 1, 1, 12, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 2, 12, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 1, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 2, 0, 0, 0, tzid=Timezone(utc=True)),
                True,
            ),
            (
                "Start before, end after - overlap",
                DateTime(2012, 1, 1, 11, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 3, 11, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 2, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 3, 0, 0, 0, tzid=Timezone(utc=True)),
                True,
            ),

            # All day
            (
                "All day: Start within, end within - overlap",
                DateTime(2012, 1, 9),
                DateTime(2012, 1, 10),
                DateTime(2012, 1, 8, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 15, 0, 0, 0, tzid=Timezone(utc=True)),
                True,
            ),
            (
                "All day: Start before, end before - no overlap",
                DateTime(2012, 1, 1),
                DateTime(2012, 1, 2),
                DateTime(2012, 1, 8, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 15, 0, 0, 0, tzid=Timezone(utc=True)),
                False,
            ),
            (
                "All day: Start before, end right before - no overlap",
                DateTime(2012, 1, 7),
                DateTime(2012, 1, 8),
                DateTime(2012, 1, 8, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 15, 0, 0, 0, tzid=Timezone(utc=True)),
                False,
            ),
            (
                "All day: Start before, end within - overlap",
                DateTime(2012, 1, 7),
                DateTime(2012, 1, 9),
                DateTime(2012, 1, 8, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 15, 0, 0, 0, tzid=Timezone(utc=True)),
                True,
            ),
            (
                "All day: Start after, end after - no overlap",
                DateTime(2012, 1, 16),
                DateTime(2012, 1, 17),
                DateTime(2012, 1, 8, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 15, 0, 0, 0, tzid=Timezone(utc=True)),
                False,
            ),
            (
                "All day: Start right after, end after - no overlap",
                DateTime(2012, 1, 15),
                DateTime(2012, 1, 16),
                DateTime(2012, 1, 8, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 15, 0, 0, 0, tzid=Timezone(utc=True)),
                False,
            ),
            (
                "All day: Start within, end after - overlap",
                DateTime(2012, 1, 14),
                DateTime(2012, 1, 16),
                DateTime(2012, 1, 8, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 15, 0, 0, 0, tzid=Timezone(utc=True)),
                True,
            ),
            (
                "All day: Start before, end after - overlap",
                DateTime(2012, 1, 7),
                DateTime(2012, 1, 16),
                DateTime(2012, 1, 8, 0, 0, 0, tzid=Timezone(utc=True)),
                DateTime(2012, 1, 15, 0, 0, 0, tzid=Timezone(utc=True)),
                True,
            ),
        )

        for title, start1, end1, start2, end2, result in data:
            self.assertEqual(timeRangesOverlap(start1, end1, start2, end2), result, msg="Failed: %s" % (title,))
Пример #11
0
 def getNow(tzid):
     utc = DateTime.getNowUTC()
     utc.adjustTimezone(tzid if tzid is not None else Timezone())
     return utc
Пример #12
0
 def setNow(self):
     tz = Timezone(utc=self.mTZUTC, tzid=self.mTZID)
     self.copy_ICalendarDateTime(self.getNow(tz))
Пример #13
0
 def getTimezone(self):
     return Timezone(utc=self.mTZUTC, tzid=self.mTZID)
Пример #14
0
 def timeZoneDescriptor(self):
     tz = Timezone(utc=self.mTZUTC, tzid=self.mTZID)
     return tz.timeZoneDescriptor(self)
Пример #15
0
def generateFreeBusyInfo(
    request,
    calresource,
    fbinfo,
    timerange,
    matchtotal,
    excludeuid=None,
    organizer=None,
    organizerPrincipal=None,
    same_calendar_user=False,
    servertoserver=False,
    event_details=None,
):
    """
    Run a free busy report on the specified calendar collection
    accumulating the free busy info for later processing.
    @param request:     the L{IRequest} for the current request.
    @param calresource: the L{CalDAVResource} for a calendar collection.
    @param fbinfo:      the array of busy periods to update.
    @param timerange:   the L{TimeRange} for the query.
    @param matchtotal:  the running total for the number of matches.
    @param excludeuid:  a C{str} containing a UID value to exclude any
        components with that UID from contributing to free-busy.
    @param organizer:   a C{str} containing the value of the ORGANIZER property
        in the VFREEBUSY request.  This is used in conjunction with the UID
        value to process exclusions.
    @param same_calendar_user: a C{bool} indicating whether the calendar user
        requesting the free-busy information is the same as the calendar user
        being targeted.
    @param servertoserver: a C{bool} indicating whether we are doing a local or
        remote lookup request.
    @param event_details: a C{list} into which to store extended VEVENT details if not C{None}
    """

    # First check the privilege on this collection
    # TODO: for server-to-server we bypass this right now as we have no way to authorize external users.
    if not servertoserver:
        try:
            yield calresource.checkPrivileges(request,
                                              (caldavxml.ReadFreeBusy(), ),
                                              principal=organizerPrincipal)
        except AccessDeniedError:
            returnValue(matchtotal)

    # May need organizer principal
    organizer_principal = (yield calresource.principalForCalendarUserAddress(
        organizer)) if organizer else None
    organizer_uid = organizer_principal.principalUID(
    ) if organizer_principal else ""

    # Free busy is per-user
    userPrincipal = (yield calresource.resourceOwnerPrincipal(request))
    if userPrincipal:
        useruid = userPrincipal.principalUID()
    else:
        useruid = ""

    # Get the timezone property from the collection.
    has_prop = (yield calresource.hasProperty(CalendarTimeZone(), request))
    if has_prop:
        tz = (yield calresource.readProperty(CalendarTimeZone(), request))
    else:
        tz = None

    # Look for possible extended free busy information
    rich_options = {
        "organizer": False,
        "delegate": False,
        "resource": False,
    }
    do_event_details = False
    if event_details is not None and organizer_principal is not None and userPrincipal is not None:

        # Check if organizer is attendee
        if organizer_principal == userPrincipal:
            do_event_details = True
            rich_options["organizer"] = True

        # Check if organizer is a delegate of attendee
        proxy = (yield organizer_principal.isProxyFor(userPrincipal))
        if config.Scheduling.Options.DelegeteRichFreeBusy and proxy:
            do_event_details = True
            rich_options["delegate"] = True

        # Check if attendee is room or resource
        if config.Scheduling.Options.RoomResourceRichFreeBusy and userPrincipal.getCUType(
        ) in (
                "RESOURCE",
                "ROOM",
        ):
            do_event_details = True
            rich_options["resource"] = True

    # Try cache
    resources = (yield FBCacheEntry.getCacheEntry(
        calresource, useruid,
        timerange)) if config.EnableFreeBusyCache else None

    if resources is None:

        caching = False
        if config.EnableFreeBusyCache:
            # Log extended item
            if not hasattr(request, "extendedLogItems"):
                request.extendedLogItems = {}
            request.extendedLogItems[
                "fb-uncached"] = request.extendedLogItems.get(
                    "fb-uncached", 0) + 1

            # We want to cache a large range of time based on the current date
            cache_start = normalizeToUTC(DateTime.getToday() + Duration(
                days=0 - config.FreeBusyCacheDaysBack))
            cache_end = normalizeToUTC(DateTime.getToday() + Duration(
                days=config.FreeBusyCacheDaysForward))

            # If the requested timerange would fit in our allowed cache range, trigger the cache creation
            if compareDateTime(timerange.start,
                               cache_start) >= 0 and compareDateTime(
                                   timerange.end, cache_end) <= 0:
                cache_timerange = TimeRange(start=cache_start.getText(),
                                            end=cache_end.getText())
                caching = True

        #
        # What we do is a fake calendar-query for VEVENT/VFREEBUSYs in the specified time-range.
        # We then take those results and merge them into one VFREEBUSY component
        # with appropriate FREEBUSY properties, and return that single item as iCal data.
        #

        # Create fake filter element to match time-range
        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                caldavxml.ComponentFilter(
                    cache_timerange if caching else timerange,
                    name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
                ),
                name="VCALENDAR",
            ))
        filter = Filter(filter)
        tzinfo = filter.settimezone(tz)

        try:
            resources = yield calresource.search(filter,
                                                 useruid=useruid,
                                                 fbtype=True)
            if caching:
                yield FBCacheEntry.makeCacheEntry(calresource, useruid,
                                                  cache_timerange, resources)
        except IndexedSearchException:
            raise HTTPError(
                StatusResponse(responsecode.INTERNAL_SERVER_ERROR,
                               "Failed freebusy query"))

    else:
        # Log extended item
        if not hasattr(request, "extendedLogItems"):
            request.extendedLogItems = {}
        request.extendedLogItems["fb-cached"] = request.extendedLogItems.get(
            "fb-cached", 0) + 1

        # Determine appropriate timezone (UTC is the default)
        tzinfo = tz.gettimezone() if tz is not None else Timezone(utc=True)

    # We care about separate instances for VEVENTs only
    aggregated_resources = {}
    for name, uid, type, test_organizer, float, start, end, fbtype, transp in resources:
        if transp == 'T' and fbtype != '?':
            fbtype = 'F'
        aggregated_resources.setdefault((
            name,
            uid,
            type,
            test_organizer,
        ), []).append((
            float,
            start,
            end,
            fbtype,
        ))

    for key in aggregated_resources.iterkeys():

        name, uid, type, test_organizer = key

        # Short-cut - if an fbtype exists we can use that
        if type == "VEVENT" and aggregated_resources[key][0][3] != '?':

            matchedResource = False

            # Look at each instance
            for float, start, end, fbtype in aggregated_resources[key]:
                # Ignore free time or unknown
                if fbtype in ('F', '?'):
                    continue

                # Ignore ones of this UID
                if excludeuid:
                    # See if we have a UID match
                    if (excludeuid == uid):
                        test_principal = (
                            yield calresource.principalForCalendarUserAddress(
                                test_organizer)) if test_organizer else None
                        test_uid = test_principal.principalUID(
                        ) if test_principal else ""

                        # Check that ORGANIZER's match (security requirement)
                        if (organizer is None) or (organizer_uid == test_uid):
                            continue
                        # Check for no ORGANIZER and check by same calendar user
                        elif (test_uid == "") and same_calendar_user:
                            continue

                # Apply a timezone to any floating times
                fbstart = parseSQLTimestampToPyCalendar(start)
                if float == 'Y':
                    fbstart.setTimezone(tzinfo)
                else:
                    fbstart.setTimezone(Timezone(utc=True))
                fbend = parseSQLTimestampToPyCalendar(end)
                if float == 'Y':
                    fbend.setTimezone(tzinfo)
                else:
                    fbend.setTimezone(Timezone(utc=True))

                # Clip instance to time range
                clipped = clipPeriod(Period(fbstart, duration=fbend - fbstart),
                                     Period(timerange.start, timerange.end))

                # Double check for overlap
                if clipped:
                    matchedResource = True
                    fbinfo[fbtype_index_mapper.get(fbtype, 0)].append(clipped)

            if matchedResource:
                # Check size of results is within limit
                matchtotal += 1
                if matchtotal > max_number_of_matches:
                    raise NumberOfMatchesWithinLimits(max_number_of_matches)

                # Add extended details
                if do_event_details:
                    child = (yield
                             request.locateChildResource(calresource, name))
                    calendar = (yield child.iCalendarForUser(request))
                    _addEventDetails(calendar, event_details, rich_options,
                                     timerange, tzinfo)

        else:
            child = (yield request.locateChildResource(calresource, name))
            calendar = (yield child.iCalendarForUser(request))

            # The calendar may come back as None if the resource is being changed, or was deleted
            # between our initial index query and getting here. For now we will ignore this error, but in
            # the longer term we need to implement some form of locking, perhaps.
            if calendar is None:
                log.error(
                    "Calendar %s is missing from calendar collection %r" %
                    (name, calresource))
                continue

            # Ignore ones of this UID
            if excludeuid:
                # See if we have a UID match
                if (excludeuid == uid):
                    test_organizer = calendar.getOrganizer()
                    test_principal = (
                        yield calresource.principalForCalendarUserAddress(
                            test_organizer)) if test_organizer else None
                    test_uid = test_principal.principalUID(
                    ) if test_principal else ""

                    # Check that ORGANIZER's match (security requirement)
                    if (organizer is None) or (organizer_uid == test_uid):
                        continue
                    # Check for no ORGANIZER and check by same calendar user
                    elif (test_organizer is None) and same_calendar_user:
                        continue

            if filter.match(calendar, None):
                # Check size of results is within limit
                matchtotal += 1
                if matchtotal > max_number_of_matches:
                    raise NumberOfMatchesWithinLimits(max_number_of_matches)

                if calendar.mainType() == "VEVENT":
                    processEventFreeBusy(calendar, fbinfo, timerange, tzinfo)
                elif calendar.mainType() == "VFREEBUSY":
                    processFreeBusyFreeBusy(calendar, fbinfo, timerange)
                elif calendar.mainType() == "VAVAILABILITY":
                    processAvailabilityFreeBusy(calendar, fbinfo, timerange)
                else:
                    assert "Free-busy query returned unwanted component: %s in %r", (
                        name,
                        calresource,
                    )

                # Add extended details
                if calendar.mainType() == "VEVENT" and do_event_details:
                    child = (yield
                             request.locateChildResource(calresource, name))
                    calendar = (yield child.iCalendarForUser(request))
                    _addEventDetails(calendar, event_details, rich_options,
                                     timerange, tzinfo)

    returnValue(matchtotal)
Пример #16
0
class CalendarIndex(AbstractCalendarIndex):
    """
    Calendar index - abstract class for indexer that indexes calendar objects in a collection.
    """
    def __init__(self, resource):
        """
        @param resource: the L{CalDAVResource} resource to
            index.
        """
        super(CalendarIndex, self).__init__(resource)

    def _db_init_data_tables_base(self, q, uidunique):
        """
        Initialise the underlying database tables.
        @param q:           a database cursor to use.
        """
        #
        # RESOURCE table is the primary index table
        #   NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
        #   UID: iCalendar UID (may or may not be unique)
        #   TYPE: iCalendar component type
        #   RECURRANCE_MAX: Highest date of recurrence expansion
        #   ORGANIZER: cu-address of the Organizer of the event
        #
        q.execute("""
            create table RESOURCE (
                RESOURCEID     integer primary key autoincrement,
                NAME           text unique,
                UID            text%s,
                TYPE           text,
                RECURRANCE_MAX date,
                ORGANIZER      text
            )
            """ % (" unique" if uidunique else "", ))

        #
        # TIMESPAN table tracks (expanded) time spans for resources
        #   NAME: Related resource (RESOURCE foreign key)
        #   FLOAT: 'Y' if start/end are floating, 'N' otherwise
        #   START: Start date
        #   END: End date
        #   FBTYPE: FBTYPE value:
        #     '?' - unknown
        #     'F' - free
        #     'B' - busy
        #     'U' - busy-unavailable
        #     'T' - busy-tentative
        #   TRANSPARENT: Y if transparent, N if opaque (default non-per-user value)
        #
        q.execute("""
            create table TIMESPAN (
                INSTANCEID   integer primary key autoincrement,
                RESOURCEID   integer,
                FLOAT        text(1),
                START        date,
                END          date,
                FBTYPE       text(1),
                TRANSPARENT  text(1)
            )
            """)
        q.execute("""
            create index STARTENDFLOAT on TIMESPAN (START, END, FLOAT)
            """)

        #
        # PERUSER table tracks per-user ids
        #   PERUSERID: autoincrement primary key
        #   UID: User ID used in calendar data
        #
        q.execute("""
            create table PERUSER (
                PERUSERID       integer primary key autoincrement,
                USERUID         text
            )
            """)
        q.execute("""
            create index PERUSER_UID on PERUSER (USERUID)
            """)

        #
        # TRANSPARENCY table tracks per-user per-instance transparency
        #   PERUSERID: user id key
        #   INSTANCEID: instance id key
        #   TRANSPARENT: Y if transparent, N if opaque
        #
        q.execute("""
            create table TRANSPARENCY (
                PERUSERID       integer,
                INSTANCEID      integer,
                TRANSPARENT     text(1)
            )
            """)

        #
        # REVISIONS table tracks changes
        #   NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key)
        #   REVISION: revision number
        #   WASDELETED: Y if revision deleted, N if added or changed
        #
        q.execute("""
            create table REVISION_SEQUENCE (
                REVISION        integer
            )
            """)
        q.execute("""
            insert into REVISION_SEQUENCE (REVISION) values (0)
            """)
        q.execute("""
            create table REVISIONS (
                NAME            text unique,
                REVISION        integer,
                DELETED         text(1)
            )
            """)
        q.execute("""
            create index REVISION on REVISIONS (REVISION)
            """)

        if uidunique:
            #
            # RESERVED table tracks reserved UIDs
            #   UID: The UID being reserved
            #   TIME: When the reservation was made
            #
            q.execute("""
                create table RESERVED (
                    UID  text unique,
                    TIME date
                )
                """)

        # Cascading triggers to help on delete
        q.execute("""
            create trigger resourceDelete after delete on RESOURCE
            for each row
            begin
                delete from TIMESPAN where TIMESPAN.RESOURCEID = OLD.RESOURCEID;
            end
            """)
        q.execute("""
            create trigger timespanDelete after delete on TIMESPAN
            for each row
            begin
                delete from TRANSPARENCY where INSTANCEID = OLD.INSTANCEID;
            end
            """)

    def _db_can_upgrade(self, old_version):
        """
        Can we do an in-place upgrade
        """

        # v10 is a big change - no upgrade possible
        return False

    def _db_upgrade_data_tables(self, q, old_version):
        """
        Upgrade the data from an older version of the DB.
        """

        # v10 is a big change - no upgrade possible
        pass

    def notExpandedBeyond(self, minDate):
        """
        Gives all resources which have not been expanded beyond a given date
        in the index
        """
        return self._db_values_for_sql(
            "select NAME from RESOURCE where RECURRANCE_MAX < :1",
            pyCalendarTodatetime(minDate))

    def reExpandResource(self, name, expand_until):
        """
        Given a resource name, remove it from the database and re-add it
        with a longer expansion.
        """
        calendar = self.resource.getChild(name).iCalendar()
        self._add_to_db(name,
                        calendar,
                        expand_until=expand_until,
                        reCreate=True)
        self._db_commit()

    def _add_to_db(self,
                   name,
                   calendar,
                   cursor=None,
                   expand_until=None,
                   reCreate=False):
        """
        Records the given calendar resource in the index with the given name.
        Resource names and UIDs must both be unique; only one resource name may
        be associated with any given UID and vice versa.
        NB This method does not commit the changes to the db - the caller
        MUST take care of that
        @param name: the name of the resource to add.
        @param calendar: a L{Calendar} object representing the resource
            contents.
        """
        uid = calendar.resourceUID()
        organizer = calendar.getOrganizer()
        if not organizer:
            organizer = ""

        # Decide how far to expand based on the component
        doInstanceIndexing = False
        master = calendar.masterComponent()
        if master is None or not calendar.isRecurring():
            # When there is no master we have a set of overridden components - index them all.
            # When there is one instance - index it.
            expand = DateTime(2100, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
            doInstanceIndexing = True
        else:
            # If migrating or re-creating or config option for delayed indexing is off, always index
            if reCreate or not config.FreeBusyIndexDelayedExpand:
                doInstanceIndexing = True

            # Duration into the future through which recurrences are expanded in the index
            # by default.  This is a caching parameter which affects the size of the index;
            # it does not affect search results beyond this period, but it may affect
            # performance of such a search.
            expand = (DateTime.getToday() +
                      Duration(days=config.FreeBusyIndexExpandAheadDays))

            if expand_until and expand_until > expand:
                expand = expand_until

            # Maximum duration into the future through which recurrences are expanded in the
            # index.  This is a caching parameter which affects the size of the index; it
            # does not affect search results beyond this period, but it may affect
            # performance of such a search.
            #
            # When a search is performed on a time span that goes beyond that which is
            # expanded in the index, we have to open each resource which may have data in
            # that time period.  In order to avoid doing that multiple times, we want to
            # cache those results.  However, we don't necessarily want to cache all
            # occurrences into some obscenely far-in-the-future date, so we cap the caching
            # period.  Searches beyond this period will always be relatively expensive for
            # resources with occurrences beyond this period.
            if expand > (DateTime.getToday() +
                         Duration(days=config.FreeBusyIndexExpandMaxDays)):
                raise IndexedSearchException()

        # Always do recurrence expansion even if we do not intend to index - we need this to double-check the
        # validity of the iCalendar recurrence data.
        try:
            instances = calendar.expandTimeRanges(
                expand, ignoreInvalidInstances=reCreate)
            recurrenceLimit = instances.limit
        except InvalidOverriddenInstanceError, e:
            log.error("Invalid instance %s when indexing %s in %s" % (
                e.rid,
                name,
                self.resource,
            ))
            raise

        # Now coerce indexing to off if needed
        if not doInstanceIndexing:
            instances = None
            recurrenceLimit = DateTime(1900,
                                       1,
                                       1,
                                       0,
                                       0,
                                       0,
                                       tzid=Timezone(utc=True))

        self._delete_from_db(name, uid, False)

        # Add RESOURCE item
        self._db_execute(
            """
            insert into RESOURCE (NAME, UID, TYPE, RECURRANCE_MAX, ORGANIZER)
            values (:1, :2, :3, :4, :5)
            """, name, uid, calendar.resourceType(),
            pyCalendarTodatetime(recurrenceLimit) if recurrenceLimit else None,
            organizer)
        resourceid = self.lastrowid

        # Get a set of all referenced per-user UIDs and map those to entries already
        # in the DB and add new ones as needed
        useruids = calendar.allPerUserUIDs()
        useruids.add("")
        useruidmap = {}
        for useruid in useruids:
            peruserid = self._db_value_for_sql(
                "select PERUSERID from PERUSER where USERUID = :1", useruid)
            if peruserid is None:
                self._db_execute(
                    """
                    insert into PERUSER (USERUID)
                    values (:1)
                    """, useruid)
                peruserid = self.lastrowid
            useruidmap[useruid] = peruserid

        if doInstanceIndexing:
            for key in instances:
                instance = instances[key]
                start = instance.start
                end = instance.end
                float = 'Y' if instance.start.floating() else 'N'
                transp = 'T' if instance.component.propertyValue(
                    "TRANSP") == "TRANSPARENT" else 'F'
                self._db_execute(
                    """
                    insert into TIMESPAN (RESOURCEID, FLOAT, START, END, FBTYPE, TRANSPARENT)
                    values (:1, :2, :3, :4, :5, :6)
                    """, resourceid, float, pyCalendarTodatetime(start),
                    pyCalendarTodatetime(end),
                    icalfbtype_to_indexfbtype.get(
                        instance.component.getFBType(), 'F'), transp)
                instanceid = self.lastrowid
                peruserdata = calendar.perUserData(instance.rid)
                for useruid, (transp, _ignore_adjusted_start,
                              _ignore_adjusted_end) in peruserdata:
                    peruserid = useruidmap[useruid]
                    self._db_execute(
                        """
                        insert into TRANSPARENCY (PERUSERID, INSTANCEID, TRANSPARENT)
                        values (:1, :2, :3)
                        """, peruserid, instanceid, 'T' if transp else 'F')

            # Special - for unbounded recurrence we insert a value for "infinity"
            # that will allow an open-ended time-range to always match it.
            if calendar.isRecurringUnbounded():
                start = DateTime(2100, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
                end = DateTime(2100, 1, 1, 1, 0, 0, tzid=Timezone(utc=True))
                float = 'N'
                self._db_execute(
                    """
                    insert into TIMESPAN (RESOURCEID, FLOAT, START, END, FBTYPE, TRANSPARENT)
                    values (:1, :2, :3, :4, :5, :6)
                    """, resourceid, float, pyCalendarTodatetime(start),
                    pyCalendarTodatetime(end), '?', '?')
                instanceid = self.lastrowid
                peruserdata = calendar.perUserData(None)
                for useruid, (transp, _ignore_adjusted_start,
                              _ignore_adjusted_end) in peruserdata:
                    peruserid = useruidmap[useruid]
                    self._db_execute(
                        """
                        insert into TRANSPARENCY (PERUSERID, INSTANCEID, TRANSPARENT)
                        values (:1, :2, :3)
                        """, peruserid, instanceid, 'T' if transp else 'F')

        self._db_execute(
            """
            insert or replace into REVISIONS (NAME, REVISION, DELETED)
            values (:1, :2, :3)
            """,
            name,
            self.bumpRevision(fast=True),
            'N',
        )
Пример #17
0
    def _add_to_db(self,
                   name,
                   calendar,
                   cursor=None,
                   expand_until=None,
                   reCreate=False):
        """
        Records the given calendar resource in the index with the given name.
        Resource names and UIDs must both be unique; only one resource name may
        be associated with any given UID and vice versa.
        NB This method does not commit the changes to the db - the caller
        MUST take care of that
        @param name: the name of the resource to add.
        @param calendar: a L{Calendar} object representing the resource
            contents.
        """
        uid = calendar.resourceUID()
        organizer = calendar.getOrganizer()
        if not organizer:
            organizer = ""

        # Decide how far to expand based on the component
        doInstanceIndexing = False
        master = calendar.masterComponent()
        if master is None or not calendar.isRecurring():
            # When there is no master we have a set of overridden components - index them all.
            # When there is one instance - index it.
            expand = DateTime(2100, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
            doInstanceIndexing = True
        else:
            # If migrating or re-creating or config option for delayed indexing is off, always index
            if reCreate or not config.FreeBusyIndexDelayedExpand:
                doInstanceIndexing = True

            # Duration into the future through which recurrences are expanded in the index
            # by default.  This is a caching parameter which affects the size of the index;
            # it does not affect search results beyond this period, but it may affect
            # performance of such a search.
            expand = (DateTime.getToday() +
                      Duration(days=config.FreeBusyIndexExpandAheadDays))

            if expand_until and expand_until > expand:
                expand = expand_until

            # Maximum duration into the future through which recurrences are expanded in the
            # index.  This is a caching parameter which affects the size of the index; it
            # does not affect search results beyond this period, but it may affect
            # performance of such a search.
            #
            # When a search is performed on a time span that goes beyond that which is
            # expanded in the index, we have to open each resource which may have data in
            # that time period.  In order to avoid doing that multiple times, we want to
            # cache those results.  However, we don't necessarily want to cache all
            # occurrences into some obscenely far-in-the-future date, so we cap the caching
            # period.  Searches beyond this period will always be relatively expensive for
            # resources with occurrences beyond this period.
            if expand > (DateTime.getToday() +
                         Duration(days=config.FreeBusyIndexExpandMaxDays)):
                raise IndexedSearchException()

        # Always do recurrence expansion even if we do not intend to index - we need this to double-check the
        # validity of the iCalendar recurrence data.
        try:
            instances = calendar.expandTimeRanges(
                expand, ignoreInvalidInstances=reCreate)
            recurrenceLimit = instances.limit
        except InvalidOverriddenInstanceError, e:
            log.error("Invalid instance %s when indexing %s in %s" % (
                e.rid,
                name,
                self.resource,
            ))
            raise
Пример #18
0
 def __init__(self):
     Timezone.sDefaultTimezone = Timezone()
Пример #19
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)
Пример #20
0
def _internalGenerateFreeBusyInfo(
    calresource,
    fbinfo,
    timerange,
    matchtotal,
    excludeuid=None,
    organizer=None,
    organizerPrincipal=None,
    same_calendar_user=False,
    servertoserver=False,
    event_details=None,
    logItems=None,
    accountingItems=None,
):
    """
    Run a free busy report on the specified calendar collection
    accumulating the free busy info for later processing.
    @param calresource: the L{Calendar} for a calendar collection.
    @param fbinfo:      the array of busy periods to update.
    @param timerange:   the L{TimeRange} for the query.
    @param matchtotal:  the running total for the number of matches.
    @param excludeuid:  a C{str} containing a UID value to exclude any
        components with that UID from contributing to free-busy.
    @param organizer:   a C{str} containing the value of the ORGANIZER property
        in the VFREEBUSY request.  This is used in conjunction with the UID
        value to process exclusions.
    @param same_calendar_user: a C{bool} indicating whether the calendar user
        requesting the free-busy information is the same as the calendar user
        being targeted.
    @param servertoserver: a C{bool} indicating whether we are doing a local or
        remote lookup request.
    @param event_details: a C{list} into which to store extended VEVENT details if not C{None}
    @param logItems: a C{dict} to store logging info to
    @param accountingItems: a C{dict} to store accounting info to
    """

    # First check the privilege on this collection
    # TODO: for server-to-server we bypass this right now as we have no way to authorize external users.
    # TODO: actually we by pass altogether by assuming anyone can check anyone else's freebusy

    # May need organizer principal
    organizer_record = (yield calresource.directoryService(
    ).recordWithCalendarUserAddress(organizer)) if organizer else None
    organizer_uid = organizer_record.uid if organizer_record else ""

    # Free busy is per-user
    attendee_uid = calresource.viewerHome().uid()
    attendee_record = yield calresource.directoryService().recordWithUID(
        attendee_uid.decode("utf-8"))

    # Get the timezone property from the collection.
    tz = calresource.getTimezone()

    # Look for possible extended free busy information
    rich_options = {
        "organizer": False,
        "delegate": False,
        "resource": False,
    }
    do_event_details = False
    if event_details is not None and organizer_record is not None and attendee_record is not None:

        # Get the principal of the authorized user which may be different from the organizer if a delegate of
        # the organizer is making the request
        authz_uid = organizer_uid
        authz_record = organizer_record
        if calresource._txn._authz_uid is not None and calresource._txn._authz_uid != organizer_uid:
            authz_uid = calresource._txn._authz_uid
            authz_record = yield calresource.directoryService().recordWithUID(
                authz_uid.decode("utf-8"))

        # Check if attendee is also the organizer or the delegate doing the request
        if attendee_uid in (organizer_uid, authz_uid):
            do_event_details = True
            rich_options["organizer"] = True

        # Check if authorized user is a delegate of attendee
        proxy = (yield authz_record.isProxyFor(attendee_record))
        if config.Scheduling.Options.DelegeteRichFreeBusy and proxy:
            do_event_details = True
            rich_options["delegate"] = True

        # Check if attendee is room or resource
        if config.Scheduling.Options.RoomResourceRichFreeBusy and attendee_record.getCUType(
        ) in (
                "RESOURCE",
                "ROOM",
        ):
            do_event_details = True
            rich_options["resource"] = True

    # Try cache
    resources = (yield FBCacheEntry.getCacheEntry(
        calresource, attendee_uid,
        timerange)) if config.EnableFreeBusyCache else None

    if resources is None:

        if accountingItems is not None:
            accountingItems["fb-uncached"] = accountingItems.get(
                "fb-uncached", 0) + 1

        caching = False
        if config.EnableFreeBusyCache:
            # Log extended item
            if logItems is not None:
                logItems["fb-uncached"] = logItems.get("fb-uncached", 0) + 1

            # We want to cache a large range of time based on the current date
            cache_start = normalizeToUTC(DateTime.getToday() + Duration(
                days=0 - config.FreeBusyCacheDaysBack))
            cache_end = normalizeToUTC(DateTime.getToday() + Duration(
                days=config.FreeBusyCacheDaysForward))

            # If the requested time range would fit in our allowed cache range, trigger the cache creation
            if compareDateTime(timerange.start,
                               cache_start) >= 0 and compareDateTime(
                                   timerange.end, cache_end) <= 0:
                cache_timerange = TimeRange(start=cache_start.getText(),
                                            end=cache_end.getText())
                caching = True

        #
        # What we do is a fake calendar-query for VEVENT/VFREEBUSYs in the specified time-range.
        # We then take those results and merge them into one VFREEBUSY component
        # with appropriate FREEBUSY properties, and return that single item as iCal data.
        #

        # Create fake filter element to match time-range
        filter = caldavxml.Filter(
            caldavxml.ComponentFilter(
                caldavxml.ComponentFilter(
                    cache_timerange if caching else timerange,
                    name=("VEVENT", "VFREEBUSY", "VAVAILABILITY"),
                ),
                name="VCALENDAR",
            ))
        filter = Filter(filter)
        tzinfo = filter.settimezone(tz)
        if accountingItems is not None:
            tr = cache_timerange if caching else timerange
            accountingItems["fb-query-timerange"] = (
                str(tr.start),
                str(tr.end),
            )

        try:
            resources = yield calresource.search(filter,
                                                 useruid=attendee_uid,
                                                 fbtype=True)
            if caching:
                yield FBCacheEntry.makeCacheEntry(calresource, attendee_uid,
                                                  cache_timerange, resources)
        except IndexedSearchException:
            raise InternalDataStoreError("Invalid indexedSearch query")

    else:
        if accountingItems is not None:
            accountingItems["fb-cached"] = accountingItems.get("fb-cached",
                                                               0) + 1

        # Log extended item
        if logItems is not None:
            logItems["fb-cached"] = logItems.get("fb-cached", 0) + 1

        # Determine appropriate timezone (UTC is the default)
        tzinfo = tz.gettimezone() if tz is not None else Timezone(utc=True)

    # We care about separate instances for VEVENTs only
    aggregated_resources = {}
    for name, uid, type, test_organizer, float, start, end, fbtype, transp in resources:
        if transp == 'T' and fbtype != '?':
            fbtype = 'F'
        aggregated_resources.setdefault((
            name,
            uid,
            type,
            test_organizer,
        ), []).append((
            float,
            start,
            end,
            fbtype,
        ))

    if accountingItems is not None:
        accountingItems["fb-resources"] = {}
        for k, v in aggregated_resources.items():
            name, uid, type, test_organizer = k
            accountingItems["fb-resources"][uid] = []
            for float, start, end, fbtype in v:
                fbstart = parseSQLTimestampToPyCalendar(start)
                if float == 'Y':
                    fbstart.setTimezone(tzinfo)
                else:
                    fbstart.setTimezone(Timezone(utc=True))
                fbend = parseSQLTimestampToPyCalendar(end)
                if float == 'Y':
                    fbend.setTimezone(tzinfo)
                else:
                    fbend.setTimezone(Timezone(utc=True))
                accountingItems["fb-resources"][uid].append((
                    float,
                    str(fbstart),
                    str(fbend),
                    fbtype,
                ))

    # Cache directory record lookup outside this loop as it is expensive and will likely
    # always end up being called with the same organizer address.
    recordUIDCache = {}
    for key in aggregated_resources.iterkeys():

        name, uid, type, test_organizer = key

        # Short-cut - if an fbtype exists we can use that
        if type == "VEVENT" and aggregated_resources[key][0][3] != '?':

            matchedResource = False

            # Look at each instance
            for float, start, end, fbtype in aggregated_resources[key]:
                # Ignore free time or unknown
                if fbtype in ('F', '?'):
                    continue

                # Ignore ones of this UID
                if excludeuid:
                    # See if we have a UID match
                    if (excludeuid == uid):
                        if test_organizer:
                            test_uid = recordUIDCache.get(test_organizer)
                            if test_uid is None:
                                test_record = (yield
                                               calresource.directoryService(
                                               ).recordWithCalendarUserAddress(
                                                   test_organizer))
                                test_uid = test_record.uid if test_record else ""
                                recordUIDCache[test_organizer] = test_uid
                        else:
                            test_uid = ""

                        # Check that ORGANIZER's match (security requirement)
                        if (organizer is None) or (organizer_uid == test_uid):
                            continue
                        # Check for no ORGANIZER and check by same calendar user
                        elif (test_uid == "") and same_calendar_user:
                            continue

                # Apply a timezone to any floating times
                fbstart = parseSQLTimestampToPyCalendar(start)
                if float == 'Y':
                    fbstart.setTimezone(tzinfo)
                else:
                    fbstart.setTimezone(Timezone(utc=True))
                fbend = parseSQLTimestampToPyCalendar(end)
                if float == 'Y':
                    fbend.setTimezone(tzinfo)
                else:
                    fbend.setTimezone(Timezone(utc=True))

                # Clip instance to time range
                clipped = clipPeriod(Period(fbstart, duration=fbend - fbstart),
                                     Period(timerange.start, timerange.end))

                # Double check for overlap
                if clipped:
                    matchedResource = True
                    fbinfo[fbtype_index_mapper.get(fbtype, 0)].append(clipped)

            if matchedResource:
                # Check size of results is within limit
                matchtotal += 1
                if matchtotal > config.MaxQueryWithDataResults:
                    raise QueryMaxResources(config.MaxQueryWithDataResults,
                                            matchtotal)

                # Add extended details
                if do_event_details:
                    child = (yield calresource.calendarObjectWithName(name))
                    # Only add fully public events
                    if not child.accessMode or child.accessMode == Component.ACCESS_PUBLIC:
                        calendar = (yield child.componentForUser())
                        _addEventDetails(calendar, event_details, rich_options,
                                         timerange, tzinfo)

        else:
            child = (yield calresource.calendarObjectWithName(name))
            calendar = (yield child.componentForUser())

            # The calendar may come back as None if the resource is being changed, or was deleted
            # between our initial index query and getting here. For now we will ignore this error, but in
            # the longer term we need to implement some form of locking, perhaps.
            if calendar is None:
                log.error(
                    "Calendar %s is missing from calendar collection %r" %
                    (name, calresource))
                continue

            # Ignore ones of this UID
            if excludeuid:
                # See if we have a UID match
                if (excludeuid == uid):
                    test_organizer = calendar.getOrganizer()
                    if test_organizer:
                        test_uid = recordUIDCache.get(test_organizer)
                        if test_uid is None:
                            test_record = (yield calresource.directoryService(
                            ).recordWithCalendarUserAddress(test_organizer))
                            test_uid = test_record.uid if test_record else ""
                            recordUIDCache[test_organizer] = test_uid
                    else:
                        test_uid = ""

                    # Check that ORGANIZER's match (security requirement)
                    if (organizer is None) or (organizer_uid == test_uid):
                        continue
                    # Check for no ORGANIZER and check by same calendar user
                    elif (test_organizer is None) and same_calendar_user:
                        continue

            if accountingItems is not None:
                accountingItems.setdefault("fb-filter-match", []).append(uid)

            if filter.match(calendar, None):
                if accountingItems is not None:
                    accountingItems.setdefault("fb-filter-matched",
                                               []).append(uid)

                # Check size of results is within limit
                matchtotal += 1
                if matchtotal > config.MaxQueryWithDataResults:
                    raise QueryMaxResources(config.MaxQueryWithDataResults,
                                            matchtotal)

                if calendar.mainType() == "VEVENT":
                    processEventFreeBusy(calendar, fbinfo, timerange, tzinfo)
                elif calendar.mainType() == "VFREEBUSY":
                    processFreeBusyFreeBusy(calendar, fbinfo, timerange)
                elif calendar.mainType() == "VAVAILABILITY":
                    processAvailabilityFreeBusy(calendar, fbinfo, timerange)
                else:
                    assert "Free-busy query returned unwanted component: %s in %r", (
                        name,
                        calresource,
                    )

                # Add extended details
                if calendar.mainType() == "VEVENT" and do_event_details:
                    child = (yield calresource.calendarObjectWithName(name))
                    # Only add fully public events
                    if not child.accessMode or child.accessMode == Component.ACCESS_PUBLIC:
                        calendar = (yield child.componentForUser())
                        _addEventDetails(calendar, event_details, rich_options,
                                         timerange, tzinfo)

    returnValue(matchtotal)
Пример #21
0
    def testSetWeekNo(self):

        dt = DateTime(2013, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
        self.assertEqual(dt.getWeekNo(), 1)
        dt.setWeekNo(1)
        self.assertEqual(
            dt, DateTime(2013, 1, 1, 0, 0, 0, tzid=Timezone(utc=True)))
        self.assertEqual(dt.getWeekNo(), 1)

        dt = DateTime(2013, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
        self.assertEqual(dt.getWeekNo(), 1)
        dt.setWeekNo(2)
        self.assertEqual(
            dt, DateTime(2013, 1, 8, 0, 0, 0, tzid=Timezone(utc=True)))
        self.assertEqual(dt.getWeekNo(), 2)

        dt = DateTime(2013, 1, 8, 0, 0, 0, tzid=Timezone(utc=True))
        self.assertEqual(dt.getWeekNo(), 2)
        dt.setWeekNo(1)
        self.assertEqual(
            dt, DateTime(2013, 1, 1, 0, 0, 0, tzid=Timezone(utc=True)))
        self.assertEqual(dt.getWeekNo(), 1)

        dt = DateTime(2014, 1, 7, 0, 0, 0, tzid=Timezone(utc=True))
        self.assertEqual(dt.getWeekNo(), 2)
        dt.setWeekNo(1)
        self.assertEqual(
            dt, DateTime(2013, 12, 31, 0, 0, 0, tzid=Timezone(utc=True)))
        self.assertEqual(dt.getWeekNo(), 1)

        dt = DateTime(2012, 12, 31, 0, 0, 0, tzid=Timezone(utc=True))
        self.assertEqual(dt.getWeekNo(), 1)
        dt.setWeekNo(1)
        self.assertEqual(
            dt, DateTime(2012, 12, 31, 0, 0, 0, tzid=Timezone(utc=True)))
        self.assertEqual(dt.getWeekNo(), 1)

        dt = DateTime(2016, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
        self.assertEqual(dt.getWeekNo(), 53)
        dt.setWeekNo(1)
        self.assertEqual(
            dt, DateTime(2016, 1, 8, 0, 0, 0, tzid=Timezone(utc=True)))
        self.assertEqual(dt.getWeekNo(), 1)
        dt.setWeekNo(2)
        self.assertEqual(
            dt, DateTime(2016, 1, 15, 0, 0, 0, tzid=Timezone(utc=True)))
        self.assertEqual(dt.getWeekNo(), 2)

        dt = DateTime(2016, 1, 8, 0, 0, 0, tzid=Timezone(utc=True))
        self.assertEqual(dt.getWeekNo(), 1)
        dt.setWeekNo(1)
        self.assertEqual(
            dt, DateTime(2016, 1, 8, 0, 0, 0, tzid=Timezone(utc=True)))
        self.assertEqual(dt.getWeekNo(), 1)
Пример #22
0
    def testMonthlyInUTC(self):

        recur = Recurrence()
        recur.parse("FREQ=MONTHLY")
        start = DateTime(2014, 1, 1, 12, 0, 0, tzid=Timezone(utc=True))
        end = DateTime(2015, 1, 1, 0, 0, 0, tzid=Timezone(utc=True))
        items = []
        range = Period(start, end)
        recur.expand(
            DateTime(2014,
                     1,
                     1,
                     12,
                     0,
                     0,
                     tzid=Timezone(tzid="America/New_York")), range, items)
        self.assertEqual(
            items,
            [
                DateTime(2014,
                         1,
                         1,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         2,
                         1,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         3,
                         1,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         4,
                         1,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         5,
                         1,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         6,
                         1,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         7,
                         1,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         8,
                         1,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         9,
                         1,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         10,
                         1,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         11,
                         1,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
                DateTime(2014,
                         12,
                         1,
                         12,
                         0,
                         0,
                         tzid=Timezone(tzid="America/New_York")),
            ],
        )
Пример #23
0
    def testConversions(self):

        tzdata = """BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//calendarserver.org//Zonal//EN
BEGIN:VTIMEZONE
TZID:America/New_York
X-LIC-LOCATION:America/New_York
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
BEGIN:VTIMEZONE
TZID:America/Los_Angeles
X-LIC-LOCATION:America/Los_Angeles
BEGIN:STANDARD
DTSTART:18831118T120702
RDATE:18831118T120702
TZNAME:PST
TZOFFSETFROM:-075258
TZOFFSETTO:-0800
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19180331T020000
RRULE:FREQ=YEARLY;UNTIL=19190330T100000Z;BYDAY=-1SU;BYMONTH=3
TZNAME:PDT
TZOFFSETFROM:-0800
TZOFFSETTO:-0700
END:DAYLIGHT
BEGIN:STANDARD
DTSTART:19181027T020000
RRULE:FREQ=YEARLY;UNTIL=19191026T090000Z;BYDAY=-1SU;BYMONTH=10
TZNAME:PST
TZOFFSETFROM:-0700
TZOFFSETTO:-0800
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19420209T020000
RDATE:19420209T020000
TZNAME:PWT
TZOFFSETFROM:-0800
TZOFFSETTO:-0700
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:19450814T160000
RDATE:19450814T160000
TZNAME:PPT
TZOFFSETFROM:-0700
TZOFFSETTO:-0700
END:DAYLIGHT
BEGIN:STANDARD
DTSTART:19450930T020000
RDATE:19450930T020000
RDATE:19490101T020000
TZNAME:PST
TZOFFSETFROM:-0700
TZOFFSETTO:-0800
END:STANDARD
BEGIN:STANDARD
DTSTART:19460101T000000
RDATE:19460101T000000
RDATE:19670101T000000
TZNAME:PST
TZOFFSETFROM:-0800
TZOFFSETTO:-0800
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19480314T020000
RDATE:19480314T020000
RDATE:19740106T020000
RDATE:19750223T020000
TZNAME:PDT
TZOFFSETFROM:-0800
TZOFFSETTO:-0700
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:19500430T020000
RRULE:FREQ=YEARLY;UNTIL=19660424T100000Z;BYDAY=-1SU;BYMONTH=4
TZNAME:PDT
TZOFFSETFROM:-0800
TZOFFSETTO:-0700
END:DAYLIGHT
BEGIN:STANDARD
DTSTART:19500924T020000
RRULE:FREQ=YEARLY;UNTIL=19610924T090000Z;BYDAY=-1SU;BYMONTH=9
TZNAME:PST
TZOFFSETFROM:-0700
TZOFFSETTO:-0800
END:STANDARD
BEGIN:STANDARD
DTSTART:19621028T020000
RRULE:FREQ=YEARLY;UNTIL=19661030T090000Z;BYDAY=-1SU;BYMONTH=10
TZNAME:PST
TZOFFSETFROM:-0700
TZOFFSETTO:-0800
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19670430T020000
RRULE:FREQ=YEARLY;UNTIL=19730429T100000Z;BYDAY=-1SU;BYMONTH=4
TZNAME:PDT
TZOFFSETFROM:-0800
TZOFFSETTO:-0700
END:DAYLIGHT
BEGIN:STANDARD
DTSTART:19671029T020000
RRULE:FREQ=YEARLY;UNTIL=20061029T090000Z;BYDAY=-1SU;BYMONTH=10
TZNAME:PST
TZOFFSETFROM:-0700
TZOFFSETTO:-0800
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19760425T020000
RRULE:FREQ=YEARLY;UNTIL=19860427T100000Z;BYDAY=-1SU;BYMONTH=4
TZNAME:PDT
TZOFFSETFROM:-0800
TZOFFSETTO:-0700
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:19870405T020000
RRULE:FREQ=YEARLY;UNTIL=20060402T100000Z;BYDAY=1SU;BYMONTH=4
TZNAME:PDT
TZOFFSETFROM:-0800
TZOFFSETTO:-0700
END:DAYLIGHT
BEGIN:DAYLIGHT
DTSTART:20070311T020000
RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
TZNAME:PDT
TZOFFSETFROM:-0800
TZOFFSETTO:-0700
END:DAYLIGHT
BEGIN:STANDARD
DTSTART:20071104T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
TZNAME:PST
TZOFFSETFROM:-0700
TZOFFSETTO:-0800
END:STANDARD
END:VTIMEZONE
END:VCALENDAR
"""
        data = (
            (
                DateTime(2014, 3, 8, 23, 0, 0,
                         Timezone(tzid="America/New_York")),
                DateTime(2014, 3, 8, 20, 0, 0,
                         Timezone(tzid="America/Los_Angeles")),
            ),
            (
                DateTime(2014, 3, 9, 3, 0, 0, Timezone(utc=True)),
                DateTime(2014, 3, 8, 19, 0, 0,
                         Timezone(tzid="America/Los_Angeles")),
            ),
            (
                DateTime(2014, 3, 9, 13, 0, 0, Timezone(utc=True)),
                DateTime(2014, 3, 9, 6, 0, 0,
                         Timezone(tzid="America/Los_Angeles")),
            ),
        )

        Calendar.parseText(tzdata.replace("\n", "\r\n"))

        for dtfrom, dtto in data:

            self.assertEqual(dtfrom, dtto)

            newdtfrom = dtfrom.duplicate()
            newdtfrom.adjustTimezone(dtto.getTimezone())
            self.assertEqual(newdtfrom, dtto)
            self.assertEqual(newdtfrom.getHours(), dtto.getHours())