def isMonthDay(self, day): if day > 0: return self.mDay == day elif day < 0: return self.mDay - 1 - utils.daysInMonth(self.mMonth, self.mYear) == day else: return False
def setDayOfWeekInMonth(self, offset, day, allow_invalid=False): # Set to first day in month self.mDay = 1 # Determine first weekday in month first_day = self.getDayOfWeek() if offset > 0: cycle = (offset - 1) * 7 + day cycle -= first_day if first_day > day: cycle += 7 self.mDay = cycle + 1 elif offset < 0: days_in_month = utils.daysInMonth(self.mMonth, self.mYear) first_day += days_in_month - 1 first_day %= 7 cycle = (-offset - 1) * 7 - day cycle += first_day if day > first_day: cycle += 7 self.mDay = days_in_month - cycle if not allow_invalid: self.normalise() else: self.changed()
def invalid(self): """ Are any of the current fields invalid. """ # Right now we only care about invalid days of the month (e.g. February 30th). In the # future we may also want to look for invalid times during a DST transition. if self.mDay <= 0 or self.mDay > utils.daysInMonth(self.mMonth, self.mYear): return True return False
def recur(self, freq, interval, allow_invalid=False): # Add appropriate interval normalize = True if freq == definitions.eRecurrence_SECONDLY: self.mSeconds += interval elif freq == definitions.eRecurrence_MINUTELY: self.mMinutes += interval elif freq == definitions.eRecurrence_HOURLY: self.mHours += interval elif freq == definitions.eRecurrence_DAILY: self.mDay += interval elif freq == definitions.eRecurrence_WEEKLY: self.mDay += 7 * interval elif freq == definitions.eRecurrence_MONTHLY: # Iterate until a valid day in the next month is found. # This can happen if adding one month to e.g. 31 January. # That is an undefined operation - does it mean 28/29 February # or 1/2 May, or 31 March or what? We choose to find the next month with # the same day number as the current one. self.mMonth += interval # Normalise month normalised_month = ((self.mMonth - 1) % 12) + 1 adjustment_year = (self.mMonth - 1) / 12 if (normalised_month - 1) < 0: normalised_month += 12 adjustment_year -= 1 self.mMonth = normalised_month self.mYear += adjustment_year if not allow_invalid: self.normalise() while self.mDay > utils.daysInMonth(self.mMonth, self.mYear): self.mMonth += interval self.normalise() normalize = False elif freq == definitions.eRecurrence_YEARLY: self.mYear += interval if allow_invalid: normalize = False if normalize: # Normalise to standard date-time ranges self.normalise() else: self.changed()
def setMonthDay(self, day, allow_invalid=False): # 1 .. 31 offset from start, or # -1 .. -31 offset from end if day > 0: # Offset current date to 1st of current month self.mDay = 1 # Increment day self.mDay += day - 1 elif day < 0: # Offset current date to last of month self.mDay = utils.daysInMonth(self.mMonth, self.mYear) # Decrement day self.mDay += day + 1 if not allow_invalid: # Normalise to get proper year/month/day values self.normalise() else: self.changed()
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 getOnDayDetails(self, start, indicatedDay, indicatedOffset): """ Get RRULE BYxxx part items from the Rule data. @param start: start date-time for the recurrence set @type start: L{DateTime} @param indicatedDay: the day that the Rule indicates for recurrence @type indicatedDay: C{int} @param indicatedOffset: the offset that the Rule indicates for recurrence @type indicatedOffset: C{int} """ month = start.getMonth() year = start.getYear() dayOfWeek = start.getDayOfWeek() # Need to check whether day has changed due to time shifting # e.g. if "u" mode is specified, the actual localtime may be # shifted to the previous day if the offset is negative if indicatedDay != dayOfWeek: difference = dayOfWeek - indicatedDay if difference in (1, -6,): indicatedOffset += 1 # Adjust the month down too if needed if start.getDay() == 1: month -= 1 if month < 1: month = 12 elif difference in (-1, 6,): assert indicatedOffset != 1, "Bad RRULE adjustment" indicatedOffset -= 1 else: assert False, "Unknown RRULE adjustment" try: # Form the appropriate RRULE bits day = Rule.DAY_NAME_TO_RDAY[dayOfWeek] offset = indicatedOffset bymday = None if offset == 1: offset = 1 elif offset == 8: offset = 2 elif offset == 15: offset = 3 elif offset == 22: offset = 4 else: days_in_month = daysInMonth(month, year) if days_in_month - offset == 6: offset = -1 elif days_in_month - offset == 13: offset = -2 elif days_in_month - offset == 20: offset = -3 else: bymday = [offset + i for i in range(7) if (offset + i) <= days_in_month] offset = 0 except: assert False, "onDay value is not recognized: %s" % (self.onDay,) return offset, day, bymday
def normalise(self): # Normalise seconds normalised_secs = self.mSeconds % 60 adjustment_mins = self.mSeconds / 60 if normalised_secs < 0: normalised_secs += 60 adjustment_mins -= 1 self.mSeconds = normalised_secs self.mMinutes += adjustment_mins # Normalise minutes normalised_mins = self.mMinutes % 60 adjustment_hours = self.mMinutes / 60 if normalised_mins < 0: normalised_mins += 60 adjustment_hours -= 1 self.mMinutes = normalised_mins self.mHours += adjustment_hours # Normalise hours normalised_hours = self.mHours % 24 adjustment_days = self.mHours / 24 if normalised_hours < 0: normalised_hours += 24 adjustment_days -= 1 self.mHours = normalised_hours self.mDay += adjustment_days # Wipe the time if date only if self.mDateOnly: self.mSeconds = self.mMinutes = self.mHours = 0 # Adjust the month first, since the day adjustment is month dependent # Normalise month normalised_month = ((self.mMonth - 1) % 12) + 1 adjustment_year = (self.mMonth - 1) / 12 if (normalised_month - 1) < 0: normalised_month += 12 adjustment_year -= 1 self.mMonth = normalised_month self.mYear += adjustment_year # Now do days if self.mDay > 0: while self.mDay > utils.daysInMonth(self.mMonth, self.mYear): self.mDay -= utils.daysInMonth(self.mMonth, self.mYear) self.mMonth += 1 if self.mMonth > 12: self.mMonth = 1 self.mYear += 1 else: while self.mDay <= 0: self.mMonth -= 1 if self.mMonth < 1: self.mMonth = 12 self.mYear -= 1 self.mDay += utils.daysInMonth(self.mMonth, self.mYear) # Always invalidate posix time cache self.changed()
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)