Here's a subclass of rrule
that incorporates two suggested python-dateutil
patches which enables rrule
output. Beware that there might be good reasons that the patches have not been accepted, and I have only tested this for the simplest cases. Line folding is not handled.
See the bug tracker for discussion:
https://bugs.launchpad.net/dateutil/+bug/943512
https://bugs.launchpad.net/dateutil/+bug/943509
FREQNAMES = ['YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY', 'HOURLY', 'MINUTELY', 'SECONDLY']
class ConvertibleRRule(rrule.rrule):
# Subclass of the `rrule class that provides a sensible __str__()
# method, outputting ical formatted rrules.
# Combined from the patches in these dateutil issues:
# https://bugs.launchpad.net/dateutil/+bug/943512
# https://bugs.launchpad.net/dateutil/+bug/943509
_bysecond_internal = False
_byminute_internal = False
_byhour = False
_bymonth_internal = False
_bymonthday_internal = False
_byweekday_internal = False
def __init__(self, freq, dtstart=None,
interval=1, wkst=None, count=None, until=None, bysetpos=None,
bymonth=None, bymonthday=None, byyearday=None, byeaster=None,
byweekno=None, byweekday=None,
byhour=None, byminute=None, bysecond=None,
cache=False):
super(ConvertibleRRule, self).__init__(
freq, dtstart=dtstart,
interval=interval, wkst=wkst, count=count, until=until, bysetpos=bysetpos,
bymonth=bymonth, bymonthday=bymonthday, byyearday=byyearday, byeaster=byeaster,
byweekno=byweekno, byweekday=byweekday,
byhour=byhour, byminute=byminute, bysecond=bysecond,
cache=cache)
if (byweekno is None and byyearday is None and bymonthday is None and
byweekday is None and byeaster is None):
if freq == rrule.YEARLY:
if not bymonth:
self._bymonth_internal = True
self._bymonthday_internal = True
elif freq == rrule.MONTHLY:
self._bymonthday_internal = True
elif freq == rrule.WEEKLY:
self._byweekday_internal = True
# byhour
if byhour is None:
if freq < rrule.HOURLY:
self._byhour_internal = True
# byminute
if byminute is None:
if freq < rrule.MINUTELY:
self._byminute_internal = True
# bysecond
if bysecond is None:
if freq < rrule.SECONDLY:
self._bysecond_internal = True
freq = property(lambda s: s._freq)
dtstart = property(lambda s: s._dtstart)
interval = property(lambda s: s._interval)
@property
def wkst(self):
if self._wkst == rrule.calendar.firstweekday():
return None
return rrule.weekday(self._wkst)
count = property(lambda s: s._count)
until = property(lambda s: s._until)
bysetpos = property(lambda s: s._bysetpos)
@property
def bymonth(self):
if self._bymonth_internal:
return None
return self._bymonth
@property
def bymonthday(self):
if self._bymonthday_internal:
return None
return self._bymonthday + self._bynmonthday
byyearday = property(lambda s: s._byyearday)
byeaster = property(lambda s: s._byeaster)
byweekno = property(lambda s: s._byweekno)
@property
def byweekday(self):
if self._byweekday_internal:
return None
bynweekday, byweekday = (), ()
if self._bynweekday:
bynweekday = tuple(rrule.weekday(d, n) for d, n in self._bynweekday)
if self._byweekday:
byweekday = tuple(rrule.weekday(d) for d in self._byweekday)
return bynweekday + byweekday
@property
def byhour(self):
if self._byhour_internal:
return None
return self._byhour
@property
def byminute(self):
if self._byminute_internal:
return None
return self._byminute
@property
def bysecond(self):
if self._bysecond_internal:
return None
return self._bysecond
def __str__(self):
parts = ['FREQ=' + FREQNAMES[self.freq]]
if self.interval != 1:
parts.append('INTERVAL=' + str(self.interval))
if self.wkst:
parts.append('WKST=' + str(self.wkst))
if self.count:
parts.append('COUNT=' + str(self.count))
for name, value in [
('BYSETPOS', self.bysetpos),
('BYMONTH', self.bymonth),
('BYMONTHDAY', self.bymonthday),
('BYYEARDAY', self.byyearday),
('BYWEEKNO', self.byweekno),
('BYWEEKDAY', self.byweekday),
('BYHOUR', self.byhour),
('BYMINUTE', self.byminute),
('BYSECOND', self.bysecond),
]:
if value:
parts.append(name + '=' + ','.join(str(v) for v in value))
return ';'.join(parts)