def testParse(self): for item in TestRecurrence.items: recur = Recurrence() recur.parse(item) self.assertEqual(recur.getText(), item, "Failed to parse and re-generate '%s'" % (item, ))
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")), ], )
def testHash(self): hashes = [] for item in TestRecurrence.items: recur = Recurrence() recur.parse(item) hashes.append(hash(recur)) hashes.sort() for i in range(1, len(hashes)): self.assertNotEqual(hashes[i - 1], hashes[i])
def testInequality(self): recur1 = Recurrence() recur1.parse("FREQ=YEARLY;COUNT=400") recur2 = Recurrence() recur2.parse("COUNT=400;FREQ=YEARLY;BYMONTH=1") self.assertNotEqual(recur1, recur2)
def instances(start, rrule): """ Expand an RRULE. """ recur = Recurrence() recur.parse(rrule) start = DateTime.parseText(start) end = start.duplicate() end.offsetYear(100) items = [] range = Period(start, end) recur.expand(start, range, items) print("DTSTART:{}".format(start)) print("RRULE:{}".format(rrule)) print("Instances: {}".format(", ".join(map(str, items))))
def testExampleRules(self): examples = os.path.join(os.path.dirname(__file__), "rrule_examples.json") with open(examples) as f: examples = json.loads(f.read()) for ctr, i in enumerate(examples): recur = Recurrence() recur.parse(i["rule"]) start = DateTime.parseText(i["start"]) end = DateTime.parseText(i["end"]) results = map(DateTime.parseText, i["results"]) items = [] range = Period(start, end) recur.expand(start, range, items) self.assertEqual( items, results, msg="Failed rule: #{} {}".format(ctr + 1, i["rule"]) )
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")), ], )
def testParseInvalid(self): items = ( "", "FREQ=", "FREQ=MICROSECONDLY", "FREQ=YEARLY;COUNT=ABC", "FREQ=YEARLY;COUNT=123;UNTIL=20110102", "FREQ=MONTHLY;UNTIL=20110102T", "FREQ=MONTHLY;UNTIL=20110102t090000", "FREQ=MONTHLY;UNTIL=20110102T100000z", "FREQ=MONTHLY;UNTIL=20110102TAABBCCz", "FREQ=MONTHLY;BYDAY=A", "FREQ=MONTHLY;BYDAY=+1,3MO", "FREQ=MONTHLY;BYHOUR=A", "FREQ=MONTHLY;BYHOUR=54", ) for item in items: self.assertRaises(ValueError, Recurrence().parse, item)
def testExampleRules(self): examples = os.path.join(os.path.dirname(__file__), "rrule_examples.json") with open(examples) as f: examples = json.loads(f.read()) for ctr, i in enumerate(examples): recur = Recurrence() recur.parse(i["rule"]) start = DateTime.parseText(i["start"]) end = DateTime.parseText(i["end"]) results = map(DateTime.parseText, i["results"]) items = [] range = Period(start, end) recur.expand(start, range, items) self.assertEqual(items, results, msg="Failed rule: #{} {}".format( ctr + 1, i["rule"]))
def testClearOnChange(self): recur = Recurrence() recur.parse("FREQ=DAILY") start = DateTime(2013, 1, 1, 0, 0, 0) end = DateTime(2017, 1, 1, 0, 0, 0) range = Period(start, end) items = [] recur.expand(start, range, items) self.assertTrue(recur.mCached) self.assertTrue(len(items) > 100) recur.setUseCount(True) recur.setCount(10) self.assertFalse(recur.mCached) items = [] recur.expand(start, range, items) self.assertEqual(len(items), 10)
def vtimezone(self, vtz, zonerule, start, end, offsetfrom, offsetto, instanceCount): """ Generate a VTIMEZONE sub-component for this Rule. @param vtz: VTIMEZONE to add to @type vtz: L{VTimezone} @param zonerule: the Zone rule line being used @type zonerule: L{ZoneRule} @param start: the start time for the first instance @type start: L{DateTime} @param end: the start time for the last instance @type end: L{DateTime} @param offsetfrom: the UTC offset-from @type offsetfrom: C{int} @param offsetto: the UTC offset-to @type offsetto: C{int} @param instanceCount: the number of instances in the set @type instanceCount: C{int} """ # Determine type of component based on offset dstoffset = self.getOffset() if dstoffset == 0: comp = Standard(parent=vtz) else: comp = Daylight(parent=vtz) # Do offsets tzoffsetfrom = UTCOffsetValue(offsetfrom) tzoffsetto = UTCOffsetValue(offsetto) comp.addProperty(Property(definitions.cICalProperty_TZOFFSETFROM, tzoffsetfrom)) comp.addProperty(Property(definitions.cICalProperty_TZOFFSETTO, tzoffsetto)) # Do TZNAME if zonerule.format.find("%") != -1: tzname = zonerule.format % (self.letter if self.letter != "-" else "",) else: tzname = zonerule.format comp.addProperty(Property(definitions.cICalProperty_TZNAME, tzname)) # Do DTSTART comp.addProperty(Property(definitions.cICalProperty_DTSTART, start)) # Now determine the recurrences (use RDATE if only one year or # number of instances is one) if self.toYear != "only" and instanceCount != 1: rrule = Recurrence() rrule.setFreq(definitions.eRecurrence_YEARLY) rrule.setByMonth((Rule.MONTH_NAME_TO_POS[self.inMonth],)) if self.onDay in Rule.LASTDAY_NAME_TO_RDAY: # Need to check whether day has changed due to time shifting dayOfWeek = start.getDayOfWeek() indicatedDay = Rule.LASTDAY_NAME_TO_DAY[self.onDay] if dayOfWeek == indicatedDay: rrule.setByDay(((-1, Rule.LASTDAY_NAME_TO_RDAY[self.onDay]),)) elif dayOfWeek < indicatedDay or dayOfWeek == 6 and indicatedDay == 0: # This is OK as we have moved back a day and thus no month transition # could have occurred fakeOffset = daysInMonth(start.getMonth(), start.getYear()) - 6 offset, rday, bymday = self.getOnDayDetails(start, indicatedDay, fakeOffset) if bymday: rrule.setByMonthDay(bymday) rrule.setByDay(((offset, rday),)) else: # This is bad news as we have moved forward a day possibly into the next month # What we do is switch to using a BYYEARDAY rule with offset from the end of the year rrule.setByMonth(()) daysBackStartOfMonth = ( 365, 334, 306, 275, 245, 214, 184, 153, 122, 92, 61, 31, 0 # Does not account for leap year ) rrule.setByYearDay([-(daysBackStartOfMonth[Rule.MONTH_NAME_TO_POS[self.inMonth]] + i) for i in range(7)]) rrule.setByDay( ((0, divmod(Rule.LASTDAY_NAME_TO_DAY[self.onDay] + 1, 7)[1]),), ) elif self.onDay.find(">=") != -1: indicatedDay, dayoffset = self.onDay.split(">=") # Need to check whether day has changed due to time shifting dayOfWeek = start.getDayOfWeek() indicatedDay = Rule.DAY_NAME_TO_DAY[indicatedDay] if dayOfWeek == indicatedDay: offset, rday, bymday = self.getOnDayDetails(start, indicatedDay, int(dayoffset)) if bymday: rrule.setByMonthDay(bymday) rrule.setByDay(((offset, rday),)) elif dayoffset == 1 and divmod(dayoffset - indicatedDay, 7)[1] == 6: # This is bad news as we have moved backward a day possibly into the next month # What we do is switch to using a BYYEARDAY rule with offset from the end of the year rrule.setByMonth(()) daysBackStartOfMonth = ( 365, 334, 306, 275, 245, 214, 184, 153, 122, 92, 61, 31, 0 # Does not account for leap year ) rrule.setByYearDay([-(daysBackStartOfMonth[Rule.MONTH_NAME_TO_POS[self.inMonth]] + i) for i in range(7)]) rrule.setByDay( ((0, divmod(indicatedDay + 1, 7)[1]),), ) else: # This is OK as we have moved forward a day and thus no month transition # could have occurred offset, rday, bymday = self.getOnDayDetails(start, indicatedDay, int(dayoffset)) if bymday: rrule.setByMonthDay(bymday) rrule.setByDay(((offset, rday),)) else: try: int(self.onDay) except: assert False, "onDay value is not recognized: %s" % (self.onDay,) # Add any UNTIL if zonerule.getUntilDate().dt.getYear() < 9999 or self.endYear() < 9999: until = end.duplicate() until.offsetSeconds(-offsetfrom) until.setTimezoneUTC(True) rrule.setUseUntil(True) rrule.setUntil(until) comp.addProperty(Property(definitions.cICalProperty_RRULE, rrule)) else: comp.addProperty(Property(definitions.cICalProperty_RDATE, start)) comp.finalise() vtz.addComponent(comp)
def testParse(self): for item in TestRecurrence.items: recur = Recurrence() recur.parse(item) self.assertEqual(recur.getText(), item, "Failed to parse and re-generate '%s'" % (item,))
def vtimezone(self, vtz, zonerule, start, end, offsetfrom, offsetto, instanceCount): """ Generate a VTIMEZONE sub-component for this Rule. @param vtz: VTIMEZONE to add to @type vtz: L{VTimezone} @param zonerule: the Zone rule line being used @type zonerule: L{ZoneRule} @param start: the start time for the first instance @type start: L{DateTime} @param end: the start time for the last instance @type end: L{DateTime} @param offsetfrom: the UTC offset-from @type offsetfrom: C{int} @param offsetto: the UTC offset-to @type offsetto: C{int} @param instanceCount: the number of instances in the set @type instanceCount: C{int} """ # Determine type of component based on offset dstoffset = self.getOffset() if dstoffset == 0: comp = Standard(parent=vtz) else: comp = Daylight(parent=vtz) # Do offsets tzoffsetfrom = UTCOffsetValue(offsetfrom) tzoffsetto = UTCOffsetValue(offsetto) comp.addProperty(Property(definitions.cICalProperty_TZOFFSETFROM, tzoffsetfrom)) comp.addProperty(Property(definitions.cICalProperty_TZOFFSETTO, tzoffsetto)) # Do TZNAME if zonerule.format.find("%") != -1: tzname = zonerule.format % (self.letter if self.letter != "-" else "",) else: tzname = zonerule.format comp.addProperty(Property(definitions.cICalProperty_TZNAME, tzname)) # Do DTSTART comp.addProperty(Property(definitions.cICalProperty_DTSTART, start)) # Now determine the recurrences (use RDATE if only one year or # number of instances is one) if self.toYear != "only" and instanceCount != 1: rrule = Recurrence() rrule.setFreq(definitions.eRecurrence_YEARLY) rrule.setByMonth((Rule.MONTH_NAME_TO_POS[self.inMonth],)) if self.onDay in Rule.LASTDAY_NAME_TO_RDAY: # Need to check whether day has changed due to time shifting dayOfWeek = start.getDayOfWeek() indicatedDay = Rule.LASTDAY_NAME_TO_DAY[self.onDay] if dayOfWeek == indicatedDay: rrule.setByDay(((-1, Rule.LASTDAY_NAME_TO_RDAY[self.onDay]),)) elif dayOfWeek < indicatedDay or dayOfWeek == 6 and indicatedDay == 0: # This is OK as we have moved back a day and thus no month transition # could have occurred fakeOffset = daysInMonth(start.getMonth(), start.getYear()) - 6 offset, rday, bymday = self.getOnDayDetails(start, indicatedDay, fakeOffset) if bymday: rrule.setByMonthDay(bymday) rrule.setByDay(((offset, rday),)) else: # This is bad news as we have moved forward a day possibly into the next month # What we do is switch to using a BYYEARDAY rule with offset from the end of the year rrule.setByMonth(()) daysBackStartOfMonth = ( 365, 334, 306, 275, 245, 214, 184, 153, 122, 92, 61, 31, 0 # Does not account for leap year ) rrule.setByYearDay([-(daysBackStartOfMonth[Rule.MONTH_NAME_TO_POS[self.inMonth]] + i) for i in range(7)]) rrule.setByDay( ((0, divmod(Rule.LASTDAY_NAME_TO_DAY[self.onDay] + 1, 7)[1]),), ) elif self.onDay.find(">=") != -1: indicatedDay, dayoffset = self.onDay.split(">=") # Need to check whether day has changed due to time shifting dayOfWeek = start.getDayOfWeek() indicatedDay = Rule.DAY_NAME_TO_DAY[indicatedDay] if dayOfWeek == indicatedDay: offset, rday, bymday = self.getOnDayDetails(start, indicatedDay, int(dayoffset)) if bymday: rrule.setByMonthDay(bymday) rrule.setByDay(((offset, rday),)) elif dayoffset == 1 and divmod(dayoffset - indicatedDay, 7)[1] == 6: # This is bad news as we have moved backward a day possibly into the next month # What we do is switch to using a BYYEARDAY rule with offset from the end of the year rrule.setByMonth(()) daysBackStartOfMonth = ( 365, 334, 306, 275, 245, 214, 184, 153, 122, 92, 61, 31, 0 # Does not account for leap year ) rrule.setByYearDay([-(daysBackStartOfMonth[Rule.MONTH_NAME_TO_POS[self.inMonth]] + i) for i in range(7)]) rrule.setByDay( ((0, divmod(indicatedDay + 1, 7)[1]),), ) else: # This is OK as we have moved forward a day and thus no month transition # could have occurred offset, rday, bymday = self.getOnDayDetails(start, indicatedDay, int(dayoffset)) if bymday: rrule.setByMonthDay(bymday) rrule.setByDay(((offset, rday),)) else: try: _ignore_day = int(self.onDay) except: assert False, "onDay value is not recognized: %s" % (self.onDay,) # Add any UNTIL if zonerule.getUntilDate().dt.getYear() < 9999 or self.endYear() < 9999: until = end.duplicate() until.offsetSeconds(-offsetfrom) until.setTimezoneUTC(True) rrule.setUseUntil(True) rrule.setUntil(until) comp.addProperty(Property(definitions.cICalProperty_RRULE, rrule)) else: comp.addProperty(Property(definitions.cICalProperty_RDATE, start)) comp.finalise() vtz.addComponent(comp)